mixle.inference.node_report module¶
Node report protocol – per-subtree diagnostics over a composed distribution tree (workstream D1).
Frame (see the ConditionalJIT track, D1-D6): the estimator tree is an IR. Every node – a leaf
distribution or a combinator subtree (Composite/Mixture/Sequence/Conditional/Optional/…) – can
report its own residual, its Q-gain (Neal-Hinton free-energy lower-bound improvement), its E/M cost,
its update kind, and cheap health receipts. Later track items (freeze/roll-up caching, a block-EM
scheduler, leaf hot-swapping, backend re-specialization, a learned controller) all read these reports;
none of them may change what a fit computes – only how it is scheduled – so this module never runs
its own EM, it only instruments/observes the existing machinery in mixle.inference.em and
mixle.inference.estimation.
Design choices (documented here because later D-track items depend on this interface):
residual – a Monte-Carlo estimate of this node’s own negative log-density,
-mean(log_density(x))over samples drawn from the node’s own current fit (node.sampler().sample(n)). This is available at EVERY node generically (everyProbabilityDistributionhassamplerandlog_density) without needing per-combinator data slicing (a leaf under aCompositeDistributionnever sees the top-level tuple directly, so an exact real-data residual cannot be computed generically for an arbitrary subtree without reimplementing every combinator’s data-projection rule – out of scope for an S-effort generic dispatcher). For an exponential-family leaf this MC residual converges to the differential/Shannon entropy of the fit; it is a legitimate “how much spread/uncertainty is still in this subtree’s fit” signal, not a real-data fit residual. Callers who want a real-data top-level residual can passdata/enc_dataand read the root row, whose residual/Q-gain instead comes from the actual EM objective (seeroot_em_report()).Q-gain –
residual_before - residual_afterfor the SAME field path across twoflat_report_table()calls that bracket one EM update (pass the earlier table asprev_table). This is a genuine before/after delta of the tracked residual, not invented. The Neal-Hinton guarantee (coordinate ascent on one computable free energy F) is proven only for the actual tracked EM objective at the TOP level (seemixle.inference.em’srun_em/mixle.inference.estimation’soptimize, whosedelta-gated loop never accepts a decreasing step); per-node Q-gain is a diagnostic decomposition, not individually guaranteed non-negative (a mixture EM step can, e.g., grow one component’s spread while improving the joint objective).root_em_report()verifies the real, provably-monotone quantity.E/M cost – a parameter-count x dataset-size proxy (
nobsif supplied, else 1): E-step cost~ param_count * nobs(every observation is scored against every parameter once), M-step cost~ param_countfor a closed-form/conjugate update,~ param_count * _GRADIENT_STEPSfor a gradient-based estimator,0for a frozen/no-op node. Proxy, not wall-clock – documented so D2’s freeze/roll-up cache and D3’s scheduler can decide whether to replace it with a measured cost later.update kind – derived from the node’s own estimator/capabilities:
"frozen"(theNeutralcapability – a Null distribution/accumulator/encoder),"em"(latent-variable nodes exposingseq_posterioror theLatentStructuredcapability – the same duck-typemixle.inference.emitself uses forPosteriorTransformEM),"conjugate_closed_form"(theConjugateUpdatablecapability),"gradient"(estimator class name signals a gradient/neural/torch optimizer), else"closed_form"(the common one-shot M-step case).health receipts – reuses the near-degenerate-variance check already used by
mixle.inference.precision_plan(sigma2/variancebelow a floor), adds a generic NaN sweep over the node’s own numeric attributes (-infis deliberately NOT flagged – it is a legitimate log-space encoding of a zero-probability event, e.g. a categorical’slog_default_value; only NaN is never a legitimate parameter value), and an ill-conditioning check (condon any square 2D numeric attribute whose name suggests a covariance/precision matrix) – a minimal, honest version where nothing richer already exists in the codebase for a given family.
- class NodeReport(field_path, node_type, update_kind, residual, q_gain, e_step_cost, m_step_cost, param_count, health=<factory>)[source]
Bases:
objectPer-node diagnostics for one point in a composed distribution tree.
field_pathfollows the existingEnumerationErrorpath convention used throughout the combinators (e.g."MixtureDistribution.components[0] -> CompositeDistribution.dists[1]"), so reports compose with the rest of the codebase’s structural-error/debugging vocabulary.- Parameters:
- property is_healthy: bool
True iff every boolean health receipt that flags a problem is False.
- node_report(dist, *, field_path='root', n_mc=_DEFAULT_MC_SAMPLES, seed=0, nobs=None, prev_residual=None)[source]
Return a
NodeReportfor a single node (leaf or combinator subtree).Dispatches generically over the five-piece contract / capability lens – no per-family branching.
- walk_tree(dist, *, path='root')[source]
Return
(field_path, node)for every node in the tree, pre-order, deduplicated by identity.
- flat_report_table(dist, *, nobs=None, prev_table=None, n_mc=_DEFAULT_MC_SAMPLES, seed=0)[source]
Walk a composed tree and return one
NodeReportper node, in traversal order.Satisfies “composed tree -> flat table”: every leaf and every combinator subtree gets exactly one row, keyed by its
field_path. Pass the previous call’s return value asprev_tableto populate each row’sq_gainas the residual improvement across an intervening EM update.
- root_em_report(enc_data, estimator, model, *, engine=None, max_its=1)[source]
Run
max_itsreal EM steps (viamixle.inference.em.run_em()) and return(new_model, objective_before, objective_after)on the ACTUAL tracked Neal-Hinton objective (observed log-likelihood / MAP / VB, whicheverrun_emresolves) – the one quantity this module does not approximate.objective_after >= objective_beforeis the real, provably monotone Q-gain guarantee;NodeReport’s per-nodeq_gainis a diagnostic decomposition, not individually guaranteed non-negative (see module docstring).