Core Concepts ============= The central idea in ``mixle`` is not "many distributions." It is that model structure, data structure, and inference structure are the same object viewed from three angles. One Observation Is a Python Value --------------------------------- ``mixle`` does not require a flat matrix. One observation can be a scalar, a tuple, a dictionary, a sequence, a graph-like object, or a neural training pair. .. list-table:: :header-rows: 1 * - Observation - Natural model shape * - ``3.2`` - scalar continuous distribution * - ``"clicked"`` - categorical distribution * - ``("us", 42.0)`` - composite tuple distribution * - ``{"country": "us", "age": 42.0}`` - record distribution * - ``[3, 4, 5]`` - sequence distribution * - ``([12, 44, 91, 7], 18)`` - next-token distribution * - ``[0.2, 1.8, 1.5, ...]`` - emission sequence with latent HMM states The fitted model scores that whole value with ``log_density(x)``. If the model can sample, it samples values with the same shape. Estimator Shape Mirrors Data Shape ---------------------------------- The most important modeling move is to make the estimator look like one row of data. .. code-block:: python from mixle.stats import CompositeEstimator, GammaEstimator, PoissonEstimator # Observation: (count, wait) est = CompositeEstimator((PoissonEstimator(), GammaEstimator())) Combinators can be nested: .. code-block:: python from mixle.stats import CategoricalEstimator, MixtureEstimator, SequenceEstimator row = CompositeEstimator( ( CategoricalEstimator(), SequenceEstimator(PoissonEstimator(), len_estimator=CategoricalEstimator()), ) ) clustered_rows = MixtureEstimator([row, row, row]) The estimator says three things at once: * what the observation looks like; * which distribution family fits each part; * which inference route is needed when the structure contains latents, neural leaves, priors, or constraints. The Five Pieces --------------- Each full distribution family is built from five cooperating pieces: .. list-table:: :header-rows: 1 * - Piece - Role * - ``...Distribution`` - Stores parameters and implements ``log_density(x)``. * - ``...Sampler`` - Draws observations from a fitted distribution. * - ``...Estimator`` - Declares the model shape and performs estimation. * - ``...Accumulator`` - Collects mergeable sufficient statistics or training telemetry. * - ``...DataEncoder`` - Packs Python values into vectorized encoded data. That contract is what makes scale-out possible. Encoded data can be folded locally, on a multiprocessing backend, on Spark/Dask/MPI, or through a device engine while the model code remains the same. What Happens During ``optimize`` -------------------------------- ``optimize(data, estimator)`` runs this outer loop: 1. choose an encoder; 2. encode raw Python observations; 3. initialize a candidate distribution; 4. accumulate evidence under the current distribution; 5. ask the estimator for an updated distribution; 6. repeat until convergence or ``max_its``. Different structures specialize the same loop: .. list-table:: :header-rows: 1 * - Structure - What the loop means * - Closed-form leaves - one or more maximum-likelihood or conjugate sufficient-statistic updates * - Mixtures - E-step responsibilities, M-step component updates * - HMMs - forward-backward expectations, emission and transition updates * - Neural leaves - gradient M-step against weighted or streamed batches * - PPL expressions - lowered estimator or target, then route selected by ``how=`` * - Task cascades - fit local model, calibrate, decide answer versus escalation This is why mixle can fit heterogeneous records and latent structures without a new training loop for every combination. The child estimators do different work, but they present the same outer shape to the parent composite or latent model. Distributions Are Query Objects ------------------------------- After fitting, stay on the distribution surface: .. code-block:: python score = model.log_density(x) samples = model.sampler(seed=0).sample(10) encoder = model.dist_to_encoder() Latent models add posterior queries: .. code-block:: python responsibilities = mixture.posterior(rows) path = hmm.viterbi(sequence) Discrete and structured models may also support enumeration: .. code-block:: python enum = dist.enumerator() top = enum.top_k(10) Capabilities, Not Class Checks ------------------------------ Ask what an object supports instead of guessing from its class: .. code-block:: python import mixle print(mixle.describe(model)) print(mixle.capabilities(model)) print(mixle.supports(model, mixle.capability.Enumerable)) Capabilities cover exact density, finite support, enumeration, ranking, conditioning, marginalization, latent posterior behavior, backend scoring, conjugate updates, and more. See :doc:`capabilities-contracts` for the full capability catalog and the contracts used by distribution, estimator, accumulator, and encoder objects. See :doc:`compute-layer` for the encoded-data and kernel machinery beneath the public distribution families. Public Surfaces --------------- .. list-table:: :header-rows: 1 * - Namespace - Use it for * - ``mixle.stats`` - distribution families, estimators, combinators, latent models * - ``mixle.inference`` - fit loops, EM, priors, calibration, diagnostics, model comparison * - ``mixle.ppl`` - compact expression language that lowers to stats/inference objects * - ``mixle.models`` - incubating applied helpers: neural leaves, GPs, random forests, graphs * - ``mixle.task`` - LLM labeling, distillation, active learning, cascades, artifacts * - ``mixle.reason`` - LLM uncertainty and cross-modal latent evidence fusion * - ``mixle.enumeration`` - top-k, rank, seek, nucleus, and structured support traversal * - ``mixle.engines`` - NumPy, Torch, JAX, symbolic, and precision-aware compute engines * - ``mixle.data`` - schemas, data sources, validation, hashes, encoded IO * - ``mixle.doe`` - design of experiments, active labeling, optimization, sensitivity * - ``mixle.stats.compute`` - low-level contracts, encoded data, generated kernels, backend scoring * - ``mixle.utils`` - automatic typing, serialization, optional dependencies, metrics, parallel runtime helpers The shortest practical advice is: start with ``mixle.stats`` when you know the model, ``mixle.task.recommend_model`` when you want help choosing one, ``mixle.ppl`` when the formula is clearer than the estimator tree, and ``mixle.describe`` whenever you are unsure what the fitted object can do.