mixle.evolve.concept_discovery module

Concept discovery: the library itself under selection (CARD L6, the culmination of the L-track loop).

One loop – discrepancy -> propose -> verify -> adopt -> remember – run over the library of families rather than over any one model’s parameters. When several tasks recur with a residual pattern no family in the starting library explains well, this module fits a genuinely new family to that pattern, gates it as a challenger against the status quo, and – only if it survives the gate – admits it to a small, inspectable, reversible registry (ConceptLibrary). A future task whose signature matches records where the concept won (mixle.task.design_prior), so the search reaches for it directly instead of re-discovering it from scratch. Today’s systematic error becomes tomorrow’s primitive.

This module adds no new modeling capability – it is pure wiring over five existing subsystems, each already carrying its own contract:

  • discrepancymixle.epistemic.discrepancy.discrepancy_report() measures how far a fitted champion’s predictive distribution is from held-out data; tracked across tasks, a sustained gap is the “recurring unmodeled residual” signal that triggers a proposal.

  • proposemixle.utils.automatic.profiling._profile_series() (the automatic profiler that landed this session’s six new univariate detectors) is asked which family it would recommend for the data; that recommendation is the candidate new family.

  • verify / adoptmixle.evolve.verify.challenger_beats_champion() is the single gate: the proposed family is only admitted if it significantly, non-regressively beats the current champion on held-out data.

  • remembermixle.task.design_prior.record_accepted_recipe() over a mixle.task.edge.DesignModel persists where the concept won, tagged by a coarse task signature, so ConceptLibrary.query() can recommend it for a matching future task.

ConceptLibrary is intentionally its own small registry – not a mutation of mixle.stats’s hardcoded distribution list – so admission (and, symmetrically, revocation) is a local, receipted, reversible act rather than a change to shared global state.

class AdmissionEvent(action, family, task_index, evidence=<factory>)[source]

Bases: object

One receipted, timestamped-by-task-index entry in a ConceptLibrary’s audit log.

Parameters:
class ConceptLibrary(base_families=('gaussian',))[source]

Bases: object

A small, inspectable registry of admitted concept families – the “library” of CARD L6.

Starts with base_families (present from the outset, never revocable via revoke() – 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 backs query(), 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 family to the library, receipted with evidence and recorded in the design-prior ledger under task_signature so query() can recommend it later.

Parameters:
Return type:

None

revoke(family, *, task_index=-1, reason='')[source]

Undo an admission: family leaves the active family set AND the design-prior ledger, so query() can never recommend it again – genuinely reversible, not soft-hidden.

Parameters:
Return type:

None

query(signature)[source]

The design-prior recommendation for signature: the best-recorded, still-admitted family seen for a matching task signature, or None if nothing qualifies.

Parameters:

signature (str)

Return type:

str | None

class TaskResult(task_index, task_signature, discrepancy, challenger_family, reused_concept, admitted_family, verdict, mdl_gain_bits)[source]

Bases: object

One task’s pass through the loop – the receipt the acceptance test reads.

Parameters:
  • task_index (int)

  • task_signature (str)

  • discrepancy (float)

  • challenger_family (str | None)

  • reused_concept (bool)

  • admitted_family (str | None)

  • verdict (Verdict | None)

  • mdl_gain_bits (float)

register_family(name, constructor)[source]

Extend the set of families the loop can propose/reuse (beyond the built-in three).

Parameters:
Return type:

None

run_concept_discovery_loop(tasks, *, library=None, champion_family='gaussian', objective=None, train_frac=0.6, 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_family on 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 for recurrence_window consecutive tasks sharing a signature with no admitted concept yet, the automatic profiler proposes a new family; it is gated via mixle.evolve.verify.challenger_beats_champion() and, only if it passes, admitted.

Returns the (possibly newly-created) ConceptLibrary and one TaskResult per task.

Parameters:
Return type:

tuple[ConceptLibrary, list[TaskResult]]

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.

Parameters:

data (Sequence[float])

Return type:

str