mixle.reason.inference_program module

Multi-hop inference programs over composed M0/L2 conditioning queries (roadmap M5).

L2’s CrossModalJoint answers one conditioning query exactly: condition on any subset of a joint’s named modalities, infer any other subset, all within a SINGLE shared latent regime. Real cross-modal reasoning needs to chain THROUGH modalities that are not all tied by one joint – the card’s own example is (image field -> shared latent -> predicted field -> text field), where the image<->latent relationship and the latent<->text relationship are two separately-fit joints. This module adds exactly that composition and nothing else: a small, explicit, LINEAR chain of infer() calls (a path DAG – no free-form planning, no branching/merging in v1; see notes/designs/M5.md part (a)).

The one real design problem a chain introduces that a single hop does not have: hop i’s posterior over the field hop i+1 needs to condition on is a full DISTRIBUTION, not a single value, but CrossModalJoint.infer demands a concrete observed value. Two receipted ways to bridge that gap are implemented (notes/designs/M5.md part (b)):

  • propagation="sampled" (default) – Monte-Carlo particles carried hop to hop, exactly the “one particle, one draw, carry the weight” pattern already proven out by mixle.reason.cycle_consistency.joint_cycle_consistency_receipt()’s round trip. Unbiased; converges to the exact marginal as n_samples grows.

  • propagation="moment" – collapse each non-final hop’s posterior to its own point estimate (analytic mean for a Gaussian-like leaf, arg-max of the component-weighted pmap for a categorical leaf) and carry that one point forward. Cheap (one infer per hop instead of n_samples), and HONESTLY the wrong choice whenever a downstream hop is sensitive to the intermediate’s uncertainty, not just its central tendency – see mixle/tests/inference_program_test.py::InferenceProgramTwoHopVsNaiveTest for a fixture where this mode measurably diverges from the closed-form answer that "sampled" recovers.

class InferenceHop(joint, target, carry=<factory>, extra_evidence=<factory>)[source]

Bases: object

One CrossModalJoint.infer call in a chain.

target is the tuple of modality names this hop infers. carry renames a value produced by the PREVIOUS hop’s target into this hop’s own joint’s modality-name space ({prior_target_name: this_hop_evidence_name}) – the two joints need not share a naming convention. Ignored (and must be empty) for the first hop in a program, which conditions on the program’s external evidence instead. extra_evidence is fixed evidence local to this hop (observed independently of anything carried down the chain).

Parameters:
class ProgramReceipt(propagation, n_hops, hop_targets, n_particles)[source]

Bases: object

What run_inference_program() actually did – the M5 analogue of M0’s ConditionReceipt.

Parameters:
class ProgramPosterior(mixture, target, receipt)[source]

Bases: object

A completed inference program’s result: the same sample/log_density/mean-shaped contract M0’s own Posterior exposes, over the final hop’s target fields, so a program’s output composes with the same downstream code (e.g. the language<->belief bridge) that already consumes a single-hop posterior.

Parameters:
  • mixture (MixtureDistribution)

  • target (tuple[str, ...])

  • receipt (ProgramReceipt)

mean(field_name)[source]

Analytic (component-weighted) mean of one target field – Gaussian-like leaves only.

Parameters:

field_name (str)

Return type:

Any

run_inference_program(evidence, hops, *, propagation='sampled', n_samples=500, seed=0)[source]

Run a linear chain of InferenceHop conditioning queries, propagating uncertainty (or not – see module docstring) between hops. evidence conditions the FIRST hop only; later hops condition on extra_evidence plus whatever their carry mapping pulls from the previous hop’s posterior over target.

Parameters:
Return type:

ProgramPosterior