mixle.enumeration.quantization.core module¶
Structural quantized enumeration: count the support without enumerating it.
The smart-enumeration index (see mixle.enumeration.algorithms and
mixle.stats.compute.pdist.DistributionEnumerator) is a seek structure over a
distribution’s exact descending-probability enumeration: it precomputes how many
support values fall in each quantized log-probability bin so that an arbitrary rank
can be resolved and unranked without walking the prefix.
For exponential-support families (sequences, Markov chains, …) the number of
values within a probability budget is astronomically large, so the index must be
built from per-bin counts computed structurally – by quantizing and binning the
complete model probability Q(ln p(x)) and lifting the model’s own likelihood
recursion into a count/histogram semiring – never by materializing the domain.
This module provides that semiring:
Quantizermaps an exactlog_probto a fine integer bucket of accumulated bits and a coarse output bin.
CountHistogramis the semiring value: counts indexed by fine bucket, withshift(add a constant log-prob term),convolve(independent additive composition),power(L-fold self-convolution), andadd(pool alternatives). Counts are exact Python integers and may exceed 2**128.
CountIndexpairs a histogram with a structural unrankerget_in_bucket(fine_bucket, offset) -> (value, exact_log_prob).
leaf_count_index()builds aCountIndexfrom any exact enumerator, bounded by depth (cheap for small/closed-form leaves).
convolve_indices()composes child indices (the Composite reference case), andbuild_budget_index()accumulates coarse bins until the cumulative count reaches the requested2**budget_bitsbudget, returning amixle.enumeration.algorithms.LazyQuantizedEnumerationIndex.
The bin assignment is the only approximation (intermediate fine-bucket rounding shifts
items by at most num_terms / oversample coarse bins); the value set, total
count, and the exact log probability of every unranked item are exact, because the
unranker returns the value and the index re-evaluates log_density on it.
- class Quantizer(bin_width_bits=1.0, oversample=8, executor=None, count_mode='exact')[source]
Bases:
objectMaps exact log probabilities to fine buckets and coarse bins.
bits(x) = -log2 p(x) >= 0. The fine bucket is floor(bits * oversample / bin_width); the coarse bin is fine_bucket // oversample, which equals floor(bits / bin_width) for a single value (so leaf coarse bins match the existing QuantizedEnumerationIndex convention).
oversample(R) controls how finely accumulated log-probabilities are tracked through convolutions, bounding the requantization error.- bin_width_bits
- oversample
- executor
- count_mode
- convolve(a, b, max_fine_bucket=None)[source]
Convolve two histograms, using the attached parallel executor when present.
- Parameters:
a (CountHistogram)
b (CountHistogram)
max_fine_bucket (int | None)
- Return type:
CountHistogram
- bits(log_prob)[source]
Information content -log2 p in bits (>= 0).
- fine_bucket(log_prob)[source]
Fine integer bucket of a log probability.
- coarse_bin(fine_bucket)[source]
Coarse output bin containing a fine bucket.
- class CountHistogram(base, data)[source]
Bases:
objectCounts of support values indexed by fine bucket of accumulated bits.
data[i]is the (exact, possibly huge) number of values whose fine bucket isbase + i. The histogram is dense over[base, base + len(data))with implicit zeros outside. This is the value type of the count semiring.- base
- data
- classmethod empty()[source]
- Return type:
CountHistogram
- classmethod delta(fine_bucket, count=1)[source]
A single bucket with the given count (the multiplicative identity when count=1, bucket=0).
- shift(k)[source]
Return a copy with every bucket moved by k (adds a constant log-prob term).
- Parameters:
k (int)
- Return type:
CountHistogram
- truncate(max_fine_bucket)[source]
Drop buckets strictly beyond
max_fine_bucket(depth bound).- Parameters:
max_fine_bucket (int)
- Return type:
CountHistogram
- add(other)[source]
Pointwise sum (pool of mutually exclusive alternatives, e.g. different lengths).
- Parameters:
other (CountHistogram)
- Return type:
CountHistogram
- convolve(other, max_fine_bucket=None, bucket_delta_bits=None)[source]
Discrete convolution: counts of sums of two independent additive log-prob terms.
Optionally drop output buckets beyond
max_fine_bucketduring accumulation so a depth bound keeps the histogram width fixed regardless of how large the counts grow.Three backends produce identical results: a direct double loop for small operands, a vectorized multi-prime number-theoretic transform (
_convolve_ntt()) when every KEPT output coefficient provably fits the prime ladder’s CRT capacity (bucket_delta_bits— the quantizer’s bits-per-fine-bucket pitch — lets the measured-envelope bound engage; see_kept_coeff_bound()), and Kronecker substitution (_convolve_kronecker()) otherwise. The latter packs each integer histogram into a single big integer and multiplies once, pushing the inner loop into CPython’s sub-quadratic big-integer multiply – exact for arbitrarily large counts (they can exceed the ladder’s ~2^207, so an unchecked floating-point FFT is never an option).
- convolve_float(other, max_fine_bucket=None)[source]
Approximate convolution carrying counts as float64 – the quantized-counting fast path.
One
numpy.convolve()at C speed replaces the exact big-integer machinery. Counts are exact while they stay below 2**53 (float64 integers) and carry a relative error of at most~width * 2**-53per convolution beyond that – negligible against the bin-assignment approximation the count-DP already makes. Counts above~2**1000would overflow float64, so such results fall back to the exact backend (the histograms are identical below the cliff).- Parameters:
other (CountHistogram)
max_fine_bucket (int | None)
- Return type:
CountHistogram
- class CountIndex(hist, getter, dropped_upper=0.0)[source]
Bases:
objectA fine-bucket count histogram paired with a structural unranker.
get_in_bucket(fine_bucket, offset)returns(value, exact_log_prob)for theoffset-th value (0-based) whose fine bucket isfine_bucket. The ordering within a bucket is deterministic but otherwise unspecified.- Parameters:
- hist
- dropped_upper
- leaf_count_index(enum, quantizer, max_fine_bucket, max_items=None)[source]
Build a CountIndex from an exact descending-probability enumerator, bounded by depth.
Pulls items until their fine bucket exceeds
max_fine_bucket(or, ifmax_itemsis given, until that many items have been taken). Cheap for closed-form or small-support leaves (a geometric/Poisson has ~depth items within a depth bound); themax_itemscap is the brake for the enumerate-and-bin fallback over exponential-support families that cannot count structurally. Returns(index, truncated)wheretruncatedis True if in-bound items were left untaken.
- child_count_index(child, path, quantizer, max_fine_bucket)[source]
Build child.quantized_count_index(…), annotating EnumerationError with the child’s path.
- convolve_indices(children, quantizer, max_fine_bucket)[source]
Compose independent child indices into their additive (convolution) product.
The joint histogram is the convolution of the child histograms (capped at the depth bound). Unranking resolves the per-child fine buckets that sum to the target, then the per-child offsets via mixed-radix decomposition using suffix-convolution counts. This is the Composite reference case; the empty product is the single empty tuple at bucket 0.
- build_budget_index(index, quantizer, budget_bits, value_combine=None, exact_log_density=None, truncated=False)[source]
Wrap a CountIndex as a budget-bounded LazyQuantizedEnumerationIndex.
Accumulates coarse bins (fine buckets grouped by
quantizer.oversample) in descending-probability order until the cumulative count reaches2**budget_bits, then stops. The returned getter maps a coarse (bin, offset) to a fine (bucket, offset), unranks the structural value, optionally maps it withvalue_combine(e.g. tuple->list), and reports the exact log density (recomputed viaexact_log_densitywhen supplied, otherwise the structurally accumulated log probability).
- 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.
- distinct_budget_stream(dist, budget_bits, bin_width_bits=1.0, oversample=8, dedup='canonical', start=0, stop=None, max_entries=1 << 16, num_workers=None)[source]
Yield DISTINCT (value, exact_log_prob) from a count-budget index, in approx descending order.
Builds the budget index and removes repeats by one of two modes (see
ProbabilityDistribution.count_budget_distinctfor the full contract):dedup='canonical': stateless per-item predicate (dist.is_canonical_copy), O(1) memory and random-accessible –start/stopchoose a STRUCTURAL rank range, so the distinct enumeration can begin anywhere and partition across workers with no shared state.dedup='window': a bounded O(max_entries) LRU over the stream (sequential;startmust be 0).
Exact-count families never duplicate, so either mode is a pass-through.
- 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.