Path 1 of gdpar is a hierarchical Bayesian fit (Stan / HMC) of the AMM canonical decomposition
\[\theta_i = \theta_{\text{ref}} + a(x_i) + b(x_i) \odot \theta_{\text{ref}} + W(\theta_{\text{ref}})\,x_i,\]
where \(\odot\) denotes the Hadamard
(elementwise) product, coherent with the canonical notation of
vignette("v00_framework_overview", package = "gdpar") §8.2
and vignette("v01_amm_identifiability", package = "gdpar")
§3.3.
The additive component a and the modulating component
W each carry a hierarchical scale parameter
(sigma_a, sigma_W). For Stan to sample
efficiently, each of these scales admits two parametrizations:
raw * sigma, with
raw ~ normal(0, 1). Recommended in the
low-information regime, where the marginal posterior of the
coefficient stays close to the prior and the funnel is broad.coef ~ normal(0, sigma). Recommended
in the high-information regime, where the marginal posterior is
tightly identified and CP avoids the funnel at the bottom of the NCP
geometry.The function gdpar() exposes a flag
parametrization (and per-component overrides
parametrization_a, parametrization_W) that
controls which parametrization is used for the long fit. Default value
"auto" runs a short pre-flight NCP fit and decides
per-component automatically. This vignette documents:
fit$parametrization$meta.For the underlying canonical form and identifiability theory, see
vignette("v01_amm_identifiability", package = "gdpar") and
vignette("v04_asymptotics_path1_bayesian", package = "gdpar").
fit <- gdpar(..., parametrization = "auto") # default
fit <- gdpar(..., parametrization = "ncp") # force NCP everywhere
fit <- gdpar(..., parametrization = "cp") # force CP everywhere"auto" runs the pre-flight diagnostic (a 200 warmup +
200 sampling, 2-chain NCP fit with adapt_delta = 0.95 and
max_treedepth = 10) and applies three filters per
component. Approximately 30% extra wall-time per gdpar()
call."ncp" and "cp" skip the pre-flight and
apply the choice to both a and W.parametrization_a and parametrization_W
accept "ncp", "cp", or NULL.NULL, the component inherits from the global
parametrization.parametrization.parametrization = "auto", parametrization_a = "cp", parametrization_W = NULL
runs the pre-flight only for W, forcing CP on
a.| Situation | Recommended mode |
|---|---|
| Default exploratory work, no prior knowledge of geometry | parametrization = "auto" |
| Calibration runs where reproducibility across configurations matters | Explicit "ncp" or "cp" to remove the
data-dependent decision |
| Production pipelines where the pre-flight wall-time is unacceptable | Explicit choice, informed by an earlier "auto" run |
Strong prior belief that the posterior is far from the prior (high
n, low residual noise) |
"cp" |
Strong prior belief that the posterior is close to the prior (small
n, weak signal) |
"ncp" |
a is high-info but W is low-info (or vice
versa) |
parametrization_a and parametrization_W
separately |
The anchor argument of gdpar() fixes the
reference point against which the AMM deviation is measured. It admits
three forms:
anchor = "prior_mean" (default): the anchor is set to
the prior mean of \(\theta_{\text{ref}}\). This is the standard
choice for AMM identifiability under (C1)-(C7) and matches the canonical
centering convention of the framework.anchor = "empirical_y": the anchor is estimated by
inverting the link function at the sample mean of \(y\). Useful when no defensible prior on
\(\theta_{\text{ref}}\) is available or
when an empirical reference point is preferred for
interpretability.anchor = <numeric>: an explicit numeric anchor.
Accepts a scalar when \(p = 1\), a
length-\(p\) vector when \(p > 1\), and a length-\(K\) vector when \(K > 1\) (per-slot anchors). Validated by
resolve_anchor() against the dimensions of the model.The anchor interacts with the parametrization toggle: under CP the prior on \(\theta_{\text{ref}}\) is centred on the anchor; under NCP the same anchor enters as the shift parameter in the non-centred reparametrization. The toggle is anchor-agnostic — both modes admit any of the three forms.
For the multi-slot case (\(K >
1\)), the anchor receives one value per slot; see vignettes
vop05_distributional_K_dharma and
vop07_eb_workflow for slot-specific recipes.
The pre-flight diagnostic of gdpar() is split into two
paths: the multivariate path
(R/preflight_multi.R) handles \(p
> 1\) and numbers its checks filters 1-3; the
scalar path (R/preflight.R) handles \(p = 1\) and numbers its checks
filters 4-6 as the scalar homologues of filters 1-3
respectively (the numbering is historical and preserved for
cross-reference with vop02 §6 where filters 1-3 are
documented in their multivariate form). This vignette covers the scalar
path; the three filters described below are the scalar homologues of the
multivariate triple.
Orthogonal to the parametrization filters, the upstream
basis-restricted identifiability check runs before the
pre-flight fires. Its strictness is controlled by the
gdpar() argument
id_check_rigor = c("full", "fast") (default
"full"), which propagates verbatim to
gdpar_check_identifiability(rigor = ...). Under
"full" the check aborts on any violation of C1-C4 (scalar
path) or C4-bis (multivariate path); under "fast" the
cross-coordinate C4-bis check is downgraded to a single consolidated
warning at the end of the per-coordinate loop, letting the fit proceed.
See vignette("v01_amm_identifiability", package = "gdpar")
§6.6.1 for the conditions C1-C4 and §6.6.1.4 for the
rigor = "full" versus rigor = "fast"
contract.
When parametrization = "auto" (and at least one
component has no explicit override), the pre-flight diagnostic samples a
short NCP fit and applies three filters in priority order. The first
filter to fire fixes the decision; subsequent filters only act on
still-undecided components.
Active only when the pre-flight produces at least one divergent transition.
For each component, every transition is scored by
\[g_t = -z_{\log \sigma}(t)\cdot z_{\|raw\|}(t),\]
where the z-scores are computed over all pre-flight transitions. High values of \(g_t\) flag transitions concentrated in the funnel region of the NCP geometry. The test statistic is the centered mean over divergent transitions, divided by the standard error estimated from all transitions under the null that divergences are random with respect to the funnel geometry:
\[S = \frac{\bar g_{\text{div}} - \bar g_{\text{all}}}{\sqrt{\widehat{\mathrm{Var}}(g) / n_{\text{div}}}}.\]
Rejected at \(\alpha = 0.025\)
against the upper t-distribution with \(n_{\text{div}}-1\) degrees of freedom —
small \(n_{\text{div}}\)
self-regularizes via the heavier-tailed critical value. A rejection
forces CP on that component with reason code
filter_attribution.
Active when the minimum E-BFMI across chains is below 0.3.
Energy mixing pathologies in the pre-flight imply that the marginal
of sigma is poorly explored regardless of which
hierarchical coefficient drives the issue. The filter forces CP on any
component not yet decided, with reason code filter_ebfmi.
This filter cannot attribute the pathology per component because E-BFMI
is a global chain-level diagnostic; it acts as a conservative
blanket.
Active on any component still undecided after filters 4 and 5.
This filter measures how much the data contracted the marginal
posterior of the component’s effective coefficient relative to its prior
scale. The implementation is Path B’, which differs
from a naive per-coordinate t-test because of the rank deficiency of
W evaluated at a scalar \(\theta_{\text{ref}}\) (see Section 7).
For component a, the effective coefficient at coordinate
\(j\) per draw \(t\) is
\[\mathrm{eff}_a[t, j] = a_{\text{coef}}[t, j], \quad j = 1, \ldots, J_a^{\text{free}},\]
and the per-draw reference scale is \(\mathrm{ref}_a[t] = \sigma_a[t]\).
For component W with a polynomial basis of degree \(K\) and \(d\) predictors, the effective coefficient
at coordinate \(j\) per draw \(t\) is the linear contribution of the basis
to the \(j\)-th column of
X in \(\eta\):
\[\mathrm{eff}_W[t, j] = \sum_{k=1}^K \bigl(\theta_{\text{ref}}[t]^k - \theta_{\text{anchor}}^k\bigr)\,W_{\text{raw}}[t, k, j]\,\sigma_W[t],\]
with the per-draw reference scale being the conditional prior standard deviation of \(\mathrm{eff}_W[t, j]\) given the hyperparameters at draw \(t\):
\[\mathrm{ref}_W[t] = \sigma_W[t]\,\sqrt{\sum_{k=1}^K \bigl(\theta_{\text{ref}}[t]^k - \theta_{\text{anchor}}^k\bigr)^2}.\]
Per coordinate \(j\), the log info ratio on the full pre-flight sample is
\[\widehat{\ell}_j = \log\!\left(\frac{\overline{\mathrm{ref}}}{\widehat{\mathrm{sd}}(\mathrm{eff}_{\cdot, j})}\right),\]
and the statistic averaged across coordinates is \(\widehat{m} = \overline{\widehat\ell}\). The standard error of \(\widehat{m}\) is estimated by a chain-aware block bootstrap: within each chain, blocks of 10 contiguous draws are resampled with replacement, and the statistic is recomputed; the bootstrap standard deviation across \(B = 200\) replicates gives \(\widehat{\mathrm{SE}}(\widehat{m})\). The chain-aware design preserves the local MCMC autocorrelation that an iid bootstrap would erase, yielding a more honest (typically larger) standard error.
Two asymptotic z-tests are applied against null hypotheses on the log info ratio:
\[z_{\text{cp}} = \frac{\widehat{m} - \log \tau_{\text{cp}}}{\widehat{\mathrm{SE}}(\widehat{m})}, \qquad z_{\text{ncp}} = \frac{\widehat{m} - \log \tau_{\text{ncp}}}{\widehat{\mathrm{SE}}(\widehat{m})}.\]
Decision rule (one-sided, \(\alpha = 0.025\), critical value \(z^* \approx 1.96\)):
filter_info_high.filter_info_low.filter_info_ambiguous_ncp (conservative).Default thresholds (as of 2026-05-10) are \(\tau_{\text{cp}} = 5\) and \(\tau_{\text{ncp}} = 2\), calibrated against
eight canonical scenarios in
inst/benchmarks/calibrate_cp_ncp.R. The benchmark achieves
overall hit rate 0.92 against the well-defined scenarios; the single
residual miss is attributable to structural confounding (Section 7), not
to a threshold defect.
After fitting, the resolved parametrization and the pre-flight
metadata are stored at fit$parametrization:
fit$parametrization$cp_a # logical: was CP chosen for a?
fit$parametrization$cp_W # logical: was CP chosen for W?
fit$parametrization$meta # list with diagnostic statisticsThe meta list contains:
| Field | Meaning |
|---|---|
used_preflight |
Logical. TRUE when the pre-flight ran (i.e., at least
one component needed a data-driven decision). |
n_divergent |
Integer. Divergent transitions in the pre-flight. |
div_pct |
Numeric. Proportion of divergent transitions. |
ebfmi_min |
Numeric. Minimum E-BFMI across pre-flight chains. |
t_attribution_a, t_attribution_W |
Numeric. Filter 4 t-statistic per component (NA if
filter 4 did not run for that component). |
t_info_cp_a, t_info_ncp_a |
Numeric. Filter 6 z-statistics for component a against
\(\tau_{\text{cp}}\) and \(\tau_{\text{ncp}}\). |
t_info_cp_W, t_info_ncp_W |
Numeric. Same for component W. |
decision_reason_a, decision_reason_W |
Character. Reason code per component (see below). |
Reason codes:
| Code | Meaning |
|---|---|
user_global |
The user passed parametrization = "ncp" or
"cp"; pre-flight skipped. |
user_explicit_a / user_explicit_W |
The user passed an explicit per-component override. |
absent_or_degenerate |
The component is not declared in amm_spec or its
dimension is degenerate. |
filter_attribution |
Filter 4 rejected; CP forced. |
filter_ebfmi |
Filter 5 fired; CP forced. |
filter_info_high |
Filter 6 rejected against \(\tau_{\text{cp}}\); CP chosen. |
filter_info_low |
Filter 6 rejected against \(\tau_{\text{ncp}}\); NCP chosen. |
filter_info_ambiguous_ncp |
Filter 6 did not reach either threshold; NCP chosen conservatively. |
filter_info_undefined_ncp |
Filter 6 returned NA (degenerate inputs); NCP chosen
conservatively. |
A minimal Gaussian fit with parametrization = "auto".
The data are simulated under a regime that should land on
filter_info_high for a: large beta
coefficients with low residual noise, n = 80.
library(gdpar)
set.seed(1)
n <- 80L
df <- data.frame(
x1 = rnorm(n), x2 = rnorm(n), x3 = rnorm(n)
)
df$y <- 1 + 0.8*df$x1 - 0.6*df$x2 + 0.4*df$x3 +
rnorm(n, sd = 0.3)
spec <- amm_spec(a = ~ x1 + x2 + x3)
fit <- gdpar(
formula = y ~ x1 + x2 + x3,
family = gdpar_family("gaussian"),
amm = spec,
data = df,
parametrization = "auto",
iter_warmup = 300L,
iter_sampling = 300L,
chains = 2L,
refresh = 0L,
verbose = FALSE,
seed = 42L
)Inspect the resolved parametrization:
Inspect the pre-flight metadata:
The relevant fields for a high-information regime are
decision_reason_a (expected
"filter_info_high") and the z-statistic
t_info_cp_a (expected to exceed 1.96). In this
configuration W is absent from the spec, so
decision_reason_W is
"absent_or_degenerate".
fit$parametrization$meta$decision_reason_a
fit$parametrization$meta$t_info_cp_a
fit$parametrization$meta$n_divergent
fit$parametrization$meta$ebfmi_mina and W
when covariates overlapWhen a and W share predictors in
x_vars, the marginal posterior of the effective
W coefficient is genuinely wider because data variability
is split between the two components. Filter 6 reads this as “low
information” and recommends NCP — which is statistically the correct
choice given the marginal geometry, even when the true generating
coefficients are large. The user-facing implication is that under
confounding, decision_reason_W may be
filter_info_low or filter_info_ambiguous_ncp
even in regimes with strong signal. This is documented behavior, not a
defect.
If you have prior knowledge that W is informative
despite the confounding, override with
parametrization_W = "cp" explicitly.
WThe Stan model evaluates W at the scalar
theta_ref, so the data linearly identify only one
combination per coordinate: \(\sum_k
(\theta_{\text{ref}}^k - \theta_{\text{anchor}}^k)\,W_{\text{raw}}[k,
j]\). Individual W_raw[k, j] for different \(k\) at the same \(j\) are not separately identified beyond
what the prior implies. Path B’ addresses this by computing the info
ratio on the effective coefficient (the sum across \(k\)) rather than on individual
W_raw[k, j], which is the correct identifiable quantity to
measure.
The chain-aware block bootstrap (block size 10) is conservative: it
preserves local MCMC autocorrelation that an iid bootstrap would erase,
yielding a typically larger standard error and a more demanding
rejection criterion. Users who want to experiment with different block
sizes can call gdpar:::preflight_info_ratio_t() directly
with custom block_size and n_boot arguments,
but this is an internal API not covered by stability guarantees.
The pre-flight adds approximately 30% wall-time per
gdpar() call (one compilation, one short fit with 200
warmup + 200 sampling, 2 chains, adapt_delta = 0.95,
max_treedepth = 10). In production pipelines where this
cost is unacceptable, run parametrization = "auto" once
during prototyping, record the resolved decision, and pass it explicitly
via parametrization_a / parametrization_W in
subsequent calls.
parametrization = "auto".
It costs ~30% extra wall-time but removes a manual decision.fit$parametrization$meta after the
fit to learn how the decision was made; the reason code points
directly to the active filter.parametrization_a /
parametrization_W when you have prior knowledge
(especially under confounding), when calibrating across configurations,
or when the pre-flight cost is unacceptable.inst/benchmarks/calibrate_cp_ncp.R.vignette("v01_amm_identifiability", package = "gdpar")
— canonical form and identifiability conditions.vignette("v04_asymptotics_path1_bayesian", package = "gdpar")
— asymptotic theory of Path 1.