mixle.lifecycle module

mixle.Model – the model lifecycle as one object with consistent verbs.

Everything here exists elsewhere in the library; this facade makes the lifecycle discoverable without knowing which subpackage owns which verb:

m = mixle.propose(data)          # a model shape recommended from the data (with confidence + caveats)
m.fit(data)                      # inference chosen from the structure (EM / MLE / closed form)
m.evaluate(holdout)              # held-out scores
m.sample(5)                      # draw new records
m.enumerate().top_k(3)           # most-probable support (discrete/structured families)
m.posterior(x)                   # latent posteriors (mixtures, HMMs, ...)
m.distill(teacher, inputs)       # tiny deployable student in front of the teacher (task spine)
m.deploy("artifacts/m")          # durable artifact directory; Model.load() restores it
m.explain()                      # what it is, what it supports, and how it was proposed
m.explain_prediction(x)          # exact per-part attribution of one score
m.forecast(history, h)           # horizon predictions with honest intervals (HMMs)
m.do({field: value})             # graph-surgery intervention (learned Bayesian networks)
m(x)                             # use it: log-density of an observation

Model wraps a prototype distribution, an estimator, or nothing (the estimator is inferred from the data); verbs delegate to mixle.inference.optimize(), dist.enumerator(), mixle.task.solve, and mixle.describe(). It adds no new inference – only one place to stand.

saddle_suspect(fitted, data, *, sample=200, tol=0.02)[source]

Family-agnostic symmetric-saddle check for latent-variable fits.

At the symmetric saddle every component is identical, so every observation’s component posterior is (numerically) uniform. Suspect when, over a data sample, NO observation’s posterior deviates from uniform by more than tol. Non-latent models (no posterior/components) return False.

Parameters:
Return type:

bool

class Model(spec=None, *, notes=None)[source]

Bases: object

One object over the model lifecycle: build / fit / evaluate / enumerate / distill / deploy / use.

Parameters:
  • spec (Any)

  • notes (list[str] | None)

fit(data, *, restarts='auto', calibrate=False, **optimize_kw)[source]

Fit via mixle.inference.optimize(); the algorithm follows from the model’s structure.

restarts="auto" (default) makes latent-variable fitting genuinely automatic: after the plain fit, a family-agnostic saddle check runs (a mixture stuck at the symmetric saddle gives every observation a ~uniform component posterior), and on suspicion the fit silently reruns as multi-restart EM (mixle.inference.best_of()), keeping the better log-likelihood and recording what happened in notes. Pass an int to force that many restarts up front, or restarts=None for the raw single fit.

calibrate (opt-in, default off): reserve a holdout slice (a fraction, or True for 25%), fit on the rest, and attach a CalibrationReport on self.calibration measuring whether the model’s uncertainty is honest on the held-out data (PIT test + held-out log-density). Off by default because it costs training data.

Parameters:
Return type:

Model

evaluate(data)[source]

Held-out fit quality: total and mean log-density over data.

Parameters:

data (Any)

Return type:

dict[str, Any]

sample(size=None, *, seed=None)[source]
Parameters:
  • size (int | None)

  • seed (int | None)

Return type:

Any

enumerate()[source]

The fitted distribution’s enumerator (top-k / top-p / rank / seek), where supported.

Return type:

Any

posterior(x)[source]

Latent posterior for one observation (mixtures, HMMs, …), where supported.

Parameters:

x (Any)

Return type:

Any

distill(teacher=None, inputs=None, **solve_kw)[source]

Distill a tiny deployable student via mixle.task.solve().

With teacher=None the fitted model itself teaches: inputs are labeled by their most-probable latent component (posterior argmax), so a fitted mixture becomes a fast, calibrated classifier of its own clusters. Returns a mixle.task.Solution (call it, report(), improve()).

Parameters:
deploy(path)[source]

Persist a durable artifact directory (model + manifest); Model.load() restores it.

Parameters:

path (str)

Return type:

str

classmethod load(path)[source]
Parameters:

path (str)

Return type:

Model

explain_prediction(x)[source]

Exact per-part attribution of log p(x)mixle.inference.explain().

Parameters:

x (Any)

forecast(history, horizon, **kw)[source]

Horizon predictions with honest intervals — mixle.inference.forecast() (HMMs).

Parameters:
do(interventions, **kw)[source]

Graph-surgery intervention — mixle.inference.do() (learned Bayesian networks).

Parameters:
explain()[source]

What this model is, what it supports, and how it was proposed.

Return type:

str

propose(data, *, fit=False, llm=None, holdout=0.25, seed=0, max_its=25, **recommend_kw)[source]

Propose a model for data from a verified frontier of candidates and return the winner.

Candidates come from every proposer the library has — the heuristic recommendation (mixle.task.recommend.recommend_model(), dependency-aware), the plain independence baseline (mixle.utils.automatic.get_estimator()), and, when an llm handle is given, an LLM-designed structure (mixle.task.design.design_model(), allowlisted-spec, fit-validated). Each candidate is fitted on a train split and scored on held-out data, so the ranking is out-of-sample, not a guess. The winner becomes the returned Model; the full ranking lands in Model.frontier and the per-field confidence / dependency / candidate notes in Model.notes (shown by explain()). Pass fit=True to also fit the winner to all of data before returning.

Parameters:
Return type:

Model