mixle.evolve.population module

The meta-search that learns which improvement operators help: a bandit + a diversity population.

The operator-choice problem is a non-stationary bandit: each step, pick an operator (arm), apply it through the Phase-1 propose+verify gate, observe the verified gate delta as reward (0 if the challenger was rejected), and update the arm’s value. Because the reward is the anti-regression verified delta, the policy cannot be fooled by overfit in-sample gains.

  • OperatorBandit – Thompson or UCB over a fixed operator pool. Reward is the verified delta; cost is tracked for a report. Non-stationary: a forgetting factor decays stale arm statistics so the policy can follow a problem whose best operator changes over the run.

  • Population – a diversity-preserving population of model structures evolved by the bandit: select operators, apply them to parents, gate the challengers, reward the bandit, and keep the verified-best plus a coarse-but-honest capability-diversity quota. run returns a SearchResult; champion is the incumbent.

class OperatorBandit(operators, *, policy='thompson', decay=0.97, prior_cost_aware=True, seed=0)[source]

Bases: object

A non-stationary bandit over a fixed pool of ImprovementOperator.

select(k) returns the k highest-value operators under the chosen policy; reward folds a verified delta + cost back into the chosen arm; report is 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 a sqrt(2 ln N / n) exploration bonus.

Non-stationarity: each reward first multiplies every arm’s statistics by decay (a forgetting factor in (0, 1]), so old evidence fades and the policy can track a shifting best operator.

Parameters:
  • operators (Sequence[ImprovementOperator])

  • policy (str)

  • decay (float)

  • prior_cost_aware (bool)

  • seed (int)

value(name)[source]

The current policy value of operator name (a Thompson draw or the UCB index).

Parameters:

name (str)

Return type:

float

select(k=1)[source]

Return the k operators with the highest policy value (a fresh Thompson draw each call).

Parameters:

k (int)

Return type:

list[ImprovementOperator]

reward(op_name, delta, cost)[source]

Fold a verified delta (0 if the challenger was rejected) and cost into the arm.

Decays every arm first (non-stationarity), then updates the chosen arm. A delta is 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).

Parameters:
Return type:

None

report()[source]

Per-operator win-rate, mean verified delta, mean cost, and (decayed) pull count.

Return type:

dict[str, Any]

class Population(seeds, *, objective, operators=None, bandit=None, size=12, diversity_quota=2, seed=0)[source]

Bases: object

A diversity-preserving population of model structures, evolved by the OperatorBandit.

seeds are fitted models (the starting structures). Each step() 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 Objective to optimize (lower-is-better aware).

  • operators (Sequence[ImprovementOperator] | None) – the proposal-move pool; defaults to the Phase-1 safe set.

  • bandit (OperatorBandit | None) – an OperatorBandit over operators (built with the default policy if omitted).

  • size (int) – the carrying capacity of the population.

  • diversity_quota (int) – how many of size slots 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 generations steps; return a SearchResult.

The returned best_model is the run incumbent, guaranteed no worse than the best seed on the objective (anti-regression). history is one row per generation (proposals / verified / score).

Parameters:
  • data (Any)

  • generations (int)

Return type:

Any

property champion: Any

The current run incumbent (the best model seen, anti-regression guaranteed).

class GenerationReport(proposals=0, verified=0, best_score=nan, operators_used=<factory>, rewards=<factory>)[source]

Bases: object

One Population.step(): which operators ran, the verified wins, and the new champion score.

Parameters:
proposals: int = 0
verified: int = 0
best_score: float = nan
operators_used: list[str]
rewards: list[float]