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 selects the best-matching family
for that pattern from a small set of REGISTERED, already-implemented families, fits it, 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). “New” here means newly admitted to the
library for this task signature – selection, not synthesis: the candidate is always one of a small,
closed set of known families (four built in, plus any added via register_family()), never a
likelihood, support, sufficient statistic, or inference procedure invented from scratch. 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:
discrepancy –
mixle.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.propose –
mixle.utils.automatic.profiling._profile_series()(the automatic profiler that landed this session’s six new univariate detectors) is asked which registered family it would recommend for the data; that recommendation is the candidate family – selected from the registry, not synthesized.verify / adopt –
mixle.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.remember –
mixle.task.design_prior.record_accepted_recipe()over amixle.task.edge.DesignModelpersists where the concept won, tagged by a coarse task signature, soConceptLibrary.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:
objectOne receipted, timestamped-by-task-index entry in a
ConceptLibrary’s audit log.
- 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 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.
- register_family(name, constructor, *, replace=False)[source]
Register an already-implemented family’s constructor so the loop can select/reuse it (beyond the four built in). This does not define a new family:
constructormust build a working, already-implemented distribution – registration only makes an existing family selectable here.The constructor is invoked once and its prototype checked, so a name can never become selectable without something fittable behind it. Replacing an already-registered name requires
replace=True: silently rebinding a name the library may already have admitted (and recorded receipts against) would retroactively change what that recorded evidence was evidence for.
- 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.