mixle.inference.calibration_gate module¶
Calibration gate – a deployable IC-6 verifier that challenges whether a posterior’s reported uncertainty is actually earned, instead of checking only its shape.
Motivation (found empirically, across several end-to-end pipelines): the UQ in this codebase is
locally honest – every module computes a real posterior – but was globally unaudited. A
systematically overconfident or miscalibrated posterior sailed straight through to a confident-looking
final answer, because nothing in the flow ever challenged the confidence. The calibration primitives
already existed (mixle.inference.calibration: pit_ensemble / interval_coverage /
coverage_curve / pit_calibration_error); what was missing was a gate that composes them into
a pass/fail verifier a pipeline (e.g. mixle.task.knowledge_routing.route_task()) can actually
stop on. This module is that gate – a thin composition, not new statistics.
Honest boundary, stated up front rather than buried. This gate catches:
overconfidence / underconfidence of a posterior relative to HELD-OUT DATA it can be checked against (
posterior_predictive_calibration()), andinference-algorithm bugs – an inference that is not self-consistent under its own generative model – via simulation-based calibration (
simulation_based_calibration()).
It does not catch model misspecification under genuine non-uniqueness. If the data you can hold out simply cannot distinguish the true state (the textbook example: surface gravity cannot resolve a source’s depth – shallow and deep bodies produce nearly identical surface fields), then a biased posterior fits the held-out data perfectly and this gate will correctly pass it. Catching that requires either data that can see the biased dimension (e.g. one borehole) or a physics-based prior – not more calibration checking. This gate reports what the available data can support; it never manufactures confidence the data cannot justify, and it never claims to.
- class CalibrationVerdict(calibration_status, pit_error, null_threshold, coverage_error, reference_level, coverage_at_reference, mean_interval_width, n_points, power_sufficient=True, expected_count_per_bin=nan, randomness_controlled=True, reasons=<factory>, kind='calibration', p_value=nan, n_null=0, power_estimate=nan, power_alternative='', power_estimate_independent=nan, power_estimate_shared=nan, power_lower_bound=nan, power_replicates=0, null_rejection_ucb=nan, declared_dependence='', coverage_at_reference_null_expectation=nan)[source]
Bases:
objectThe outcome of a calibration check with an explicit three-way decision state.
The DECISION is a Monte-Carlo p-value compared to
1 - null_quantile(STAT-RR17-08): the predictive gate uses a column-swap randomization null that is exact in exactly TWO regimes – the shared-draw ensemble construction (any cross-row dependence of the held-out data), and fully independent rows AND entries – and in no other: dependent rows with fresh entries break the vector exchangeability and the test false-alarms (measured 83.9% at nominal 5%, STAT-RR19-06/STAT-RR21-10; an earlier version of this docstring claimed exactness “whatever the cross-row dependence”). SBC uses an i.i.d.-uniform null, which its protocol genuinely satisfies. Both use(1 + count)/(1 + B), level-valid at any replicate count.null_thresholdremains as a descriptive scale and thescoreanchor, never the decision constant (deciding against that estimated quantile realized levels of 0.0033-0.0181 at nominal 0.01 depending only on its seed).calibration_statusis'passed'only when the p-value clears alpha AND measured power against the named canonical alternative is adequate AND randomness is controlled;'indeterminate'means the data cannot support either promotion or rejection;'failed'means the decision rule rejected, which is real evidence of a problem even when power was otherwise low. A gate may promote only the literal'passed'state.- Parameters:
calibration_status (Literal['passed', 'failed', 'indeterminate'])
pit_error (float)
null_threshold (float)
coverage_error (float)
reference_level (float)
coverage_at_reference (float)
mean_interval_width (float)
n_points (int)
power_sufficient (bool)
expected_count_per_bin (float)
randomness_controlled (bool)
kind (str)
p_value (float)
n_null (int)
power_estimate (float)
power_alternative (str)
power_estimate_independent (float)
power_estimate_shared (float)
power_lower_bound (float)
power_replicates (int)
null_rejection_ucb (float)
declared_dependence (str)
coverage_at_reference_null_expectation (float)
- property passed: bool
Whether this result is affirmative evidence suitable for promotion.
- property indeterminate: bool
Whether the check lacks enough evidence to pass or fail.
- property low_power: bool
Compatibility diagnostic for indeterminate results caused by inadequate power.
- property score: float
1 at zero PIT error, 0.5 exactly AT the calibrated-null threshold, 0 at twice the threshold, linear in between. Normalized by the null threshold so it is comparable across sample sizes. It is NOT 1 for every passing posterior – a genuinely calibrated posterior typically lands around 0.6-0.7, so rank with it, never gate on a fixed cutoff like 0.9 (use
calibration_statusto gate).- Type:
A 0..1 ranking score
- posterior_predictive_calibration(ensemble, held_out_y, *, reference_level=0.90, null_quantile=0.99, pit_tol=None, bins=10, low_power_threshold=1.0, min_expected_count_per_bin=5.0, pit_seed=0, ensemble_dependence='shared-draws')[source]
Check a posterior-predictive ensemble against held-out observations it never saw.
ensemble_dependencedeclares how the ensemble AND the held-out rows were BUILT. The column-swap p-value is exact in exactly TWO regimes, and the declaration selects which executed power gates promotion:"shared-draws"(the default, fail-safe): ONE set ofmposterior draws pushed through every row’s forward model. Exact under ANY cross-row dependence of the held-out data – the columns then carry the same dependence the observations do – which is why this is the construction to use when rows share a fitted posterior or latent state (the typical single-fit evaluation). Power is often low here and promotion honestly refuses."independent": the caller’s recorded assertion that the ROWS THEMSELVES are mutually independent (no latent shared across held-out points) AND every ensemble entry is a fresh draw. BOTH halves are load-bearing: with rows sharing a latent but entries drawn fresh – a construction a caller might reasonably call “independent draws” – the swap group is NOT an invariance and the test false-alarms on perfectly calibrated data (measured 83.4-84.3% rejection at alpha = 0.05, k = 100, m = 20; STAT-RR19-06). If the held-out rows share anything, do not declare this: rebuild the ensemble with the shared-draw construction, which is exact regardless.
The declaration is recorded in the verdict and the receipt; misdeclaring it is how a low-power non-rejection masquerades as affirmative evidence (pass 18 measured a shared-column mis-calibrated posterior promoted on an independent-rows power fiction of 0.95 against an actual 0.018) or how a dependent-row construction inherits a false-alarm rate of 84% (pass 20).
ensembleis(k, m): for each ofkheld-out points,mposterior-predictive draws (pushmdraws from the posterior through the forward model to the observation of each held-out point).held_out_yis the(k,)array of real observed values at those points.The decision is an exact column-swap randomization test on the randomized PIT of the held-out data (STAT-RR17-08): the observation vector is swapped with each whole ensemble column in turn and the identical statistic recomputed, giving
p = (1 + count)/(1 + m). Under calibration,(y, col_1..col_m)are exchangeable as vectors for the shared-draw construction (the samemposterior draws pushed through the forward model for every point – the i.i.d.-uniform reference this replaces wrongly failed a correctly calibrated shared-parameter posterior 298/300 times there) and for FULLY independent data (rows and entries both), so the p-value is level-exact in those two regimes – and in NO OTHER: dependent rows with fresh entries break the exchangeability and the test false-alarms (STAT-RR19-06; seeensemble_dependenceabove). It is quantized at1/(m+1): with few draws per point the test cannot reject at small alpha, and the measured power reports that honestly. The decision rejects whenp <= 1 - null_quantile.null_thresholdis still computed as a descriptive scale and thescoreanchor, never the decision constant. Passpit_tolto override with a fixed tolerance if you have a reason to (that branch reports no p-value).Coverage numbers (
coverage_curve()/interval_coverage()) are computed and reported as human-readable diagnostics and to label the direction of any miscalibration (over- vs under-confident), but the pass/fail itself is the PIT test, which subsumes them and is scale-correct.A non-rejection is promotable only when MEASURED power is adequate: the Monte-Carlo power of this decision at this
kagainst the named canonical alternative (predictive dispersion 0.8x the truth) must reach 0.50, alongside themin_expected_count_per_bindata floor. The previous bin-count/threshold-magnitude heuristics computed no power at all and reportedpower_sufficient=Truewhere the actual rejection rate against that alternative was ~4.5% (STAT-RR17-08). Power against ONE named alternative is a floor, not a universal power claim. Otherwise the result isindeterminate, never a pass.- Parameters:
- Return type:
CalibrationVerdict
- simulation_based_calibration(prior_sampler, simulate, fit, *, n_sims=200, param_index=0, null_quantile=0.99, error_tol=None, bins=10, low_power_threshold=1.0, min_expected_count_per_bin=5.0, seed=0)[source]
Simulation-based calibration (Talts et al. 2018): does the inference recover its own generative model? Draw
theta ~ prior, simulatey ~ p(y|theta), refit a posterior, and record the rank of the truethetaamong the posterior draws. Under a correct inference those ranks are Uniform; a systematically over/under-dispersed posterior makes them pile up at the middle or the edges.PRECONDITION (STAT-RR17-08 / audit GATE-6): rank uniformity requires the posterior draws each
fitreturns to be (near-)independent – exchangeability oftheta_truewith the draws. An UNTHINNED autocorrelated MCMC chain from a CORRECT inference produces systematically non-uniform ranks and fails this gate without any defect in the inference; thin the chain to approximate independence first (Talts et al., section 4). The decision itself is the level-exact Monte-Carlo p-value against the i.i.d.-uniform null, which the SBC protocol genuinely satisfies across simulations (each draws its own parameters and data).prior_sampler(rng) -> theta(a(d,)parameter vector, or scalar-as-(1,));simulate(theta, rng) -> y(any shape the fitter accepts);fit(y, rng) -> posterior_draws((n_draws, d)or(n_draws,)). The fitter must derive all of its stochasticity from the supplied RNG; accepting an opaquefit(y)callable would makeseedunable to control the inference being calibrated.param_indexselects which parameter’s rank to test.Unlike
posterior_predictive_calibration(), this needs no held-out real data – it tests the inference machinery against synthetic ground truth it generates itself. It therefore catches a different class: an inference bug / wrong likelihood / mis-scaled posterior, even on data that would look perfectly fit. (It does not test whether the model matches reality – that isposterior_predictive_calibration()’s job, on real held-out data.)- Parameters:
prior_sampler (Callable[[RandomState], ndarray])
simulate (Callable[[ndarray, RandomState], ndarray])
fit (Callable[[ndarray, RandomState], ndarray])
n_sims (int)
param_index (int)
null_quantile (float)
error_tol (float | None)
bins (int)
low_power_threshold (float)
min_expected_count_per_bin (float)
seed (int | RandomState | None)
- Return type:
CalibrationVerdict
- class CalibrationVerifier(*, reference_level=0.90, null_quantile=0.99, pit_tol=None, ensemble_dependence='shared-draws')[source]
Bases:
objectAn IC-6-shaped verifier (
.verify(claim, context) -> dict) wrappingposterior_predictive_calibration(), so a calibration gate drops straight intomixle.task.knowledge_routing.route_task()(or any IC-6 consumer) as a real verifier that can fail a miscalibrated result – the thing the routing verifiers used in the experiments only ever checked structurally.The tool result being verified must carry, on
claim["payload"](orcontext), an"ensemble"(k, m)posterior-predictive draw matrix and a"held_out_y"(k,)array. A payload without them fails closed with an explicit reason – a calibration verifier that silently passes anything it can’t actually check would defeat its own purpose.