176 """Is this row a measurement? Returns (valid, flags).
178 THE VACANCY TOP-BIN DOES NOT DISQUALIFY (2026-08-20). `topbin_v` is the
179 vacancy CONTENT fraction in the top bin, and on a log grid that fraction has
180 a floor set by the bin ratio, not by truncation: for a cavity tail
181 c(n) ~ n^-alpha it tends to 1 - r^-(2-alpha) whatever the ceiling is. The
182 campaign's cavity tail has alpha ~ 0.47, so at the production r = 1.514 the
183 floor is ~0.47 and TOPBIN_TOL = 0.02 is unreachable at any affordable bin
184 count (it would need ~630 vacancy bins).
186 Measured 2026-08-20 on the reference point and on the 4/6 anchor, refining
187 v_bin 20 -> 47 (r 1.514 -> 1.202):
188 * topbin_v fell 0.468 -> 0.1715 and 0.472 -> 0.1718 -- the SAME value to
189 three digits at mean_n_v = 1566 and 7115, a 4.5x span in mean void size.
190 A truncation measure cannot be blind to that; a geometric one is.
191 * the four loop observables moved by <= 0.1 % (grid-exact), and the 4/6
192 anchor still scored 4/6 with every in-band observable still in band.
193 So the vacancy top bin is recorded as a soft TOPBINV flag instead.
195 The SIA-axis top bin KEEPS disqualifying: those bins run at r = 1.204, where
196 the geometric floor is small, and the argument above does not transfer.
197 `pile` also keeps disqualifying -- it is measured on the reconstructed
198 distribution and is the real truncation test (1095 of the 1226 grid-flagged
199 rows fail it, so this change rescues 105 rows, not the whole campaign).
201 This restores agreement with run_ensemble.py, which withdrew truncation as a
202 reject criterion outright on 2026-08-05 (TRUNCATION_GATES = False); learn.py
203 had reintroduced it, so the ledger and CALIBRATION_GUIDE.md were reporting a
204 best-of-campaign that excluded the campaign's best rows -- and plan.py plans
210 hard_grid = pile
or sia_top
or unmeas
212 flags.append(
"TOPBINV")
214 hard_grid = bool(row.get(
"grid_limited")) \
215 or not row.get(
"grid_converged",
True)
218 flags.append(
"NOCONV")
219 reached = row.get(
"dose_reached", 0.0)
or 0.0
220 if abs(reached - dose_target) > 0.02 * dose_target:
222 if row.get(
"starved"):
223 flags.append(
"STARV")
224 if not row.get(
"admissible",
True):
225 flags.append(
"INADM")
226 if not row.get(
"conserving",
True):
227 flags.append(
"NOCONS")
231 hard = {
"GRID",
"NOCONV",
"DOSE",
"STARV",
"INADM"}
232 return (
not (hard & set(flags))), flags
293def score(row: dict, targets: dict, ext: dict =
None) -> dict:
294 """Band membership, reported THREE ways.
296 `n_in_range` - every observable, the historical figure.
297 `n_verified` - how many observables survived an extent change.
298 `n_in_range_verified` - in band AND extent-verified. THIS is the score
299 that ranks rows; the other two are diagnostics.
301 o = targets[
"observables"]
303 per, n_ok, n_ok_ver = {}, 0, 0
309 ok = o[k][
"lo"] <= v <= o[k][
"hi"]
311 n_ok_ver += (ok
and k
in ver)
312 per[k] = {
"value": v,
"in_range": bool(ok),
"verified": k
in ver,
313 "ratio": v / o[k][
"target"]
if o[k][
"target"]
else None}
314 return {
"n_in_range": n_ok,
"n_verified": len(ver),
315 "n_in_range_verified": n_ok_ver,
"verified": sorted(ver),
"per": per}
319 """Decades from the nearest band edge, for the observable closest to one.
321 Band membership is BINARY and so rewards edge-sitting: V15 row 7109 counts
322 the same 3/4 as row 7100 while sitting 1 % inside the N_111 ceiling
323 (1.49e22 against 1.5e22) where 7100 is 18 % clear on its tightest
324 observable. One of those is a calibration; the other is a coin flip against
325 solver noise and the next grid change. `log_distance` cannot separate them
326 either -- it is a global RMS to the target CENTRES, so it says nothing about
327 proximity to an EDGE.
329 Returned in decades so it is scale-free across densities and diameters.
330 Negative when an observable is outside its band (how far out it is).
331 None when nothing is scoreable.
333 o = targets[
"observables"]
335 for k
in (only
if only
is not None else OBS):
337 if v
is None or v <= 0:
339 lo, hi = o[k][
"lo"], o[k][
"hi"]
341 ms.append(min(math.log10(v / lo), math.log10(hi / v)))
343 ms.append(-min(abs(math.log10(v / lo)), abs(math.log10(v / hi))))
344 return min(ms)
if ms
else None
397 dose_target: float = 15.0) -> dict:
398 """Response of each observable to each co-varying lever group.
400 Only pairs that differ in EXACTLY ONE group are used, so the response is
401 attributable. Magnitude is measured on valid rows only; a pair where one
402 side went invalid is recorded as a direction with `drove_invalid`.
408 by_id = {str(r[
"row_id"]): r
for r
in rows
if r.get(
"row_id")
is not None}
412 others = [g
for g
in groups
if g
is not group]
413 pairs, drove_invalid = [],
False
414 ids = [i
for i
in by_id
if i
in design]
419 da, db = design[a], design[b]
424 ra, rb = by_id[a], by_id[b]
428 pairs.append((da, db, ra, rb))
435 for da, db, ra, rb
in pairs:
436 x, y = ra.get(k), rb.get(k)
437 if x
and y
and x > 0
and y > 0:
438 best = max(best, abs(y / x - 1.0))
443 vals = sorted({float(design[i][c])
for i
in ids})
444 span[c] = [vals[0], vals[-1]]
446 peak = max(resp.values())
if resp
else 0.0
447 out[
"+".join(group)] = {
448 "columns": group,
"span": span,
"n_pairs": len(pairs),
449 "response": resp,
"peak_response": peak,
450 "drove_invalid": drove_invalid,
451 "verdict": (
"dead" if (pairs
and peak < DEAD_THRESHOLD)
453 else "inconclusive"),
460 """Which lever VALUES prevent a row from ever reaching dose.
462 This is a COST verdict, not a physics one, and it is measured with a
463 controlled contrast: within one stage, a level counts as unaffordable only
464 if it produced zero full-dose rows while ANOTHER level of the same
465 co-varying group -- everything else held equal -- produced at least one.
467 S14 is the motivating case. All six rows at E_b_i2 = 0.60 burned the full
468 20 h budget and reached 0.004-6.06 of 15 dpa; all six at 0.75 finished in
469 5.3-19.8 h. Nothing about that is visible in the physics verdicts, because
470 a row that never reaches dose is excluded from scoring -- so without this
471 the planner cheerfully proposes another stage in the same dead direction.
476 by_id = {str(r[
"row_id"]): r
for r
in rows
if r.get(
"row_id")
is not None}
480 for rid, drow
in design.items():
484 reached = abs((r.get(
"dose_reached")
or 0.0) - dose_target) \
485 <= 0.02 * dose_target
487 slot = tally.setdefault(key, [0, 0])
492 any_ok = any(v[1] > 0
for v
in tally.values())
501 for key, (n, ok)
in tally.items():
502 for col, val
in zip(group, key):
503 rec = out.setdefault(col, {
"unaffordable": [],
"affordable": [],
505 if ok == 0
and n >= 3
and any_ok:
506 if val
not in rec[
"unaffordable"]:
507 rec[
"unaffordable"].append(val)
508 rec[
"evidence"].append(
509 "%s: 0 of %d rows reached dose at %s=%s"
510 % (stage.get(
"stem",
"?"), n, col, val))
511 elif ok > 0
and val
not in rec[
"affordable"]:
512 rec[
"affordable"].append(val)
514 return {c: r
for c, r
in out.items()
if r[
"unaffordable"]}
519 """Compare SIA content locked in loops against the measurement.
521 Uses the ROW OWN n<->d relation (n / d^2 from its own mean_n and mean d),
522 so no lattice constant or Burgers vector is assumed here and the comparison
523 cannot drift from whatever the solver actually used.
525 o = targets[
"observables"]
527 for char, nkey, dkey, Nkey, tN, td
in (
528 (
"100",
"mean_n_100",
"d_100_nm",
"N_loops_100",
"N_loops_100",
"d_100_nm"),
529 (
"111",
"mean_n_111",
"d_111_nm",
"N_loops_111",
"N_loops_111",
"d_111_nm"),
531 n, d, N = row.get(nkey), row.get(dkey), row.get(Nkey)
532 if not (n
and d
and N
and d > 0):
534 n_per_d2 = n / (d ** 2)
535 n_exp = n_per_d2 * (o[td][
"target"] ** 2)
537 "model_N": N,
"model_mean_n": n,
"model_content": N * n,
538 "exp_N": o[tN][
"target"],
"exp_mean_n_at_target_d": n_exp,
539 "exp_content": o[tN][
"target"] * n_exp,
540 "ratio": (N * n) / (o[tN][
"target"] * n_exp),
542 tot_m = sum(v[
"model_content"]
for v
in out.values())
543 tot_e = sum(v[
"exp_content"]
for v
in out.values())
544 out[
"total"] = {
"model_content": tot_m,
"exp_content": tot_e,
545 "ratio": tot_m / tot_e
if tot_e
else None}
551 """Wall time per row, per machine -- from COMPLETED rows only.
553 A row cut at the budget records wall_s == the budget, which measures the
554 timeout and not the cost. Counting those pins max_row_h to the budget, and
555 since plan.py refuses a design whose worst row exceeds the budget, one
556 timed-out stage would make the planner refuse every subsequent design on
557 that machine. S14 did exactly this: six rows at 20.0 h that never reached
558 dose. Timeouts are counted separately, because a machine that times out
559 often is still worth knowing about.
561 done, timed_out = {}, {}
563 w, m = r.get(
"wall_s"), r.get(
"machine_id")
566 tgt = r.get(
"dose_target")
567 reached = r.get(
"dose_reached")
568 finished = (tgt
is None or reached
is None
569 or abs(reached - tgt) <= 0.02 * tgt)
570 (done
if finished
else timed_out).setdefault(m, []).append(float(w))
575 for m
in sorted(set(done) | set(timed_out)):
576 ws = sorted(done.get(m, []))
577 rec = {
"n": len(ws),
"n_timed_out": len(timed_out.get(m, []))}
579 rec.update({
"median_row_h": round(ws[len(ws) // 2] / 3600.0, 2),
580 "max_row_h": round(ws[-1] / 3600.0, 2),
581 "min_row_h": round(ws[0] / 3600.0, 2)})
590 prev = json.loads(LEDGER.read_text(encoding=
"utf-8"))
if LEDGER.exists()
else {}
592 goal_dose = float(targets[
"condition"][
"dose_dpa"])
599 for stem, meta
in sorted(stages_meta.items()):
602 rows_by_stage[stem] = rows
603 all_rows = [r
for rows
in rows_by_stage.values()
for r
in rows]
606 stages, best, best_raw = {},
None,
None
607 for stem, rows
in rows_by_stage.items():
608 meta = stages_meta[stem]
615 doses = [r.get(
"dose_target")
for r
in rows
if r.get(
"dose_target")]
616 stage_dose = max(set(doses), key=doses.count)
if doses
else goal_dose
617 in_scope = abs(stage_dose - goal_dose) < 1e-9
620 recs, n_valid = [], 0
622 valid, flags =
classify(r, stage_dose)
624 sc =
score(r, targets, ext)
625 if sc[
"n_verified"] == 0:
626 flags = flags + [
"UNVERIFIED"]
627 drow = design.get(str(r.get(
"row_id")), {})
628 rec = {
"row_id": r.get(
"row_id"),
629 "label": meta[
"labels"].get(str(r.get(
"row_id"))),
630 "valid": valid,
"flags": flags,
631 "n_in_range": sc[
"n_in_range"],
632 "n_verified": sc[
"n_verified"],
633 "n_in_range_verified": sc[
"n_in_range_verified"],
634 "verified": sc[
"verified"],
647 "dose_reached": r.get(
"dose_reached"),
648 "observables": {SHORT[k]: (sc[
"per"][k][
"value"]
649 if sc[
"per"][k]
else None)
652 if valid
and in_scope:
653 cand = {
"stage": stem,
"row_id": r.get(
"row_id"),
654 "label": rec[
"label"],
"n_in_range": sc[
"n_in_range"],
655 "n_verified": sc[
"n_verified"],
656 "n_in_range_verified": sc[
"n_in_range_verified"],
657 "verified": sc[
"verified"],
658 "worst_margin": rec[
"worst_margin"],
659 "V_tested": (ext.get(r.get(
"theta_hash"))
or {}).get(
"V_tested"),
660 "log_distance": rec[
"log_distance"],
662 "observables": rec[
"observables"],
663 "ratios": {SHORT[k]: (round(sc[
"per"][k][
"ratio"], 4)
664 if sc[
"per"][k]
and sc[
"per"][k][
"ratio"]
665 else None)
for k
in OBS},
666 "params": {c: v
for c, v
in drow.items()
667 if c
not in BOOKKEEPING},
679 return (c[
"n_in_range_verified"], c[
"n_verified"],
681 c[
"worst_margin"]
if c[
"worst_margin"]
is not None
684 if best
is None or rank(cand) > rank(best):
706 return (c[
"n_in_range"], -c[
"log_distance"])
707 if best_raw
is None or rank_raw(cand) > rank_raw(best_raw):
715 "design": (meta[
"design"].relative_to(HERE).as_posix()
716 if meta[
"design"]
else None),
717 "results": meta[
"results"].relative_to(HERE).as_posix(),
718 "dose_target": stage_dose,
"in_scope": in_scope,
719 "n_rows": len(rows),
"n_valid": n_valid,
721 "sensitivity":
sensitivity(meta, rows, targets, stage_dose),
722 "affordability": (
affordability(dict(meta, stem=stem), rows, stage_dose)
723 if in_scope
else {}),
732 for stem, st
in stages.items():
733 if not st[
"in_scope"]:
735 for name, s
in st[
"sensitivity"].items():
736 L = levers.setdefault(name, {
"columns": s[
"columns"],
"seen_in": [],
737 "peak_response": 0.0,
"response": {},
738 "spans": {},
"n_pairs": 0,
739 "drove_invalid":
False})
740 L[
"seen_in"].append(stem)
741 L[
"peak_response"] = max(L[
"peak_response"], s[
"peak_response"])
742 L[
"n_pairs"] += s[
"n_pairs"]
743 L[
"drove_invalid"] = L[
"drove_invalid"]
or s[
"drove_invalid"]
744 for k, v
in s[
"response"].items():
745 L[
"response"][k] = max(L[
"response"].get(k, 0.0), v)
746 for c, sp
in s[
"span"].items():
747 old = L[
"spans"].get(c)
748 L[
"spans"][c] = ([min(sp[0], old[0]), max(sp[1], old[1])]
750 for name, L
in levers.items():
754 if L[
"n_pairs"] == 0:
755 L[
"verdict"] =
"inconclusive"
756 elif L[
"peak_response"] < DEAD_THRESHOLD:
757 L[
"verdict"] =
"dead"
759 L[
"verdict"] =
"live"
760 moving = [k
for k, v
in L[
"response"].items()
if v >= DEAD_THRESHOLD]
761 moving.sort(key=
lambda k: -L[
"response"][k])
762 L[
"moves"] = [SHORT[k]
for k
in moving]
779 ratio = (best[
"ratios"]
or {}).get(s_)
782 need = (1.0 / ratio)
if ratio < 1
else ratio
784 for name, L
in levers.items():
785 r = L[
"response"].get(k, 0.0)
790 "needed_factor": round(need, 3),
791 "needed_pct": round((need - 1) * 100, 1),
792 "best_single_lever_pct": round(top * 100, 1),
793 "best_single_lever": who,
794 "single_lever_sufficient": bool(top >= (need - 1.0)),
802 tested_cols = {c
for L
in levers.values()
for c
in L[
"columns"]}
804 for stem, st
in stages.items():
805 if not st[
"in_scope"]:
807 d =
read_design(HERE / st[
"design"])
if st[
"design"]
else {}
809 all_cols |= {c
for c
in next(iter(d.values()))
if c
not in BOOKKEEPING}
810 untested = sorted(all_cols - tested_cols)
814 for stem, st
in stages.items():
815 for col, rec
in (st.get(
"affordability")
or {}).items():
816 a = afford.setdefault(col, {
"unaffordable": [],
"affordable": [],
818 for kind
in (
"unaffordable",
"affordable"):
819 for v
in rec.get(kind, []):
822 a[
"evidence"].extend(rec[
"evidence"])
823 for col, a
in afford.items():
824 for kind
in (
"unaffordable",
"affordable"):
826 a[kind +
"_numeric"] = sorted(float(v)
for v
in a[kind])
827 except (TypeError, ValueError):
834 "statement": (
"Find one parameter vector that puts all six "
835 "observables inside their experimental ranges at "
836 "EUROFER97 330 C / 15 dpa, neutron."),
837 "targets_file": TARGETS.name,
838 "condition": targets[
"condition"],
842 "untested_columns": untested,
843 "reachability": reach,
844 "affordability": afford,
846 "best_raw": best_raw,
847 "residuals": best[
"ratios"]
if best
else {},
853 "policy": {**DEFAULT_POLICY, **(prev.get(
"policy")
or {})},
854 "notes": prev.get(
"notes", []),
855 "next": prev.get(
"next"),
856 "next_by_machine": prev.get(
"next_by_machine", {}),
869 stamp = datetime.now(timezone.utc).strftime(
"%Y-%m-%d %H:%M:%SZ")
872 return json.dumps({k: v
for k, v
in d.items()
if k !=
"updated"},
873 sort_keys=
True, indent=2)
874 if body(ledger) == body(prev):
875 stamp = prev.get(
"updated")
or stamp
876 ledger[
"updated"] = stamp
1194 ap = argparse.ArgumentParser(description=
"Re-ingest calibration results.")
1195 ap.add_argument(
"--quiet", action=
"store_true")
1196 a = ap.parse_args(argv)
1199 LEDGER.write_text(json.dumps(led, indent=2), encoding=
"utf-8")
1204 print(
"ingested %d stage files, %d rows (%d valid)"
1205 % (len(led[
"stages"]),
1206 sum(s[
"n_rows"]
for s
in led[
"stages"].values()),
1207 sum(s[
"n_valid"]
for s
in led[
"stages"].values())))
1209 print(
"best (verified): %s (%s) %d/6 in range, log-distance %s"
1210 % (b[
"label"]
or b[
"row_id"], b[
"stage"], b[
"n_in_range"],
1212 br = led.get(
"best_raw")
1213 if br
and b
and (br[
"stage"], br[
"row_id"]) != (b[
"stage"], b[
"row_id"]):
1214 print(
"best (raw score, unpromoted): %s (%s) %d/6 in range, "
1216 % (br[
"label"]
or br[
"row_id"], br[
"stage"],
1217 br[
"n_in_range"], br[
"log_distance"]))
1219 for k, v
in led[
"levers"].items():
1220 by.setdefault(v[
"verdict"], []).append(k)
1221 print(
"in-scope stages: %d of %d"
1222 % (sum(1
for s
in led[
"stages"].values()
if s[
"in_scope"]),
1223 len(led[
"stages"])))
1224 print(
"levers: %d live, %d dead, %d inconclusive"
1225 % (len(by.get(
"live", [])), len(by.get(
"dead", [])),
1226 len(by.get(
"inconclusive", []))))
1227 for verdict
in (
"live",
"dead",
"inconclusive"):
1229 print(
" %-13s %s" % (verdict +
":",
", ".join(sorted(by[verdict]))))
1230 if led[
"untested_columns"]:
1231 print(
" never varied: " +
", ".join(led[
"untested_columns"]))
1232 print(
"wrote %s and %s" % (LEDGER.name, GUIDE.name))