RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
make_M_latex.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""make_M_latex.py — LaTeX table bodies for campaign M.
3
4Writes docs/Formulation/verification_campaign/tables/M*.tex, which campaign_results.tex \\input{}s.
5Generated rather than transcribed: this report has already had one table carry
6a fabricated N_eq (28006 for a run whose value was 8006) because a number was
7filled in from memory when the tool left a blank.
8
9Every table refuses to print a metric for a run that missed its comparison dose
10(plan S3.4.1); such a cell prints \\dnr instead.
11"""
12from __future__ import annotations
13
14import json
15import sys
16from pathlib import Path
17
18HERE = Path(__file__).resolve().parent
19DT = HERE.parent
20MOD = DT.parent
21REPO = MOD.parent
22for p in (str(REPO), str(DT), str(HERE)):
23 if p not in sys.path:
24 sys.path.insert(0, p)
25
26import runs as M # noqa: E402
27
28SYNC = HERE / ".sync" / "claims"
29OUT = REPO / "docs" / "Formulation" / "verification_campaign" / "tables"
30DOSE = "20"
31
32
33def board():
34 out = {}
35 for f in sorted(SYNC.glob("*.json")):
36 try:
37 j = json.loads(f.read_text(encoding="utf-8"))
38 out[j.get("run_id", f.stem)] = j
39 except Exception:
40 pass
41 return out
42
43
44def cell(rec):
45 if not rec or rec.get("status") != "done":
46 return None
47 v = (rec.get("scored") or {}).get(DOSE)
48 return None if (not v or v.get("missing") or v.get("off_grid")) else v
49
50
51def why_blank(rec):
52 """WHY a cell is empty -- the three reasons are not the same thing.
53
54 A run that timed out short of the comparison dose is a cost result; a
55 configuration the bin layout cannot realise is a statement about the grid,
56 not about cost; and a cell never attempted is neither. Printing one symbol
57 for all three would let a reader read the M2D grid's expensive corner as if
58 the method had failed there.
59 """
60 if rec is None:
61 return r"\dnr"
62 if rec.get("status") == "failed":
63 return r"\nreal"
64 return r"\dnr"
65
66
67def w(name, lines):
68 OUT.mkdir(parents=True, exist_ok=True)
69 (OUT / name).write_text("\n".join(lines) + "\n", encoding="utf-8")
70 print(f" wrote docs/Formulation/verification_campaign/tables/{name}")
71
72
73def main():
74 B = board()
75 ref = cell(B.get(M.M_REF_RUN))
76
77 # ── M1: the cost cliff ──────────────────────────────────────────────────
78 L = [r"\begin{tabular}{lrrrr}", r"\toprule",
79 r"Run & $I=V$ & $N_{\rm eq}$ & Dose reached & Wall \\", r"\midrule"]
80 # M1_LADDER is local to manifest(); enumerate from the manifest itself so
81 # the table cannot drift from the runs that exist.
82 ent = {x["run_id"]: x for x in M.manifest()}
83 m1 = [x for x in M.manifest() if x["table"] in ("M1", "M1REF")]
84 for e in sorted(m1, key=lambda x: x["I"]):
85 rid = e["run_id"]
86 r = B.get(rid)
87 if not r:
88 continue
89 wall = r.get("wall_s")
90 name = rid.replace("_", chr(92) + "_")
91 if not wall:
92 # Claimed but never published -- the ladder was stopped by hand once
93 # 2000 had failed, because 4000 and up cannot succeed and each costs
94 # an hour to confirm it. Say that, rather than printing an empty
95 # row a reader would take for a missing measurement.
96 L.append(f"{name} & {e['I']} & --- & \\multicolumn{{2}}{{c}}"
97 f"{{\\itshape not run --- see text}} \\\\")
98 continue
99 reach = r.get("dose_reached", float("nan"))
100 bold = r"\bfseries " if reach >= 0.99 * e["dose"] else ""
101 L.append(f"{bold}{name} & {e['I']} "
102 f"& {r.get('N_eq', '---')} "
103 f"& {reach:.4g} / {e['dose']:g} "
104 f"& {wall/60:.1f} min \\\\")
105 L += [r"\bottomrule", r"\end{tabular}"]
106 w("M1_cost.tex", L)
107
108 # ── M2R: one-dimensional ladder, deviations ─────────────────────────────
109 L = [r"\begin{tabular}{lrrrrrrrr}", r"\toprule",
110 r"Rung & $I_{\rm bin}$ & $r$ & $N_{\rm eq}$ & $N_{111}$ & $d_{111}$ "
111 r"& $N_{100}$ & $d_{100}$ & $d_{\rm cav}$ \\",
112 r"\midrule"]
113 if ref:
114 for nb in M.M2R_BINS:
115 rec = B.get(f"M2R_B{nb}")
116 v = cell(rec)
117 if v is None:
118 L.append(f"B{nb} & {nb} & --- & --- & "
119 + " & ".join([r"\dnr"] * 5) + r" \\")
120 continue
121 d = [(v[k] - ref[k]) / ref[k] * 100 for k in
122 ("N_loops_111", "d_111_nm", "N_loops_100", "d_100_nm",
123 "d_cavity_nm")]
124 L.append(f"B{nb} & {rec.get('bin_I_bin', nb)} "
125 f"& {rec.get('bin_r', float('nan')):.2f} "
126 f"& {rec.get('N_eq', '---')} & "
127 + " & ".join(f"{x:+.1f}" for x in d) + r" \\")
128 L += [r"\bottomrule", r"\end{tabular}"]
129 w("M2R_dev.tex", L)
130
131 # ── M2D: the 2-D sweep, d_111 deviation ─────────────────────────────────
132 for key, fname in (("d_111_nm", "M2D_d111.tex"),
133 ("d_cavity_nm", "M2D_dcav.tex")):
134 L = [r"\begin{tabular}{l" + "r" * len(M.M2D_BINS) + "}", r"\toprule",
135 r"$i_{\rm discrete}$ & "
136 + " & ".join(f"$I_{{\\rm bin}}={nb}$" for nb in M.M2D_BINS)
137 + r" \\", r"\midrule"]
138 for i_d in M.M2D_IDISC:
139 row = [f"{i_d}"]
140 for nb in M.M2D_BINS:
141 v = cell(B.get(f"M2D_I{i_d}_B{nb}"))
142 if v is None or not ref or not ref[key]:
143 row.append(why_blank(B.get(f"M2D_I{i_d}_B{nb}")))
144 else:
145 row.append(f"{(v[key]-ref[key])/ref[key]*100:+.1f}")
146 L.append(" & ".join(row) + r" \\")
147 L += [r"\bottomrule", r"\end{tabular}"]
148 w(fname, L)
149 return 0
150
151
152if __name__ == "__main__":
153 sys.exit(main())
w(name, lines)