|
| dict | load_targets () |
| |
| dict | discover_stages () |
| |
| list | read_rows (Path path) |
| |
| dict | read_design (path) |
| |
| | grid_components (dict row) |
| |
| | classify (dict row, float dose_target=15.0) |
| |
| | recover_V (dict row) |
| |
| dict | extent_pairs (list all_rows) |
| |
| set | verified_obs (dict row, dict ext) |
| |
| dict | score (dict row, dict targets, dict ext=None) |
| |
| float | worst_margin (dict row, dict targets, set only=None) |
| |
| | _margin_or_none (dict row, dict targets, dict sc) |
| |
| float | log_distance (dict row, dict targets) |
| |
| list | covarying_groups (dict design) |
| |
| tuple | group_key (dict design_row, list group) |
| |
| dict | sensitivity (dict stage, list rows, dict targets, float dose_target=15.0) |
| |
| dict | affordability (dict stage, list rows, float dose_target) |
| |
| | inventory (dict row, dict targets) |
| |
| dict | cost_model (list all_rows) |
| |
| dict | ingest () |
| |
| | fmt (v, sig=3) |
| |
| str | render_guide (dict led) |
| |
| | main (argv=None) |
| |
Ingest every calibration stage and re-derive what the campaign has learned.
WHY THIS EXISTS. S1-S14 each answered one question, and the answer lived in a
commit message. A machine that pulls the repo cannot read commit messages into
a plan, and a human returning after a crash re-derives the same verdicts by
hand (this happened on 2026-08-18). This script turns the result files back
into two artefacts that DO travel:
calibration_ledger.json machine-readable state -- what was swept, what it
did, which levers are dead, where the best point
is, what each machine costs per row.
CALIBRATION_GUIDE.md the same thing for a human, regenerated so it can
never drift from the results it summarises.
`plan.py` reads the ledger and proposes the next stage. Nothing here writes a
design or launches a run -- learning and planning are kept separate so that
re-ingesting results can never, by itself, start a job.
THE ONE RULE THIS FILE ENFORCES. A row is a MEASUREMENT only if it reached the
target dose, is not grid-limited, and its grid converged. Rows that piled at
the ceiling still carry information about DIRECTION, and are used for that, but
their size readouts are never scored or fitted. S12 and the V2 block of S14
are the reason: both look like "big loops" and neither is a number.
Usage:
python learn.py # re-ingest, rewrite ledger + guide
python learn.py --quiet # no stdout summary
| dict learn.affordability |
( |
dict |
stage, |
|
|
list |
rows, |
|
|
float |
dose_target |
|
) |
| |
Which lever VALUES prevent a row from ever reaching dose.
This is a COST verdict, not a physics one, and it is measured with a
controlled contrast: within one stage, a level counts as unaffordable only
if it produced zero full-dose rows while ANOTHER level of the same
co-varying group -- everything else held equal -- produced at least one.
S14 is the motivating case. All six rows at E_b_i2 = 0.60 burned the full
20 h budget and reached 0.004-6.06 of 15 dpa; all six at 0.75 finished in
5.3-19.8 h. Nothing about that is visible in the physics verdicts, because
a row that never reaches dose is excluded from scoring -- so without this
the planner cheerfully proposes another stage in the same dead direction.
Definition at line 459 of file learn.py.
| learn.classify |
( |
dict |
row, |
|
|
float |
dose_target = 15.0 |
|
) |
| |
Is this row a measurement? Returns (valid, flags).
THE VACANCY TOP-BIN DOES NOT DISQUALIFY (2026-08-20). `topbin_v` is the
vacancy CONTENT fraction in the top bin, and on a log grid that fraction has
a floor set by the bin ratio, not by truncation: for a cavity tail
c(n) ~ n^-alpha it tends to 1 - r^-(2-alpha) whatever the ceiling is. The
campaign's cavity tail has alpha ~ 0.47, so at the production r = 1.514 the
floor is ~0.47 and TOPBIN_TOL = 0.02 is unreachable at any affordable bin
count (it would need ~630 vacancy bins).
Measured 2026-08-20 on the reference point and on the 4/6 anchor, refining
v_bin 20 -> 47 (r 1.514 -> 1.202):
* topbin_v fell 0.468 -> 0.1715 and 0.472 -> 0.1718 -- the SAME value to
three digits at mean_n_v = 1566 and 7115, a 4.5x span in mean void size.
A truncation measure cannot be blind to that; a geometric one is.
* the four loop observables moved by <= 0.1 % (grid-exact), and the 4/6
anchor still scored 4/6 with every in-band observable still in band.
So the vacancy top bin is recorded as a soft TOPBINV flag instead.
The SIA-axis top bin KEEPS disqualifying: those bins run at r = 1.204, where
the geometric floor is small, and the argument above does not transfer.
`pile` also keeps disqualifying -- it is measured on the reconstructed
distribution and is the real truncation test (1095 of the 1226 grid-flagged
rows fail it, so this change rescues 105 rows, not the whole campaign).
This restores agreement with run_ensemble.py, which withdrew truncation as a
reject criterion outright on 2026-08-05 (TRUNCATION_GATES = False); learn.py
had reintroduced it, so the ledger and CALIBRATION_GUIDE.md were reporting a
best-of-campaign that excluded the campaign's best rows -- and plan.py plans
from that ledger.
Definition at line 175 of file learn.py.
| dict learn.cost_model |
( |
list |
all_rows | ) |
|
Wall time per row, per machine -- from COMPLETED rows only.
A row cut at the budget records wall_s == the budget, which measures the
timeout and not the cost. Counting those pins max_row_h to the budget, and
since plan.py refuses a design whose worst row exceeds the budget, one
timed-out stage would make the planner refuse every subsequent design on
that machine. S14 did exactly this: six rows at 20.0 h that never reached
dose. Timeouts are counted separately, because a machine that times out
often is still worth knowing about.
Definition at line 550 of file learn.py.
| learn.grid_components |
( |
dict |
row | ) |
|
Split `grid_limited` into its parts. Returns (pile, sia_top, vac_top,
unmeasured, decomposable).
run_ensemble records the components; only the OR of them survives into
`grid_limited`, which is too coarse to act on (see classify).
Definition at line 157 of file learn.py.