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.
Core 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.
Search and population 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).
L1 surface (closed-loop self-evolution, mixle.evolve.closed_loop):
ClosedLoopSelfEvolution– harvest -> A5 acquire -> distill/refine/evolve challenger production ->challenger_beats_champion()-> deploy, as one budgeted background loop.OperatorCreditBandit– a per-context meta-bandit (overmixle.task.bandit.UCB1) crediting WHICH challenger-production operator wins for which kind of failure.GenealogyLedger– parent/operator/measured-gap receipts on every adopted champion, with a reallineage(model)walk-back.
- 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 after ascending-cost ordering.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.
- 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 typed search-space selection; must currently be
Noneforauto_select.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 challenger is the same automatic family refit on that SAME train split, so the comparison on the held-out data is genuinely held out. Promotion deploys a fresh refit of the verified winner on all the data – never the object that was itself scored – so a promoted model still benefits from the held-out split’s extra evidence).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 train-only challenger beat the train-only champion 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.
- 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.
Computed directly from the log-density (
-log p(y_i)), never by exponentiating the log-density to a plain probability and then re-deriving a log-scale score from it. That round trip is destructive:exp(x)underflows to exactly0.0for anyxbelow about-745(the float64 underflow boundary), so every log-density past that point becomes the identical clipped probability and hence the identical score, even though the underlying fits are genuinely – and very differently – bad. Working from the log-density keeps that information: float64 has ample range in log-space, just not in probability-space.- 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 per-observation paired vector for a histogram statistic, sopointwisereturnsNoneand the verify gate falls back to a bare scalar comparison with no p-value or CI, reported for human review only – see this module’s docstring andmixle.evolve.verifymodule docstring point 8.(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.)ensembleandbinsmust both be exact positive integers – they are the budget the reported calibration error is computed from.
- decision_regret_objective(loss, actions, *, n=2000, seed=0, vectorized=None)[source]
Realized decision regret of a model’s chosen action against actual
data(scalar-only, lower is better).For the chosen Bayes action
a* = bayes_action(posterior(model), loss, actions)– optimal under the model’s OWN predictive belief – this reports the realized lossmean(loss(a*, y_i) for y_i in data)against the actualdata, not draws from that same model’s own posterior. Scoring against self-generated draws would let a confidently wrong model look flawless: its chosen action and its “outcomes” both come from the same wrong belief, so they always agree with each other regardless of how far that belief is from reality. Checking the chosen action against real, shareddatais what makes this a genuine promotion metric rather than a self-consistency check. Scalar-only (this is a single realized-loss number per model, not a per-observation quantity), sopointwisereturnsNone.- Parameters:
loss (Callable[[Any, Any], float]) –
loss(action, draw) -> float(or a numpy-vectorizedloss(action, draws) -> array).n (int) – posterior draws used only to pick the Bayes-optimal action under the model’s own predictive belief; the reported score is the realized loss of that action on
data.seed (int) – RNG seed for the action-selection draws.
vectorized (bool | None) – the loss’s calling convention, forwarded to
bayes_action()and used for the realized-loss evaluation.True-> called once with the whole outcome array;False-> called once per outcome;None(default) -> readloss.vectorizedif the loss declares it, else auto-detect by probing the array call once. A probe is a real invocation of the loss, and a search scores many models against the same loss, so the resolved convention is reused for the rest of the search rather than re-probed per model. A loss that keeps state or has side effects should declare its convention.
- Return type:
Objective
- 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) – must stay
None. A single champion/challenger comparison produces exactly one p-value, so it can never be corrected for multiplicity in isolation – passing a method name here raises rather than silently no-op’ing (everymixle.inference.multiple_testingmethod is the identity transform at family size 1). Kept as a parameter only to fail loudly on the mistake instead of TypeError’ing on removal; pool the raw p-values from every simultaneous comparison and callmixle.inference.multiple_testing.adjust_pvalues()once yourself, then compare each adjusted p-value toalpha.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; it is alwaysFalsefor a scalar-only objective, regardless offavored– see module docstring point 8 andVerdict.has_statistical_evidence.- Raises:
ValueError – if a policy value is outside its domain (see
_check_policy()), or if a pointwise vector does not carry exactly one score per held-out row (see_check_pointwise()).- Return type:
Verdict
- class Verdict(favored, delta, p_value, ci, calibration_status, evidence=<factory>)[source]
Bases:
objectThe outcome of a single champion/challenger comparison.
- Parameters:
- property calibrated: bool
True unless calibration regressed, or its computation broke unexpectedly.
Kept for callers that only need the promotion-relevant boolean;
calibration_statuscarries the more precise state a bare bool cannot: ‘unavailable’ (calibration wasn’t requested, or is genuinely inapplicable to this model/data – an explicit, checked applicability decision, not itself evidence of a regression, so it does not block promotion, the same as ‘passed’) is distinct from both ‘failed’ (calibration was computed for both models and the challenger’s is worse by more thancalib_tol) and ‘error’ (the computation raised unexpectedly despite looking applicable – an implementation defect, not a legitimate skip). ‘failed’ and ‘error’ are the only statuses that block promotion; an ‘error’ must not silently read the same as a pass.
- property has_statistical_evidence: bool
True unless this verdict came from a scalar-only objective with no paired test to run.
A scalar-only objective (module docstring point 8) has no per-observation vector to pair, so
p_value/ciare the explicitnan“not applicable” sentinel: there is no sampling-uncertainty estimate – no p-value, CI, replication, or bootstrap – behind the raw scalar delta at all. Automatic promotion requires this to be True; a scalar-only win is still reported (favored/delta/evidence['scalar_only']) for a human to review, but it can never auto-promote on its own – seepromote.
- property promote: bool
True iff the challenger is favored, calibration didn’t fail or error, and the verdict is backed by an actual statistical test (see
has_statistical_evidence) – the single promotion predicate.
- class ImprovementOperator(*args, **kwargs)[source]
Bases:
ProtocolA uniform proposal move: an applicability pre-flight plus a fitted-challenger
propose.- applicable(model, data, *, ctx)[source]
Structural gate: whether this operator can run on the model and 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.
- 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.
- applicable(model, data, *, ctx)[source]
Return whether
modelcan be re-fit on a non-empty batch.
- 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;
applicablechecksConjugateUpdatable).
'forgetting'– power-priorBayesianStreamingEstimator.
- property name: str
Registry name including the selected online-update mode.
- applicable(model, data, *, ctx)[source]
Return whether the selected update mode is legal for
modelanddata.
- 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.- applicable(model, data, *, ctx)[source]
Return whether there is data available for automatic family selection.
- 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.- applicable(model, data, *, ctx)[source]
Return whether
modelcan be sampled and the batch is non-empty.
- class Recompose(name='recompose', cost_hint=4.0, max_its=30)[source]
Bases:
objectPropose a richer two-component mixture structure for the champion family.
Each component is warm-fit on a different half of the data so EM starts away from the identical-component solution. The normal verification gate decides whether the additional structure improves held-out evidence enough to promote. The operator is registered but omitted from the default set because it is intentionally more expensive than the conservative updates.
- applicable(model, data, *, ctx)[source]
Return whether the model can be re-estimated on enough rows for a split.
- class Mutate(name='mutate', cost_hint=4.0, max_its=30)[source]
Bases:
objectApply a random structural mutation to the champion and refit.
Available moves are
growfor adding a bootstrap-fit component,shrinkfor dropping the lowest-weight component from an existing mixture, andperturbfor a bootstrap re-fit. Repeated use inside aPopulationgives the search driver a structure-induction move, while the verification gate remains responsible for promotion. The operator is registered but omitted from the default set because it is comparatively expensive.- applicable(model, data, *, ctx)[source]
Return whether the model can be structurally mutated and re-fit.
- structural_distance(a, b)[source]
A symmetric
[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) + minimum-cost matching of children, with unmatched subtrees fully inserted/deleted. Symmetric, zero exactly on identical trees, and non-negative.
- 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, integrity-chained log of improvement attempts.
- property rows: tuple[dict[str, Any], ...]
a tuple of deep copies of the recorded rows.
The backing list used to be the public attribute holding the live row dicts, so a caller could edit a recorded delta or verdict in place, or
clear()the whole trail, with nothing to show it happened. Reading, indexing, iterating andjson.dumps-ing the returned rows all work as before; writing to them simply does not reach the ledger.- Type:
A read-only view
- record(*, operator, delta, verdict, cost, parent_hash, meta=None)[source]
Append one attempt row and return a copy of it.
The returned dict is a deep copy, not the stored object: the previous behaviour handed back the exact row still held by the ledger, so a caller that adjusted a field on “its” result silently rewrote the recorded attempt.
deltaandcostare the row’s two measurements and must be finite;costmust also be non-negative, since it is spend, not a refund. See_finite_number(). (Ananp-value insideverdictis a different thing entirely – a documented “no paired test was run” sentinel – and is preserved, exactly, by the serialization codec.)
- head()[source]
The chain’s current tip: the last row’s
row_hash, or_GENESIS_HASHif empty.This is the anchor
verify()needs to detect truncation. Record it somewhere the ledger ‘s own storage cannot reach – a run report, a build artifact, a separate durable store – each time you finish appending; passing it back toverify(expect_head=...)is what turns “these rows are self-consistent” into “these are the rows that were written”.- Return type:
- verify(*, expect_length=None, expect_head=None)[source]
Whether every row still matches its digest and its place in the chain.
Checks, per row: the sequence number matches its position,
prev_hashmatches the previous row’srow_hash(_GENESIS_HASHfor the first), androw_hashstill matches the row’s canonical content. A rewritten delta or verdict, a deleted or reordered attempt, and a front-truncated ledger all fail.Truncation from the end cannot be detected from the rows alone, and no hash chain can do it. Deleting the last row leaves a shorter chain that is perfectly self-consistent – every remaining
prev_hashstill matches, everyrow_hashstill verifies – because a prefix of a valid chain is a valid chain. Detecting it needs one fact the ledger does not hold: how long the chain is supposed to be, or what its tip was. Supply either and the check becomes real:head = ledger.head() # store this outside the ledger … ledger.verify(expect_head=head, expect_length=n)
expect_headis the stronger of the two: it pins the exact final row, so it also rejects a ledger truncated and then re-extended to the original length.expect_lengthalone catches only a change in count. Omitting both is still meaningful – it proves internal consistency – but it is not proof that nothing was removed from the end.
- to_json(**dumps_kwargs)[source]
Serialize the full ledger to a strict JSON string.
Non-finite floats anywhere in a row (a scalar-only verdict’s
nanp-value, say) are encoded into the reversible tagged form_FLOAT_TAGdocuments, andallow_nanis then forced off so anything the encoder missed fails loudly here rather than emitting a bareNaN/Infinitytoken a conforming JSON parser refuses to read back. Round-trips exactly throughfrom_json(), digests included.
- classmethod from_json(s)[source]
Rebuild a ledger from
to_json()output, checking the anchor it carries.A document written by
to_json()is an envelope holding the rows plus the length and chain tip they had when written; both are verified here, so a row deleted from the end – whichverify()alone cannot see – is caught on load.A bare JSON array is still accepted for documents written before the envelope existed. Those carry no anchor, so loading one proves only that the rows are internally consistent; pass a separately stored
expect_headtoverify()for the stronger check.- Parameters:
s (str)
- Return type:
EvolutionLedger
- 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], list[Any]], Any]) – caller-supplied
(config, train_data) -> fitted model(the search is family-agnostic).train_datais ALWAYS the train splitsearchcomputed – neverval– so a config can actually be fit to real data without risking a held-out leak.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) – the TOTAL evaluation budget (build/score attempts), consistently for
boandevolutionary– each reserves every evaluation against it before spending, so neither can exceed it (the evolutionary backend used to treat it as a generation count and spendmu + lam * n_iter). Both need at least one evaluation to produce any result, son_iter<=0is rejected with a clear error instead of silently spending an evaluation anyway. (banditinterpretsn_iteras a generation count, unchanged – it delegates toPopulation, whose per-generation work depends on the operator policy rather than on a fixed offspring count.)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, plussearch_failed/n_evaluations/n_successesforbo/evolutionary(seeSearchResult).- Return type:
SearchResult
- class SearchResult(best_config, best_model, best_score, history=<factory>, search_failed=False, n_evaluations=0, n_successes=0)[source]
Bases:
objectThe outcome of a
search()(orPopulation.run()) run.search_failed/n_evaluations/n_successesdistinguish “we searched and found a real (if mediocre) model” from “every configuration failed and best_model is a hollow placeholder” – without them, a totally failed search (best_model=None,best_scoreat the internal penalty sentinel) is shaped identically to a real result. Every backend populates them, including thebanditone viamixle.evolve.population.Population.run()– it used to leave them at their defaults, so a run that evaluated seeds and generations still reportedn_evaluations=0andsearch_failed=False, which reads as a successful search that did no work.
- 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 dims: MappingProxyType
The named dimensions, read-only.
This was a plain public dict.
__init__validates that a space is non-empty and that every dimension has a supported type, and then the caller couldspace.dims.clear()and get an object reportingndim == 0andsample() == {}– a space that the constructor would have refused to build, reached by mutating one it had just approved. Checks that run once only hold if the thing they checked cannot change afterwards. Build a newSpaceto change the dimensions.
- property names: tuple[str, ...]
The fixed dimension order, always read straight from
dims.This was a tuple snapshotted in
__init__whiledimsstayed a public mutable dict, so adding or removing a dimension afterwards left the two disagreeing:ndimand every encode/decode kept using the old order and silently ignored the new dimension, whiledimsreported it as present. Deriving the order removes the possibility rather than documenting it.
- property ndim: int
Number of dimensions in the fixed search-space order.
- 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].Both bounds must be finite. An infinite (or NaN) bound would construct fine but then silently poison downstream
sample/neighborsdraws withinf/nan(or crash deep inside NumPy’s RNG with an opaqueOverflowError), far from this actual root cause – so it is rejected here instead.- bounds()[source]
Return the numeric bounds used by continuous optimizers.
- sample(rng)[source]
Draw a uniformly distributed value from the interval.
- Parameters:
rng (RandomState)
- Return type:
- encode(value)[source]
Clip and encode
valueas a floating-point coordinate.
- decode(x)[source]
Clip a numeric optimizer coordinate back into the interval.
- class Integer(lo, hi)[source]
Bases:
objectAn integer dimension over the inclusive range
[lo, hi].- bounds()[source]
Return widened numeric bounds so rounding covers each integer level.
- sample(rng)[source]
Draw an integer uniformly from the inclusive range.
- Parameters:
rng (RandomState)
- Return type:
- encode(value)[source]
Round, clip, and encode
valueas a numeric coordinate.
- decode(x)[source]
Round and clip a numeric optimizer coordinate to an integer value.
- class Categorical(choices)[source]
Bases:
objectAn unordered categorical dimension over a finite list of
choices.choicesmust be unique under==(the same equality_index_of()uses to look a value up) – a duplicate raisesValueErrorat construction. Without this check,encodewould silently collapse a duplicate to its first occurrence’s index whilesample/bounds/ the dimension’s own length would still count it as a distinct, independently-selectable level, so the dimension’s notion of “how many choices” would disagree with itself depending which path asked.- bounds()[source]
Return widened index bounds so rounding covers each categorical choice.
- sample(rng)[source]
Draw one choice uniformly at random.
- Parameters:
rng (RandomState)
- Return type:
- encode(value)[source]
Encode a choice as its floating-point index.
- decode(x)[source]
Round and clip an optimizer coordinate back to a choice.
- class Population(seeds, *, objective, operators=None, bandit=None, size=12, diversity_quota=2, holdout=0.25, 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 – pooling the whole generation’s p-values for a single Benjamini-Hochberg correction before the per-candidate gate, 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.Data roles (MXR-080-0042). This class keeps three distinct data roles separate:
Fit data – what operators actually fit challengers on (
datainstep()/run(), or its auto-derived fit-split whenverify_datais omitted – see below).Reusable validation data (
verify_data) – read bystep()every single generation, to gate every challenger (challenger_beats_champion()) AND to score population fitness / drive survivor selection. When omitted,step()/run()derive a genuinely disjoint split fromdata(_auto_split()) rather than the old default of reusingdataitself verbatim – but disjointness from the fit data is only half the fix. This SAMEverify_datais then queried again, adaptively, by EVERY generation of arun(): survivor selection keeps whoever currently scores best against it, andOperatorBanditlearns from whichever operators currently win against it. The per-generation Benjamini-Hochberg pool (seestep()) only controls multiplicity WITHIN one generation’s simultaneous comparisons; it does nothing to correct for that same set being re-queried, and acted on, by every OTHER generation – the textbook way a held-out set stops being a fair test once you repeatedly tune against it. Treat everyVerdict/score this class computes against a reusedverify_dataas a NOISY HEURISTIC that steers the search, never as a promotion-grade statistical claim on its own.One-shot final holdout – data never passed to this population as
dataorverify_dataat any point, evaluated exactly ONCE viaevaluate_holdout()after every intendedstep()/run()call is done. This is the only comparison this class makes that carries real evidentiary weight – a caller that needs a defensible promotion decision (as opposed to search guidance) must use it rather than trustingchampionor aGenerationReportalone.
- 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.holdout (float) – fraction of
datareserved for the auto-derivedverify_datasplit (_auto_split()) whenever a caller omitsverify_data– irrelevant if the caller always passes an explicit, already-disjointverify_data.seed (int) – RNG seed for parent sampling, the bandit, and the auto-derived split.
- step(data, verify_data=None)[source]
Run one generation: select -> propose -> gate -> reward -> survivor selection.
- Parameters:
data (Any) – the pool operators FIT candidates from (data role 1, “fit data”, in the class docstring). When
verify_datais omitted,datais split (_auto_split()) and only the fit-half is actually fit on – the other half becomes the auto-derivedverify_databelow, so a challenger is never fit and scored on the same rows.verify_data (Any | None) – the held-out data used to score population fitness and to gate every challenger against its parent (
challenger_beats_champion()) – data role 2, “reusable validation data”, in the class docstring: read again every generation, and its repeated, adaptive use across arun()is a HEURISTIC search signal, not evidence on its own – seeevaluate_holdout()for the genuine one-shot check. Defaults to the verify-half of_auto_split()applied todatawhen omitted (neverdataitself – MXR-080-0042: the old default silently made verification equal training data). Pass an explicit, already-disjoint split here (e.g.search()does) to control the split yourself instead.
- Return type:
GenerationReport
A generation proposes many challengers at once, so the gate applies a genuine population-wide Benjamini-Hochberg correction: every candidate is first compared on its own RAW p-value (pass 1 below), the whole generation’s raw p-values are then pooled and corrected together in ONE
adjust_pvalues()call (pass 2), and only then is each candidate’s promotion finalized against its adjusted p-value (pass 3). Correcting each candidate’s single p-value in isolation – family size 1 – is the identity transform for every method inmultiple_testing, which is whychallenger_beats_champion()refuses to do that itself. This pooled correction operates WITHIN one generation only – it does not, and cannot, correct for the sameverify_databeing queried again by every OTHER generation of arun()(see the class docstring’s “Data roles” note andevaluate_holdout()).A scalar-only
self.objective(calibration_objective(),decision_regret_objective()) has no paired test to run at all – every candidate’sVerdictthen carriesp_value = nanby design (see that function’s scalar-only branch) – so pass 2 excludes non-finite p-values from the pooled family entirely rather than handing them toadjust_pvalues(which rejects non-finite input outright); such a candidate’s own unadjustedverdict.promotestands as its final promotion decision, since there is no p-value for a population-wide correction to act on – which, perpromote, is now ALWAYSFalsefor a scalar-only objective regardless offavored(no sampling-uncertainty estimate backs a bare scalar delta – seehas_statistical_evidence).
- run(data, verify_data=None, generations=5)[source]
Evolve for
generationssteps; return aSearchResult.- Parameters:
data (Any) – the pool operators fit candidates from each generation (data role 1, “fit data”, in the class docstring). When
verify_datais omitted,datais split ONCE, up front (_auto_split()), and only the fit-half is fit on for every generation of this run.verify_data (Any | None) – the held-out data used to score fitness and gate every challenger – data role 2, “reusable validation data” (see the class docstring’s “Data roles” note): the SAME split is reused for EVERY generation, and both survivor selection and
OperatorBanditadapt to it, which is exactly the sequential/adaptive reuse the class docstring warns is a heuristic search signal, not evidence. Defaults to the verify-half of_auto_split()applied todatawhen omitted (neverdataitself – MXR-080-0042). Pass an explicit, already-disjoint split for a caller-controlled gate (e.g.search()always does).generations (int) – number of
step()calls.
- Return type:
The returned
best_modelis the run incumbent, guaranteed no worse than the best seed on whatever ``verify_data`` this run used (anti-regression is relative to that reused validation signal, not an unconditional guarantee about unseen data – seeevaluate_holdout()for the one-shot check that actually earns that stronger claim).historyis one row per generation (proposals / verified / score).
- evaluate_holdout(holdout_data, *, reference=None, **verdict_kwargs)[source]
The one genuinely evidence-bearing check this class can make (data role 3, “one-shot final holdout”, in the class docstring) – call this EXACTLY ONCE, after every
step()/run()call you intend to make is already done, onholdout_datathat was never passed to this population asdataorverify_dataat any point.Every
Verdict/ scorestep()computes internally along the way is checked against the SAME reusedverify_data, generation after generation – a heuristic search signal, not a defensible promotion claim (see the class docstring’s “Data roles” note). This method is the genuine, single-look alternative: ONEchallenger_beats_champion()comparison betweenreference(defaults to this population’s incumbent BEFORE its firststep()/run()call – i.e. the best seed) andchampion(the current incumbent).Calling this more than once against the same
holdout_datareintroduces exactly the adaptive-reuse problem it exists to avoid – treatholdout_dataas spent after one call, the same as any other held-out test set.- Parameters:
holdout_data (Any) – fresh data this population has never scored anything on. Verifying that is the caller’s responsibility –
Populationhas no way to check it from the object alone.reference (Any | None) – the champion to compare
championagainst; defaults to the pre-run incumbent (see above). Pass the currently-deployed production model explicitly if that differs.verdict_kwargs (Any) – forwarded to
challenger_beats_champion()(e.g.alpha,min_effect,seed);nonnesteddefaults to whetherreferenceandchampionare different model types, but can be overridden.
- Returns:
The one-shot
Verdict;verdict.promoteis the genuine, evidence-backed promotion decision.- Return type:
Verdict
- 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).deltaandcostmust be finite (andcostnon-negative).max(nan, 0.0)returnsnan, so a single non-finite update used to poison the arm permanently: mean delta, mean cost, and every subsequent Thompson draw for it came backnan, and ananpolicy value sorts unpredictably against real ones. Policy state is not a place to absorb an invalid measurement.
- class ClosedLoopSelfEvolution(champion, *, objective, operators=None, context_fn=None, acquire_k=32, acquire_strategy='entropy', bandit_c=1.0, holdout=0.25, seed=0)[source]
Bases:
objectThe whole L1 loop, one budgeted step at a time: harvest -> acquire -> propose -> gate -> deploy, with per-context operator credit and genealogy receipts on every adoption.
- Parameters:
champion (Any) – the initial fitted model (e.g. a
CategoricalDistribution).objective (Objective) – the
Objectivethe loop optimizes (e.g.accuracy_objective()).operators (dict[str, ImprovementOperator] | None) – challenger-production operators by name; defaults to
default_challenger_operators().context_fn (Callable[[Sequence[Any]], str] | None) –
batch -> context keyfor the per-context bandit; defaults to a single global context ("default") when the caller has no domain/failure-type signal to condition on.acquire_k (int) – how many harvested failures A5’s
acquireprioritizes per step.acquire_strategy (str) – the
acquire()ranking strategy (default"entropy", the one that works for a marginal/non-conditional champion via_ConstantProbaAdapter).holdout (float) – fraction of each arriving
batchset aside, BEFORE harvest/acquire ever see it, as the defaultverify_datawhen a caller omitsverifyinstep()– seestep()’s “Note on verify” (MXR-080-0042). Irrelevant if the caller always passes an explicitverify.seed (int) – RNG seed threaded through the bandit, the split, and the gate.
bandit_c (float)
- step(batch, *, verify=None, context=None, budget_remaining=None)[source]
One cycle of the loop on one arriving
batchof held-out-shaped observations.harvest failures under the CURRENT champion,
rank them with A5’s
acquire,pick a challenger-production operator via the per-context bandit,
propose + gate the challenger (skipped if
budget_remainingcan’t afford it),score the model the step would deploy, while it is still only staged,
commit as one transaction iff the gate promotes: genealogy receipt, deployment, bandit reward, and history entry, in that order.
Steps 5 and 6 are deliberately ordered that way. A step is all-or-nothing: either it returns a
LoopStepResultthat is also inhistoryand whose champion/receipt/bandit state all agree, or it raises having changed nothing at all and can simply be retried. Scoring used to happen after the champion had already been swapped and its receipt written, so a scoring failure stranded the loop with a deployed challenger, an adoption row, and a bandit reward for a step that – as far as its caller and its own history were concerned – never happened.Note on
verify(MXR-080-0042 – data roles, seemixle.evolve.population’s module docstring for the fuller three-role framing this loop shares):train_data(what the challenger is fit on) isacquire’s top-priority SUBSET ofbatch. Whenverifyis omitted,verify_datano longer defaults to the wholebatch(which CONTAINStrain_data, so the gate used to score a challenger on rows it had just been fit on – weaker evidence than a disjoint split). Instead,batchis split (_split(), fractionself.holdout) into a candidate pool andverify_dataFIRST, before harvest/acquire ever see it, and ONLY the candidate pool is harvested + ranked fortrain_data. This is deliberately a RANDOM split up front, notbatchminus whateveracquirehappened to pick:acquireselects the pool’s most informative/hardest-to-classify items on purpose (that is the whole point of active acquisition), so its complement is systematically skewed toward whatever the CURRENT champion already gets right – an unrepresentative, biased verify set that would under-detect exactly the improvement the loop exists to find, which is arguably worse than the overlap it would replace. Splitting first keepsverify_dataa fair, representative sample ofbatch’s actual distribution, genuinely disjoint from anything harvest/acquire/the challenger’s fit ever touches. Callers who want to control the split themselves (or whosebatchis already disjoint from wherevertrain_datawill come from) should pass an explicitverifybatch instead. Note too that ONE step’s gate decision (verdict.promoteabove) is a fresh, one-shot look at that step’s own (now-disjoint, representative) data – the per-step promotion decision itself is evidence-bearing. What is only a heuristic is the per-CONTEXTOperatorCreditBandit’s ACCUMULATED reward across many steps: exactly likePopulation’s bandit, it is fine when each step draws fresh data (the normalrun()-over-a-stream usage), but a caller who callsstep()repeatedly against the SAME (or overlapping)verify/batchdata should treat the bandit’s learned preferences as search guidance, not as a promotion-grade statistical claim about any one operator.Note on
budget_remaining: when given, the bandit-selected operator’s owncost_hintis checked against it BEFORE the operator runs – an operator whose cost exceeds what remains is skipped exactly like an inapplicable one (nopropose/gate attempted, nothing charged).result.costreports what this step actually charges: the operator’scost_hintiff the attempt was genuinely applicable and completed without raising,0.0otherwise – seerun(), which sumsresult.costto track total spend. It must be a finite, nonnegative amount: every budget comparison here is an ordered one, and every ordered comparison against NaN is false, so a NaN ceiling is not a ceiling (_budget_amount()).
- run(stream, *, verify_batches=None, contexts=None, budget=None)[source]
Run the loop over a SEQUENCE of batches (a stream) – the budgeted background loop’s outer iteration.
budget(if given) is a ceiling on total spend: each step’s operator cost is checked against the REMAINING budget and must fit BEFORE that operator runs (seestep()’sbudget_remaining), and onlyresult.cost– what the step actually charged – accumulatesspent, so a skipped-by-budget, inapplicable, or failed attempt is genuinely free, exactly as documented; only a genuinely-applicable, genuinely-completed attempt is charged itscost_hint. The loop stops early once the budget is fully spent, leaving the champion at whatever it last became.verify_batchesandcontexts, when given, are per-step and must line up ONE-TO-ONE withstream. Both are validated here, before the first step runs, rather than being indexed as the loop goes (MXR-080-1902): a shortcontextsused to raiseIndexErrorpart-way through, after the earlier steps had already deployed champions, written adoption receipts, rewarded per-context bandit arms and appended history – an argument error discovered only once it had produced durable, half-finished state that no return value described. A long one was worse, because it raised nothing at all: the surplus entries were silently dropped, so a caller who had mis-zipped their stream against their contexts got a completed-looking run in which every step had been attributed to the wrong context.budgetis validated the same way and for the same reason (_budget_amount()).
- class LoopStepResult(context, operator, promoted, delta, champion_score, champion, cost=0.0)[source]
Bases:
objectOne
ClosedLoopSelfEvolution.step()outcome.costis what this step actually charges against aClosedLoopSelfEvolution.run()budget: the selected operator’scost_hintiff the attempt was genuinely applicable and completed without raising,0.0otherwise – skipped-by-budget, inapplicable, and failed attempts are all free.
- class OperatorCreditBandit(operator_names, *, c=1.0, seed=0)[source]
Bases:
objectA per-context meta-bandit over challenger-production operators, wrapping one
mixle.task.bandit.UCB1per context (e.g. a failure type/domain) – so the loop learns, independently for each context, which operator actually produces winning challengers there.- select(context)[source]
The bandit-chosen operator name for
context.
- reward(context, operator, reward)[source]
Fold an observed (non-negative, anti-regression) reward back into
operator’s arm.rewardmust be finite:max(nan, 0.0)isnan, which would poison the arm’s running mean and every selection that reads it, permanently.
- class GenealogyLedger(ledger=<factory>)[source]
Bases:
objectGenealogy receipts for adopted (gate-passing) champions, built directly on
EvolutionLedger(never storing model objects in the ledger rows themselves – only their operator, measured gap, and a stable id – exactly the ledger’s own JSON-serializability discipline).A model’s id is DURABLE and content-addressed (
mixle.data.hashing.model_hash(), a hex SHA-256 over its serialized parameters) – never the model object’sid()(a CPython memory address). That makes ids stable across processes (a model reloaded from disk hashes to the exact same id it had when first recorded, solineage()finds it) and immune toid()-reuse aliasing (once a model is garbage-collected, an unrelated new object reallocated at the same address can never be mistaken for it, because the id no longer depends on the address at all). Combined with the ledger rows’ existing JSON-serializability, the lineage this class records is genuinely replayable: every id in a persisted ledger can be recomputed independently from any model, in any later process.- Parameters:
ledger (EvolutionLedger)
- record_adoption(*, parent, child, operator, gap, context, meta=None)[source]
Record ONE adoption:
childreplacedparentviaoperator, measuredgap(the verified challenger-beats-champion delta).parent=Nonemarks the root of a lineage.
- lineage(model)[source]
Reconstruct
model’s full lineage: the ordered (root-first) chain of adoption receipts{operator, delta (the measured gap), parent_hash, meta: {child_hash, context, ...}}back to the first recorded ancestor. Returns[]ifmodelwas never recorded as an adoption – including ifmodelcan’t be hashed at all (e.g. it isn’t a fitted mixle model), which is just another way of never having been recorded. Because the id is content-addressed (see class docstring), this works for ANY object whose serialized content matches a previously-recorded one – not only the exact live Python instancerecord_adoption()was originally called with – e.g. the same model reloaded from disk in a fresh process.
- accuracy_objective()[source]
Higher-is-better accuracy for a model exposing a single “most likely label” prediction (a fitted
CategoricalDistribution’sargmax(pmap)): per-observation1{y == predicted_label}.- Return type:
Objective
- harvest_failures(champion, batch, objective)[source]
The objective-generic analog of
mixle.task.router.Router.harvested(): the observations inbatchwherechampiondid NOT score at its own best-attainable pointwise value underobjective– foraccuracy_objective()this is exactly “the cases it got wrong.” A scalar-only objective (no honest per-observation vector) harvests the whole batch – there is no finer signal to rank on.
- harvested_from_router(router)[source]
Pull the REAL harvested failure pool from a live
mixle.task.router.Router: every input that escalated all the way to the frontier, paired with its frontier label – exactlyrouter.harvested(), just zipped into one pool formixle.task.acquire.acquire().
- default_challenger_operators()[source]
The three L1 challenger-production operators, each a reused
ImprovementOperator(no bespoke fitting logic):"distill"->AutoSelect– cold-start: fit a fresh model from the harvested pool, the “train a new model from teacher-labeled data” shape of distillation."refine"->Refit– warm-started refit of the champion’s own parameters on the harvested pool."evolve"->Mutate– genetic-programming structure edit (grow/shrink/perturb), mixle.evolve’s own structure-search operator.
- principled_crossover(model_a, model_b, *, weight_a=0.5)[source]
Combine two champions the ONLY principled way this framework allows: mixture composition (
mixle.ops.mixture()), never gene-splicing (cutting/pasting incompatible weight sub-blocks). Returns an unfitted mixture prototype; refit it against data (e.g. via its own.estimator()) before treating it as a challenger.
- class ConceptLibrary(base_families=('gaussian',))[source]
Bases:
objectA small, inspectable registry of admitted concept families – the “library” of CARD L6.
Starts with
base_families(present from the outset, never revocable viarevoke()– they are the starting library, not something this loop discovered). Everything admitted afterwards is a first-class, named, evidenced entry that can be queried by task signature and, if it turns out to be a mistake, genuinely revoked – removed from both the active family set and the design-prior ledger that backsquery(), not merely hidden.- Parameters:
base_families (Sequence[str])
- property families: tuple[str, ...]
the starting base plus everything still admitted.
- Type:
Every family currently in the library
- admit(family, evidence, *, task_signature, task_index, quality)[source]
Admit
familyto the library, receipted withevidenceand recorded in the design-prior ledger undertask_signaturesoquery()can recommend it later.familymust name a REGISTERED family, andqualitymust be finite. An admission is the library’s promise that a laterquery()can hand this name back as something the search can actually reach for and fit; admitting an unregistered name madequery()recommend a family that does not exist, and a non-finite quality made the ranking that picks between recorded recipes meaningless.
- revoke(family, *, task_index=-1, reason='')[source]
Undo an admission:
familyleaves the active family set AND the design-prior ledger, soquery()can never recommend it again – genuinely reversible, not soft-hidden.
- class AdmissionEvent(action, family, task_index, evidence=<factory>)[source]
Bases:
objectOne receipted, timestamped-by-task-index entry in a
ConceptLibrary’s audit log.
- class TaskResult(task_index, task_signature, discrepancy, challenger_family, reused_concept, admitted_family, verdict, mdl_gain_bits)[source]
Bases:
objectOne task’s pass through the loop – the receipt the acceptance test reads.
- run_concept_discovery_loop(tasks, *, library=None, champion_family='gaussian', objective=None, train_frac=0.6, min_train=8, min_holdout=8, recurrence_window=2, discrepancy_threshold=0.003, min_effect=0.01, nonnested=False)[source]
Run discrepancy -> propose -> verify -> adopt -> remember over
tasks, in order.For every task: fit the
champion_familyon a train split, measure discrepancy against a held-out split (mixle.epistemic.discrepancy.discrepancy_report()). If the library already has an admitted concept recommended for this task’s signature (ConceptLibrary.query()), that concept is tried directly – no re-search. Otherwise, once high discrepancy has recurred forrecurrence_windowconsecutive tasks sharing a signature with no admitted concept yet, the automatic profiler proposes a candidate family from its registered set (selection, not synthesis); it is gated viamixle.evolve.verify.challenger_beats_champion()and, only if it passes, admitted.Each task’s data is split
train, held_out = data[:n_train], data[n_train:]withn_trainderived fromtrain_fracbut clamped so both sides meet an explicit floor:min_trainrows to fit the champion/challenger on,min_holdoutrows so discrepancy and the verify gate never run on empty (or near-empty) evidence.train_fracmust lie in the open interval(0, 1); a task with fewer thanmin_train + min_holdoutrows raisesValueErrorrather than silently starving the train or held-out side of the split.Returns the (possibly newly-created)
ConceptLibraryand oneTaskResultper task.- Parameters:
- Return type:
- task_signature(data)[source]
A coarse, reusable descriptor of a task’s data-generating shape.
Deliberately coarse (kind + skew sign, not a fingerprint of the exact data): the point is that different tasks sharing the same hidden family land on the same signature, so
ConceptLibrary.query()can recommend a concept discovered on an earlier task to a later one.
Submodules¶
- mixle.evolve.closed_loop module
- mixle.evolve.concept_discovery module
- mixle.evolve.improve module
- mixle.evolve.ledger module
- mixle.evolve.objective module
- mixle.evolve.operators module
- mixle.evolve.population module
- mixle.evolve.search module
- mixle.evolve.space module
- mixle.evolve.structure module
- mixle.evolve.verify module