Operations ========== ``mixle.ops`` contains the verbs that transform distributions. If distributions are the nouns and capabilities describe what those nouns can do, operations describe how a model moves from one capability set to another. This matters because many practical workflows are not simply "fit a model". They are "condition this model on evidence", "quantize a continuous leaf so it can be enumerated", "project a neural source into a simpler family", or "pool several experts into one distribution". ``mixle.ops`` gives those moves a single public home. Capability Signatures --------------------- .. list-table:: :header-rows: 1 * - Operation - Input requirement - Output behavior * - ``quantize(dist, bits)`` - Any sampleable or CDF-capable distribution - Finite categorical approximation with enumeration support. * - ``truncate(dist, allowed=..., forbidden=...)`` - Distribution - Renormalized restricted distribution. * - ``condition(dist, observed)`` - ``Conditionable`` - Conditional distribution over unobserved coordinates. * - ``marginalize(dist, keep)`` - ``Marginalizable`` - Marginal distribution over selected coordinates. * - ``mixture(dists, w)`` - Sequence of distributions - Latent mixture distribution. * - ``transform(dist, f)`` - Distribution plus invertible transform - Change-of-variables distribution. * - ``tilt(dist, theta)`` - ``ExponentialFamily`` - Exponentially tilted distribution. * - ``project(source, target)`` - Sampleable source, fittable target family - Forward-KL M-projection into the target family. * - ``product_of_experts(dists)`` - Tractable shared family - Log-linear pooled distribution. The table is intentionally capability-oriented. You should not need to know the concrete class name before asking whether a transformation is legal. Quantization ------------ ``quantize`` turns a continuous distribution into a finite categorical approximation. The result can be enumerated, ranked, and used by algorithms that require finite support. .. code-block:: python from mixle.ops import quantize from mixle.stats import GaussianDistribution dist = GaussianDistribution(0.0, 1.0) finite = quantize(dist, bits=8) top = finite.enumerator().top(5) Use quantization when you need a bridge from continuous uncertainty to enumeration, top-k search, discrete decision policies, or compact artifacts. Keep the number of bits tied to the downstream need: more bins improve fidelity but make enumeration heavier. Truncation ---------- ``truncate`` restricts a distribution to an allowed set or removes a forbidden set, then renormalizes mass. .. code-block:: python from mixle.ops import truncate visible = truncate(categorical_model, forbidden={"unknown", "masked"}) This is useful for policy constraints, legal label subsets, active learning pools, and diagnostic "what if this option were unavailable?" analysis. Conditioning And Marginalization -------------------------------- ``condition`` and ``marginalize`` are only available when the input distribution declares the required capability. .. code-block:: python from mixle.ops import condition, marginalize posterior = condition(record_model, {0: "premium"}) only_amount = marginalize(record_model, keep=[2]) Prefer these operations to manual slicing. The distribution itself knows how to preserve normalization, sufficient statistics, and any family-specific closed forms. Mixtures -------- ``mixture`` constructs a latent mixture from component distributions and weights. .. code-block:: python from mixle.ops import mixture from mixle.stats import GaussianDistribution model = mixture( [GaussianDistribution(-2.0, 0.4), GaussianDistribution(2.0, 0.8)], w=[0.35, 0.65], ) Mixtures are the simplest way to express unobserved regimes. For fitted mixture estimators and EM workflows, see :doc:`distributions`, :doc:`inference`, and :doc:`hmms-latent`. Transforms And Tilts -------------------- ``transform`` applies an invertible change of variables with the appropriate Jacobian correction. ``tilt`` exponentially reweights an exponential-family distribution. Use transforms when the natural modeling scale is not the data scale: logs, positive constraints, calibrated score transforms, or physical unit changes. Use tilts when you want to encode a moment preference while staying inside the exponential-family calculus. Projection ---------- ``project`` fits a target family to samples drawn from a source model. This is a practical M-projection: it minimizes the forward divergence from the source to the target family as estimated by samples. .. code-block:: python from mixle.ops import project from mixle.stats import HiddenMarkovModelEstimator simpler = project(neural_sequence_model, HiddenMarkovModelEstimator(...)) Projection is useful for distillation, compression, and production fallback: sample from a rich model, fit a simpler model, then compare the result with proper scores before promoting it. Exact Mixture Projection ------------------------ Some projections do not need samples. For Gaussian mixtures, ``mixle.inference`` exposes exact moment-based compression helpers: .. code-block:: python from mixle.inference import collapse_mixture, moment_project, reduce_mixture one_gaussian = collapse_mixture(gaussian_mixture) four_components = reduce_mixture(gaussian_mixture, n_components=4) projected = moment_project(gaussian_mixture) ``collapse_mixture`` returns the single Gaussian with the same overall mean and covariance as the mixture. ``reduce_mixture`` repeatedly merges Gaussian components using an analytic merge cost while preserving the mixture's global first two moments. ``moment_project`` chooses the exact path when possible and can delegate back to ``mixle.ops.project`` for sampling-based projection onto a target family. The distinction is important: * use ``mixle.ops.project`` for a general source and target family; * use ``collapse_mixture`` or ``reduce_mixture`` when the source is a Gaussian mixture and the exact closed-form route is available; * record which route was used, because a sampled projection and an exact moment projection have different error profiles. Product Of Experts ------------------ ``product_of_experts`` pools distributions geometrically: .. code-block:: python from mixle.ops import product_of_experts pooled = product_of_experts([language_prior, policy_filter], weights=[1.0, 0.5]) The exact implementation is available for tractable cases: * categorical distributions over a shared finite support; * Gaussian distributions, using precision-weighted pooling. For arbitrary continuous experts, the normalizing constant is generally intractable. Use sampling, MCMC, or projection when exact pooling is not available. Operations As Audit Boundaries ------------------------------ Operations should be visible in model provenance. A production artifact should make clear whether a distribution was: * fitted directly from data; * conditioned on runtime evidence; * truncated by a policy rule; * quantized for enumeration; * projected from a richer source model; * pooled from multiple experts. That distinction matters for debugging, calibration, and governance. A quantized or projected model can be perfectly useful, but it should not be mistaken for the original source model. Common Pitfalls --------------- * Do not call ``condition`` or ``marginalize`` and then silently fall back to a manual approximation. If the capability is missing, either choose a model that supports the operation or record the approximation explicitly. * Do not over-quantize early. Keep continuous models continuous until a finite support is required. * Do not pool experts with incompatible supports unless you have decided what zero-probability conflicts mean. * Do not promote a projected model solely because it is cheaper. Compare it against the source model on held-out data and calibration metrics.