mixle.enumeration package¶
The enumeration concern — one home for “what can be enumerated, and how”.
Enumeration is not a property of distributions; it is a capability shared by distributions,
relations, quantized objects, and any combinatorial model. Anything that can iterate its support in
descending probability implements DistributionEnumerator (the contract) and reports the
Enumerable capability; anything finite-or-structural can additionally be unranked by integer
rank (RankableByIndex) through the count-budget seek index.
This module gathers that concern in one place — the contract, the capability lens, the k-best
algorithms, the count-budget unranking, and the count semiring — so you can read off what
enumeration is and who participates without hunting through utils/stats. The implementations
live here, split by concern: generic stream primitives in streams, best-first
/ product search in best_first, the quantized seek/unrank index and the
structural count-budget index in quantization, and the k-best combinatorial
enumerators in assignment / spanning. Only the
contract (DistributionEnumerator) lives in the compute layer and is re-exported here.
mixle.enumeration.algorithms remains as a back-compat re-export of the stream / best-first / index
names. Layout per docs/ARCHITECTURE.md.
Who plugs in: every finite/countable leaf (Categorical, Poisson, …), the combinators over enumerable
children (Sequence, Composite, Mixture, …), the graph/ranking families (Markov chains, Mallows,
Chow-Liu trees, spanning trees), and mixle.relations.Relation — all via the same enumerator().
- class Enumerable[source]
Bases:
PredicateCapabilityIts support can be iterated in descending-probability order (
enumerator()works).
- class FiniteSupport[source]
Bases:
PredicateCapabilityHas a finite number of distinct support points (
support_size()is an int).
- class RankableByIndex[source]
Bases:
PredicateCapabilitySupports random access by integer rank (unranking) via the count-budget seek index.
Finite-support enumerables are rankable by index through the count-DP semiring; the implication edge
finite ==> enumerable ==> rankableis exactly this composition. (Decomposable combinators over infinite-but-countable children are also rankable structurally; this conservative check covers the finite-leaf case used by the dispatch layers.)
- supports(obj, capability)[source]
Return whether
objprovidescapability(a Protocol or a PredicateCapability).
- top_k(dist, k)[source]
Return the
khighest-probability(value, log_prob)pairs.Dispatches on capability, not class: a
RankableByIndexdistribution could random-access by rank, but everyEnumerabledistribution can stream its support in descending probability — so one implementation covers all of them, including user-defined ones. Raises a clearCapabilityErrorfor distributions that support neither.
- 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; if the cap is hit before the threshold, the (sub-threshold) prefix gathered so far is returned.p >= 1.0on an infinite support therefore requiresmax_items.
- 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 honest 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.- 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.)
- 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]’.
- 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
- supports_enumeration(dist)[source]
Return True if dist.enumerator() can be constructed.
- Return type:
- class DensityRankResult(cumulative_probability, rank, exact, stderr, log_prob, method, rank_stderr=0.0)[source]
Bases:
objectOutcome of a rank / cumulative-probability query.
- Parameters:
- cumulative_probability
G(x) = sum_{y: p(y) >= p(x)} p(y)(the descending-order CDF at x).- Type:
- rank
number of observations strictly more probable than x (0-based position). Exact when the head enumeration resolved it; in
"sampling"mode it is the rounded unbiased Monte-Carlo estimate (seerank_stderr);Noneonly whenlog p(x) = -infor an exact-analytic CDF (no count) was used.- Type:
int | None
- exact
True when the head enumeration resolved the query exactly; False for a sampling estimate.
- Type:
- stderr
standard error of
cumulative_probability(0.0 when exact).- Type:
- log_prob
log p(x).- Type:
- method
"exact-head","exact-exhausted","exact-analytic", or"sampling".- Type:
- rank_stderr
standard error of the Monte-Carlo
rankestimate (0.0 when exact). Large relative torankexactly in the deep-tail / high-entropy band where exact marginal rank is provably hard – treat the estimate as unreliable there.- Type:
- cumulative_probability: float
- exact: bool
- stderr: float
- log_prob: float
- method: str
- rank_stderr: float = 0.0
- density_rank(dist, value, max_exact=100_000, n_samples=20_000, seed=0, tol=1.0e-9)[source]
Rank and cumulative probability of
valueunderdist’s descending-probability order.- Strategy:
If
distsupports enumeration, walk the exact descending stream, accumulating the mass of every item at least as probable asvalueand counting those strictly more probable. If the stream drops belowp(value)(or is exhausted) withinmax_exactitems, the rank and cumulative probability are returned EXACTLY.Otherwise (
valueis deeper thanmax_exact, or enumeration is unsupported), estimate the cumulative probability by Monte Carlo:G_hat = mean_i 1[log p(Y_i) >= log p(value)]withY_i ~ dist. Reliable here precisely becauseGis large in the tail.
- Parameters:
dist (Any) – A distribution exposing
log_densityandsampler; optionallyenumerator.value (Any) – The observation to locate.
max_exact (int) – Cap on items pulled from the exact enumerator before falling back to sampling.
n_samples (int) – Monte-Carlo sample count for the fallback.
seed (int) – Sampler seed for the fallback (reproducible).
tol (float) – Log-probability tolerance for the
>=comparison (ties).
- Returns:
DensityRankResult.
- Return type:
DensityRankResult
- count_budget_index(dist, budget_bits, bin_width_bits=1.0, oversample=8, max_depth_bits=4096.0, num_workers=None, count_mode='exact')[source]
Driver for the count-budget mode: deepen until the budget is covered, then build the index.
Calls
dist.quantized_count_index(quantizer, max_fine_bucket)at geometrically increasing depths until the structural count reaches2**budget_bitsor the support is exhausted (no further truncation), then wraps the result withbuild_budget_index(). The exact log density of each unranked value is reported viadist.log_density.When
num_workersis greater than 1, the heavy count-histogram convolutions are computed on a process pool (parallel quantization); the parallel result is identical to the serial one.count_mode='float'carries counts as float64 (C-speed convolutions, exact below 2**53, ~1e-16 relative error per operation beyond) – the approximate-counting mode for deep budgets.
- class SeekIndex(model, *, bin_width_bits=1.0, oversample=8, count_mode='exact', max_depth_bits=4096.0)[source]
Bases:
objectBuild a model’s count-budget index once and answer many enumeration queries against it.
modelis anything implementing the count-index contract – every mixle distribution with aquantized_count_index(Composite/Sequence/Markov exactly; Mixture/HMM as the documented tropical bound) andAutoregressiveEnumerable(so LLMs plug in).Queries auto-deepen: asking for a rank (or a log-prob threshold) beyond the built depth rebuilds the DP at a geometrically larger budget, reusing whatever the model itself memoizes.
max_depth_bitscaps the deepening; past it, queries raiseIndexErrorfor out-of-range ranks.- Parameters:
- property built_bits: float
The depth (bit budget) the index is currently built to (0 before the first build).
- property truncated: bool
True when values beyond the built depth exist (deepening can still grow the index).
- property builds: int
How many times the structural DP has been (re)built – the cost a persistent index amortizes.
- property dropped_upper: float
Certified upper bound on in-budget values excluded by an approximation knob (e.g.
branch_cap).0.0 for exhaustive indices. The true in-budget count lies in
[len(self), len(self) + dropped_upper]; deepening does not recover these (that is what distinguishes it fromtruncated).
- ensure_bits(depth_bits)[source]
Make the index cover at least
depth_bitsof information (build or deepen if needed).- Parameters:
depth_bits (float)
- Return type:
SeekIndex
- ensure_count(n)[source]
Make the index hold at least
nvalues (geometric deepening from the current depth).Every rebuild at least doubles the built depth, so an ascending sequence of queries costs O(log) rebuilds total and the final (deepest) build dominates the work – the amortization a persistent index exists for.
- Parameters:
n (int)
- Return type:
SeekIndex
- unrank(i)[source]
The
i-th most probable value (0-based, quantized order) and its exact log-probability.
- slice(start, k)[source]
Up to
kvalues starting at rankstart(one deepen at most, then table reads).
- iter_from(start=0)[source]
Iterate values from rank
startthrough the end of the built index (no auto-deepen).
- count(min_log_prob)[source]
How many values have
log_density >= min_log_prob– read off the fine histogram.Exact-carrier families return an
int;count_mode='float'(or a float-carrying model like the deep-budget autoregressive fast path) returns a float with the documented relative error. Mixture/HMM counts are the structural (tropical) upper bound those families document.
- threshold(rank)[source]
Log-probability of the
rank-th most probable value (the top-rankboundary).
- rank_bracket(value)[source]
A
[lo, hi]bracket onvalue’s quantized rank, from its structural fine bucket.locounts every value in strictly shallower buckets;hiadds the rest of the value’s own bucket. For Mixture/HMM the bucket is the documented tropical projection, so the bracket carries that projection’s semantics (see the family docstrings).
- logit_error_bucket_slack(eps_nats, steps, quantizer)[source]
Sound bucket-shift bound when step log-probabilities carry up to
eps_natsof error each.The license to run enumeration forwards on a quantized model (int8/int4 inference, a distilled twin): enumeration never needs logits more accurate than the fine-bucket width. If every step log-probability is within
eps_natsof the true model’s, asteps-long value’s accumulated fine bucket shifts by at mostceil(steps * eps_nats / ln2 * fine_per_bit)buckets – this bound. It composes additively with the existingsteps-floor structural smear, so every count / rank / mass bracket in this package widens by exactly this many buckets and remains sound.Practical reading: with the default quantizer (1/8-bit fine buckets) a per-step logit error of ~0.01 nats (typical of int8 inference) costs
ceil(steps * 0.115)buckets – about one bucket per nine steps – while the forwards run 2-4x faster.
- quantized_index(enum, max_bits, bin_width_bits=1.0)[source]
Convenience wrapper for QuantizedEnumerationIndex.from_enumerator.
- class QuantizedEnumerationIndex(bins, bin_width_bits, max_bits, truncated)[source]
Bases:
objectBounded, indexable view of an exact probability-ordered enumeration.
Items are grouped into log-probability bins measured in bits:
bits(x) = -log2(p(x)) bin(x) = floor(bits(x) / bin_width_bits)
Only items whose bit cost is at most max_bits are indexed. The returned (value, log_prob) pairs always carry the original exact log probability; the quantized bins are used only for counting and rank lookup.
The generic implementation can build from an exact enumerator stream, so it works for every currently enumerable discrete stats model. Simple distributions may also build directly from scored support items, and distribution-specific dynamic programs can produce the same bin layout without relying on an exact global stream.
- Parameters:
- static bin_for_log_prob(log_prob, bin_width_bits=1.0)[source]
Return the quantized bit bin for a log probability.
- classmethod from_enumerator(enum, max_bits, bin_width_bits=1.0)[source]
Build an index from an exact non-increasing-probability enumerator.
- Parameters:
- Returns:
QuantizedEnumerationIndex over the bounded prefix/domain slice.
- Return type:
QuantizedEnumerationIndex
- classmethod from_items(items, max_bits, bin_width_bits=1.0, sorted_items=False, truncated=None)[source]
Build an index from known support items and exact log probabilities.
- Parameters:
items (Sequence[tuple[Any, float]]) – Finite collection of (value, log_prob) pairs. Zero-probability items with log_prob == -inf are ignored.
max_bits (float) – Include only values with -log2(p) <= max_bits.
bin_width_bits (float) – Width of each quantized probability bin in bits.
sorted_items (bool) – If True, preserve the input order. Otherwise, stable-sort included items by descending log probability.
truncated (bool | None) – Optional explicit truncation flag. If omitted, it is True when any finite-probability item was outside the bit bound.
- Returns:
QuantizedEnumerationIndex over the bounded finite item set.
- Return type:
QuantizedEnumerationIndex
- bin_for_index(index)[source]
Return (bin_id, offset_within_bin) for a bounded quantized rank.
- get(index)[source]
Return the indexed (value, exact_log_prob) pair.
- slice(start, k)[source]
Return up to k indexed pairs starting at start.
- iter_from(start=0)[source]
Iterate indexed pairs from start to the end of the bounded index.
- bin_items(bin_id)[source]
Return all indexed items in a quantized probability bin.
- class LazyQuantizedEnumerationIndex(counts, bin_width_bits, max_bits, truncated, getter)[source]
Bases:
QuantizedEnumerationIndexQuantized index whose bin counts are precomputed but items are unranked lazily.
This supports compositional distributions where a dynamic program can count how many values fall in each quantized log-density bin without materializing every Cartesian-product value. The getter receives a bin id and offset within that bin and returns the exact (value, log_prob) pair for that quantized rank.
- Parameters:
- bin_for_index(index)[source]
Return (bin_id, offset_within_bin) for a bounded quantized rank.
- get(index)[source]
Return the indexed (value, exact_log_prob) pair.
- iter_from(start=0)[source]
Iterate indexed pairs from start to the end of the bounded index.
- class CountSemiring[source]
Bases:
DecomposableSemiringWitness-retaining carrier over reified nodes / CountIndex: structural counting + unranking.
leaf/plus/scale/map_valuesbuild_CNodetrees unranked by the iterative_unrank()(bounded call-stack regardless of chain depth);times/productconvolve viamixle.enumeration.quantization.convolve_indices()(flat unranker – identical bin counts and within-bucket order to the previous path). Both element kinds expose.histand.get_in_bucket, so they interoperate freely.- zero()[source]
- Return type:
_CNode
- one()[source]
- Return type:
_CNode
- leaf(value, log_prob, quantizer)[source]
- from_enumerator(enum, quantizer, max_fine_bucket)[source]
Lift an atomic distribution’s exact enumerator into a carrier element (depth-bounded).
- plus(a, b)[source]
- Return type:
_CNode
- times(a, b, quantizer, max_fine_bucket)[source]
- Parameters:
quantizer (Quantizer)
max_fine_bucket (int)
- Return type:
CountIndex
- product(elements, quantizer, max_fine_bucket)[source]
n-ary
times. Default foldstimes; carriers may override for efficiency/order.
- scale(a, log_prob, quantizer, max_fine_bucket=None)[source]
Multiply by a constant probability factor: shift buckets by the factor, value unchanged.
This is the action of the base log-prob monoid on the carrier (e.g. a sequence length term, a Markov transition/initial term). Optionally truncate the shifted histogram to a depth bound.
- map_values(a, fn)[source]
Relabel values (pushforward) without touching counts or buckets – e.g. tuple -> list.
- power_prefix(a, max_k, quantizer, max_fine_bucket)[source]
Return [a^(times 0), …, a^(times K)] (K <= max_k), the k-fold self-products.
The histograms are built incrementally (O(max_k) convolutions, shared), so this is the count side of an iid sequence. Each element’s unranker is the flat product of k copies, built lazily and cached – identical bin counts and within-bucket order to calling
product([a]*k)directly, but without the O(k^2) eager cost. Stops early if the element mass is exhausted within the depth bound.
- class DecomposableSemiring[source]
Bases:
ABCA witness-retaining semiring over which a likelihood reduction is evaluated.
zero/oneare the additive/multiplicative identities;pluspools mutually exclusive alternatives (e.g. different sequence lengths or Markov end-states);timescomposes independent factors (whose log-probabilities add);productis n-arytimes.leaflifts a single scored atom into the carrier. Implementations choose the carrierE; for counting it isCountIndex, whose per-bucket structure is the witness needed to unrank.- abstractmethod zero()[source]
- Return type:
E
- abstractmethod one()[source]
- Return type:
E
- abstractmethod leaf(value, log_prob, quantizer)[source]
- abstractmethod plus(a, b)[source]
- Parameters:
a (E)
b (E)
- Return type:
E
- class TropicalSemiring[source]
Bases:
DecomposableSemiringMax-plus (Viterbi) carrier: the single best configuration and its log-probability.
The second realization of
DecomposableSemiring– it swaps only the carrier (the contract this module was built around). WhereCountSemiringcounts the support in each quantized bin, the tropical carrier folds the sameleaf/plus/timesreduction over the(max, +)semiring:pluskeeps the higher-probability alternative,times/productadd log-probabilities and concatenate witnesses. The result is the Viterbi (most-probable) configuration and its exact log-probability – the top of the descending-probability order, and hence an exact bound usable by any family expressible inleaf/plus/times.Crucially this includes the non-decomposable families (HMM / Mixture): exact counting couples across structure (which is why the count index does not serve them), but the Viterbi bound factors cleanly through the tropical fold. (Exact bounded counting for those families is the state-augmented count DP – a further step; for HMM the exact descending enumeration is already available via
mixle.enumeration.hmm_paths.hmm_best_paths().)The witness is the flat tuple of leaf atoms along the best configuration (the tropical carrier keeps a single witness, not the per-child structured value the count carrier reconstructs on unrank), which is exactly what a Viterbi decode wants.
quantizer/max_fine_bucketare accepted for interface symmetry but unused – the tropical fold needs no quantization.- zero()[source]
- Return type:
_Trop
- one()[source]
- Return type:
_Trop
- leaf(value, log_prob, quantizer=None)[source]
- plus(a, b)[source]
- Parameters:
a (_Trop)
b (_Trop)
- Return type:
_Trop
- best_first_union(streams, log_offsets, exact_log_density, tol=1.0e-10)[source]
Enumerate the union of sorted streams with overlapping supports.
Candidate values are pulled from the streams (stream k shifted by log_offsets[k]), de-duplicated via freeze, re-scored exactly with exact_log_density, and buffered until their exact score is at least the upper bound on any not-yet-seen value: logsumexp_k(log_offsets[k] + head_lp_k). This is the mixture algorithm: any unseen x satisfies p_k(x) <= head_k for every k, hence sum_k w_k p_k(x) <= exp(bound).
- merge_enumerators(streams, offsets)[source]
Lazy k-way merge of sorted (value, log_prob) streams with per-stream offsets.
Stream k’s log probs are shifted by offsets[k]. Correct only when the streams have pairwise disjoint supports (no de-duplication or re-scoring is performed).
- class ProductEnumerator(streams, combine=tuple, offset=0.0)[source]
Bases:
objectBest-first enumeration of the Cartesian product of sorted child streams.
Yields (combine(values), log_prob) with log_prob = offset + sum of child log probs, in non-increasing order. Standard k-best lattice search: a max-heap over index tuples, with successors advancing one coordinate; correctness follows from each child stream being sorted (coordinate-wise monotonicity).
Duplicate-free successor rule: each heap entry carries the lowest coordinate it is allowed to advance, and a node only spawns successors for coordinates at or above that index. Every tuple is then reached by exactly one path – the one that increments coordinates in non-decreasing index order – so no
visitedset (and no O(n) per-successor tuple hashing) is needed. Best-first order is preserved because each successor’s score is <= its generating node’s (a sorted coordinate only decreases when advanced), so canonical edges are score-monotone.
- sound_top_k(dist, k, start=0, budget_bits=40.0, oversample=8, bin_width_bits=1.0, max_budget_bits=512.0, total_mass=1.0, tol=1.0e-12)[source]
Exact true-descending observations ranked
[start, start+k)for ANY normalized model.Mass-threshold certificate, correct regardless of the seek stream’s ordering (so it holds for deep/nested mixtures and HMMs where the tropical order is badly displaced): pull distinct
(value, log_prob)from the count-budget index, accumulate exact probability mass, and keep the beststart+kby probability. Since every unpulled item’s probability is at most the remaining masstotal_mass - accumulated, onceremainingdrops below the (start+k)-th best probability the heap is exactly the true topstart+k; returnsorted_desc[start:start+k]. If the in-budget stream is exhausted first, the budget is doubled (up tomax_budget_bits).total_mass(default 1.0) may be a known upper bound for sub-normalized models.Cost is
O(number pulled)– small for peaked distributions, large for flat ones, so for top-k from rank 0dist.enumerator()is usually leaner; this adds the soundness certificate and the arbitrary start offset the sequential enumerator lacks.
- quantized_best_first_decode(next_logprobs=None, eos=None, max_len=None, top_k=None, top_p=None, bucket_bits=12, batch_next_logprobs=None, batch_size=64, start=(), max_results=None, min_mass=None)[source]
Fast descending-probability sequence enumeration specialized for neural / transformer decoders.
Three structure-aware accelerations over
best_first_decode():Nucleus / top-k pruning. Neural next-token distributions are sharply peaked, so each step is restricted to its
top_ktokens or itstop_pnucleus – dropping the long low-probability tail collapses the branching factor (a ~50k vocab down to a handful) at negligible mass loss.Quantized bucket priority queue. Cumulative log-probs only decrease, so instead of an O(log n) comparison heap the frontier is bucketed by quantized score (
bucket = floor(score * 2**bucket_bits)) and drained highest-bucket first – O(1) pushes/pops, and prefixes of near-equal score are grouped. Buckets are disjoint score ranges, so order is exact across buckets and within ~2**-bucket_bits inside one.Batched scoring. The cost is dominated by model forward passes. Pass
batch_next_logprobsto score up tobatch_sizefrontier prefixes in one call (one padded GPU forward) instead of one at a time.
With
top_k=top_p=Noneand a largebucket_bitsthis reduces to the exact enumeration; pruning is the only approximation (reportmin_massto stop once enough probability is covered).- Parameters:
next_logprobs (Callable[[tuple], Iterable[tuple[Any, float]]] | None) –
next_logprobs(prefix) -> [(token, log_prob), ...](used whenbatch_next_logprobsis not given). Log-probs must be <= 0.eos (Any) – end-of-sequence token (a prefix ending in
eosis complete).max_len (int | None) – maximum sequence length. Give
eosand/ormax_len.top_k (int | None) – keep only the
top_khighest-probability tokens per step.top_p (float | None) – keep the smallest set of tokens per step whose probability sums to >=
top_p(nucleus).bucket_bits (int) – score-quantization resolution; larger = finer ordering, slower bookkeeping.
batch_next_logprobs (Callable[[list[tuple]], list[Iterable[tuple[Any, float]]]] | None) – optional
batch_next_logprobs([prefix, ...]) -> [[(token, log_prob), ...], ...]scoring a batch of prefixes in one forward pass.batch_size (int) – number of frontier prefixes expanded per (batched) scoring call.
start (tuple) – initial prefix.
max_results (int | None) – stop after this many complete sequences.
min_mass (float | None) – stop once the yielded sequences cover at least this much probability mass.
- Yields:
(sequence_tuple, total_log_prob), highest probability first (exact across score buckets).- Return type:
- best_first_decode(next_logprobs, eos=None, max_len=None, start=(), heuristic=None, max_results=None)[source]
Exactly enumerate an autoregressive model’s sequences in descending total log-probability.
- Parameters:
next_logprobs (Callable[[tuple], Iterable[tuple[Any, float]]]) –
next_logprobs(prefix)returns an iterable of(token, log_prob)continuations ofprefix(e.g. the log-softmax of a transformer’s next-token logits). Log-probs must be <= 0.eos (Any) – end-of-sequence token; a prefix whose last token is
eosis complete (and not extended).max_len (int | None) – maximum sequence length; a prefix of this length is complete. At least one of
eos/max_lenshould be given or enumeration may not terminate.start (tuple) – the initial prefix (default empty).
heuristic (Callable[[tuple], float] | None) – optional admissible upper bound on the remaining log-probability from a prefix (e.g.
remaining_steps * max_step_logprob); tightens the search. Omit for the exact h=0 search.max_results (int | None) – stop after this many complete sequences.
- Yields:
(sequence_tuple, total_log_prob)in nonincreasing total log-probability.- Return type:
- class AutoregressiveEnumerable(next_logprobs, max_len=None, eos=None, max_depth=1024, bin_width_bits=1.0, oversample=8, batch_next_logprobs=None, batch_size=256, count_mode='auto', branch_cap=None, batch_score_sequences=None, all_position_logprobs=None)[source]
Bases:
objectAdapter: an autoregressive
next_logprobs(prefix)model as a count-/rank-/unrank-able object.The support depends on the model. A fixed-length model (
max_lenset, noeos) has support on every length-max_lensequence. A terminating model (eosset) has support only on sequences that end ineos– so the enumeration counts/ranks/unranks exactly those, of any length, bounded by the probability budget (a tight terminating model has finitely many sequences above any threshold). A length bound is NOT the support boundary there: an un-terminated truncation has zero mass as an output and is never counted.max_depthis only a safety cap on recursion for non-tight models.- Parameters:
next_logprobs (Callable[[tuple], Iterable[tuple[Any, float]]]) –
next_logprobs(prefix) -> [(token, log_prob), ...]– the next-token log-probabilities (<= 0) given a prefix tuple, e.g. thelog_softmaxof a transformer’s next-token logits. For speed it may instead return the(tokens, log_probs)numpy-array pair (skips per-token boxing). For a terminating modeleosmust be one of the tokens it can return.max_len (int | None) – for a fixed-length model, the sequence length (the support is all length-
max_lensequences). Omit for a terminating model (or pass it as a hard length cap, but un-terminated truncations are still dropped).eos (Any) – end-of-sequence token. When given, the model is terminating: only sequences ending in
eosare in the support.max_depth (int) – safety bound on recursion depth for a terminating model (the probability budget is the real bound). Raise it if a tight model legitimately produces very long sequences.
bin_width_bits (float) – quantization resolution of the count index (finer = exacter ordering, more memory). The defaults match the distribution count-DP.
oversample (int) – quantization resolution of the count index (finer = exacter ordering, more memory). The defaults match the distribution count-DP.
batch_next_logprobs (Callable[[list[tuple]], list[Any]] | None) – optional
batch_next_logprobs([prefix, ...]) -> [result, ...]scoring many prefixes in one (padded) forward. When given, the count index warms its forward cache breadth-first inbatch_sizechunks – the large speed-up for transformers, where one-at-a-time forwards dominate (e.g. distilGPT-2 length-2 to rank 1e5: ~25 s one-at-a-time -> ~1 s batched).batch_size (int) – prefixes per batched forward.
count_mode (str) – how counts are carried past the int64-exact regime (budgets over ~2**60 sequences).
'auto'(default) switches the numpy fast path to float64 there – approximate counts (exact below 2**53, ~1e-16 relative error per pooling beyond) at full numpy speed.'exact'preserves arbitrary-precision counts by falling back to the slow Python recursion.'float'forces float64 everywhere.branch_cap (int | None) – recurse into only the top-
branch_capin-budget tokens per prefix – the certified approximation for wide (LLM-sized) vocabularies, shrinking the tree by ~V/cap per level. The skipped remainder is soundly bounded (count_bracket/dropped_upper: a skipped subtree withrremaining budget bits holds at most2**rcompletions); enumeration covers the sub-support of sequences whose every token is among its context’s top-branch_cap.batch_score_sequences (Callable[[list[tuple]], Any] | None) – optional teacher-forcing scorer
[sequence, ...] -> array of total log-probs– ONE forward per sequence (all positions score in parallel) instead of one forward per token. Used byscore_sequences()and, when a sequence’s prefixes are not already cached, bylog_density(); the substrate for draft-rescored (speculative) enumeration.all_position_logprobs (Callable[[tuple], list[Any]] | None) – optional
sequence -> [next_logprobs result for seq[:d], d in 0..len-1]– one forward yields the full next-token distribution at EVERY position; harvested into the forward cache byharvest(). Makes corpus-calibrated envelopes ~L-times cheaper.
The model is queried lazily and memoized by prefix, so deepening the index (or recomputing a log-density) never re-runs a forward pass it has already seen. With integer tokens the histogram build is the numpy fast path (int64 counts below ~``2**60`` budgets; float64 beyond, per
count_mode).- quantized_count_index(quantizer, max_fine_bucket)[source]
Count index over the model’s support (length-
max_lensequences, or all eos-terminated sequences), bounded by the bit budgetmax_fine_bucket.
- log_density(sequence)[source]
Exact total log-probability of a sequence (
-infif any token is off-support given its prefix).When a
batch_score_sequencesscorer is configured and any of the sequence’s prefixes is not already cached, the score comes from ONE teacher-forcing forward instead of one forward per token.
- score_sequences(sequences)[source]
Exact total log-probabilities of many sequences – batched teacher forcing when available.
With
batch_score_sequencesthis is one call (one forward per sequence, all positions in parallel); otherwise it falls back to per-sequencelog_density()over the cached walk. The rescoring primitive for draft-based (speculative) enumeration.
- harvest(sequence)[source]
Cache the next-token distribution at every prefix of
sequencefrom one forward.Requires
all_position_logprobs; a no-op without it. Feeding typical sequences (a corpus, a provider’s fast generations) through this warms the same memo cache the count index and the envelope read – L cache entries per model call.
- structural_fine_bucket(sequence, quantizer)[source]
- enumerator()[source]
A descending-probability iterator over the support (lazy best-first); use
top_kfor the head.
- seek_index(*, max_depth_bits=4096.0)[source]
The cached persistent
SeekIndexover this model.Built lazily on first use and reused by every convenience query (
unrank/count/threshold/mass_above), deepening in place when a query needs more depth – so a sweep of a thousand unranks pays for one tree build, not a thousand. The forward cache is shared with it, so deepening only runs new forwards for newly-live prefixes.- Parameters:
max_depth_bits (float)
- budget_index(budget_bits, max_depth_bits=4096.0)[source]
The count-budget seek index covering at least
2**budget_bitssequences (for unrank/iterate).
- envelope_index(*, n_paths=64, seed=0, budget_bits=64.0)[source]
An
AREnvelopeIndexover this model – approximate enumeration at depths the exact tree index cannot reach (O(L) forwards per unrank instead of Theta(count) tree expansion; exact for iid-step models, mean-field estimate otherwise).
- top_k(k)[source]
The
kmost probable sequences, exact, by best-first listing (use for smallk).
- count(min_log_prob)[source]
How many sequences have
log_density >= min_log_prob– computed from counts, not listed.With
branch_capset this is the count over the capped sub-support (a sound lower bound);count_bracket()adds the certified upper bound including the skipped remainder.
- count_bracket(min_log_prob)[source]
A sound
[lo, hi]bracket on the number of sequences withlog_density >= min_log_prob.locounts the (exactly enumerated) kept sub-support;hiaddsdropped_upper– the certified bound on completions excluded bybranch_cap(identical tolowhen no cap is set).
- unrank(i)[source]
The
i-th most probable sequence (0-based) and its exact log-probability, by random access.
- threshold(rank)[source]
Log-probability of the
rank-th most probable sequence – the boundary of the top-rankset.
- mass_above(min_log_prob)[source]
A
(lower, upper)bracket on the total probability of sequences withlog_density >= min_log_prob.Computed from the count histogram alone (no enumeration): each fine bucket of
csequences contributes betweenc * 2**(-hi_bits)andc * 2**(-lo_bits), where the bucket spans[lo_bits, hi_bits)of information. Tighten by raisingoversample.
- rank(sequence)[source]
DensityRankResult– rank + cumulative mass of a sequence.
- cumulative(sequence)[source]
G(seq) = P(p(Y) >= p(seq))– total mass of sequences at least as probable asseq.
- autoregressive_count_index(steps, prefix, depth, quantizer, max_fine_bucket, eos=None, branch_cap=None)[source]
Tree-recursive count index over completions of
prefixup todepthmore tokens.Returns
(CountIndex, truncated). The histogram counts completions by fine bucket of total bits;CountIndex.get_in_bucket(fb, offset)unranks the structural(token, ...)sequence and its exact log-probability.truncatedis True if any completion was dropped at themax_fine_bucketdepth bound (so a caller can deepen).Each step’s bits
-log2 p(x_t | prefix)are added to every completion bucket viaCountHistogram.shift(); the per-token children are pooled withCountHistogram.add(). Becausestepsare taken in descending probability, once a token’s own bits exceed the remaining budget every later token does too, so the loop can stop – this is what bounds the work to the live prefixes.branch_caprecurses into only the top-capin-budget tokens per node – the certified approximation for wide vocabularies. Each skipped token’s subtree is bounded soundly (completions withinrremaining bits number at most2**r, since their conditional probabilities sum to at most 1) and the total accumulates inCountIndex.dropped_upper: the true in-budget count lies in[total(), total() + dropped_upper]. Dropped tokens do NOT settruncated(deepening cannot recover them; raisingbranch_capcan).
- class AREnvelopeIndex(model, *, n_paths=64, seed=0, budget_bits=64.0, calibration_sequences=None)[source]
Bases:
objectEnvelope (mean-field) seek index over a fixed-length
AutoregressiveEnumerable.- Parameters:
model (Any) – the autoregressive adapter (
max_lenset; terminating/eos models are rejected).n_paths (int) – contexts sampled per depth for the envelope calibration (more = sharper envelope; the root depth is always exact). The calibration forwards are memoized on the model, so they are shared with any exact index built later.
seed (int) – calibration sampling seed (the index is deterministic given it).
budget_bits (float) – initial depth of the suffix tables; queries deepen geometrically as needed.
calibration_sequences (list[tuple] | None) – optional typical sequences (a corpus, a provider’s fast generations) to calibrate the envelope from INSTEAD of ancestral sampling. Each is harvested through
model.harvest– with theall_position_logprobscontract that is ONE forward per sequence for all its per-depth contexts, ~L-times cheaper than sampling token by token.
- ensure_bits(depth_bits)[source]
Deepen the suffix tables to cover
depth_bits(geometric, cheap: L capped convolutions).- Parameters:
depth_bits (float)
- Return type:
AREnvelopeIndex
- total()[source]
Estimated number of sequences within the built depth (exact for an iid-step model).
- Return type:
- count(min_log_prob)[source]
Estimated number of sequences with
log_density >= min_log_prob(mean-field; iid-exact).
- mass_above(min_log_prob)[source]
A mean-field
(lower, upper)estimate of the head mass abovemin_log_prob.Same bucket arithmetic as the exact index’s
mass_above(each bucket ofcsequences holds betweenc * 2**-hi_bitsandc * 2**-lo_bitsof mass), applied to the envelope histogram – so the bracket carries the envelope’s estimation error on top of the quantization smear.
- unrank(i)[source]
The approximately-
i-th most probable sequence and its exact log-probability.Costs one model forward per step (L total, memoized) – never a tree expansion. The rank coordinate inherits the envelope approximation; the returned sequence and its log-probability are exact model quantities. Raises
IndexErrorpast the estimated support size.
- threshold(rank)[source]
Exact log-probability of the sequence the envelope places at
rank(approximate boundary).
- rank_bracket(sequence)[source]
Estimated
[lo, hi]rank bracket ofsequence– its envelope bucket’s rank span.locounts the estimated sequences in strictly shallower buckets;hiadds the sequence’s own bucket. Exact for iid-step models; otherwise a mean-field estimate (floats, not certificates).
- class LatticeEnvelopeIndex(model, *, n_clusters=None, cluster_fn=None, n_paths=128, seed=0, budget_bits=64.0)[source]
Bases:
objectCluster-conditioned envelope index: the Markov refinement between mean-field and exact.
AREnvelopeIndexaverages one envelope per depth – exact only for iid steps. This index conditions each depth’s envelope on the cluster of the last token (cluster_fn): the calibration histograms are kept per(depth, cluster)and split by the next token’s cluster, and the suffix tables become the lattice DPS_d[c] = sum_c' H_d[c][c'] (*) S_{d+1}[c']– the same shape asHMMPathIndexwith clusters as states. The result is exact for any order-1 Markov model whose next-token distribution depends only on(depth, cluster_fn(last token))– withcluster_fnthe identity, that is every last-token Markov model, where the mean-field envelope is provably lossy. Coarser clusterings interpolate:m = 1recovers mean-field,m = Vconditions on the full last token.Queries mirror
AREnvelopeIndex: counts and thresholds off the root table,unrankdescends the REAL model apportioning by cluster-conditioned subtree estimates (O(L) forwards), every returned log-probability exact. A(depth, cluster)never visited in calibration borrows the depth’s aggregate envelope (mean-field fallback for that pocket) – raisen_pathsto shrink the pockets. Fixed-length models only.- Parameters:
- total()[source]
Estimated sequences within the built depth (exact for depth+last-cluster Markov models).
- Return type:
- count(min_log_prob)[source]
Estimated number of sequences with
log_density >= min_log_prob(Markov-refined estimate).
- unrank(i)[source]
The approximately-
i-th most probable sequence with its exact log-probability.
- class RescoredIndex(draft_index, target, *, rerank_window=64, assumed_gap=None)[source]
Bases:
objectDraft-ordered, target-scored enumeration with window reranking.
- Parameters:
draft_index (Any) – any index with
unrank(i) -> (sequence, draft_log_prob)– aSeekIndexover a cheapAutoregressiveEnumerable, anAREnvelopeIndex, or anything equivalent.target (Any) – the expensive model – an
AutoregressiveEnumerable(itsscore_sequences()batch scorer is used) or a bare callable[seqs] -> log_probs.rerank_window (int) – extra draft candidates pulled around a query and reranked by target score. Larger = more robust to draft/target disagreement, one batched forward either way.
assumed_gap (float | None) – optional global bound on
|target_lp - draft_lp|(nats). When supplied, results carry a soundcertifiedverdict; otherwisecertifiedisNoneand the observedgapis reported as a diagnostic.
- top_k(k)[source]
The
kbest sequences by TARGET score among thek + rerank_windowdraft head.Returns
{"items": [(seq, target_lp), ...], "certified": bool | None, "gap": float}– target-exact scores, draft+window-approximate completeness (see the class docstring).
- slice(start, k)[source]
Target-reranked
[start, start + k)slice of the pulledstart + k + rerank_windowhead.Same semantics as
top_k(): order within the pulled set is target-exact; the certificate covers whether an unpulled sequence could belong in (or before) the slice.
- hmm_best_paths(log_pi, log_A, log_b, k=None)[source]
Enumerate HMM state paths in nonincreasing joint log-probability.
- Parameters:
log_pi (ndarray) –
(K,)log initial-state distribution.log_A (ndarray) –
(K, K)log transition matrix (rowj-> columnkislog p(z_t=k | z_{t-1}=j)).log_b (ndarray) –
(T, K)per-position emission log-likelihoodslog p(x_t | z_t=k).k (int | None) – stop after the
kbest paths;Noneenumerates allK**Tlazily.
- Yields:
(path, joint_log_prob)withpatha length-Ttuple of state indices, highest first. The first yield is the Viterbi (MAP) path.- Return type:
- class HMMPathIndex(log_pi, log_A, log_b, *, bin_width_bits=1.0, oversample=8, budget_bits=None)[source]
Bases:
objectQuantized random-access index over an HMM’s state paths for one observation sequence.
Precompute (once,
O(T * K^2 * W)): quantize every step score –log_pi[s] + log_b[0, s]andlog_A[s, s'] + log_b[t, s'], one floor per step so the accumulated smear is at mostTfine buckets – and run a forward count DP:C_t[s']is the histogram, over integer total-score buckets, of the number of length-t+1prefixes ending in states'. Counts are float64 (exact below 2**53; the number of paths isK**T, so deep problems carry the documented ~1e-16/op relative error instead of overflowing).Query (each
O(T * K)):unrank(i)walks the stored tables backward – pick the final state whose bucket count covers the offset, then repeatedly the predecessor whose shifted prefix bucket does – returning thei-th best path by quantized score and its exact joint log-probability.count/threshold/mass_aboveread the pooled final histogram.Ordering contract: paths are ordered by their quantized bucket (width
bin_width_bits/oversamplebits); within a bucket the order is deterministic but unspecified – exactly the count-index semantics elsewhere inmixle.enumeration.hmm_best_paths()remains the exact-order tool for the head; this index is the random-access tool for depth (rank 1e6 costs one table walk, not 1e6 A* expansions).- Parameters:
- bucket_of(log_joint)[source]
The index’s bucket frame for a true joint score (bits behind the per-position optimum).
- total()[source]
Number of state paths within the budget (== K**T when nothing truncated; float64 counts).
- Return type:
- count(min_log_joint)[source]
How many paths have quantized joint log-probability at least
min_log_joint.Counts every true qualifier (the structural bucket never over-states a path’s bits) plus at most the paths within the
T-floor smear band below the threshold.
- mass_above(min_log_joint)[source]
A
(lower, upper)bracket on the total joint probability/density of paths above the threshold.Bucket arithmetic with the
T-floor smear: a path in bucketbcarries betweenexp(total_offset) * 2**-((b + T) / fpb)andexp(total_offset) * 2**-(b / fpb)of joint mass (the offset restores the per-position shift, so unnormalized emission likelihoods work).
- unrank(i)[source]
The
i-th best state path by quantized score (0-based) and its exact joint log-probability.One backward table walk –
O(T * K)– regardless of how deepiis.
- threshold(rank)[source]
Exact joint log-probability of the
rank-th best (quantized-order) path.
Subpackages¶
Submodules¶
- mixle.enumeration.algorithms module
- mixle.enumeration.assignment module
- mixle.enumeration.autoregressive module
- mixle.enumeration.best_first module
- mixle.enumeration.density_rank module
- mixle.enumeration.envelope module
- mixle.enumeration.hmm_paths module
- mixle.enumeration.model_enumeration module
- mixle.enumeration.rescore module
- mixle.enumeration.seek_index module
- mixle.enumeration.spanning module
- mixle.enumeration.streams module