mixle.stats.compute.pdist module¶
Defines abstract classes for SequenceEncodableProbabilityDistribution, SequenceEncodableStatisticAccumulator, ProbabilityDistribution, StatisticAccumulator, StatisticAccumulatorFactory, DataSequenceEncoder, ParameterEstimator, ConditionalSampler, and DistributionSampler for classes of the mixle.stats.
- class DensitySemantics(*values)[source]
Bases:
EnumWhat a distribution’s
log_densityreturns relative to the true log-density.The default contract is
EXACT; overridedensity_semantics()on models whoselog_densityis a variational bound or an approximation, so callers can tell an exact likelihood from one (e.g. LDA’s per-document ELBO). Surfaced via theExactDensitycapability anddescribe.
- every_factor_is_composable(children)[source]
Whether a combinator over
childrenmay itself serve as a latent-model component.A combinator whose density is a PRODUCT of its children’s (a composite over fields, a sequence law over elements and a length) differs from a law by a positive scale exactly when each of its children does – and a mixture absorbs that scale into the component’s own mixing weight, leaving responsibilities unchanged. Without this delegation a combinator over an admissible factor was refused where the bare factor was accepted, and the library’s own EM produces exactly that state: a component that wins no responsibility re-estimates to an empty categorical, so rebuilding the model from its components failed with advice about
len_estimator=in a run that already had one (P10-F13).A child that declines – a
scoring_onlyfactor – makes the whole product decline.- Return type:
- join_density_semantics(semantics)[source]
Combine child density semantics for a combinator whose log_density is monotone in its children.
A combinator whose score rises with each child’s log_density – a mixture’s
logsumexp, a composite’s sum – inherits: a lower bound if any child is a lower bound, an upper bound if any is an upper bound, exactness only if all children are exact, and an undirectedESTIMATEif bounds of both directions (or any estimate) are mixed.- Return type:
DensitySemantics
- class FitProvenance(algorithm, estimator, objective, iterations, max_iterations, converged, delta=None, final_objective=None, objective_gain=None, n_observations=None, repairs=(), seed=None, last_accepted_objective=None)[source]
Bases:
objectHow a fitted distribution was produced – the estimator-side half of MXR-080-1190/1202.
density_semantics()already tells a caller whether a score is an exact density, a bound, or an unnormalized factor. It says nothing about fitting: a GaussianDistribution returned byoptimize()used to be byte-identical to one written by hand, so a consumer could not tell a converged fit from one that hit the iteration cap, nor learn that a covariance was repaired on the way. This record carries that, andis_approximate()folds it into the one question most callers actually ask.NonefromProbabilityDistribution.fit_provenance()means the object was constructed directly and no fit produced it – which is different from a fit that produced no receipt.- Parameters:
- algorithm
the fitting procedure, e.g.
"em","block-em","fused-em".- Type:
- estimator
class name of the
ParameterEstimatorthat drove it.- Type:
- objective
which objective was maximized –
"mle","map","vb".- Type:
- iterations
iterations actually run (not the cap).
- Type:
- max_iterations
the cap that was in force.
- Type:
- converged
True only if the objective gain fell below
deltabefore the cap. False means the run stopped without the gain ever falling belowdelta– either at themax_iterationscap with the objective still improving, or earlier (iterations < max_iterations) because the family’s next update failed the monotone acceptance gate. In that second case more iterations would not help; the initialization (init_p,rng) is what determines the returned parameters. Likeiterationsandobjective_gainthis describes the RUN, not the returned model: under validation-based best-model selection the returned model may be an earlier iterate than the step that converged (its value isfinal_objective; the trajectory’s islast_accepted_objective).- Type:
- delta
the convergence threshold, or
Nonewhen the caller asked for a fixed iteration count. A fixed-count request (delta=None) can still seeiterations < max_iterations: the monotone acceptance gate runs regardless ofdeltaand a rejected step ends the loop early exactly as it would with a realdelta.optimize()raises aUserWarningwhen that happens; compareiterationstomax_iterationshere to check yourself.- Type:
float | None
- final_objective
the objective value of the model this receipt is attached to – the model the fit actually returned. When selection returns an earlier iterate than the trajectory’s last accepted step (validation-based best-model selection, or
monotone=False), this is that returned iterate’s value; the trajectory’s own last value is thenlast_accepted_objective.- Type:
float | None
- objective_gain
the last accepted improvement; compare against
delta.- Type:
float | None
- last_accepted_objective
the objective value of the trajectory’s LAST accepted step, recorded by loops that may return a different (better) iterate. Equal to
final_objectivefor a monotone run that returned its last step;Nonewhen the loop recorded no separate trajectory value (the returned model is the last accepted step by construction).- Type:
float | None
- n_observations
rows the fit consumed.
- Type:
int | None
- repairs
numerical repairs applied during the fit, e.g.
("min_covar-clamped",). Empty means none were recorded – by a fit that records them.
- seed
the seed governing any stochastic initialization, when one was set.
- Type:
int | None
- is_approximate()[source]
Whether this fit is a stopped-early or repaired approximation rather than a clean optimum.
True when the run hit its iteration cap without converging, or when a numerical repair changed the parameters. Both mean the returned law is not simply “the estimator’s answer on this data”.
- Return type:
- classmethod from_dict(value)[source]
Rebuild a receipt from
as_dict()output, re-validating every field.Fields are passed through as they were recorded rather than coerced. The coercions here used to be lossy in exactly the direction that matters:
bool("false")isTrue, so a persisted record whose convergence verdict had been corrupted to a string was rebuilt as a CONVERGED fit, andint("3")/float("nan")accepted text where a measurement belongs (MXR-080-1190/1202).__post_init__now type-checks each field, so handing it the recorded value makes a malformed record fail loudly instead of being silently reinterpreted.
- exception EnumerationError(dist, path='', reason='')[source]
Bases:
NotImplementedErrorRaised when a distribution (or a child of a combinator) cannot enumerate its support.
The path argument identifies the offending child within a combinator, e.g. ‘CompositeDistribution.dists[1]’.
- exception KeyValidationError[source]
Bases:
ValueErrorRaised when keyed sufficient-statistic sites are incompatible.
A key denotes an equality constraint across model sites. Sites sharing a key must therefore have the same accumulator family and compatible estimator settings before their sufficient statistics are pooled.
- exception ContractError(path, expected, actual, fix='')[source]
Bases:
ValueErrorRaised when malformed data trips a combinator’s
seq_encode/estimatecontract boundary.Mirrors
EnumerationError’s path-composition convention:pathnames the full field path through nested combinators (composed with" -> ", outermost first), e.g."CompositeDistribution.dists[2] -> SequenceDistribution.entries". EveryContractErroralso carries what was expected, what actually arrived, and (when there is one) a concrete, non-generic suggestion for the likely fix – so a caller reading only the message, not the traceback, knows what to change.
- prefix_contract_error(prefix, err)[source]
Return a new ContractError with
prefixprepended toerr’s field path.Used by a combinator to annotate a
ContractErrorraised deep inside a child’sseq_encode/estimatewith the outer field position, so the final message names the FULL path down to where the failure actually occurred (e.g. a mixture-of-composites-of- sequences error names every level, not just the outermost combinator).- Parameters:
prefix (str)
err (ContractError)
- Return type:
ContractError
- child_enumerator(child, path)[source]
Construct child.enumerator(), annotating EnumerationError with the child’s path.
Combinator enumerators use this so a failure deep in a nested model reports the full path to the offending leaf, e.g. ‘CompositeDistribution.dists[1] -> MixtureDistribution.components[0] -> GaussianDistribution …’.
- Parameters:
child (ProbabilityDistribution)
path (str)
- Return type:
DistributionEnumerator
- class FitProvenanceCarrier[source]
Bases:
objectMixin giving a fitted object the three provenance accessors.
Split out of
ProbabilityDistributionbecause not every fitted result inherits from it:learn_structurereturns aDependencyTreeDistribution, whose MRO is(cls, object), so a public fitting entry point produced a model that could not report how it was fitted at all (MXR-080-1190/1202). The methods are plain attribute lookups with no other base-class dependency, so any fitted type can carry them.- fit_provenance()[source]
How this object was fitted, or
Noneif it was constructed directly.A distribution is a value: nothing about its parameters says whether they came from a converged EM run, a run that hit its iteration cap, or a keyboard.
optimize()attaches aFitProvenanceso that difference is machine-readable (MXR-080-1190/1202).- Return type:
FitProvenance | None
- numerical_repairs()[source]
Numerical repairs applied while building this object –
()when none were recorded.Repairs recorded on child distributions are reported too, under the child’s label, so a container’s receipt says what its leaves had to repair (P02-F05).
- with_fit_provenance(provenance)[source]
Record
provenanceon this object and return it.- Parameters:
provenance (FitProvenance)
- Return type:
FitProvenanceCarrier
- class ProbabilityDistribution[source]
Bases:
FitProvenanceCarrier,ABCBase class for all probability distributions in mixle.stats.
A distribution evaluates the (log-)density of a single observation of its data type, creates a DistributionSampler for drawing observations, and creates a ParameterEstimator for re-estimating itself from data. Discrete distributions may additionally provide a DistributionEnumerator over their support.
- fit_provenance()[source]
How this object was fitted, or
Noneif it was constructed directly.A distribution is a value: nothing about its parameters says whether they came from a converged EM run, a run that hit its iteration cap, or a keyboard.
optimize()attaches aFitProvenanceso that difference is machine-readable (MXR-080-1190/1202).- Return type:
FitProvenance | None
- numerical_repairs()[source]
Numerical repairs applied while building this object –
()when none were recorded.A repair means the parameters are not exactly what the estimator computed: a covariance that was jitter-healed back to positive-definite, a variance clamped to a floor. Reported here so
fit_provenance()can carry it, since a repaired fit is an approximation whether or not the objective converged.An empty tuple means no repair was recorded, which for a family that records none is not the same as a proof that none occurred.
Repairs recorded on child distributions are reported too, under the child’s label, so a container’s receipt says what its leaves had to repair (P02-F05).
- with_fit_provenance(provenance)[source]
Record
provenanceon this object and return it.Mutates in place and returns
selfrather than copying: fitted models are large, the caller is the fitting routine that just produced this object, and no one else holds a reference yet.- Parameters:
provenance (FitProvenance)
- Return type:
ProbabilityDistribution
- to_dict()[source]
Return a safe JSON-compatible representation of this distribution.
- classmethod from_dict(payload)[source]
Reconstruct a distribution from
to_dictoutput.
- to_json(**kwargs)[source]
Serialize this distribution as safe strict JSON.
Read back through
from_jsonbefore returning, the same checkmixle.stats.dump_models()(verify=True) performs: encoding alone only proves the registry accepted this object’s__dict__, not that the state reconstructs through the class’s own constructor. A family whose estimator pins extra state onto the fitted object (e.g.VonMisesDistribution’sfit_metadata) encodes cleanly and then refuses to load, and without this check that failure would surface later, on a different object or in a different process, instead of here where the object is still live and recoverable another way (to_dict(), or pickling).
- classmethod from_json(text)[source]
Deserialize a distribution from
to_jsonoutput.- Parameters:
text (str)
- Return type:
ProbabilityDistribution
- density(x)[source]
Return the probability density or mass at a single observation.
Concrete default: exponentiate
log_density(the abstract method subclasses must provide). Leaves with a cheaper closed form may override this.
- capabilities()[source]
Return the capability names this distribution supports (see
mixle.capability).Feature detection by behaviour rather than class — e.g.
"Enumerable","Conditionable","ExponentialFamily","RankableByIndex". Equivalent tomixle.capabilities(self); combinators report the set their children jointly preserve.
- density_semantics()[source]
What
log_densityreturns relative to the true log-density (default: exact).Override to declare that this distribution’s
log_densityis a variational lower bound (ELBO), an upper bound, or an approximation rather than the exactlog p(x). This is surfaced as theExactDensitycapability and noted inmixle.describe(), so code that needs an exact likelihood canrequire(x, ExactDensity)instead of silently trusting a bound.- Return type:
DensitySemantics
- tropical_displacement_bits()[source]
Worst-case gap (in bits) between this law’s structural count cost and its true log-density.
The structural count-DP (and the
seekbuilt on it) bins each value by a cost that is EXACT for decomposable families – composites/sequences/markov chains whoselog p(x)is a sum of independent per-factor terms – so this returns0.0by default.For a marginal family the latent index is summed out and the count index bins by the tropical (dominant-component/path) cost
M(x)instead of the truelog p(x). BecauseM(x) <= log p(x) <= M(x) + log Nfor anN-way logsumexp, the two costs differ by at mostlog2(N)bits.mixle.enumeration.density_rank.marginal_seek()widens its rank bracket by exactly this many bits so the bracket is a guaranteed bound on the true marginal rank (not merely the tropical rank); override to returnlog2(N)for such a family.- Return type:
- abstractmethod log_density(x)[source]
Return the log-density or log-mass at a single observation.
- abstractmethod sampler(seed=None)[source]
Return a sampler for drawing observations from this distribution.
- Parameters:
seed (int | None)
- Return type:
DistributionSampler
- abstractmethod estimator(pseudo_count=None)[source]
Return an estimator for fitting this distribution from data.
- Parameters:
pseudo_count (float | None)
- Return type:
ParameterEstimator
- to_fisher(**kwargs)[source]
Return a Fisher-geometry view of this distribution.
The default view is accumulator-backed, so distributions inherit a generic sufficient-statistic/Fisher-vector interface. Each distribution owns its Fisher view by overriding this method in its own module; families not yet migrated to a per-file hook are resolved by the transitional type-name dispatch in
mixle.inference.fisher._legacy_to_fisher().
- to_exponential_family(engine=None)[source]
Return the canonical exponential-family view, or
None.The canonical form is
p(x) = h(x) * exp(<eta, T(x)> - A(eta)). The default readsdeclaration_for(self).exponential_family(the per-familyExponentialFamilySpec) and wraps it in anExponentialFamilyForm; it returnsNonewhen this family is not a (single) exponential family. There is no type switch – adding a family is a matter of providing its spec.- Parameters:
engine (Any)
- get_prior()[source]
Return the conjugate/parameter prior carried by this distribution, if any.
A distribution participates in the Bayesian (variational) protocol by carrying a prior over its parameters. The default returns whatever was stored on the
priorattribute (Nonefor a plain point model), so frequentist distributions answerNoneand behave as MLE models.- Return type:
ProbabilityDistribution | None
- set_prior(prior)[source]
Attach a parameter prior to this distribution.
The default just records the prior; conjugate families override this to precompute the variational expected natural parameters used by
expected_log_density.- Parameters:
prior (ProbabilityDistribution | None)
- Return type:
None
- has_conjugate_prior()[source]
Return whether this family supports a closed-form conjugate Bayesian update.
Uniform, family-level signal backed by the single
conjugate_posteriorregistry:Truemeansmixle.stats.bayes.conjugate_posterior(self, data)returns an exact closed-form posterior;Falsemeans Bayesian inference must go through the numerical fitters (MAP / Laplace / MCMC / VI). This is the top tier of the inference-capability ladder (seemixle.capability.ConjugateUpdatable). Distinct from the per-instancehas_conj_priorflag, which records whether a prior is currently attached.- Return type:
- expected_log_density(x)[source]
Return the variational expectation
E_q[log p(x | theta)].When the distribution carries a conjugate parameter posterior
qthis is the Bayesian E-step term; for a plain point model (no prior) it degenerates to the plug-inlog_density(x). Conjugate families override this with their closed form.
- enumerator()[source]
Return a DistributionEnumerator over this distribution’s support.
Distributions with an enumerable (discrete) support override this; the default raises EnumerationError.
- Return type:
DistributionEnumerator
- support_size()[source]
Return the number of distinct support points, or
Noneif infinite/unknown.This is the cardinality primitive for bounding a truncated descending-probability sum: after enumerating the top
kitems (whose smallest probability isp_k), every un-enumerated item has probability<= p_k, so the remaining mass is<= (support_size - k) * p_k(seemixle.enumeration.density_rank.truncated_sum_bound()). Finite discrete leaves return their cardinality; decomposable combinators compose it structurally; an upper bound is acceptable (it only loosens the tail bound). Infinite or continuous supports returnNone.- Return type:
int | None
- support_is_finite()[source]
Return whether the support has a known finite cardinality.
- Return type:
- quantized_index(max_bits, bin_width_bits=1.0)[source]
Build a bounded bit-quantized index over this distribution’s support.
This is a convenience wrapper around
self.enumerator().quantized_index. Non-enumerable distributions raise EnumerationError through enumerator().
- density_quantile(q, n_samples=20000, seed=None, *, invalid_score='raise')[source]
Return a representative value at cumulative-density index
q(descending-density order).The arbitrary-index / inverse of the probability-ordered cumulative that
mixle.enumeration.density_rank.density_rank()computes (G(x) = P(p(Y) >= p(x))):q = 0is the mode,q -> 1walks into the tail. Families with a closed form override this (univariate continuous leaves expose the spatialquantile; multivariate Gaussians and von Mises-Fisher overridedensity_quantileexactly); the default here is the universal Monte-Carlo representative for any samplable family whose support is uncountable or coupled (parameter priors, continuous mixtures, …): drawn_samplespoints, order them by descending density, and return the one at fractional rankq. Stochastic and approximate – use the exact methods (enumerator/count_dp_seekfor discrete) where available.
- density_enumeration(num_points, n_samples=20000, seed=None, *, dedup_key=None, invalid_score='raise')[source]
Return
num_pointsrepresentative(value, log_density)pairs in descending density.The continuous analogue of
enumerator()(which enumerates a countable support exactly): for an uncountable or coupled support there is no exact element enumeration, so this returns a Monte-Carlo representative sweep –n_samplesdraws ordered by descending density, keeping thenum_pointsmost probable (distinct) representatives, i.e. “the support, most-probable region first”. Stochastic and approximate; preferenumerator()where the support is countable.- Parameters:
num_points (int) – Number of representatives to return.
n_samples (int) – Monte-Carlo sample budget.
seed (Optional[int]) – Sampler seed (reproducible).
dedup_key (Optional[Callable]) – Stable equality key for values not supported by the default canonical
freezeoperation.invalid_score (str) –
"raise"for non-finite sampled scores, or"omit"to discard them explicitly.
- Return type:
- quantized_count_index(quantizer, max_fine_bucket)[source]
Build a structural CountIndex over this distribution’s support, bounded by depth.
This is the count-semiring counterpart of
quantized_index: it returns per-fine-bucket counts of the complete model probability together with a structural unranker, so the support can be indexed without being enumerated. The default builds a leaf index from the exactenumerator()truncated atmax_fine_bucket(efficient for closed-form/small-support families); exponential-support composers (Composite/Sequence/MarkovChain) override this with a dynamic program over the model’s likelihood recursion.- Parameters:
quantizer (mixle.enumeration.quantization.Quantizer) – Fine/coarse bucketing.
max_fine_bucket (int) – Inclusive depth bound on indexed fine buckets.
- Returns:
Tuple (CountIndex, truncated) – truncated is True when in-support values were dropped because they fell beyond the depth bound.
- count_budget_index(budget_bits, bin_width_bits=1.0, oversample=8, num_workers=None)[source]
Build a budget-bounded quantized seek index covering the top
2**budget_bitsvalues.Computes per-bin counts structurally (never enumerating the domain) and accumulates coarse bins in descending-probability order until the cumulative count reaches the budget. The returned LazyQuantizedEnumerationIndex supports arbitrary-rank seek/unranking; each unranked value carries its exact
log_density.- Parameters:
- Returns:
mixle.enumeration.algorithms.LazyQuantizedEnumerationIndex.
- count_budget_distinct(budget_bits, bin_width_bits=1.0, oversample=8, dedup='canonical', start=0, stop=None, max_entries=1 << 16, num_workers=None)[source]
Iterate DISTINCT (value, exact_log_prob) over the count-budget index, approx descending.
For exact-count families this equals the ordered index stream. For the over-counting MARGINAL families (Mixture/HMM) it removes the component/path duplicates by one of two modes:
dedup='canonical'(default): a STATELESS predicate (is_canonical_copy) keeps a value only at its dominant copy (best-weighted component / min-cost path), via the value’s structural fine bucket (structural_fine_bucket– the SAME sum-of-floored sub-buckets the count index used, so nested composite/sequence values are binned consistently and never dropped). O(1) memory and random-accessible:start/stopselect an arbitrary STRUCTURAL rank range, so you can begin anywhere and the work partitions across workers with no shared state. GUARANTEE: every distinct in-budget value is emitted at least once (completeness). It is NOT strictly once – a value is emitted once per component/path that ties within its minimal 1-bit coarse bin; that residue is inherent to a stateless(value, coarse_bin)rule (the tied copies are indistinguishable to it) and is bounded by the number of components/contributing paths (not reduced byoversample, which only refines the intermediate fine bucket). For a strictly-once stream, usededup='window'or de-duplicate downstream.dedup='window': a boundedmax_entriesLRU over the stream (catches every duplicate within the window regardless of dominance, but is sequential –startmust be 0).
Note:
start/stopindex the STRUCTURAL enumeration, not the distinct rank. Jumping to the k-th distinct value in O(1) is not possible – it needs exact distinct per-bin counts, which require materializing the component/path overlap structure.
- is_canonical_copy(value, coarse_bin, quantizer)[source]
Return True if
coarse_binisvalue’s dominant (canonical) bin in the count index.Stateless deduplication hook for the over-counting MARGINAL families: a value that the structural index emits once per component / state-path is kept only at the copy whose bin is the minimal (most probable) one. The default returns True – exact-count families (Composite/Sequence/MarkovChain) never duplicate, so every copy is canonical.
- structural_fine_bucket(value, quantizer)[source]
Minimum fine bucket where
valueis placed by this distribution’s count index.Mirrors
quantized_count_indexexactly so that stateless canonical-copy dedup can predict the bucket the index actually used. The count DP bins a composite/nested value by a SUM of floored per-factor buckets (a convolution), which differs from a single floor of the exact joint log-density by up to the number of factors – so a canonical check that usedfine_bucket(log_density(value))would mispredict and silently drop nested values. The leaf default is that single floor (correct for atomic families); combinators (Composite/Sequence/Mixture) override to recurse the same way their count index composes.- Return type:
- quantized_multi_cross_index(others, max_bits, bin_width_bits=1.0)[source]
Build an aligned bounded cross-bin view against other distributions.
The generic implementation is a bounded candidate join: it unions the bounded quantized indexes of all participating distributions, then evaluates every candidate under every distribution. Structured distributions can override this to build the same aligned rows from support algebra instead.
- class SequenceEncodableProbabilityDistribution[source]
Bases:
ProbabilityDistributionProbabilityDistribution with vectorized log-density evaluation on encoded data.
dist_to_encoder() returns a DataSequenceEncoder whose seq_encode() output is consumed by seq_log_density() (and by the matching accumulator’s seq_update / seq_initialize), enabling fast vectorized estimation over iid sequences.
- supported_engines()[source]
Return engine names this distribution can evaluate on directly.
- supports_engine(engine)[source]
Return True when the distribution can safely use
engine.
- decomposition()[source]
Return how this distribution may be split across devices (model parallelism).
Defaults to
Decomposition.atomic()– not split, replicated. Combinators / latent families override this to declare their component / factor / state axis. Seemixle.stats.compute.decomposition.- Return type:
Decomposition
- seq_ld_lambda()[source]
Return vectorized log-density callables for encoded data.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- seq_expected_log_density(x)[source]
Vectorized
expected_log_densityover sequence-encoded observations.Degenerates to
seq_log_densityfor a plain point model; conjugate families override with a closed form. Seeexpected_log_density.
- seq_log_density_lambda()[source]
Return vectorized log-density callables for encoded data.
- kernel(engine=None, estimator=None)[source]
Return an engine-aware evaluation kernel for this distribution.
- Parameters:
estimator (ParameterEstimator | None)
- abstractmethod dist_to_encoder()[source]
Return the data encoder used by this distribution for vectorized methods.
- Return type:
DataSequenceEncoder
- class DistributionSampler(dist, seed=None, *, rng=None)[source]
Bases:
ABCDraws iid observations from a distribution using a seeded RandomState.
sample(size=None) returns a single observation of the distribution’s data type; sample(size=n) returns a length-n collection of observations.
- Parameters:
dist (SequenceEncodableProbabilityDistribution)
seed (int | None)
rng (RandomState | None)
- new_seed()[source]
Return a fresh random seed drawn from this sampler’s RandomState.
- Return type:
- abstractmethod sample(size=None, *, batched=True)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=True(the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class NucleusResult(items, *, target_probability, cumulative_probability, reached_target, capped, exhausted)[source]
Bases:
list[tuple[Any,float]]List-compatible nucleus prefix with explicit termination provenance.
- class DistributionEnumerator(dist)[source]
Bases:
ABCLazy iterator over the support of dist in non-increasing probability order.
- Yields (value, log_prob) pairs, possibly infinitely many. Contract:
Each support value is yielded exactly once (deduplication is the enumerator’s responsibility).
log_prob equals dist.log_density(value) up to float round-off (~1e-10), and the sequence of log_probs is non-increasing up to the same tolerance.
Values with zero probability are skipped, never yielded.
Ties are broken deterministically by insertion order; no further guarantee.
- Parameters:
dist (SequenceEncodableProbabilityDistribution)
- top_k(k)[source]
Return the k most probable (value, log_prob) pairs (fewer if the support is smaller).
- top_p(p, max_items=None)[source]
Return the smallest descending-probability prefix whose total probability reaches
p.The nucleus / minimal high-probability set: because values are yielded in non-increasing probability order, the returned prefix is a minimum-size set of outcomes whose summed mass is
>= p(e.g.p=0.95gives a 95%-coverage support set – the discrete analogue of nucleus sampling). Accumulation stops as soon as the cumulative probability reachesp.max_itemscaps how many values are pulled so an infinite or heavy-tailed support cannot run away. A cap is mandatory when the support has no known finite cardinality. The returned list-compatibleNucleusResultreports whether the target was reached, the accumulated mass, and whether a cap stopped the search.
- quantized_index(max_bits, bin_width_bits=1.0)[source]
Precompute a bounded bit-quantized index over this enumeration.
The index groups values by floor((-log2 p(x)) / bin_width_bits), includes only values with -log2 p(x) <= max_bits, and returns exact log probabilities for indexed values. Building the index consumes this enumerator.
- rank(value)[source]
Rank and cumulative probability of
valuein the descending-probability order.Returns a
DensityRankResultwith.rank(0-based count of strictly-more-probable outcomes;Noneif only the sampling estimate was used) and.cumulative_probability(G(value) = P(p(Y) >= p(value))). Exact head enumeration where the support is countable, with a Monte-Carlo fallback for the deep tail.- Parameters:
value (Any)
- seek(index)[source]
The value at descending-probability
index(0-based) – the inverse ofrank().Returns a
CountDPSeekResultcarrying the value and a provable[rank_lower, rank_upper]bracket. Uses the structural count-DP for decomposable families, so arbitrarily deep indices are reachable without enumerating the prefix.- Parameters:
index (int)
- seek_certified(index)[source]
The value at descending
indexwith a GUARANTEED bracket on its TRUE marginal rank.Unlike
seek()– whose bracket bounds only the tropical rank for a marginal family (mixture/HMM) – this widens the rank window by the family’stropical_displacement_bitsand divides out the component over-count, so the returnedMarginalSeekResult[true_rank_lower, true_rank_upper]provably contains#{u : log p(u) > log p(value)}. It pins the rank exactly (.exact) for decomposable / provably-disjoint families and for shallow indices, and otherwise returns the certified provable envelope. For a decomposable family it agrees withseek().- Parameters:
index (int)
- cumulative(value)[source]
G(value) = P(p(Y) >= p(value))– total mass of outcomes at least as probable asvalue.Returns a
CumulativeProbabilityResultwith.probabilityplus explicit certification (.exact,.dropped_upper) of what that figure rests on.- Parameters:
value (Any)
- nucleus_size(p)[source]
Size of
top_p()(the minimal>= p-mass set) WITHOUT materializing it.Returns a
CountDPTopPResultwith a provable size bracket, from the structural count-DP – usable when the nucleus is far too large to list.- Parameters:
p (float)
- from_index(start, stop=None)[source]
Iterate
(value, log_prob)in descending-probability order starting at structuralstart.Yields the same stream as iterating a fresh enumerator but beginning at index
start(and ending beforestopif given). A fresh underlying enumeration is used, so this does not consumeself. (Decomposable families admit a direct structural jump via the count-budget index; the current implementation skips the best-first prefix – the structural fast path is a WS-3 performance follow-up.)
- class ConditionalSampler[source]
Bases:
ABCSampler mixin for conditional draws: sample_given(x) draws from P(. | x).
- abstractmethod sample_given(x)[source]
Draw a sample from the conditional distribution given
x.
- class StatisticAccumulator[source]
-
Accumulates weighted sufficient statistics of type SS from observations.
update(x, weight, estimate) adds one observation (estimate is the previous model, used for E-step posteriors; it may be None during initialization). Accumulators merge across partitions via combine(suff_stat) / value() / from_value(), and key_merge / key_replace pool statistics shared across model components through a stats_dict keyed by the accumulator’s key.
- update(x, weight, estimate)[source]
Accumulate one weighted observation under an optional current estimate.
- initialize(x, weight, rng)[source]
Initialize sufficient statistics from one weighted observation.
- Parameters:
x (Any)
weight (float)
rng (RandomState)
- Return type:
None
- abstractmethod combine(suff_stat)[source]
Merge serialized sufficient statistics into this accumulator.
- Parameters:
suff_stat (SS)
- Return type:
StatisticAccumulator
- abstractmethod value()[source]
Return this accumulator’s serialized sufficient statistics.
- Return type:
SS
- abstractmethod from_value(x)[source]
Restore this accumulator from serialized sufficient statistics.
- Parameters:
x (SS)
- Return type:
SequenceEncodableStatisticAccumulator
- scale(c)[source]
Scale linear sufficient statistics in-place by
c.The structural default is correct for ordinary weighted sums, nested tuples/lists/dicts, and numeric arrays. Families whose
value()payload includes non-linear metadata such as support bounds must override this method and leave that metadata unscaled.- Parameters:
c (float)
- Return type:
StatisticAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto the one already there. Accumulators whose key does not cover their whole state (several named keys, a wrapped child accumulator, a non-accumulator payload) override this – and ONLY those: as of 2026-07-14 every whole-state family uses this default, after a sweep found sixteen hand-rolled copies that had drifted into three broken shapes. If you are tempted to reimplement this per family, don’t; extend the base or override withsuper()calls. AkeysofNone(the default) is a no-op.Why this exact shape is correct – and the drifted shapes are not: the POOL must live in the dict. Combining INTO the dict-held accumulator (below) mutates the pool in place, so after all sites merge, the dict entry holds everyone’s statistics and
key_replacehands that pool to every site. The observed broken variants each violate one half: pulling the dict’s stats intoselfwithout writing back (the dict keeps the FIRST site only – later sites’ data silently discarded); merging only when the key is already present (nobody ever inserts: both passes are no-ops and tying does nothing); and replacing by pushingselfINTO the dict (the LAST site overwrites the pool). The keyed-protocol sweep (keyed_protocol_sweep_test.py) enforces this behaviorally for every catalog family.
- class SequenceEncodableStatisticAccumulator[source]
Bases:
StatisticAccumulator[SS]StatisticAccumulator with vectorized updates on encoded data sequences.
seq_update / seq_initialize consume the output of the matching DataSequenceEncoder’s seq_encode() (obtained via acc_to_encoder()) together with a per-observation weight vector.
- get_seq_lambda()[source]
Return optional low-level sequence-update kernels used by generated code.
- supported_rows(x)[source]
Encoded rows this accumulator can be fitted on, or
Nonewhen every row qualifies.Support-limited laws admit out-of-support observations at encode time so that a mixture can encode one batch against every component (P02-F03) – the component that does not own a value scores it -inf, and the E-step gives it no responsibility for that row. Initialization draws responsibilities before any model exists, so a latent model asks each component for this mask first and never seeds a component from a row it could not be fitted on.
- Return type:
ndarray | None
- abstractmethod seq_update(x, weights, estimate)[source]
Accumulate weighted sufficient statistics from sequence-encoded observations.
- Parameters:
weights (ndarray)
- Return type:
None
- abstractmethod seq_initialize(x, weights, rng)[source]
Initialize sufficient statistics from sequence-encoded observations.
- Parameters:
weights (ndarray)
rng (RandomState)
- Return type:
None
- abstractmethod acc_to_encoder()[source]
Return a sequence encoder compatible with this accumulator.
- Return type:
DataSequenceEncoder
- scale_suff_stat(x, c)[source]
Return
xwith numeric sufficient-statistic leaves multiplied byc.
- class StatisticAccumulatorFactory[source]
Bases:
ABCFactory whose make() returns a fresh, zeroed accumulator for one estimator.
- abstractmethod make()[source]
Create a fresh accumulator instance.
- Return type:
SequenceEncodableStatisticAccumulator
- class ParameterEstimator[source]
-
Estimates a distribution from accumulated sufficient statistics.
accumulator_factory() supplies accumulators that gather sufficient statistics of type SS, and estimate(nobs, suff_stat) maps those statistics (plus optional regularization configured on the estimator) to a new distribution.
- to_dict()[source]
Return a safe JSON-compatible representation of this estimator.
- classmethod from_dict(payload)[source]
Reconstruct an estimator from
to_dictoutput.
- to_json(**kwargs)[source]
Serialize this estimator as safe strict JSON.
- classmethod from_json(text)[source]
Deserialize an estimator from
to_jsonoutput.- Parameters:
text (str)
- Return type:
ParameterEstimator
- abstractmethod estimate(nobs, suff_stat)[source]
Estimate a distribution from accumulated sufficient statistics.
- Parameters:
nobs (float | None)
suff_stat (SS)
- Return type:
SequenceEncodableProbabilityDistribution
- abstractmethod accumulator_factory()[source]
Return the accumulator factory used to collect this estimator’s sufficient statistics.
- Return type:
StatisticAccumulatorFactory
- resident_accumulation_supported()[source]
Return whether engine-resident (fixed-width) sufficient statistics suffice for
estimate.Most exponential-family M-steps consume only the resident sufficient statistics, so the default is
True. Estimators whose M-step needs more than that (e.g. a full count histogram for the negative-binomial dispersion solve) override this toFalseso stacked/generated kernels fall back to the host accumulator, keeping every backend’s fixed point identical.- Return type:
- get_prior()[source]
Return the parameter prior configured on this estimator, if any.
The unified estimation contract treats the prior as the single regularization concept:
Nonegives maximum likelihood, a conjugate prior gives the Bayesian posterior update insideestimate. The default reads thepriorattribute (Nonewhen unset).- Return type:
ProbabilityDistribution | None
- model_log_density(model)[source]
Return the prior log-density of
model’s parameters (the ELBO global term).Used by the variational/MAP objective in
fit. The default is0.0(no prior); conjugate estimators override this to evaluate their prior at the model’s parameters, mapping to the prior’s parameterization first.- Parameters:
model (ProbabilityDistribution)
- Return type:
- class DataSequenceEncoder[source]
Bases:
ABCEncodes an iid data sequence into the vectorized form used by seq_* methods.
seq_encode(x) transforms a sequence of observations into the encoding consumed by seq_log_density / seq_update / seq_initialize. Encoders must define __eq__ (so equivalent encoders are interchangeable when batching) and a readable __str__.
- seq_encode(x)[source]
Encode the iid observation sequence x for vectorized evaluation.
- nbytes(x)[source]
Return the approximate in-memory byte size of an encoded payload.
- row_count(x)[source]
Return the number of observations represented by
x.The default covers simple array/list encodings and structurally aligned tuples/dicts. Encoders with flattened, ragged, or otherwise specialized layouts must override this method so metadata wrappers can verify counts instead of trusting a caller assertion.
- encoded_nbytes(x)[source]
Return an approximate byte size for nested encoded array payloads.
Encoders mostly return arrays or tuples/lists/dicts of arrays. The helper keeps accounting structural and deterministic; Python object overhead is included only for scalar leaves where no array-native byte count exists.
- validate_estimator_keys(estimator)[source]
Validate keyed estimator and accumulator sites before EM folds stats.
The validator catches the classic keying footgun: two different families, or two sites with incompatible estimator settings, accidentally sharing the same key string. Validation is intentionally protocol-level and best-effort; a family can still perform stricter checks in its own factory if needed.
- Parameters:
estimator (ParameterEstimator)
- Return type:
None
- estimator_has_keys(estimator)[source]
Whether any site in the estimator tree carries a tying key.
Keyed sites require the
merge_accumulator_keyspass after accumulation; an EM driver that cannot run it (or whose update decomposition conflicts with pooling, e.g. block-EM’s sparse per-component M-steps) must refuse or reroute keyed estimators rather than silently untie them.- Parameters:
estimator (ParameterEstimator)
- Return type:
- validate_accumulator_keys(accumulator)[source]
Validate keyed sites in an already-created accumulator tree.
- Parameters:
accumulator (StatisticAccumulator)
- Return type:
None
- merge_accumulator_keys(accumulator)[source]
Pool keyed statistics across
accumulator’s tree – the parameter-tying pass.Runs the
key_merge/key_replacepair every EM driver applies exactly once after accumulation (seemixle.stats.compute.sequence.seq_estimate()), so sites sharing a key estimate from the pooled statistics. A no-op when no site in the tree carries a key. Call it on the fully-combined accumulator, never per shard: pooling twice would double-count the shared statistics on the secondcombine.- Parameters:
accumulator (StatisticAccumulator)
- Return type:
None