mixle.epistemic.coherence module

Program plan §2’s three CI-checkable coherence properties, as plain testable functions.

“Checked continuously in CI, not asserted” – these compute a violation magnitude (or boolean) over a HypothesisPortfolio and a caller-supplied likelihood; nothing here is wired into an enforcement path, matching the program plan’s own framing. Where a fitting primitive already exists (mixle.data.exchangeability.exchangeability_check()), it’s a dataset-shaped permutation test over row order in a flat table, not over a portfolio-update trajectory – the shapes don’t line up cleanly enough to delegate directly, so exchangeability_violation() below is a thin, purpose-built adapter using the same permutation-test idea rather than a forced reuse.

CONSERVATION_ATOL = 1e-09

The contractual ABSOLUTE scale at which evidence_conservation_violation() calls two posteriors equal.

Compared against with rtol=0. numpy.allclose() was previously given atol=1e-9 while its default rtol=1e-5 stayed enabled, so the effective threshold was 1e-9 + 1e-5 * |w| – up to 250x the advertised absolute scale on ordinary weights. A repeat ingestion that moved a weight by 2.5e-7 was reported as conserved. Double ingestion is exactly what this check exists to catch, so the stated absolute test is the intended one and the relative term is switched off.

exchangeability_violation(portfolio0, observations, likelihood, *, n_permutations=20, rng=None)[source]

Max posterior-weight deviation across random reorderings of observations (program plan §2(i)).

Sequentially reweights portfolio0 through observations in its given order, then again through n_permutations random permutations of the same set; returns the largest per-hypothesis (or open-world) weight deviation seen across permutations. Zero (up to float noise) for a likelihood whose per-step reweighting is a pure multiplicative update with no hidden order dependence – successive Bayesian multiply-and-renormalize steps commute exactly in that case.

n_permutations must be a positive exact integer: a zero budget returned 0.0 – read by any caller as “no violation” – having permuted nothing at all. An open-world-only portfolio (no active hypotheses) is a valid input and reports on w_open alone; see _max_abs().

observations must hold at least two entries, for the same reason on the other axis: a sequence of length 0 or 1 has exactly one ordering, so every “permutation” is the base order, every deviation is identically 0.0, and the function reports “no violation” from a test it could not run. Order-dependence is undefined on a sequence that has no alternative order, so this refuses rather than certifies (MXR-080-1758).

Parameters:
Return type:

float

martingale_violation(portfolio, observation_sampler, likelihood, *, n=1000, rng=None)[source]

|E[w_{t+1} | B_t] - w_t| under the model’s own predictive (program plan §2(ii)).

observation_sampler(rng) -> observation must draw from the portfolio’s own predictive distribution (e.g. sample a hypothesis proportional to its current weight, then simulate one observation from it) – the martingale property is a statement about self-consistency under the model’s own predictive measure, not about any particular real data-generating process. Returns the largest per-hypothesis (or open-world) deviation between the prior weight and the weight averaged over n resampled predictive observations.

n must be a positive exact integer: n=0 divided the (zero) accumulator by zero, producing a ZeroDivisionError on the open-world term and a NaN weight vector rather than reporting that no measurement was requested. An open-world-only portfolio is a valid input; see _max_abs().

Parameters:
Return type:

float

evidence_conservation_deviation(portfolio0, observation, likelihood)[source]

How far re-ingesting the identical observation moves the posterior again, as a magnitude.

The same quantity evidence_conservation_violation() thresholds, reported rather than collapsed to a Boolean – like the other two properties in this module, whose violations are magnitudes. The maximum absolute deviation over the per-hypothesis weights and w_open, both on the same absolute scale (0.0 for an open-world-only portfolio’s empty weight vector).

Parameters:
Return type:

float

evidence_conservation_violation(portfolio0, observation, likelihood)[source]

Whether re-ingesting the identical observation a second time changes the posterior further.

Program plan §2(iii): “the same underlying measurement, ingested twice through different routes, updates once.” This function tests the math given an already-deduped input path – it does not itself provide the content-addressed dedup key that real conservation needs (program plan §3.1 is where that lives, a storage-layer concern outside this plan’s scope). Concretely: a plain likelihood with no memory of what it has already seen WILL show a violation here (double application double-counts the evidence, changing the weights again) – that is the honest, correct outcome for an undeduped path, not a bug in this function. A likelihood that is itself dedup-aware (e.g. returns a neutral 1.0 for an observation it has already scored, tracked by identity/content-key in a closure the caller owns) shows no violation, demonstrating the property holds once dedup is actually wired in.

The comparison is purely absolute, at CONSERVATION_ATOL; use evidence_conservation_deviation() for the magnitude behind this Boolean.

Parameters:
Return type:

bool