mixle.reason.core module¶
Reasoning front door for fusing modality evidence into a belief.
reason(prior, evidence) folds a sequence of linear-Gaussian observations into a belief state
by exact Kalman assimilation, tracking how many nats of uncertainty each modality removed. The
returned ReasonedAnswer is the posterior belief plus the tools a scientific answer needs:
credible intervals, per-modality attribution, and an epistemic/aleatoric split of any prediction.
- class Latent[source]
Bases:
objectFactories for the shared latent prior used at the start of assimilation.
- static gaussian(mean, cov)[source]
A Gaussian prior
N(mean, cov)over the latent.
- static vector(dim, *, mean=0.0, var=1.0)[source]
An isotropic Gaussian prior over a
dim-vector latent:N(mean*1, var*I).- Raises:
ValueError – if
dimis not an exact positive integer (MXR-080-0274) – a fractionaldimused to be silently truncated withint()rather than rejected.- Parameters:
- Return type:
GaussianBelief
- static mechanistic(A, steps, *, x0_mean=None, x0_cov=None, process_cov=None)[source]
Return a linear-dynamics prior over
z_0 .. z_{steps-1}.The trajectory follows
z_{t+1} = A z_t + w_twithw_t ~ N(0, Q). The returned belief is the joint Gaussian over the stacked trajectory(steps * d,). Because the states are coupled, evidence at one time can inform other times through the dynamics, so fusing observations viareason()performs exact Kalman smoothing for this linear-Gaussian model.- Parameters:
A (Any) –
(d, d)linear state-transition operator (one discrete step).steps (int) – number of time steps
Tin the trajectory.x0_mean (Any) – mean of
z_0(default zeros).x0_cov (Any) – covariance of
z_0(default identity).process_cov (Any) – process-noise covariance
Q(default zeros – deterministic dynamics).
- Raises:
ValueError – if
stepsis not an exact positive integer; ifx0_meandoesn’t have shape(d,)or isn’t finite; or ifx0_cov/process_covisn’t a finite, symmetric, positive-semidefinite(d, d)covariance (MXR-080-0274) – previouslystepswas silently truncated withint()and the covariances were never validated at all, so a malformed one either propagated silently into the joint prior or surfaced only as a confusing low-level error far from its actual cause.- Return type:
GaussianBelief
- class LinearGaussianEvidence(H, y, R, name='')[source]
Bases:
objectOne modality’s evidence about the latent
z:y = H z + noise,noise ~ N(0, R).His the (possibly linearized) forward operator mapping the latent to this modality’s measurement space,ythe observed data,Rits noise covariance (matrix, diagonal, or scalar). Application forward models (e.g.mixle_pdegeophysics operators) produce these.
- Evidence
Short alias –
Evidence(H, y, R, name).
- class NonlinearEvidence(h, y, R, jacobian=None, iterations=2, name='')[source]
Bases:
objectOne modality’s evidence through a nonlinear forward model.
Assimilated by (iterated) extended-Kalman linearization: at the current belief mean
mthe forward is replaced by its tangenth(z) ~ h(m) + J(m)(z - m)and the exact linear update runs on that tangent; withiterations > 1the linearization point is refined at the updated mean and the update repeats from the pre-update belief, which matters when the prior mean is far from the truth.jacobianis analytic when you have it; otherwise a central finite difference is used. This is a Gaussian approximation around the linearization point; for strongly multimodal posteriors it reports one mode’s belief, not the full mixture.
- block_selector(step, n_blocks, block_dim, within=None)[source]
An observation matrix that reads time-block
stepof a stacked trajectory latent.For a latent built by
Latent.mechanistic()(shape(n_blocks * block_dim,)), returns theHselecting blockstep– use it to buildLinearGaussianEvidencefor an observation at that time.withinoptionally reads only part of the block (a(k, block_dim)local readout); by default the whole block is read (identity).- Raises:
ValueError – if
n_blocks/block_dimis not an exact positive integer; ifstepis not an exact integer or falls outside[0, n_blocks); or ifwithindoesn’t haveblock_dimcolumns (MXR-080-0274).stepused to accept Python-style negative indexing by accident (silently selecting a DIFFERENT, in-range block with no signal that anything unusual happened) while a step at or beyondn_blocksfell through to a confusing low-level “could not broadcast” error instead of a clear one.- Parameters:
- Return type:
- class ReasonedAnswer(belief, prior, trace, *, full_prior=None, evidence=None, query_idx=None)[source]
Bases:
objectA posterior belief about a query, with the UQ a scientific answer needs.
Beyond
mean/interval/entropy(delegated to the belief), it exposesattribution()– the nats of uncertainty each modality removed – andpredict(), which splits a prediction’s uncertainty into epistemic (from latent uncertainty) and aleatoric (observation noise) via the law of total variance.- Parameters:
- property mean: ndarray
Return posterior mean from the underlying belief.
- interval(level=0.9)[source]
Per-coordinate central credible interval at
level(an(d, 2)array of[lo, hi]).
- information_gain()[source]
Total nats of uncertainty the evidence removed from the prior (
H[prior] - H[posterior]).Both entropies are always taken from this answer’s OWN coordinate space – the full prior for an unqueried answer, or that prior’s matching marginal after
marginal()– so querying a coordinate the evidence never touched correctly reports zero gain (MXR-080-0272), rather than a full-state prior entropy mixed against a lower-dimensional posterior marginal’s.- Return type:
- attribution(*, normalize=False, method='sequential', **kwargs)[source]
Per-modality information gain in nats – which modality sharpened the belief, and by how much.
Two allocation rules are available, and they generally disagree for REDUNDANT sources (two items that both constrain overlapping coordinates) – pick deliberately, not by default (MXR-080-0275):
method="sequential"(default): each item’s nats areH[belief before it] - H[belief after it], in the orderevidencewas given toreason(). Cheap (one pass over the fold trace) and always sums exactly toinformation_gain(), but for redundant sources it is a CONDITIONAL, fold-order-dependent credit split, not an intrinsic measure of modality importance: whichever redundant source is folded first is credited with uncertainty a different order would have credited to another source. The FINAL POSTERIOR does not depend on order for linear-Gaussian evidence (sequential exact Kalman assimilation commutes – seereason()); this per-source SPLIT of it still does. Treat the result as “credit under the order evidence happened to be listed in,” not as “how important is this modality.”method="shapley": the order-INVARIANT Shapley value – each source’s nats averaged over (up to) every permutation of fold order, satisfying the same efficiency property (values sum toinformation_gain()) without depending on which order the caller listed evidence in. Exact for up to_SHAPLEY_EXACT_MAX_SOURCESsources (O(n!)re-folds of the full evidence sequence); beyond that it averagesn_permutationsuniformly random permutations instead (O(n_permutations * n)re-folds), a standard permutation-sampling Monte Carlo estimator whose standard error shrinks asO(1 / sqrt(n_permutations)). Passn_permutations=/rng=to control the sampling estimate. Each re-fold repeats the full (possibly nonlinear, possibly expensive) assimilation from the original prior, so this costsO(n)toO(n!)times a singlereason()call – fine for the small modality counts this module is used with, not intended for hundreds of sources.
With
normalize=True, values are the fraction of the total gain (they then sum to ~1), computed after the chosen allocation rule.
- predict(H, R=0.0)[source]
Split the uncertainty of a new prediction
y* = H z + noise(R)(law of total variance).epistemic = diag(H P Hᵀ)(from the latent’s remaining uncertainty, reducible by more data) andaleatoric = diag(R)(irreducible observation noise). Exact for the Gaussian belief.- Parameters:
H (Any) –
(k, d)observation operator, wheredis this belief’s own latent dimension. Its shape must match exactly – it is never reshaped from a looser guess, since a wrong-widthHcan have a total element count that happens to divide evenly bydand so silently describe a DIFFERENT (still valid-looking) operator instead.R (Any) – the prediction’s noise covariance: a scalar (shared by all
koutputs), a length-kvector of per-output variances, or a(k, k)covariance matrix (symmetrized, and required finite and positive semi-definite).
- Raises:
ValueError – if
H’s width doesn’t match this belief’s latent dimension, orRis the wrong shape, non-finite, or not a valid (non-negative / positive semi-definite) noise covariance (MXR-080-0273).- Return type:
UncertaintyDecomposition
- marginal(indices)[source]
Restrict the answer to a subset of latent coordinates (query a specific variable).
The prior and every fold step’s before/after belief are re-marginalized to the SAME coordinates as the posterior (MXR-080-0272), so
information_gain()and the defaultattribution()on the result are computed entirely within the queried subspace – a coordinate the evidence never touched now correctly reports zero gain instead of leaking in the full-state prior entropy.indicesare local to THIS answer’s own coordinates (so repeated/nestedmarginal()calls compose correctly); the original full-space prior and evidence are carried through unchanged soattribution(method="shapley")keeps working after marginalizing.- Parameters:
indices (Any)
- Return type:
ReasonedAnswer
- reason(prior, evidence, *, query=None)[source]
Fuse
evidenceintopriorby exact Kalman assimilation; return the queried posterior.- Parameters:
prior (Any) – the latent’s prior belief (
GaussianBelief; build one withLatent).evidence (Any) –
a sequence of
LinearGaussianEvidenceand/orNonlinearEvidence– one per modality / observation. Nonlinear items assimilate by iterated-EKF linearization (a Gaussian approximation; seeNonlinearEvidence). They are folded in one at a time, and the nats each removes are recorded forReasonedAnswer.attribution().Order independence holds only when every item is
LinearGaussianEvidence: sequential exact Kalman assimilation of linear-Gaussian observations commutes, so folding them in one at a time then equals conditioning on all of them at once, in any order. That guarantee does NOT extend toNonlinearEvidence: each nonlinear item linearizes at the CURRENT belief mean, which itself depends on whatever was already folded in before it. Mixing nonlinear evidence with anything else – other nonlinear evidence included – therefore makes both the posterior and the per-sourceReasonedAnswer.attribution()depend on the orderevidenceis given in (aRuntimeWarningis raised when this applies); pass evidence in a fixed, deliberate order rather than relying on permutation invariance.The POSTERIOR’s order independence does not extend to
attribution()’s default credit split, even for pureLinearGaussianEvidence(MXR-080-0275): two sources that both constrain overlapping coordinates (“redundant” evidence) split their combined information gain differently depending on fold order, because the default allocation is sequential (whichever redundant source is folded first is credited with uncertainty a different order would credit to the other). Useattribution(method="shapley")for an order-invariant split when that matters more than the cost of recomputing it.query (Any) – optional latent coordinate indices to restrict the answer to.
- Returns:
A
ReasonedAnswer– the posterior belief plus attribution and prediction UQ.- Return type:
ReasonedAnswer