mixle.evolve package¶
mixle.evolve – the self-improvement core: measure -> propose -> verify -> promote.
A pure, serving-agnostic orchestration algebra over capabilities that already exist in the library.
It adds no new modeling capability: the proper scores and calibration diagnostics
(mixle.inference.scoring / mixle.inference.calibration), the comparison tests
(mixle.inference.model_comparison), the fitters (mixle.inference.estimation.optimize(),
the streaming estimators, mixle.utils.automatic.get_estimator()), and the decision layer
(mixle.inference.decision.bayes_action()) are all pre-existing. evolve wires them into a
single anti-regression loop.
Phase 1 surface:
measure –
Objective+nll/log_score/crps/interval/calibration/decision_regretbuilders.propose –
ImprovementOperator+Refit,OnlineUpdate,AutoSelect,Recalibrate, a scoped operator registry.verify –
challenger_beats_champion()+Verdict(the anti-regression gate).drive –
improve()+ImprovementResult,auto_select(), and theEvolutionLedgertelemetry.
Phase 2-3 surface:
search –
search()over a typedSpace(Real/Integer/Categorical),method='evolutionary'/'bandit'/'bo', returning aSearchResult.meta-search –
Population+OperatorBandit(the policy that learns which operators help).structure search –
Recompose/Mutate(genetic-programming structural moves over the model’s compositional tree) +structural_distance()(a tree-edit genotype distance) driving the population’s diversity; registered but off by default (structural + expensive).
- improve(model, data, *, objective, operators=None, holdout=0.25, alpha=0.05, min_effect=0.0, budget=None, seed=0, ledger=None, parent_hash=None, require_calibration=True)[source]
Propose challengers, gate them, and return the best verified improvement (or the champion).
- Parameters:
model (Any) – the champion (a fitted mixle distribution).
data (Sequence[Any]) – the raw held-out dataset (split once into train/verify here).
objective (Objective) – the
Objectiveto improve on.operators (Sequence[ImprovementOperator] | None) – the proposal moves; defaults to the applicable subset of
[Refit, OnlineUpdate, AutoSelect, Recalibrate].holdout (float | tuple) – train/verify split fraction, or an explicit
(train, verify)tuple.alpha (float) – significance level for the verify gate.
min_effect (float) – practical effect-size floor passed to the gate.
budget (float | None) – optional cost ceiling – operators whose
cost_hintexceeds the remaining budget are skipped (cheapest-first).seed (int) – RNG seed for the split and the sampled objectives.
ledger (EvolutionLedger | None) – optional
EvolutionLedgerto record every attempt into.parent_hash (str | None) – optional lineage hash for the champion (carried onto candidates and ledger rows).
require_calibration (bool) – forward to the gate’s calibration no-regression check.
- Returns:
An
ImprovementResult.result.verified is Trueguaranteesresult.modelbeat the champion significantly and non-regressively on the verify split.- Return type:
ImprovementResult
- class ImprovementResult(model, verified, operator, delta, verdict, evidence=<factory>, parent_hash=None)[source]
Bases:
objectThe outcome of an
improve()run.- Parameters:
- model: Any
- verified: bool
- delta: float
- verdict: Verdict | None
- evidence: dict
- auto_select(data, *, space=None, criterion='bic', verify=True, holdout=0.25, seed=0, max_its=20)[source]
Infer and fit a model from raw
data, optionally gated by a held-out proper score.- Parameters:
space (Any | None) – reserved for the Phase-2 typed search space; must be
Nonein Phase 1.criterion (str | Objective) –
'bic'(delegate to the automatic in-sample pick) or a proper-scoreObjective(add the held-out verify gate on top of BIC).verify (bool) – when
criterionis anObjective, whether to run the held-out gate (the BIC pick fitted on the train split is the champion; the BIC pick refitted on all data is the challenger, promoted only if it wins out of sample).holdout (float) – held-out fraction for the proper-score gate.
seed (int) – RNG seed for the split and sampled objectives.
max_its (int) – EM iterations for the fits.
- Returns:
An
ImprovementResult. Forcriterion='bic'it carries the fitted automatic model withverified=False(no out-of-sample test was requested). For anObjectivecriterion withverify=Trueit carries the gate verdict andverifiedreflects whether the full-data model beats the train-only model out of sample.- Return type:
ImprovementResult
- class Objective(*args, **kwargs)[source]
Bases:
ProtocolA lower-is-better-or-higher-with-a-flag scalar fitness with an optional paired vector.
- name: str
- lower_is_better: bool
- pointwise(model, data)[source]
The
(n,)per-observation score, orNoneif the objective is scalar-only.
- nll_objective()[source]
Negative log-likelihood: per-obs
-log p(y_i)(strictly proper, lower is better).- Return type:
Objective
- log_score_objective()[source]
Logarithmic score (log loss) of the predictive density at the realised outcomes.
- Return type:
Objective
- crps_objective(*, ensemble=256, seed=0)[source]
Continuous Ranked Probability Score from a sampled predictive ensemble (lower is better).
- interval_objective(level=0.9, *, ensemble=256, seed=0)[source]
Winkler interval score for the central
levelpredictive interval (lower is better).
- calibration_objective(*, ensemble=256, seed=0, bins=10)[source]
PIT calibration error of the predictive distribution (scalar-only, lower is better).
Uses the rank-based Probability Integral Transform of a sampled ensemble: under a calibrated continuous forecast the PIT values are Uniform(0, 1), and
pit_calibration_errormeasures the histogram’s mean absolute deviation from uniform. There is no honest per-observation paired vector for a histogram statistic, sopointwisereturnsNoneand the verify gate scores this on the bootstrapped scalar.(For classification models the natural calibration scalar is
mixle.inference.calibration.expected_calibration_error(); this builder targets the continuous-predictive case, which is the common one for the streaming/auto-select loop.)
- decision_regret_objective(loss, actions, *, n=2000, seed=0)[source]
Realized decision regret under a fitted predictive posterior (scalar-only, lower is better).
For the chosen Bayes action
a* = bayes_action(posterior(model), loss, actions)this reports the expected lossE_draw[ loss(a*, draw) ]– the realized cost of acting optimally under the model’s own belief. A better-calibrated model yields a lower expected loss for the same loss and action set, so it is a first-class promotion metric. Scalar-only (the regret is an expectation over posterior draws, not a per-observation quantity), sopointwisereturnsNone.
- challenger_beats_champion(champion, challenger, data, *, objective, alpha=0.05, min_effect=0.0, require_calibration=True, nonnested=False, multiplicity=None, calib_tol=1.0e-3, seed=0, elpd_pointwise=None)[source]
Decide whether
challengersignificantly and non-regressively beatschampionondata.- Parameters:
champion (Any) – two fitted models scored on the same held-out
data.challenger (Any) – two fitted models scored on the same held-out
data.data (Any) – the held-out responses (one batch -> both models scored in the same order).
objective (Objective) – the
Objectiveto compare on.alpha (float) – significance level for the paired test (and the CI is at
1 - alpha).min_effect (float) – practical effect-size floor on
|mean score difference|.require_calibration (bool) – if True, run the calibration no-regression check.
nonnested (bool) – if True (a family swap), additionally require Vuong + Clarke to favor the challenger.
multiplicity (str | None) –
Noneor amixle.inference.multiple_testing.adjust_pvalues()method ('bonferroni'/'bh'/ …) when this is one of many simultaneous challengers – adjusts the p-value before the gate.calib_tol (float) – tolerance on the calibration-error increase the challenger may carry.
seed (int) – RNG seed for the (sampled) calibration scalars.
elpd_pointwise (tuple[ndarray, ndarray] | None) – optional
(pointwise_champion, pointwise_challenger)LOO/WAIC arrays; when given, thecompare_elpd()2-SE band is required to also favor the challenger.
- Returns:
A
Verdict.verdict.promoteis the single promotion predicate.- Return type:
Verdict
- class Verdict(favored, delta, p_value, ci, calibrated, evidence=<factory>)[source]
Bases:
objectThe outcome of a single champion/challenger comparison.
- Parameters:
- favored: str
- delta: float
- p_value: float
- calibrated: bool
- evidence: dict
- property promote: bool
True iff the challenger is favored and passed calibration – the promotion predicate.
- class ImprovementOperator(*args, **kwargs)[source]
Bases:
ProtocolA uniform proposal move: a cheap applicability pre-flight plus a fitted-challenger
propose.- name: str
- cost_hint: float
- applicable(model, data, *, ctx)[source]
Cheap structural gate – can this operator even run on this model/data?
- class Candidate(model, operator, parent_hash=None, meta=<factory>)[source]
Bases:
objectA proposed, already-fitted challenger plus the provenance of how it was made.
- model: Any
- operator: str
- meta: dict
- class Refit(name='refit', cost_hint=1.0, max_its=20)[source]
Bases:
objectRe-fit the champion’s family on fresh data, warm-started from the champion’s parameters.
- name: str = 'refit'
- cost_hint: float = 1.0
- max_its: int = 20
- applicable(model, data, *, ctx)[source]
- class OnlineUpdate(mode='streaming', cost_hint=0.2)[source]
Bases:
objectFold a fresh batch into the champion via a streaming estimator.
mode:'streaming'– decay-modeStreamingEstimator(running-accumulator forgetting).'incremental'– Neal-HintonIncrementalEstimator(replace one chunk).'posterior_carry'– exact recursive-BayesBayesianStreamingEstimator(needs aconjugate family;
applicablehonestly checksConjugateUpdatable).
'forgetting'– power-priorBayesianStreamingEstimator.
- mode: str = 'streaming'
- cost_hint: float = 0.2
- property name: str
- applicable(model, data, *, ctx)[source]
- class AutoSelect(name='auto_select', cost_hint=3.0, max_its=20)[source]
Bases:
objectInfer an estimator from the raw data (
get_estimator) and fit it – a possible family swap.- name: str = 'auto_select'
- cost_hint: float = 3.0
- max_its: int = 20
- applicable(model, data, *, ctx)[source]
- class Recalibrate(name='recalibrate', cost_hint=0.5, ensemble=256, seed=0, grid=(0.6, 0.75, 0.9, 1.0, 1.1, 1.25, 1.5, 2.0))[source]
Bases:
objectLearn a predictive spread temperature
Tthat flattens the PIT, no parameter refit.applicablerequires a sampler (used both to estimate the predictive center and to evaluate PIT calibration). The temperature is chosen on the train split by minimising the PIT calibration error over a small grid;T == 1(the identity) is always in the grid, so the recalibrated model can never be worse-calibrated than the base on the fitting data.- name: str = 'recalibrate'
- cost_hint: float = 0.5
- ensemble: int = 256
- seed: int = 0
- applicable(model, data, *, ctx)[source]
- class Recompose(name='recompose', cost_hint=4.0, max_its=30)[source]
Bases:
objectPropose a richer STRUCTURE for the champion: a 2-component mixture of its family, each component warm-fit on a different half of the data so EM starts non-degenerate (avoiding the identical-component collapse). It wins the verify gate only when the data has structure a single component misses — anti-regression keeps it honest. Expensive, so it is registered but kept OUT of the default operator set.
- name: str = 'recompose'
- cost_hint: float = 4.0
- max_its: int = 30
- applicable(model, data, *, ctx)[source]
- class Mutate(name='mutate', cost_hint=4.0, max_its=30)[source]
Bases:
objectGenetic-programming structure search (Koza 1992): apply a random structural mutation to the champion and refit. Moves:
grow(add a bootstrap-fit component),shrink(drop the lowest-weight component), andperturb(bootstrap re-fit). Repeated application inside aPopulation+ the verify gate + a tree-edit genotype distance IS structure induction by selection (cf. Bayesian model merging, Stolcke & Omohundro 1994) — not open research. Registered but OUT of the default set (expensive).- name: str = 'mutate'
- cost_hint: float = 4.0
- max_its: int = 30
- applicable(model, data, *, ctx)[source]
- structural_distance(a, b)[source]
A
[0, 1]genotype distance between two models’ structures (tree-edit distance, size-normalized).
- model_signature(model)[source]
The compositional structure of
modelas a(type_label, [child signatures])tree.
- tree_edit_distance(a, b)[source]
Unordered tree-edit distance: relabel cost (0/1) + greedy min-cost matching of children, with unmatched subtrees fully inserted/deleted. Exact for identical trees; a standard greedy approximation otherwise.
- register_operator(operator)[source]
Register
operatorin the scoped evolve operator registry (returns it for decorator use).- Parameters:
operator (ImprovementOperator)
- Return type:
ImprovementOperator
- unregister_operator(name)[source]
Remove a previously-registered operator by name (no-op if absent).
- Parameters:
name (str)
- Return type:
None
- registered_operators()[source]
A copy of the current scoped operator registry.
- default_operators()[source]
The Phase-1 default operator set: refit, online update, auto-select, recalibrate. (Recompose + Mutate are structural + expensive, so they are available via the registry but not enabled by default.)
- Return type:
list[ImprovementOperator]
- class EvolutionLedger(rows=<factory>)[source]
Bases:
objectAn ordered, JSON-serializable log of improvement attempts.
- record(*, operator, delta, verdict, cost, parent_hash, meta=None)[source]
Append one attempt row and return it.
- search(space, data, *, objective, build_fn, method='bo', n_iter=25, holdout=0.25, seed=0, **method_kwargs)[source]
Search
spacefor the config whosebuild_fnmodel scores best on a held-out split.- Parameters:
space (Space) – the typed
Spaceto search.data (Sequence[Any]) – the raw dataset (split once into train/val here for the inner objective).
objective (Objective) – the held-out
Objective(lower-is-better aware).build_fn (Callable[[dict[str, Any]], Any]) – caller-supplied
config -> fitted model(the search is family-agnostic).method (str) –
'bo'(Bayesian optimization over the numeric box),'evolutionary'(a (mu + lambda) loop oversample/neighbors), or'bandit'(delegate the operator policy to anOperatorBandit).n_iter (int) – search budget (BO acquisition steps / evolutionary generations / bandit generations).
holdout (float) – held-out fraction for the inner objective.
seed (int) – RNG seed.
method_kwargs (Any) – backend-specific knobs (e.g.
mu/lamfor the evolutionary loop,operators/sizefor the bandit population).
- Returns:
A
SearchResultwithbest_config/best_model/best_score(native orientation) /history.- Return type:
SearchResult
- class SearchResult(best_config, best_model, best_score, history=<factory>)[source]
Bases:
objectThe outcome of a
search()(orPopulation.run()) run.- Parameters:
- best_model: Any
- best_score: float
- class Space(dims)[source]
Bases:
objectA typed, named search space over
Real/Integer/Categoricaldims.Construct from a dict mapping each parameter name to its dimension:
space = Space({"mu": Real(-5, 5), "k": Integer(1, 4), "family": Categorical(["a", "b"])})
Dimension order is the insertion order of the dict;
encode()/decode()andto_bounds()all use that same fixed order so the numeric vector and the box align.- property ndim: int
- to_bounds()[source]
The continuous
(low, high)box for the BO backend (categoricals as integer indices).
- sample(rng)[source]
Draw a random config dict, each dimension sampled natively.
- Parameters:
rng (RandomState)
- Return type:
- encode(config)[source]
Encode a config dict into the numeric vector (in the fixed dimension order).
- decode(vector)[source]
Decode a numeric vector (BO proposal) back into a config dict (rounding / clipping).
- class Real(lo, hi)[source]
Bases:
objectA continuous dimension over
[lo, hi].- lo: float
- hi: float
- sample(rng)[source]
- Parameters:
rng (RandomState)
- Return type:
- class Integer(lo, hi)[source]
Bases:
objectAn integer dimension over the inclusive range
[lo, hi].- lo: int
- hi: int
- sample(rng)[source]
- Parameters:
rng (RandomState)
- Return type:
- class Categorical(choices)[source]
Bases:
objectAn unordered categorical dimension over a finite list of
choices.- sample(rng)[source]
- Parameters:
rng (RandomState)
- Return type:
- class Population(seeds, *, objective, operators=None, bandit=None, size=12, diversity_quota=2, seed=0)[source]
Bases:
objectA diversity-preserving population of model structures, evolved by the
OperatorBandit.seedsare fitted models (the starting structures). Eachstep()selects operators via the bandit, applies them to parents chosen by fitness, gates the challengers with the Phase-1 champion/challenger rule (Benjamini-Hochberg multiplicity, since a generation produces many challengers at once), rewards the bandit with the verified deltas, and keeps the verified-best plus a coarse capability-diversity quota.- Parameters:
seeds (Sequence[Any]) – the initial fitted models (at least one).
objective (Objective) – the
Objectiveto optimize (lower-is-better aware).operators (Sequence[ImprovementOperator] | None) – the proposal-move pool; defaults to the Phase-1 safe set.
bandit (OperatorBandit | None) – an
OperatorBanditoveroperators(built with the default policy if omitted).size (int) – the carrying capacity of the population.
diversity_quota (int) – how many of
sizeslots are reserved for capability-diverse members (the rest go to the fittest); the quota keeps the search from collapsing onto one structure too early.seed (int) – RNG seed for parent sampling and the bandit.
- step(data)[source]
Run one generation: select -> propose -> gate -> reward -> survivor selection.
- Parameters:
data (Any)
- Return type:
GenerationReport
- run(data, generations=5)[source]
Evolve for
generationssteps; return aSearchResult.The returned
best_modelis the run incumbent, guaranteed no worse than the best seed on the objective (anti-regression).historyis one row per generation (proposals / verified / score).
- property champion: Any
The current run incumbent (the best model seen, anti-regression guaranteed).
- class OperatorBandit(operators, *, policy='thompson', decay=0.97, prior_cost_aware=True, seed=0)[source]
Bases:
objectA non-stationary bandit over a fixed pool of
ImprovementOperator.select(k)returns thekhighest-value operators under the chosen policy;rewardfolds a verified delta + cost back into the chosen arm;reportis the “which operators help” artifact.- Policies:
'thompson'– Beta-Bernoulli Thompson sampling on the win indicator (reward > 0), scaled by the mean positive reward, so an operator that wins rarely but big and one that wins often but small are compared on expected verified delta.'ucb'– UCB1 on the mean reward with asqrt(2 ln N / n)exploration bonus.
Non-stationarity: each
rewardfirst multiplies every arm’s statistics bydecay(a forgetting factor in(0, 1]), so old evidence fades and the policy can track a shifting best operator.- Parameters:
- value(name)[source]
The current policy value of operator
name(a Thompson draw or the UCB index).
- select(k=1)[source]
Return the
koperators with the highest policy value (a fresh Thompson draw each call).
- reward(op_name, delta, cost)[source]
Fold a verified
delta(0 if the challenger was rejected) andcostinto the arm.Decays every arm first (non-stationarity), then updates the chosen arm. A
deltais clipped at 0 below – a rejected challenger is a zero reward, never negative, matching the anti-regression guarantee (we never punish an operator for a rejected proposal beyond not rewarding it).