campaign.py — run the verification study, one claimed run at a time.
# take runs until none are left, syncing after each
python campaign.py --loop
# a single named run
python campaign.py --run T4_C4
# what is the board doing
python campaign.py --status
Each run produces the SAME artifact set as the tracked reference run --
`output/<stamp>_<tag>_<mode>_<physics>_I..V.._im..vm../` with provenance.md,
summary.csv, diagnostics.txt, results_*.npy and plots/ -- because it goes
through `RadClusterSimulation._save_output`, the same code path the reference
came from. The `<tag>` is the run_id (T1_B3, T4_D, ...); without it the four
Table-1 rungs differ only in `i_discrete` and would produce four directory
names distinguishable only by timestamp.
LIVE PROGRESS. The C++ solver emits a `[diag]` line per output step; the
progress callback turns that into two local files that update DURING a run:
verification/logs/<machine>.jsonl append-only event log
verification/logs/<machine>.status a rendered snapshot, rewritten per step
so `cat`/`tail -f` answers "where is this run" without waiting for it to end.
The six observables are NOT available mid-run -- they need the full state, and
the callback carries only the solver's own diagnostic scalars -- so the live
view reports position (dose reached of target, elapsed, step) and the final
record carries the physics. Saying otherwise would be inventing numbers.
MULTI-MACHINE. Runs are claimed through `gitsync.CampaignSync`: the git remote
is the lock, one file per run, push races resolved by retry. After every run
the result is committed and pushed, so any machine (or the user, anywhere) sees
it immediately. Local output directories stay local -- `output/` is gitignored
apart from the pinned reference -- so what travels is the compact record:
provenance.md, summary.csv, diagnostics.txt and the observables JSON.
| dict campaign._solver_method |
( |
dict |
e | ) |
|
|
protected |
The linear-solver block, with an OPTIONAL preconditioner bandwidth.
cpp_bridge derives prec_bw as max(2*i_mobile, 2*v_mobile) + 1, which is the
right structural width when the mobile species set the Jacobian band. At
i_mobile = v_mobile = 1 that gives 3, and measured 2026-09-09 on the
monomer-mobility campaign (I = V = 1000, discrete, to 0.05 dpa):
prec_bw 3 (auto) 100 s
prec_bw 11 65 s
prec_bw 51 139 s
-- so the auto value is too narrow there and 51 is past the point where the
band factorisation costs more than it saves. Carried as a per-entry key
rather than a change to the default, because the default is correct for
every run in Tables 1-5 and this is a 1.5x effect, not a defect.
Definition at line 294 of file campaign.py.