Tests whether an observed treatment effect is consistent with the effect a calibrated mechanistic model predicts. Unlike a model-free causal test, which asks only whether treatment and outcome are associated, it anchors both the null and the alternative in a process model: the reference distribution is built from the model's own posterior-predictive draws. The verdict is three-way – the data are consistent with the model-implied effect, the data show an effect the model does not predict, or there is no detectable effect.
Usage
mechanism_consistency_test(
posterior,
mechanism,
X,
treatment,
outcome,
confounders = NULL,
density_ratio = "logistic",
h0_mode = c("permute_within_model", "model_without_treatment"),
treatment_type = c("auto", "binary", "continuous"),
baseline = NULL,
noise_sd = NULL,
n_perm = 300L,
alpha = 0.05,
mechanism_provenance = NULL,
posterior_provenance = NULL,
seed = NULL
)Arguments
- posterior
A numeric matrix or data.frame of posterior parameter draws, one row per draw, columns in the order the
mechanismexpects. Typically the parameter ensemble of a fitted simulator (for example a PESTO IES posterior); any numeric draw matrix is accepted.- mechanism
A function
mechanism(theta, X, t)that, given one posterior drawtheta(a numeric vector), a covariate matrixX(nbyp), and a treatment vectort(lengthnor scalar), returns the model-implied mean outcomeE[Y]as a length-nnumeric vector.- X
Numeric matrix of covariates,
nrows. Use a single constant column when the mechanism has no covariate dependence.- treatment
Numeric treatment vector of length
n. Binary or continuous; the construction adapts viatreatment_type.- outcome
Numeric outcome vector of length
n.- confounders
Numeric matrix of backdoor confounders, or
NULL(default) for the unadjusted statistic. When supplied, density-ratio weights break the backdoor path so an observational treatment is handled correctly.- density_ratio
Character density-ratio backend used for backdoor adjustment, passed to
fit_density_ratio(). Default is"logistic".- h0_mode
Character null construction.
"permute_within_model"(default) permutes the model-implied outcome to break the treatment association while keeping the model's marginal;"model_without_treatment"simulates the outcome with treatment held at the control baseline so the model itself asserts no effect.- treatment_type
Character.
"auto"(default) calls a treatment with two or fewer distinct levels binary, otherwise continuous."binary"recovers thet = 0tot = 1contrast exactly;"continuous"contrasts across the observed dose range.- baseline
Numeric control level at which the mechanism is switched off for the null. Defaults to
0for a binary treatment and the treatment mean for a continuous one.- noise_sd
Numeric observation-noise standard deviation for the model-implied draws, or
NULL(default) to estimate it from the model residual at the posterior mean. The model residual is shape-correct for a saturating dose-response, where a treatment-only detrend would leave curvature and over-noise the reference band.- n_perm
Integer number of model-implied reference replicates. Default is
300L.- alpha
Numeric significance level. Default is
0.05.- mechanism_provenance
Optional. A record of where the
mechanism's calibration came from (e.g. a PESTO manifestrun_id+apsim_version, a citation, a fitted-model handle). The test builds its entire reference band from themechanismand cannot itself verify the calibration corresponds to reality; supplying this declares the mechanism grounded. WhenNULL(the default), the result'sgroundingis"ungrounded"and the human-facingverdictstring is suffixed[ungrounded]so a verdict built on an un-grounded mechanism is never presented as unconditionally confident (Independent Oracle Principle). Thedecisionenum is unchanged.- posterior_provenance
Optional. The analogous record for the
posteriordraws; carried through to the result for completeness.- seed
Integer random seed, or
NULL. Set it for a reproducible reference distribution.
Value
An object of class "mechanism_consistency_result": a list carrying the observed
bd-HSIC statistic, the H0 tail p-value and in-tail flag, the H1 central
interval, the H1 consistency flag and percentile, a borderline flag, the
three-way decision, a posterior_adequacy diagnostic, and the reference
draws. A print() method is provided.
Details
The statistic is a weighted bd-HSIC between treatment and outcome, identical
to the engine bd_hsic_test() uses. It differs in the reference: rather
than a permutation null, it draws n_perm model-implied replicates, sampling
a posterior draw with replacement each time so the band integrates over the
posterior. The alternative band H1 is the model's prediction at the observed
treatment; the null band H0 is one of the two h0_mode constructions. The
observed statistic is read against both: a small H0 tail p-value means an
effect is present, and consistency with the H1 band means that effect matches
the model's prediction.
A posterior-adequacy guard protects against a degenerate H1 band. When the
posterior pins the model-implied effect too precisely (effect coefficient
of variation below 0.02), "consistency with H1" is not meaningful; the guard
warns and flags posterior_adequacy$ok = FALSE. The remedy is to widen the
posterior at its source, for example with ensemble inflation in the simulator
calibration. The guard reads the spread of the effect itself, not per-
parameter spread, so a well-identified nuisance covariate does not trip it.
References
Hu, R., Sejdinovic, D., & Evans, R. J. (2024). A kernel test for causal association via noise contrastive backdoor adjustment. JMLR, 25(160), 1-56.
See also
Other causal association tests:
bd_hsic_test(),
hierarchical_test(),
kernel_causal_test()
Examples
set.seed(1)
n <- 80
nrate <- runif(n, 0, 200)
yield <- 1.1 + 4.2 * (1 - exp(-0.018 * nrate)) + rnorm(n, 0, 0.25)
post <- cbind(ymax = rnorm(200, 4.2, 0.30),
rate = rnorm(200, 0.018, 0.004),
y0 = rnorm(200, 1.1, 0.15))
mitscherlich <- function(theta, X, t) {
theta[3] + theta[1] * (1 - exp(-theta[2] * t))
}
res <- mechanism_consistency_test(post, mitscherlich, X = matrix(1, n, 1),
treatment = nrate, outcome = yield, n_perm = 100, seed = 1)
print(res)
#> mechanism-consistency test
#> treatment: continuous (H0 baseline = 105.1)
#> statistic: unadjusted bd-HSIC
#> observed bd-HSIC: 0.06608
#> H0 tail p-value: 0.010 (in tail: TRUE)
#> H1 central [0.05187, 0.07807] obs at H1 pctile 0.35 consistent: TRUE
#> DECISION: MECHANISM_CONSISTENT_EFFECT
#> GROUNDING: ungrounded (mechanism provenance not declared -- verdict not grounded)
res$grounding # "ungrounded" -- no mechanism provenance declared
#> [1] "ungrounded"
# Declaring mechanism provenance grounds the verdict (Independent Oracle
# Principle). The test builds its whole reference band from `mechanism`, which it
# cannot itself check against reality; naming where the calibration came from
# -- a citation, a fitted-model handle, or a PESTO manifest's run id and the
# simulator version it was calibrated with -- moves `grounding` from
# "ungrounded" to "grounded".
res_grounded <- mechanism_consistency_test(
post, mitscherlich, X = matrix(1, n, 1),
treatment = nrate, outcome = yield, n_perm = 100, seed = 1,
mechanism_provenance = list(
run_id = "ies-2026-06-12-0042",
simulator = "APSIM NG 2024.6.7579",
reference = "Mitscherlich (1909) N-response form"
)
)
res_grounded$grounding # "grounded"
#> [1] "grounded"
