mixle.reason package

mixle.reason – the cross-modal scientific-reasoning front door.

A scientific question is a query on a joint posterior over a shared latent that every modality is evidence about. This package wires that idea into one call:

answer = reason(prior, [evidence_from_modality_1, evidence_from_modality_2, …]) answer.mean, answer.interval(0.9) # posterior with calibrated intervals answer.attribution() # which modality sharpened the belief (nats) answer.predict(H, R).epistemic # split a prediction’s uncertainty (epi vs aleatoric)

The exact core here is linear-Gaussian: each modality contributes a linear-Gaussian observation y = H z + noise(R) and the beliefs fuse by exact Kalman assimilation (a product of experts). Learned encoders and application-specific forward models plug in by producing such evidence – a linearized (H, y, R) or a Gaussian expert – so the front door stays stable while encoders vary underneath it.

Built on mixle.inference.belief (the belief state) and mixle.inference.uncertainty (the epistemic/aleatoric split).

class AnchorHarnessReport(modalities, hop_names, coverage_by_hop, abstained_site_ids, abstain_rate, answer_rate, accepted_mae, all_answer_mae, driller_projection_components, scout_projection_components, driller_readout, scout_readout, projection_decision_agreement, gravity_only_mae, both_modalities_mae, gravity_ablation_penalty, test_coverage, coverage_target, coverage_lower_bound, walk_is_calibrated, training_rows, calibration_rows, test_rows, notes=<factory>)[source]

Bases: object

Measured train/calibration/test report for the anchor task.

Parameters:
property walk_mae: float

Compatibility name for the two-stage, two-modality posterior mean MAE.

summary()[source]

Render the measured evaluation boundary and results.

Return type:

str

run_anchor_harness(*, n_train=1200, n_calibration=None, n_test=200, seed=0)[source]

Fit on training data, calibrate on a disjoint split, and evaluate once.

Parameters:
  • n_train (int)

  • n_calibration (int | None)

  • n_test (int)

  • seed (int)

Return type:

AnchorHarnessReport

class Ontology(classes=<factory>, relations=<factory>, axioms=<factory>, disjoint=<factory>, version=None)[source]

Bases: object

A typed schema over knowledge: class hierarchy + relation signatures + axioms + disjointness.

Parameters:
add_class(name, parent=None)[source]

Add a NEW ontology class, optionally under a known parent.

MXR-080-0297: a duplicate name is rejected rather than silently overwriting the existing class’s parent – the prior behavior let a second, differently-parented add_class(name, other_parent) retroactively change what every relation/disjointness declaration mentioning name means, with no trace that a redefinition happened. Use replace_class() to deliberately reparent an existing class.

Raises:

ValueError – name is already registered, or parent is not a known class.

Parameters:
  • name (str)

  • parent (str | None)

Return type:

Ontology

replace_class(name, parent=None)[source]

Deliberately reparent an existing class.

MXR-080-0297: unlike add_class() (which rejects a duplicate name outright), this exists specifically to allow redefining an existing class’s parent – but a parent that IS name, or that is already a descendant of name in the CURRENT hierarchy, is rejected here, before the mutation, rather than silently installed and merely limped around later by is_a()’s cycle guard (a partial-answer, quietly-wrong result).

Raises:
  • KeyError – name is not already registered.

  • ValueError – parent is not a known class, or reparenting would create a self-loop or a cycle.

Parameters:
  • name (str)

  • parent (str | None)

Return type:

Ontology

add_relation(name, domain, range_, *axioms)[source]

Add a NEW relation with domain, range, and optional ontology axioms.

MXR-080-0297: a duplicate name is rejected rather than silently overwriting the existing relation’s signature and axioms. Use replace_relation() to deliberately redefine an existing one.

Raises:

ValueError – name is already registered, domain/range_ is not a known class, or an axiom is not one of AXIOMS.

Parameters:
Return type:

Ontology

replace_relation(name, domain, range_, *axioms)[source]

Deliberately redefine an existing relation’s domain, range, and/or axioms.

MXR-080-0297: the explicit, differently-named counterpart to add_relation()’s reject-on-duplicate.

Raises:
  • KeyError – name is not already registered.

  • ValueError – domain/range_ is not a known class, or an axiom is not one of AXIOMS.

Parameters:
Return type:

Ontology

add_disjoint(a, b)[source]

Declare two classes mutually exclusive.

MXR-080-0297: an unknown class reference, or declaring a class disjoint with ITSELF, is rejected rather than silently accepted – add_disjoint(x, x) previously made check_triple() flag every single instance of x as violating disjointness against itself, and an unknown class reference could never match any real entity, silently defeating the declaration instead of failing loudly at the typo.

Raises:

ValueError – a or b is not a known class, or a == b.

Parameters:
Return type:

Ontology

is_a(cls, ancestor)[source]

Whether cls is ancestor or a descendant of it (walks the parent chain).

MXR-080-0297: construction (add_class()/replace_class()) now rejects every path that could install a cycle, so this walk should never see one. As a defense-in-depth check (e.g. against .classes being mutated directly, bypassing the API) this RAISES if a cycle is nonetheless encountered, rather than silently stopping and returning a partial, possibly wrong answer – a malformed schema must never look like an ordinary “not an ancestor”.

Parameters:
Return type:

bool

check_triple(h, r, t, types)[source]

Every named violation of (h, r, t) given entity types ({} means unconstrained).

Parameters:
Return type:

list[str]

check_graph(triples, types)[source]

Audit a triple set: per-triple violations plus the cross-triple axioms (functional/symmetric/ asymmetric/transitive).

Returns {consistent, n_triples, violations: [{triple, problems}]} – every problem named. Every cross-triple (graph-level) violation also carries an "axiom" key naming which axiom it is (the per-triple violations sourced from check_triple() do not, since one of those can bundle several different kinds of problem for a single triple) – filter_triples() relies on this to apply a deterministic policy per axiom kind (MXR-080-0298).

Parameters:
Return type:

dict[str, Any]

filter_triples(triples, types)[source]

Split triples into (kept, rejected-with-reasons) – the decode mask.

MXR-080-0298: per-triple consistency (check_triple()) alone is not enough – it cannot see that a functional relation was asserted with two different tails, or any other cross-triple axiom violation, so kept could previously contain a set that is globally inconsistent even though every individual triple in it looks fine alone. After the per-triple pass, the accepted set is re-validated AS A GRAPH and any set-level conflict is resolved by _resolve_graph_conflicts()’s deterministic policy; every triple that policy withholds is added to rejected with its reason, same as a per-triple violation.

Parameters:
Return type:

tuple[list[tuple], list[dict[str, Any]]]

class OntologyConstrainedKG(kg, ontology, *, entities, relations, types)[source]

Bases: object

A fitted KG embedding, typed by an ontology: probability mass only on schema-consistent triples.

Wraps a KnowledgeGraphDistribution (entities and relations as integer indices) together with the symbolic ontology and the index<->name maps. The tail posterior is masked to entities that satisfy the FULL triple contract for the completion – range, the head’s domain, irreflexivity, and disjointness (MXR-080-0299) – over a validated, numerically-stable-normalized model output (MXR-080-0300), and renormalized, so completion can never propose an ontology-violating tail nor silently degrade into NaN “probabilities” – Graph(ontology) as a distribution.

Parameters:
tail_posterior(head, relation)[source]

p(tail | head, relation) over ONLY entities satisfying the full triple contract for this completion (renormalized).

MXR-080-0299: previously masked on the candidate tail’s range class alone, so e.g. an irreflexive relation’s self-completion competed for probability mass on equal footing with every genuinely valid tail – for one such relation, self-completion was assigned the LARGEST probability of any candidate. The mask now also checks the head’s domain, irreflexivity, and disjointness.

MXR-080-0300: the raw model output is validated before it is ever normalized – a NaN, +inf, wrong-length, or non-vector tail_log_posterior raises instead of silently producing a dictionary of NaN “probabilities”. Returns {} when the ontology structurally admits no candidate for this completion (an ordinary, expected outcome – e.g. an empty range class), but RAISES when the schema admits candidates and every one of them is nonetheless degenerate (-inf / carries no finite mass): that is an anomalous model/data failure, not a schema fact, and returning {} for both would hide the difference.

Parameters:
Return type:

dict[str, float]

complete(head, relation)[source]

The most probable ontology-consistent tail (or None when the range admits no entity).

Parameters:
Return type:

tuple[str, float] | None

constrained_decode(llm, prompt, ontology, types, *, n=None, floor=0.5, calibrator=None, calibration_population=None)[source]

Decode only schema-consistent facts above a confidence floor.

Samples llm (a GraphLLM) n times, masks every sampled graph through Ontology.filter_triples() (violating triples are rejected with named reasons), then marginalizes the constrained graphs into a GraphDistribution and keeps only facts whose edge marginal clears floor – the calibrated confidence floor (pass a fitted calibrator from fit_fact_calibrator() to apply the floor on calibrated truth probability rather than the raw marginal). Consistent-but-underconfident facts are reported as withheld, never silently dropped: the decode says what it refused to assert and why.

MXR-080-0298: per-sample filtering only guarantees each INDIVIDUAL sampled graph is internally consistent; the published facts set is built by thresholding each triple’s marginal independently, so two triples that never co-occurred in any single sample (e.g. two different tails of a functional relation, each confident in a disjoint subset of samples) could previously both clear the floor and be asserted together – globally inconsistent while still labeled “ontology-constrained”. facts is re-validated as a graph after confidence-sorting, so Ontology.filter_triples()’s “first in the list wins” conflict policy resolves to “highest-confidence wins” at this aggregate stage; every casualty is named in rejected.

MXR-080-0301: floor and every calibrated confidence must be finite and within [0, 1] – see _require_unit_probability() for why these raise rather than silently clip or misclassify. The result’s receipt binds the calibrator’s identity (its class and, when available, its method), the calibration_population the caller asserts it was fit on (this function cannot recover that from an arbitrary calibrator object, so it is taken on trust from the caller – pass whatever identifies the labeled set used with fit_fact_calibrator()), and ontology.version, so a result can be independently audited instead of trusted blind.

Parameters:
Return type:

ConstrainedDecode

class ConstrainedDecode(facts, rejected, below_floor, n_samples, receipt=<factory>)[source]

Bases: object

The result of ontology-constrained LLM decoding: what survived, what the schema rejected, and why.

Parameters:
asserted()[source]

Return facts that passed constraints and confidence floor.

Return type:

list[Any]

as_dict()[source]

Return constrained decoding results as JSON-compatible data.

Return type:

dict[str, Any]

class NonlinearEvidence(h, y, R, jacobian=None, iterations=2, name='')[source]

Bases: object

One modality’s evidence through a nonlinear forward model.

Assimilated by (iterated) extended-Kalman linearization: at the current belief mean m the forward is replaced by its tangent h(z) ~ h(m) + J(m)(z - m) and the exact linear update runs on that tangent; with iterations > 1 the 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. jacobian is 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.

Parameters:
class DiscreteAnswer(belief, attribution=<factory>)[source]

Bases: object

The posterior over hypotheses plus per-source attribution (nats of entropy removed).

Parameters:
property probs: ndarray

Return posterior probabilities over hypotheses.

map()[source]

Return the most likely hypothesis.

Return type:

Any

top(k=3)[source]

Return the top k hypotheses and probabilities.

Parameters:

k (int)

Return type:

list[tuple[Any, float]]

summary()[source]

Render hypothesis probabilities and attribution contributions.

Return type:

str

decide(loss, actions=None, *, abstain_cost=None)[source]

The Bayes-optimal action under this posterior — EXACT over the finite hypothesis set.

Parameters:
  • loss (Any) – an (A, K) matrix (loss[a, k] = cost of action a when hypothesis k is true) or a callable loss(action, hypothesis) -> float.

  • actions (Any) – action labels (defaults to the hypothesis labels — the “declare k” actions).

  • abstain_cost (float | None) – when given, an extra "abstain" action with this flat cost — chosen whenever every committal action’s expected loss exceeds it (the escalate-don’t-guess decision, priced explicitly).

Returns:

{action, expected_loss, alternatives} with the exact expected loss of every candidate.

Return type:

dict[str, Any]

class DecisionAction(*values)[source]

Bases: Enum

Typed out-of-band actions that cannot collide with user action labels.

model_evidence(name, models, x)[source]

Evidence from fitted mixle models: hypothesis k <-> models[k], scored on observation x.

Returns (name, log_lik) with log_lik[k] = models[k].log_density(x).

Parameters:
Return type:

tuple[str, ndarray]

reason_discrete(prior, evidence)[source]

Fold evidence into a categorical belief and return the posterior with per-source attribution.

Parameters:
  • prior (Any) – a CategoricalBelief, an int K (uniform over K), or a list of hypothesis labels (uniform over them).

  • evidence (Any) – a sequence of (name, log_lik_vector) pairs — e.g. from model_evidence() — assimilated in order by exact Bayes.

Return type:

DiscreteAnswer

reason(prior, evidence, *, query=None)[source]

Fuse evidence into prior by exact Kalman assimilation; return the queried posterior.

Parameters:
  • prior (Any) – the latent’s prior belief (GaussianBelief; build one with Latent).

  • evidence (Any) –

    a sequence of LinearGaussianEvidence and/or NonlinearEvidence – one per modality / observation. Nonlinear items assimilate by iterated-EKF linearization (a Gaussian approximation; see NonlinearEvidence). They are folded in one at a time, and the nats each removes are recorded for ReasonedAnswer.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 to NonlinearEvidence: 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-source ReasonedAnswer.attribution() depend on the order evidence is given in (a RuntimeWarning is 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 pure LinearGaussianEvidence (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). Use attribution(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

class Latent[source]

Bases: object

Factories 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.

Parameters:
Return type:

GaussianBelief

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 dim is not an exact positive integer (MXR-080-0274) – a fractional dim used to be silently truncated with int() 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_t with w_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 via reason() 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 T in 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 steps is not an exact positive integer; if x0_mean doesn’t have shape (d,) or isn’t finite; or if x0_cov / process_cov isn’t a finite, symmetric, positive-semidefinite (d, d) covariance (MXR-080-0274) – previously steps was silently truncated with int() 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

Evidence

alias of LinearGaussianEvidence

class LinearGaussianEvidence(H, y, R, name='')[source]

Bases: object

One modality’s evidence about the latent z: y = H z + noise, noise ~ N(0, R).

H is the (possibly linearized) forward operator mapping the latent to this modality’s measurement space, y the observed data, R its noise covariance (matrix, diagonal, or scalar). Application forward models (e.g. mixle_pde geophysics operators) produce these.

Parameters:
block_selector(step, n_blocks, block_dim, within=None)[source]

An observation matrix that reads time-block step of a stacked trajectory latent.

For a latent built by Latent.mechanistic() (shape (n_blocks * block_dim,)), returns the H selecting block step – use it to build LinearGaussianEvidence for an observation at that time. within optionally 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_dim is not an exact positive integer; if step is not an exact integer or falls outside [0, n_blocks); or if within doesn’t have block_dim columns (MXR-080-0274). step used 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 beyond n_blocks fell through to a confusing low-level “could not broadcast” error instead of a clear one.

Parameters:
Return type:

ndarray

class ReasonedAnswer(belief, prior, trace, *, full_prior=None, evidence=None, query_idx=None)[source]

Bases: object

A posterior belief about a query, with the UQ a scientific answer needs.

Beyond mean / interval / entropy (delegated to the belief), it exposes attribution() – the nats of uncertainty each modality removed – and predict(), which splits a prediction’s uncertainty into epistemic (from latent uncertainty) and aleatoric (observation noise) via the law of total variance.

Parameters:
  • belief (BeliefState)

  • prior (BeliefState)

  • trace (list[tuple[str, BeliefState]])

  • full_prior (BeliefState | None)

  • evidence (list[Any] | None)

  • query_idx (Any)

property mean: ndarray

Return posterior mean from the underlying belief.

cov()[source]

Return posterior covariance from the underlying belief.

Return type:

ndarray

sd()[source]

Return posterior standard deviations from the underlying belief.

Return type:

ndarray

entropy()[source]

Return posterior entropy from the underlying belief.

Return type:

float

interval(level=0.9)[source]

Per-coordinate central credible interval at level (an (d, 2) array of [lo, hi]).

Parameters:

level (float)

Return type:

ndarray

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:

float

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 are H[belief before it] - H[belief after it], in the order evidence was given to reason(). Cheap (one pass over the fold trace) and always sums exactly to information_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 – see reason()); 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 to information_gain()) without depending on which order the caller listed evidence in. Exact for up to _SHAPLEY_EXACT_MAX_SOURCES sources (O(n!) re-folds of the full evidence sequence); beyond that it averages n_permutations uniformly random permutations instead (O(n_permutations * n) re-folds), a standard permutation-sampling Monte Carlo estimator whose standard error shrinks as O(1 / sqrt(n_permutations)). Pass n_permutations= / rng= to control the sampling estimate. Each re-fold repeats the full (possibly nonlinear, possibly expensive) assimilation from the original prior, so this costs O(n) to O(n!) times a single reason() 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.

Parameters:
Return type:

dict[str, float]

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) and aleatoric = diag(R) (irreducible observation noise). Exact for the Gaussian belief.

Parameters:
  • H (Any) – (k, d) observation operator, where d is this belief’s own latent dimension. Its shape must match exactly – it is never reshaped from a looser guess, since a wrong-width H can have a total element count that happens to divide evenly by d and so silently describe a DIFFERENT (still valid-looking) operator instead.

  • R (Any) – the prediction’s noise covariance: a scalar (shared by all k outputs), a length-k vector 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, or R is 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 default attribution() 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. indices are local to THIS answer’s own coordinates (so repeated/nested marginal() calls compose correctly); the original full-space prior and evidence are carried through unchanged so attribution(method="shapley") keeps working after marginalizing.

Parameters:

indices (Any)

Return type:

ReasonedAnswer

class GaussianBelief(mean, cov)[source]

Bases: BeliefState

A multivariate-Gaussian belief N(mean, cov) over a continuous latent.

Evidence is a linear-Gaussian observation y = H z + noise, noise ~ N(0, R); update() applies the exact Kalman measurement update (Joseph form, so the covariance stays symmetric positive-definite). fuse() combines two beliefs about the same latent as a product of Gaussian experts only with an explicit fusion contract. condition() does noiseless Gaussian conditioning on a coordinate subset.

Parameters:
  • mean (Any)

  • cov (Any)

property dim: int

Return the latent dimensionality.

mean()[source]

Return a copy of the Gaussian mean vector.

Return type:

ndarray

cov()[source]

Return a copy of the Gaussian covariance matrix.

Return type:

ndarray

entropy()[source]

Return differential entropy of the Gaussian belief in nats.

Return type:

float

interval(level=0.9)[source]

Return marginal central intervals for each coordinate.

Parameters:

level (float)

Return type:

ndarray

sample(n=1, rng=None)[source]

Draw samples from the Gaussian belief.

Parameters:
Return type:

ndarray

update(H, y, R)[source]

Kalman measurement update: condition on y = H z + noise, noise ~ N(0, R).

Parameters:
  • H (Any) – (k, d) observation matrix (or (d,) / scalar for a single linear readout).

  • y (Any) – (k,) observed value (or scalar).

  • R (Any) – (k, k) observation-noise covariance (or (k,) diagonal / scalar).

Return type:

GaussianBelief

fuse(other, *, shared_prior=None, other_is_likelihood=False)[source]

Fuse a posterior or an explicitly identified Gaussian likelihood message.

For two posteriors derived from the same prior, pass shared_prior; its information is subtracted once. If other is instead a likelihood factor represented by a Gaussian kernel, set other_is_likelihood=True. Omitting both is ambiguous and rejected.

Parameters:
  • other (GaussianBelief)

  • shared_prior (GaussianBelief | None)

  • other_is_likelihood (bool)

Return type:

GaussianBelief

condition(indices, values)[source]

Noiseless Gaussian conditioning: fix latent coordinates indices to values.

Returns the belief over the remaining coordinates. This is the exact R -> 0 limit of an observation that reads off those coordinates.

Parameters:
Return type:

GaussianBelief

marginal(indices)[source]

Return the marginal Gaussian belief over selected coordinates.

Parameters:

indices (Any)

Return type:

GaussianBelief

class BeliefState[source]

Bases: ABC

A distribution over a latent, exposing a uniform query + update interface.

Realizations answer where they are defined: mean(), cov(), var(), sd(), entropy(), interval(), sample(), marginal(), and – the point of a belief state – update(), which returns a new belief conditioned on fresh evidence.

abstractmethod mean()[source]

The posterior mean of the latent.

Return type:

ndarray

abstractmethod entropy()[source]

The differential/Shannon entropy H[q] (nats) – watch it shrink as evidence arrives.

Return type:

float

abstractmethod sample(n=1, rng=None)[source]

Draw n latent samples from the belief.

Parameters:
Return type:

ndarray

abstractmethod update(*args, **kwargs)[source]

Return a new belief conditioned on fresh evidence (the assimilation step).

Parameters:
Return type:

BeliefState

cov()[source]

The posterior covariance (not defined for every realization).

Return type:

ndarray

var()[source]

Per-coordinate posterior variance.

Return type:

ndarray

sd()[source]

Per-coordinate posterior standard deviation.

Return type:

ndarray

interval(level=0.9)[source]

Per-coordinate central credible interval at level – an (d, 2) array of [lo, hi].

Parameters:

level (float)

Return type:

ndarray

marginal(indices)[source]

The belief restricted to a subset of latent coordinates.

Parameters:

indices (Any)

Return type:

BeliefState

class AcquisitionPlan(items=<factory>, total_cost=0.0, total_gain=0.0, belief=None)[source]

Bases: object

A budgeted evidence-acquisition plan: the chosen items, the nats they bought, and the final belief.

Parameters:
property indices: list[int]

Return selected evidence item indices.

select_evidence_batch(store, belief, *, budget, query=None, fine_cost=1.0, coarse_cost=0.2, fidelities=('coarse', 'fine'), candidates=None, max_items=None, min_gain=1e-9, evidence_is_observed=False)[source]

Select already-observed evidence by realized gain under a total budget.

This function evaluates the actual evidence built from each payload, so it is not a pre-acquisition expected-information-gain planner. Callers must explicitly set evidence_is_observed=True to confirm the candidate payloads are already observed and selection does not leak hidden outcomes. A future acquisition requires a predictive observation model.

Parameters:
Return type:

AcquisitionPlan

as_belief(obj, node=None)[source]

Adapt any object exposing mean/cov (a FieldPosterior node, a fitted Gaussian, a ParameterPosterior) into a GaussianBelief.

node is forwarded when the source is node-addressable (e.g. FieldPosterior.mean(node) / .cov(node)); otherwise mean/cov are called with no argument.

Parameters:
Return type:

GaussianBelief

class CrossModalStore(keys, payloads, *, coarse, fine, metric='euclidean')[source]

Bases: object

A corpus indexed by embedding keys, with raw payloads conditioned on when embeddings fall short.

Parameters:
  • keys (Any) – (N, d_key) embedding vectors used as the retrieval index.

  • payloads (Sequence[Any]) – length-N sequence of raw items (arbitrary; passed to coarse/fine).

  • coarse (Callable[[Any], LinearGaussianEvidence]) – payload -> LinearGaussianEvidence at embedding fidelity (low-cost, lossy).

  • fine (Callable[[Any], LinearGaussianEvidence]) – payload -> LinearGaussianEvidence at raw fidelity (precise, “expensive”).

  • metric (str) – "euclidean" (default) or "cosine" for retrieval.

retrieve(query_key, k=8)[source]

Return indices of the nearest k embedding keys to query_key.

Parameters:
Return type:

list[int]

assimilate(belief, query_key, *, k=8, query=None, epsilon=0.0)[source]

Retrieve neighbors and fold selected evidence into belief.

For each retrieved item the sufficiency test compares how much raw evidence would reduce query entropy relative to embedding evidence. If the surplus exceeds epsilon the raw payload is used, else the embedding evidence is. Returns the updated belief and a per-item provenance trail.

Parameters:
  • belief (GaussianBelief)

  • query_key (Any)

  • k (int)

  • query (Any)

  • epsilon (float)

Return type:

tuple[GaussianBelief, list[RetrievalStep]]

next_evidence(belief, *, query=None, candidates=None, fidelity='fine')[source]

Active retrieval: the corpus item whose evidence most reduces the query entropy (EIG).

Returns (index, expected_gain_nats). fidelity selects the fine (raw) or coarse (embedding) evidence builder for the look-ahead.

Parameters:
  • belief (GaussianBelief)

  • query (Any)

  • candidates (Sequence[int] | None)

  • fidelity (str)

Return type:

tuple[int, float]

class RetrievalStep(index, fidelity, gain)[source]

Bases: object

Provenance for one assimilated item: which corpus index, at what fidelity, and the nats it removed.

Parameters:
class LLMUncertainty(generate, *, equivalent=None, n=10)[source]

Bases: object

Calibrated uncertainty and selective prediction for any generate(prompt) -> str LLM.

Parameters:
  • generate (Callable[[str], Any]) – callable(prompt) -> str – one stochastic sample from the model.

  • equivalent (Callable[[Any, Any], bool] | None) – callable(a, b) -> bool deciding whether two answers mean the same thing (default exact match; pass a normalizer / embedding / entailment check for real text).

  • n (int) – default number of samples per prompt (must be a positive integer).

sample(prompt, n=None)[source]

Draw n stochastic responses to prompt, normalized to Generation records.

generate may return a plain string, or a (text, logprob) pair – the sequence log-probability log P(s). Normalized ONCE here, at the boundary (MXR-080-0296): every one of assess() / decompose() / assess_claims() consumes the same list[Generation] shape rather than each re-deriving (or failing to re-derive) whether generate returned tuples. generate must return the same shape on every call (plain text throughout, or (text, logprob) throughout) – a generator that mixes the two raises rather than silently letting the unrecognized shape through unflagged. n (explicit or the constructor default) must be a positive integer – see _require_positive_n().

Parameters:
Return type:

list[Generation]

assess(prompt, n=None)[source]

Sample, marginalize the string distribution over meaning classes, and report the answer.

The reported confidence is the marginal probability of the top meaning (summed over its equivalence class of strings), and semantic_entropy the entropy of that meaning marginal – not a per-string token probability.

Parameters:
Return type:

LLMAssessment

decompose(prompts, n=None)[source]

Epistemic/aleatoric split across member prompts (paraphrases of one question).

Each prompt is a member; all members’ samples are pooled to define shared meaning-clusters, then each member’s distribution over those clusters feeds decompose_entropy(). Epistemic = disagreement across paraphrasings (prompt-sensitivity / model uncertainty); aleatoric = within-member spread. Clusters on each sample’s TEXT (MXR-080-0296) – previously this clustered whatever sample() returned raw, so a (text, logprob) generator got its tuples compared for equality instead of their text, and every logprob was silently discarded.

Parameters:
Return type:

UncertaintyDecomposition

assess_claims(prompt, *, extract=None, corroborates=None, n=None, threshold=0.5)[source]

Score the reliability of each claim in the response by cross-sample corroboration.

Finer-grained than assess(): a response can be internally consistent (low semantic entropy) yet contain one fabricated fact. This decomposes the response into claims and checks each unit of information separately – a claim the model knows recurs, unnegated, across independent resamples; a hallucinated one appears once; a claim a resample actively disagrees with is flagged regardless of how much vocabulary it shares with that resample. This is UQ on the information in what is said, not just on the answer as a whole.

Parameters:
  • prompt (str) – the query.

  • extract (Callable[[str], Sequence[str]] | None) – response -> [claim, ...] (default sentence_claims()).

  • corroborates (Callable[[str, str], Corroboration | bool] | None) – (other_sample, claim) -> Corroboration – does a resample support the claim? (default: information_corroborator()’s overlap as candidacy, gated by a negation/polarity check – see _default_claim_corroborator(); pass an entailment/NLI check for real text.) A plain bool-returning callable is still accepted for compatibility: True -> SUPPORTED, False -> UNVERIFIED, never CONTRADICTED (a bare bool cannot express disagreement).

  • n (int | None) – number of samples (the first is the response scored; the rest corroborate). Must be a positive integer – see _require_positive_n().

  • threshold (float) – support below which a claim is flagged as unreliable/fabricated.

Return type:

InformationAssessment

calibrate(examples, *, correct=None, alpha=0.1, delta=0.05, n=None, policy_token=None)[source]

Calibrate a selective-risk threshold with a finite-sample (alpha, delta)-PAC guarantee.

For each labeled example, the model’s answer (majority meaning-cluster) and its confidence are computed; correct(answer, gold) (default the equivalent relation) marks it right or wrong. The threshold is chosen by _selective_risk_threshold() (MXR-080-0293): the smallest confidence tau (the most inclusive answered set) from a pre-specified 1001-point candidate grid whose exact Clopper-Pearson upper confidence bound on the selective error rate – Bonferroni-corrected across the whole grid – is <= alpha.

Statistical guarantee and assumptions: assuming the calibration (prompt, gold) examples and future queries are i.i.d. draws from the same distribution, AND the entire stochastic serving policy – the generator’s conditional output law, the equivalence relation’s behavior, and the per-prompt sample count – remains BEHAVIORALLY IDENTICAL to the calibrated one, with probability >= 1 - delta over the randomness of the calibration set, the deployed threshold’s TRUE selective risk (P(wrong | confidence >= threshold) on a fresh query) is <= alpha. The behavioral condition is load-bearing and only PARTLY enforceable: answer() refuses rebound callables and sample-count overrides (STAT-RR21-17), but IN-PLACE state mutation inside the same callable passes any identity check – an exact-wheel probe that flipped generator.mode served selective risk 1.0 under a retained 0.10 certificate (STAT-RR22-09). Pin the model identifier, prompt template, decoding parameters, and equivalence version, pass them as policy_token, and recalibrate whenever any of them changes; the token is stored on the certificate and echoed so serving infrastructure can compare it against the live deployment. This is a proper finite-sample (alpha, delta)-PAC guarantee (Geifman & El-Yaniv 2017 “Selective Classification for Deep Neural Networks”; Angelopoulos et al. 2021 “Learn then Test”) – NOT a same-sample point estimate. The previous implementation picked the smallest threshold whose SAME-SAMPLE empirical error happened to be <= alpha, with no correction for either (a) the calibration set being finite, or (b) implicitly searching over every candidate threshold on that same data – so a small calibration set could approve a threshold after one lucky correct example, with no relationship to the advertised 1 - alpha guarantee. delta is the honest second parameter that same-sample selection omitted entirely: no finite calibration set can certify a threshold with zero failure probability.

If no threshold’s bound clears alpha (too little calibration data for the requested alpha/delta), the threshold is set just above the maximum observed confidence, so answer() abstains on everything rather than deploying an uncertified threshold.

Parameters:
  • examples (Sequence[tuple[str, Any]]) – labeled (prompt, gold_answer) pairs to calibrate against; must be non-empty.

  • correct (Callable[[Any, Any], bool] | None) – (answer, gold) -> bool (default the equivalent relation). The return value must be a real Boolean or an exact 0/1 integer; anything else raises rather than being truthiness-coerced (bool("false") is True).

  • alpha (float) – target selective risk (miscoverage), in the open interval (0.0, 1.0) – the guarantee is on the TRUE risk, not this calibration set’s empirical risk.

  • delta (float) – failure probability of the guarantee itself, in the open interval (0.0, 1.0) – the guarantee holds with probability >= 1 - delta over the random calibration set.

  • n (int | None) – samples per prompt (passed to assess()).

  • policy_token (str | None)

Return type:

LLMUncertainty

certified_policy_token()[source]

The caller-pinned policy version the certificate covers, or None if none was supplied.

STAT-RR22-09: callable identity cannot bind behavior; this token is the caller’s own record of the model/prompt/decoding/equivalence version, stored at calibration for infrastructure-level comparison against the live deployment.

Return type:

str | None

answer(prompt, n=None)[source]

Answer prompt if confident enough, else None (abstain).

Requires a prior calibrate(). Returns the LLMAssessment when confidence >= threshold (so the answer meets the calibrated (alpha, delta) selective-risk guarantee), else None.

The guarantee covers exactly the CALIBRATED policy – generator, equivalence relation, and per-prompt sample count – so serving under any other is refused rather than silently keeping the certificate (STAT-RR21-17: an n=1 override on an n=2 certificate answered 20,000/20,000 fresh queries at selective risk 0.6015 against the certified 0.10; a mutated equivalence reached 0.604 and a swapped generator 1.0). WHAT THE REFUSALS CAN AND CANNOT CATCH (STAT-RR22-09): rebinding and count drift are caught by identity checks; IN-PLACE mutation of state inside the same callable is not detectable from here – a flipped generator.mode passed the guard and served risk 1.0 under the retained 0.10 certificate – so the certificate is conditional on the serving policy remaining behaviorally identical, and certified_policy_token() exposes the caller-pinned model/prompt/decoding/version token for infrastructure-level comparison. To serve a different policy, calibrate it: build the object with that generator/equivalence/n and call calibrate() again. assess() remains available at any n – it claims no certificate.

Parameters:
Return type:

LLMAssessment | None

fit_factuality(examples, *, signal=None, correct=None, method='isotonic', n=None)[source]

Learn a calibrated P(answer is correct) from a raw signal, on labeled (prompt, gold).

The model’s raw confidence (its self-consistency, or a token likelihood) is not a probability that the information is correct – it can be systematically over/under-confident, or unrelated to truth. This fits a ProbabilityCalibrator mapping the signal to the empirical correctness rate, so the output is a probability of the information being right. discrimination is the RESUBSTITUTION (fit-sample) tie-corrected AUC of signal vs correctness – the same rows that fit the calibrator, so it is optimistic and unstable at small n (STAT-RR23-07: a 20-row independent-null draw read 0.96 there while 100,000 fresh rows measured 0.4997); measure discrimination you intend to report on rows this fit never saw. correct must return a REAL Boolean or exact 0/1 integer – the correctness verdicts are the calibration TARGET, and truthiness coercion let a callback returning the string “false” turn an always-wrong generator (actual correctness 0.0) into probability(...) == 1.0; the same refusal already guards calibrate().

Parameters:
  • examples (Sequence[tuple[str, Any]]) – labeled (prompt, gold_answer) pairs.

  • signal (Callable[[str], float] | None) – prompt -> float raw score (default: the self-consistency confidence from assess()).

  • correct (Callable[[Any, Any], bool] | None) – (answer, gold) -> bool (default the equivalent relation).

  • method (str) – calibration map – "isotonic" or "platt".

  • n (int | None) – samples per prompt.

Return type:

FactualityModel

class LLMAssessment(answer, confidence, semantic_entropy, clusters, samples)[source]

Bases: object

One prompt’s assessed answer with uncertainty.

answer is the majority meaning-cluster’s representative; confidence its cluster share in [0, 1]; semantic_entropy the nats of meaning-uncertainty; clusters the [(representative, probability), ...] distribution over meanings; samples the raw draws.

Parameters:
class ClaimAssessment(claim, support, contradicted, reliable)[source]

Bases: object

Reliability of one claim inside a response, by cross-sample corroboration.

support is the fraction of independent resamples whose corroboration verdict was Corroboration.SUPPORTED (in [0, 1]); contradicted is the fraction verdicted Corroboration.CONTRADICTED – a distinct, stronger-than-merely-unsupported signal: some resample did not just fail to back the claim, it actively disagreed (MXR-080-0295; mirrors mixle.substrate.factuality.ClaimVerdict). reliable requires both good support AND zero contradiction (contradicted == 0.0 and support >= threshold): lexical overlap with a resample is never enough by itself, since a negated resample can share most of a claim’s vocabulary while disagreeing with it. A claim the model actually knows recurs, unnegated, across samples (high support, zero contradiction); a fabricated one appears once and vanishes (low support); a claim the model contradicts itself on is flagged regardless of raw overlap.

Parameters:
class InformationAssessment(claims, reliability)[source]

Bases: object

UQ over the information content of a response: every claim scored, plus a summary.

claims is the per-claim reliability; reliability the mean support across claims (how trustworthy the response’s information is overall) – None when the response had no extractable claims at all (empty, unparseable, or evasive) rather than a vacuous 1.0 (MXR-080-0295; mirrors mixle.substrate.factuality.FactualityReceipt.grounded_fraction): there is nothing that was checked, so this is UNASSESSED, not “everything checked out.” fabricated is the claims below threshold (likely hallucinated).

Parameters:
  • claims (list[ClaimAssessment])

  • reliability (float | None)

property fabricated: list[ClaimAssessment]

Return claims assessed as unreliable.

is_reliable(threshold=1.0)[source]

True iff overall reliability meets threshold (default 1.0: every claim must be reliable).

Fails closed when there is nothing to assess: a response with no extractable claims is never reported as “reliable”, regardless of threshold – mirrors mixle.substrate.factuality.FactualityReceipt.is_grounded().

Parameters:

threshold (float)

Return type:

bool

class FactualityModel(calibrator, signal, discrimination)[source]

Bases: object

A fitted map from a per-prompt uncertainty signal to a calibrated P(answer is correct).

The signal (self-consistency, a token likelihood, …) is only a raw number; the calibrator turns it into a genuine probability of the information being correct, learned against labeled facts. discrimination (RESUBSTITUTION AUC on the fit rows, tie-correct – see _auc(); “held-out” was a false label, STAT-RR23-07) reports how much the signal actually knew about correctness – ~0.5 means the signal was unrelated to truth, no matter how confident it looked.

Parameters:
probability(prompt)[source]

Calibrated probability that the answer’s information is correct.

Parameters:

prompt (str)

Return type:

float

sentence_claims(text)[source]

Split a response into atomic claims (sentence-ish units) – the default claim extractor.

Parameters:

text (str)

Return type:

list[str]

content_overlap(sample, claim, *, threshold=0.6)[source]

Simple corroboration test: does sample cover >= threshold of claim’s content words?

Counts every content word equally, so boilerplate shared across responses (“the tower is located in …”) can mask that the informative word (the city) differs. information_corroborator() fixes that by weighting words by their information content, and is the candidacy signal behind LLMUncertainty.assess_claims()’s default corroborator. Like information_corroborator(), this measures topical overlap only, never polarity: a negated sentence sharing a claim’s content words still counts as covering them. mixle.substrate.factuality layers a negation/polarity check on top of this exact function for that reason (see its Corroboration), and _default_claim_corroborator() below does the analogous thing for this module.

Parameters:
Return type:

bool

class ModalityView(name, dist, symmetry_group='none', notes=<factory>)[source]

Bases: object

One modality as a typed structured belief: a real mixle distribution plus its symmetry group.

dist is any fitted SequenceEncodableProbabilityDistribution: for example a categorical model over labels, a Gaussian or Student-t model over measurements, a neural density over an embedding-shaped field, or a Bayesian network over a structured record. symmetry_group names the invariance the modality declares, such as "none", "translation", "permutation", or "rotation".

Parameters:
score(x)[source]

Return log p(x) under this modality’s structured belief.

Parameters:

x (Any)

Return type:

float

sample(n=1, *, seed=None)[source]

Draw from this modality’s own sampler.

Parameters:
Return type:

Any

seq_score(xs)[source]

Return vectorized log p(x) over a batch via the modality encoder.

Parameters:

xs (Any)

Return type:

Any

class ModalityGraph(views=<factory>)[source]

Bases: object

A named collection of ModalityView for one entity.

Belief walks hop across this joint representation. A receiver reads named modalities and their own scores rather than an implicit shared vector.

Parameters:

views (dict[str, ModalityView])

add(view, *, replace=False)[source]

Add a modality view and return the graph for chaining.

A modality identity is unique inside one graph. Assigning by name alone silently destroyed the previous view – its distribution AND its notes – so a second "sensor" belief changed every later joint score with no trace. A duplicate name is rejected unless replace=True makes the substitution explicit; the replaced view is returned to the caller’s own bookkeeping through __getitem__() before it is dropped.

Parameters:
  • view (ModalityView)

  • replace (bool)

Return type:

ModalityGraph

modalities()[source]

Return modality names in sorted order.

Return type:

list[str]

joint_score(observations)[source]

Return per-modality log p(x) for named observations.

Every observation must name a modality this graph actually holds. Filtering unknown names away meant a query mentioning "missing_sensor" was answered from a different evidence set than the caller supplied – silently, and indistinguishably from having scored it.

Raises:

KeyError – if any observation names a modality that is not in the graph.

Parameters:

observations (dict[str, Any])

Return type:

dict[str, float]

fuse_models(prior, claims, *, query=None, disagree_sigma=3.0, cross_covariances=None)[source]

Fuse several models’ claims about the same latent into one belief (precision-weighted Kalman).

Each claim’s noise is scaled by 1 / reliability and named model_id@version (DR-ALG L5/M2 precision weighting), then all claims are folded into prior in one pass via mixle.reason.core.reason() – the existing multi-source assimilation path, with one evidence per model rather than per modality. Per-model attribution comes straight from ReasonedAnswer.attribution(). Before returning, each pair of models’ individual (solo) raw measurement claims are compared with a rank-aware Mahalanobis test whose chi-square confidence mass corresponds to disagree_sigma. An incomparable pair also causes abstention rather than silently passing.

Parameters:
  • prior (GaussianBelief) – the latent’s prior belief.

  • claims (list[ModelClaim]) – one ModelClaim per model (must have distinct model_id@version identity).

  • query (Any) – optional latent coordinate indices to restrict the fused answer (and the disagreement check) to.

  • disagree_sigma (float) – the standardized-distance threshold above which models are considered to be in conflict.

  • cross_covariances (Mapping[tuple[str, str], Any] | None) – optional pairwise covariance between model measurement errors, keyed by (model_name, model_name). Missing pairs are explicitly treated as independent.

Returns:

A ModelFusionResult with the fused answer, per-model weights, the disagreement report, and the abstain flag.

Return type:

ModelFusionResult

class TaskReadout(name, label, projection_error=None, max_projection_error=0.0)[source]

Bases: object

Task readout used to decide which mixture components can be merged.

label(mean) maps a component mean to a discrete readout value. Components sharing a readout are indistinguishable for this task and may be merged; components with different readouts remain separate.

Parameters:
task_sufficient_projection(mixture, task)[source]

pi_T(mixture): collapse mixture’s components into groups sharing task.label.

Components are grouped by task.label(component_mean). Groups with more than one component are moment-matched by collapse_mixture(); singleton groups pass through unchanged. The result never has more components than the input.

Parameters:
  • mixture (Any)

  • task (TaskReadout)

Return type:

MixtureDistribution

read_out(mixture, task, x)[source]

Return the task label of the component most responsible for x.

The same readout applies to a full or projected belief, so a projection can be evaluated by the task labels it preserves.

Parameters:
  • mixture (Any)

  • task (TaskReadout)

  • x (Any)

Return type:

Hashable

cycle_inconsistency(sampler, given_value, *, n_draws=20, forward, scale)[source]

Return scaled A -> B -> A round-trip inconsistency for one observation.

sampler draws B conditioned on the originating A value and forward maps each B draw back into A space. scale supplies the positive, unit-aware residual scale for A. A constant but wrong forward map therefore receives a nonzero error instead of a falsely perfect dispersion score.

This answers “is the forward map right here”, NOT “is B determined here”. Closure error cannot see posterior ambiguity, and on a collapsing observation map it is anti-correlated with it: where many B values share one A value, every draw round-trips back onto that same A, so the cycle closes perfectly exactly where B is least identified. Use posterior_ambiguity for that question.

Parameters:
Return type:

float

posterior_ambiguity(sampler, given_value, *, n_draws=20, target_scale)[source]

Return the standardized spread of the B draws for one A observation.

A well-determined posterior yields draws that agree closely; an observation in a collapsed region – where many B values produce the same A – yields draws that disagree, without needing the true B. target_scale supplies the positive, unit-aware scale for B.

This is the abstention signal, and it is deliberately NOT folded into cycle_inconsistency. The two answer different questions and disagree by construction: on a collapsing observation map every draw round-trips back onto the same A, so closure error is LOWEST exactly where B is least determined. Summing them lets the anti-correlated closure term outvote the spread. Use cycle_inconsistency to ask whether the forward map is right, and this to ask whether B is pinned down.

Parameters:
Return type:

float

class CycleKLReceipt(source, target, raw_estimate, standard_error, confidence_interval, sample_count, nonnegative_estimate, clipped_to_nonnegative)[source]

Bases: object

Monte Carlo evidence for a probabilistic round-trip comparison.

Parameters:
fit_cycle_transport(given, target, *, k=3, hidden=32, layers=2, max_its=30, m_steps=80, lr=3e-3, seed=0, delta=1.0e-9, reuse_estep_ll=True)[source]

Fit p(target | given) via a mixture density network.

given/target are (n, d) arrays of paired observations. delta/reuse_estep_ll default to optimize()’s own early-stopping; pass delta=None, reuse_estep_ll=False for a harder, more multimodal target.

Parameters:
Return type:

Any

posterior_mean_estimate(sampler, given_value, *, n_draws=20)[source]

Return the posterior-sample mean of the target given given_value.

Parameters:
Return type:

ndarray

selective_error(errors, abstain_scores, keep_frac)[source]

Return mean error on examples kept by the lowest abstention scores.

Lower is better: a useful abstention signal keeps examples the policy can answer and escalates examples with higher expected error.

Parameters:
Return type:

float

class CrossModalJoint(names, joint)[source]

Bases: object

A joint over named modalities sharing one latent regime (a mixture component index).

names fixes the modality-name -> composite-field-index mapping; joint is a MixtureDistribution whose components are CompositeDistribution instances over len(names) heterogeneous fields, in names order. The mixture weights are the shared latent’s prior p(regime); each component is p(modality_0, modality_1, ... | regime=k).

Parameters:
  • names (tuple[str, ...])

  • joint (MixtureDistribution)

classmethod from_components(names, component_fields, weights)[source]

Build a shared-latent joint from per-regime per-modality distributions.

component_fields[k] is the sequence of len(names) per-modality distributions for latent regime k (in names order); weights[k] is p(regime=k). Each regime is wrapped as one CompositeDistribution over the (heterogeneous) modality fields and the regimes are mixed, so the resulting joint’s own component index is exactly the shared latent tying every modality together.

Parameters:
Return type:

CrossModalJoint

infer(observed, target=None)[source]

Posterior over target modalities given observed values for any OTHER subset.

observed maps modality name -> its observed value, for any subset (including the empty set, which returns the marginal/prior). target names the modalities to infer the joint posterior over; defaults to every modality not in observed. Every target name must be absent from observed (you cannot condition on and infer the same modality).

Returns a MixtureDistribution over a len(target)-tuple, in target order (a 1-modality target is a mixture over a 1-tuple, matching CompositeDistribution’s own convention for a single field).

Parameters:
Return type:

MixtureDistribution

joint_cycle_consistency_receipt(joint, source, target, *, backward_joint=None, n_round_trip=300, n_kl_samples=500, seed=0)[source]

Cross-modal generalization (workstream L2) of this module’s round-trip closure signal.

cycle_inconsistency above measures round-trip closure (A -> B -> A) for a NEURAL transport, where the true target is unknown at serving time and self-AGREEMENT among repeated draws is the only available proxy. A CrossModalJoint is a typed grammar object, not an opaque transport: its true marginal p(source) is available in closed form (CrossModalJoint.infer() with no observations), so the round-trip receipt here compares the round-trip estimate DIRECTLY against that true marginal, rather than against itself.

Two ways to arrive at a belief about source through the joint: (1) directly, its own marginal p(source); (2) via a round trip, p(source) -> infer p(target | source) -> infer p(source | target) back, averaged over many draws into one aggregate “round-trip” belief. This receipt is a Monte-Carlo KL-divergence estimate between (2) and (1); a well-specified joint recovers its own marginal on a round trip (the receipt is ~0 up to Monte-Carlo noise), while a deliberately mis-specified backward projection (backward_joint – e.g. a joint whose target-given-regime distributions have been shuffled relative to joint’s, standing in for a broken/incompatible A<-B projection) breaks that identity and the receipt becomes clearly, measurably elevated.

Parameters:
  • joint (CrossModalJoint)

  • source (str)

  • target (str)

  • backward_joint (CrossModalJoint | None)

  • n_round_trip (int)

  • n_kl_samples (int)

  • seed (int)

Return type:

CycleKLReceipt

class HopTransport(name, fit, receipt, input_dim, output_dim)[source]

Bases: object

One edge of the belief walk and its independently produced premise receipt.

Explicit dimensions prevent a sampler from silently changing its batch contract while the receipt binds the edge to a verifier and held-out data.

Parameters:
  • name (str)

  • fit (Any)

  • receipt (PremiseReceipt)

  • input_dim (int)

  • output_dim (int)

sampler(seed=None)[source]

Return the sampler for this hop’s fitted transport.

Parameters:

seed (int | None)

Return type:

Any

class WalkResult(hop_names, samples)[source]

Bases: object

The belief walk’s outcome: an empirical posterior over the final hop’s variable.

Parameters:
property mean: ndarray

Return posterior sample mean for the final hop.

property std: ndarray

Return posterior sample standard deviation for the final hop.

credible_interval(alpha=0.1)[source]

Return marginal credible interval bounds from walk samples.

Parameters:

alpha (float)

Return type:

tuple[ndarray, ndarray]

simultaneous_credible_interval(alpha=0.1)[source]

Return Bonferroni intervals with declared joint target 1 - alpha.

Parameters:

alpha (float)

Return type:

tuple[ndarray, ndarray]

belief_walk(hops, x0, *, n_draws=200, seed=0)[source]

Propagate a belief forward through a chain of hops, starting from a single value x0.

Each hop’s transport is applied by drawing n_draws samples of the current belief and pushing each through the hop’s sample_given method. Raises unless every hop carries an affirmative premise receipt.

Parameters:
Return type:

WalkResult

coverage_by_hop_count(hops, x0_test, true_final, *, alpha=0.1, n_draws=150, seed=0)[source]

Return empirical calibration by hop count.

For k = 1 .. len(hops), walks the first k hops for every test point in x0_test and checks credible-interval coverage of true_final[k] against the nominal 1 - alpha rate with a two-sided binomial test. true_final must supply ground truth for each checked hop count.

Parameters:
Return type:

dict[int, dict[str, float]]

information_corroborator(samples, *, overlap=0.5)[source]

Build a corroboration test that weights each word by its information content over samples.

A word appearing in nearly every sample is boilerplate (low information, low weight); a rare word carries the actual claim (high weight). A sample corroborates a claim when it covers at least overlap of the claim’s information-weighted words – so whether the distinctive fact (a city, a number, a name) matches drives the decision, not the shared filler. Inverse-document-frequency weighting: w(word) = log((N + 1) / (df + 0.5)).

This is a CANDIDACY test, not a support test: it says nothing about polarity, so a sample that negates the claim while sharing its distinctive words still corroborates by this measure alone (content_overlap() has the identical limitation). LLMUncertainty.assess_claims()’s default corroborator (_default_claim_corroborator()) uses this as its candidacy signal, gated by a negation/polarity check – see Corroboration.

Parameters:
Return type:

Callable[[str, str], bool]

class GraphLLM(generate, parse, *, n=10)[source]

Bases: object

Turn a generate(prompt) -> str LLM into a distribution over knowledge graphs.

Parameters:
  • generate (Callable[[str], str]) – callable(prompt) -> str for one stochastic generation.

  • parse (Callable[[str], Iterable[Any]]) – callable(str) -> iterable[triple] to extract asserted facts. Generations that parse to the same triple set are treated as the same canonical graph.

  • n (int) – default number of samples per prompt.

sample_graphs(prompt, n=None)[source]

Sample n generations and parse each into a canonical graph.

Parameters:
Return type:

list[frozenset]

distribution(prompt, n=None, *, log_probs=None, graphs=None, strings=None)[source]

Sample, parse, and marginalize strings onto graphs.

Marginalization uses Monte-Carlo counting by default (P(G) = fraction of samples that parse to G); pass log_probs (one log P(string) per sample) to instead sum the sequence likelihoods within each graph. This lower-variance estimator does not assume every string realizing a graph is equiprobable.

Parameters:
Return type:

GraphDistribution

class GraphDistribution(graphs, probs)[source]

Bases: object

A distribution over knowledge graphs.

graphs are the distinct canonical graphs observed; probs[i] = P(graphs[i]) is the string distribution marginalized onto graphs (so it sums to 1 over distinct graphs). Every query is answered by marginalizing this distribution over the graphs that produce the queried outcome.

Parameters:
marginalize(outcome)[source]

Return P(outcome = c) = sum_{G : outcome(G) = c} P(G).

outcome maps a graph to a hashable value (a fact’s object, a boolean property, an aggregate). Returns [(value, probability), ...] sorted by descending probability.

Parameters:

outcome (Callable[[frozenset], Hashable])

Return type:

list[tuple[Any, float]]

entropy(outcome)[source]

Return entropy in nats of the marginal outcome distribution.

Parameters:

outcome (Callable[[frozenset], Hashable])

Return type:

float

edge_marginals()[source]

Return P(triple in G) for every asserted triple.

Return type:

dict[tuple, float]

fact_probability(triple)[source]

P(triple in G) for one fact (0 if never asserted).

Parameters:

triple (Any)

Return type:

float

calibrated_edge_marginals(calibrator)[source]

Map edge marginals through a fitted calibrator.

A raw edge marginal is the model’s internal assertion rate for a fact, not a probability that the fact is true – a confidently-hallucinated fact has a high marginal yet is false. Fit the calibrator with fit_fact_calibrator() on labeled facts, then this reports, per fact, the empirical truth rate at that marginal. Confident hallucinations that look exactly like known facts still require an external check.

Parameters:

calibrator (ProbabilityCalibrator)

Return type:

dict[tuple, float]

query(*prefix)[source]

Return per-object fact marginals for triples matching prefix.

A graph may assert several matching objects; each is then an event with its own marginal and may have probability one. The result is therefore not renormalized into a categorical outcome. Use functional_query() when the relation is declared single-valued and a categorical outcome, including no assertion, is required.

Parameters:

prefix (Any)

Return type:

list[tuple[Any, float]]

functional_query(*prefix, no_assertion=None)[source]

Return a categorical outcome for an explicitly functional relation.

Every graph must assert at most one matching suffix. Graphs with no match contribute to no_assertion rather than disappearing.

Parameters:
Return type:

list[tuple[Any, float]]

most_likely_graph()[source]

The single most probable graph and its probability.

Return type:

tuple[frozenset, float]

canonical_graph(triples)[source]

Return an order-independent, deduplicated graph representation.

Parameters:

triples (Iterable[Any])

Return type:

frozenset

fit_fact_calibrator(distributions, truth, *, method='isotonic')[source]

Fit edge marginal -> P(fact is true) over the facts asserted across many graph distributions.

Turn the model’s internal assertion rate (the edge marginal) into a calibrated probability of truth, learned against ground-truth labels. Collect every (triple, marginal) the model asserts, label it with truth(triple), and fit a ProbabilityCalibrator.

This does not by itself identify confident hallucinations: a false fact the model reliably emits can have a high marginal. Calibration can improve the aggregate reliability curve, but separating those cases requires an external signal such as retrieval or a checker.

Parameters:
Return type:

ProbabilityCalibrator

class EdgeTransportVerdict(edge_name, status, receipt, coverage_rates=<factory>, coverage_lower_bounds=<factory>, relative_interval_widths=<factory>, subgroup_coverage_rates=<factory>, p_values=<factory>, reason='')[source]

Bases: object

Premise decision for one real modality edge, computed on that edge.

Parameters:
property usable: bool

Compatibility alias for an affirmative premise verdict.

class PremiseReceipt(edge_name, verifier, dataset_digest, metric, evaluated_at, status)[source]

Bases: object

Tamper-evident identity and outcome of one held-out edge check.

Parameters:
  • edge_name (str)

  • verifier (str)

  • dataset_digest (str)

  • metric (str)

  • evaluated_at (str)

  • status (PremiseStatus)

property passed: bool

Whether the premise supplied affirmative evidence for composition.

class PremiseStatus(*values)[source]

Bases: StrEnum

Evidence state for an edge premise.

coverage_consistent_with_nominal(covered_flags)[source]

(observed_rate, p_value) for a two-sided binomial test of coverage against 1 - ALPHA.

Return type:

tuple[float, float]

fit_conditional_transport(data, *, x_dim, y_dim, k=3, max_its=30, m_steps=80, lr=3e-3, seed=0, delta=1.0e-9, reuse_estep_ll=True)[source]

Fit p(cond | target) and return a sampler with sample_given.

Uses mixle.models.mixture_density.build_mdn() and NeuralConditionalDensity, fit through optimize(). Pass delta=None, reuse_estep_ll=False for an edge whose relationship needs the full iteration budget rather than early stopping. The seed is applied before module construction, to optimizer data order, and to the returned sampler. Exact bitwise replay can still depend on device-specific nondeterministic kernels.

Parameters:
marginal_coverage(sampler, x_test, y_test, *, n_draws=200)[source]

Return per-dimension credible-interval coverage flags.

Parameters:

n_draws (int)

verify_edge_transport(edge_name, sampler, x_test, y_test, *, n_draws=200)[source]

Check calibration and sharpness on aligned held-out edge data.

A passing result requires enough holdout rows, a one-sided confidence lower bound above the declared coverage floor, acceptable conditional subgroup coverage, and intervals narrower than a data-only baseline. Merely failing to reject a binomial null is never treated as evidence of calibration.

Parameters:
  • edge_name (str)

  • n_draws (int)

Return type:

EdgeTransportVerdict

Submodules