mixle.ops module¶
Operations — the verbs that transform distributions, and how they change capabilities.
The missing third axis of mixle: alongside objects (distributions) and capabilities (what they support), the operations that move an object from one capability set to another. Each verb documents its capability signature — what it requires of its input and what the output gains — so “what can I do to a distribution, and what does it become?” has one home.
quantize(dist, bits) : any Distribution -> FiniteSupport · Enumerable · RankableByIndex truncate(dist, …) : Distribution -> Distribution (Enumerable preserved) condition(dist, observed) : Conditionable -> Distribution marginalize(dist, keep) : Marginalizable -> Distribution mixture(dists, w) : Distributions -> LatentStructured transform(dist, f) : Distribution + inv. f -> Distribution (Jacobian-corrected) tilt(dist, theta) : ExponentialFamily -> Distribution project(source, target) : Sampleable x Fittable -> Distribution (forward-KL M-projection) product_of_experts(dists) : tractable family -> Distribution (geometric/log-linear pool)
See docs/ARCHITECTURE.md and the operation table in docs/CAPABILITIES.md.
- quantize(dist, bits=8, *, lo_q=1e-3, hi_q=1.0 - 1e-3, n_samples=200_000, seed=0)[source]
Discretize a continuous
distinto a finite distribution over2**bitsbins.Capability signature:
any Distribution -> FiniteSupport · Enumerable · RankableByIndex. The result is aCategoricalDistributionover bin midpoints; the bin masses are exact (via the basecdfwhen available) or sampled otherwise. This is the concrete answer to “if I quantize a distribution, what does it become and how” —describe(quantize(g))shows the gained capabilities.Target measure.
[lo_q, hi_q]brackets where the bins are placed, not which mass survives: the whole distribution is quantized, with mass below the bracket assigned to the first bin and mass above it to the last – the clipped image ofdistunder the bin map. The exact CDF route used to instead drop both tails and renormalize the interior to one, i.e. silently return the distribution conditional on the bracket, while the sampling fallback clipped its draws into the edge bins. The two advertised implementations therefore targeted different distributions: withlo_q=.25, hi_q=.75, bits=2on a uniform, the CDF route returned four masses of.25where the clipped answer is.375/.125/.125/.375. Both routes now agree on the clipped measure.
- truncate(dist, *, allowed=None, forbidden=None)[source]
Restrict
distto (allowed) or away from (forbidden) a finite set; mass renormalizes.
- condition(dist, observed)[source]
Condition
diston a subset of coordinates. Requires theConditionablecapability.
- marginalize(dist, keep)[source]
Marginalize
distto the kept coordinates. Requires theMarginalizablecapability.
- mixture(dists, w=None)[source]
Weighted mixture of
dists— a latent-variable model (LatentStructured).Capability signature:
Distributions -> LatentStructured.distsis materialized once (a one-shot iterator is consumed exactly once, not walked again for the weight count), must contain at least one component, andw— when given — must supply exactly one weight per component.- Raises:
CapabilityError – if
distsis empty. A mixture of nothing is not a distribution, and the default uniform weight1 / len(dists)used to compute it before anything checked, somixture([])reported an incidentalZeroDivisionErrorfrom the façade’s own arithmetic rather than the contract error this boundary declares (compareproduct_of_experts(), which already refuses an empty expert list here).ValueError – if
whas a different length thandists— caught at the façade, where the caller’s own two arguments are still in view, rather than inside the component layer.
- Parameters:
- transform(dist, f)[source]
Change of variables
y = f(x)with a Jacobian-corrected density (fis aTransform).
- tilt(dist, theta)[source]
Exponentially tilt
distbytheta. Requires theExponentialFamilycapability.
- project(source, target, *, n_samples=20_000, seed=0, max_its=50, init=None, delta=1.0e-6)[source]
Variationally project
sourceonto the family oftarget(the sample-based M-projection).Capability signature:
Sampleable source x Fittable target -> Distribution. Drawsn_samplesfromsourceand fitstargetto them by maximum likelihood. Works for anysourceexposing asampler(a distribution, mixture, GP, or a trained neural model) onto any fittabletargetfamily, so e.g. a neural sequence model can be distilled onto an HMM.This is the empirical M-projection, not the exact one. Maximum likelihood on the population would minimize the forward divergence
KL(source || target_family)exactly; what runs here is maximum likelihood on one finiten_samples-sized Monte Carlo draw, optimized by an iterative fitter that is capped atmax_itsand, for latent-variable families, only guaranteed to reach a local optimum. The result therefore carries sampling error (of order1/sqrt(n_samples), not estimated or reported here), optimization error, and whatever misspecification the target family imposes. It converges to the exact projection asn_samplesgrows, and nothing in the returned object certifies how close this particular fit got. Treat it as an approximation and validate it (e.g. score held-outsourcedraws under the result) before claiming a KL guarantee.targetmay be a distribution (itsestimator()supplies the family) or an estimator directly. The returned model is a member of the target family;describe(project(...))shows its capabilities.initis the member of the target family EM starts from. Without it the fit starts from the estimator’s own random initialization atseed, which for a latent family means one restart at whichever optimum that draw reaches: a mixture-reduction benchmark read as “closed-form beats EM by 5x” was measuring a single EM start stuck at a degenerate optimum, and the same call warm started from the closed-form solution improved monotonically past it (P10-F03). Passing the closed-form projection, or any other good guess, is the usual way to use this.deltais the fitter’s convergence tolerance, orNoneto remove the convergence stop so the fit may use allmax_itsiterations – what a budget-matched comparison between two projections wants, since a run that stopped early and one that used its whole budget are not comparable on wall-clock.Nonedoes not guarantee the whole budget runs: a rejected update (a non-improving or non-finite step) still ends the loop, and the fit then warns with how many of themax_itsiterations ran. The mixture-reduction benchmark’s owndelta=Noneprojections stopped well short of their 50 this way (Q10-F03), so compare runs byfit_provenance().iterationsrather than by the budget that was requested.
- product_of_experts(dists, weights=None)[source]
Geometric (log-linear) pooling of densities — a Product of Experts.
Capability signature:
tractable family -> Distribution. Multiplies the expert densities,p(x) ∝ ∏_k p_k(x)**w_ki.e.log p(x) = Σ_k w_k·log p_k(x) − log Z, with raw exponent weightsw_k(default1.0each — not normalized to sum to one, since PoE weights are exponents). Only the cases with a tractable normalizer are constructed exactly; the general continuous case raisesCapabilityError.Categorical / finite shared support (the LLM-vocab fusion case): exact over the intersection of the weighted experts’ supports —
new_pmap[x] ∝ ∏_k p_k(x)**w_kforxwith positive mass under every expert carrying a nonzero exponent, then renormalized. Returns aCategoricalDistribution.Gaussian (closed form): a Gaussian with precision-weighted combination
1/σ² = Σ_k w_k/σ_k²andμ = σ²·Σ_k w_k·μ_k/σ_k². Returns aGaussianDistribution.
A
w_k = 0expert contributes the constantp_k(x)**0 = 1and so is not in the pool at all; it is dropped before the support intersection is taken, exactly as it drops out of the Gaussian route’s precision sum. (It used to be intersected first: an expert supported only on{"a"}at weight zero deleted every other label from a weight-one expert’s answer.)- Parameters:
dists (Any) – the experts to pool (an iterable of distributions, all of the same tractable kind).
weights (Any) – per-expert exponents
w_k;None(default) uses1.0for each expert. Each must be a finite real number; negative exponents are permitted (a “divide by this expert” pool) but are the caller’s responsibility to keep proper.
- Raises:
CapabilityError – if the experts have no shared finite support and are not all Gaussian — the PoE normalizer
Z = ∫ ∏_k p_k(x)**w_k dxis then intractable in closed form — or if every exponent is zero, which leaves the constant function 1 rather than a distribution.