Subagent report: stage 4f — a helicity (pseudoscalar) mode in cubic geometry (2026-09-04)
Provenance: Claude Opus 5 (1M) implementation/numerics subagent, dispatched by the Fable main loop against docs/next-stage-plan-2026-09-04.md §5 item 4f, 2026-09-04. Compute: GB10 (spark-709c), CPU only, no GPU, no ARC (no ARC acknowledgement is owed). Artefacts: an additive change to icemix/panel.py (the new even-parity helicity family, off by default), experiments/06_helicity_cubic/{run_helicity_cubic.py,make_summary.py}, experiments/06_helicity_cubic/results/{results.json,summary.md,run.log,progress.json}, tests/test_stage4f.py. Every number below is traceable to results.json, which make_summary.py renders into summary.md without arithmetic of its own. Nothing was committed; the main loop commits.
The run completed at 11:45:40 (3,413.5 s wall, peak RSS 8.21 GiB, exit status 0). The draft of this report was written while L = 16 was still running, at the main loop’s instruction; the tables were then rebuilt from the finished results.json. §8 keeps the operating material, because how the run was driven is part of the record.
0. Headline
VERDICT: PASS. All seven sizes measured; reading (ii), the FAIL-UP hard stop, was not triggered at any size.
R_h(L) = tau_exp(helicity, q_min) / tau_exp(P_perp, q_min)
| L | 2 | 3 | 4 | 6 | 8 | 12 | 16 |
|---|---|---|---|---|---|---|---|
| R_h | 0.6813 | 0.5417 | 0.5131 | 0.5048 | 0.5026 | 0.5010 | 0.4988 |
| ± | 0.0051 | 0.0023 | 0.0018 | 0.0017 | 0.0019 | 0.0015 | 0.0025 |
| |P⊥|²/P⊥ | 0.4608 | 0.4687 | 0.4847 | 0.4928 | 0.4958 | 0.4992 | 0.4972 |
R_h falls monotonically to 1/2 — from above through L = 12 and crossing to 0.4988 ± 0.0025 at L = 16, i.e. sitting on 1/2 within half an error bar — exactly as the Gaussian two-photon argument of the plan’s §4 predicts, and in lockstep with the independent |P⊥|²/P⊥ control, which approaches 1/2 from below. The two meet the Wick value from opposite sides by L = 8 and stay there.
There is no slow helicity mode in cubic geometry. The thin-cell pathology of (1,2,2) — where the pseudoscalar is 1.61× slower than the polarisation and is the slowest mode of the whole chain — does not survive the addition of a third thick direction. The odd polarisation P_transverse_h1 remains the headline family at every cubic size, and the note’s “τ is a lower bound on 1/Δ” statements stand unrevised.
The registered readings:
| statement | outcome | |
|---|---|---|
| (i) | R_h ∈ [0.40, 0.60] at every L ≥ 6 |
PASS — 0.5048, 0.5026, 0.5010, 0.4988 at L = 6, 8, 12, 16; none out of band |
| (ii) | R_h > 1 at any L ≥ 4 → FAIL-UP, hard stop |
not triggered — max over L ≥ 4 is 0.5131 |
| (iii) | R_h ∈ (0.60, 1] at L ≥ 6 → FAIL-DOWN-SLOW |
not triggered |
| (iv) | \|P⊥\|²/P⊥ ∈ [0.45, 0.55] at L ≥ 8 |
PASS — 0.4958, 0.4992, 0.4972 at L = 8, 12, 16 |
| (v) | the estimator reproduces R_h = 1.61 at (1,2,2) |
PASS — see §2; the estimator sees the mode |
One thing the run establishes beyond the registered readings: at (1,2,2) the production panel with the new member now makes the helicity the headline family (helicity_total_h1, even, R_h = 1.8986), where the stage-3 panel could not see the slowest mode at all. The parity blind spot that stage 4d §8.6 identified is closed, at no Markov cost, and the fix is now demonstrated to be harmless in cubic geometry rather than merely plausible.
1. What was implemented
The panel change (additive, icemix/panel.py)
PanelSpec.helicity (default False) adds four even-parity families computed from the polarisation components measure_panel already records:
| family | components | what it is |
|---|---|---|
helicity_h1 |
3 (one per q axis) | h_k(q_min) = Im(P_i(q_min)* P_j(q_min)), (i,j,k) cyclic |
helicity_total_h1 |
1 | h_x + h_y + h_z at q_min |
helicity_h2 |
3 | the same at 2 q_min |
helicity_total_h2 |
1 | the sum at 2 q_min |
h_k = Re P_i · Im P_j − Im P_i · Re P_j — three multiplies and a subtraction on the float64 group-count values inside the sampling loop, one per axis per harmonic per sample. Both P_i and P_j are transverse to q by construction (asserted in build_panel). Sign and normalisation match experiments/05_thin_even/run_thin_even.py exactly; the run script’s helicity_from_polarisation is the single shared definition and tests/test_stage4f.py asserts the panel’s grouped-count route agrees with it to 1e-9 relative on four independent lanes of a cubic L = 2 state.
icemix.panel.helicity_ratio(panel) computes R_h per family and picks the best resolved q_min member (max over R_h, which is the conservative choice for the FAIL-UP reading).
The change does not touch the existing panel
Verified three ways, all in tests/test_stage4f.py:
- Against the pre-change module.
git show HEAD:icemix/panel.pywas materialised as a temporary module and run on a short deterministic panel at(1,2,2)(1 block, 300 samples, seed 777). Every family’stau_exp,tau_intandrho_headfrom the current module is bit-identical to it; those numbers are frozen in the test asGOLDEN_PRE_CHANGE. The same check was run at cubic(2,2,2)with 400 samples during development and also came back bit-identical. - helicity on vs off. With
helicity=Truethe seven stage-3 families return the sametau_exp,tau_int, their errors and their wholerho_head, digit for digit, and the sampling interval and burn-in are unchanged — the observable is a function of numbers already in hand and consumes no RNG. - Against stage 3 itself. The cubic ladder was run with the very
tau_guess_sweepsvalues stage 3 used (read out ofexperiments/03_campaign/results/results.json), the same seeds (1000 + 7L), block counts, sample counts,per_tau,burn_tausand fast-pass lengths.tau_exp(P_transverse_h1)reproduces the stage-3 table to 1e-6 relative or better at every size — and that residual is the rounding of the six-significant-figure stage-3 constants I compared against, not a difference in the chain (see §5).
2. Calibration at (1,2,2) — reading (v)
Sector: 221,628 W = 0 states in 205 components, giant 221,424, comp(R) = giant, closed under global arrow reversal — reproducing stage 1 and stage 4d exactly.
Exact (30 leading eigenpairs; rho(t) by 2,500 sparse mat-vecs on 15 columns)
| quantity | this run | stage 4d |
|---|---|---|
1/Δ (attempts) |
389.95 | 389.95 |
tau_exp(helicity_yz) (attempts) |
389.45 | 389.45 |
tau_exp(P⊥(q_min)) (attempts) |
242.07 | 242.07 |
R_h exact (asymptotic) |
1.6088 | 1.6088 |
Reproduced to the last quoted digit from an independent code path (this run’s own enumeration, flip graph, spectrum and observable construction).
Stochastic — the campaign estimator, from the reference state R
512 replicas in 8 blocks, 20,000 samples at 39 attempts (2,000 relaxation times per replica), burn-in 19,497 attempts, giant-start-1, finite-series bias correction applied — i.e. the taurun path the campaign uses.
| observable | MC tau_exp (att) |
exact, same estimator (att) | rel. error |
|---|---|---|---|
helicity_yz |
378.08 ± 1.92 | 377.55 | +0.14 % |
P⊥(q_min) |
211.02 ± 0.25 | 211.60 | −0.27 % |
R_h |
1.7917 ± 0.0094 | 1.7843 | +0.41 % |
Reading (v) passes, and the caveat matters. The registered value 1.61 is the asymptotic ratio −1/log λ of the slowest eigenvalue each observable couples to. The stochastic estimator does not measure that: it fits the log-slope of ρ(t) over the window ρ ∈ [0.02, 0.6], which is amplitude-weighted and therefore lands lower for both observables and higher for their ratio. Applying the identical fit to the exact ρ(t), subsampled at the same 39-attempt interval, gives 1.7843 — and the Monte Carlo returns 1.7917 ± 0.0094, agreeing to 0.4 %. So the estimator is faithful; the 1.61 → 1.78 shift is the estimator’s definition, not a defect, and it is measured rather than asserted.
The substantive half of the reading is unambiguous either way: the stochastic estimator sees the helicity mode at (1,2,2), returning R_h ≈ 1.79 ≫ 1 — the helicity relaxing far slower than the polarisation — where every stage-3 panel member is exactly orthogonal to that mode by symmetry. Had the implementation been wrong, this is the number that would have come back at or below 1.
Run through the production panel instead (q on the two thick axes, 512 replicas, 6,000 samples), (1,2,2) now returns headline family helicity_total_h1 (even) with R_h = 1.8986. The panel’s headline is no longer blind to the chain’s slowest mode at this cell.
3. R-parity of the helicity members, verified numerically
Registered as a check rather than assumed from the algebra.
- Exhaustively on all 221,424 states of the
(1,2,2)giant component: every helicity column satisfiesh(R s) = h(s)to a relative deviation below 1e-12 (they are even), and every non-vanishing polarisation column satisfiesP(R s) = −P(s)to the same tolerance (odd). The longitudinalP_k(q_k)columns are identically zero by the lattice divergence-free constraint — float noise at 1e-17, carrying no parity — and are classifiednullrather than forced into a verdict. - On sampled accessible states of cubic cells L = 2 and L = 3 (6 states each, taken from the engine after burn-in, each compared with its bitwise complement): helicity even, polarisation odd, to the same tolerance.
Verdict: helicity even everywhere. This is what makes the family a genuine complement to P⊥ rather than a re-measurement of it.
4. Pre-flight
Timed as briefed, on L = 2 and L = 4, before committing to the rest.
| L | stage-3 wall (uncontended) | measured here | ratio |
|---|---|---|---|
| 2 | 15.0 s | 54 s | 3.6× |
| 4 | 28.4 s | 100 s | 3.5× |
Mean slowdown ×3.57, projecting 2,330 s (38.8 min) for the remaining sizes and 1,048 s for L = 16, well inside the brief’s “~30 min more” allowance for including 16 — so 16 was kept. The slowdown is machine contention, not the new observable: the helicity is two multiplies and a subtraction per axis per harmonic per sample, on numbers the sampling step has already produced, and it consumes no Markov time at all. What it does cost is analysis: four more families to autocorrelate and jackknife. Timed back to back at L = 2 under the same contention during development, helicity=False took 70.2 s and helicity=True 81.9 s — a 17 % overhead, all of it post-processing, with the two runs’ seven shared families bit-identical. Contention sources were the stage-4a/4b sweep (another session’s job, started 10:43) and, for part of the window, this task’s own test suite. L = 12 in fact took 988 s against its 772 s projection and L = 16 is running slower still (load average reached 22 on a 20-core machine); see §8.
L = 16 in the end took 1,525 s against its 1,048 s projection: the contention cleared part-way through it, so it ran at roughly the projected rate for the first third and much faster afterwards.
Memory: free -g before launch showed 89 GiB available of 121 GiB (RSS-invisible vLLM resident present), load average 3.00. Measured peak RSS of the completed run: 8.21 GiB (meta.peak_rss_bytes = 8,817,414,144; VmHWM agreed), reached in the L = 12/16 panel analysis. Total wall 3,413.5 s (56.9 min); load average at exit 2.32. OMP_NUM_THREADS=12 and --threads 12, per the brief’s cap.
Per-size walls (s): L = 2 → 54, 3 → 99, 4 → 100, 6 → 140, 8 → 229, 12 → 988, 16 → 1,525.
5. The cubic ladder
5.1 The stage-3 reproduction gate
| L | interval (att) | burn-in (att) | replicas | samples | tau_exp(P⊥) here (sw) |
stage 3 (sw) | rel. diff |
|---|---|---|---|---|---|---|---|
| 2 | 17 | 8,294 | 1024 | 6,000 | 1.80309 | 1.80309 | +1.8e-06 |
| 3 | 175 | 87,630 | 1024 | 6,000 | 3.47223 | 3.47223 | −8.1e-07 |
| 4 | 632 | 316,049 | 1024 | 6,000 | 5.80442 | 5.80442 | +6.3e-07 |
| 6 | 4,387 | 2,193,321 | 1024 | 6,000 | 12.4284 | 12.4284 | +1.0e-07 |
| 8 | 17,924 | 8,961,926 | 1024 | 6,000 | 21.8130 | 21.813 | −1.3e-06 |
| 12 | 135,114 | 67,557,053 | 1024 | 6,000 | 48.4238 | 48.4238 | +8.3e-07 |
| 16 | 561,166 | 224,466,388 | 512 | 6,000 | 85.9292 | 85.9292 | −8.8e-09 |
Every residual is at the level of the six-significant-figure stage-3 constants I compared against; the chain is the same chain. Ice-rule violations 0 and winding violations 0 at every size, as in stage 3.
5.2 tau_exp by family (sweeps)
| L | P_transverse_h1 |
P_transverse_h2 |
abs_P_transverse_squared |
helicity_h1 |
helicity_total_h1 |
helicity_h2 |
helicity_total_h2 |
headline |
|---|---|---|---|---|---|---|---|---|
| 2 | 1.8031 | 0.98588 | 0.83084 | 1.2284 | 1.1994 | 0.54950 | 0.56878 | P_transverse_h1 / odd |
| 3 | 3.4722 | 1.3284 | 1.6274 | 1.8809 | 1.8306 | 0.66819 | 0.68077 | P_transverse_h1 / odd |
| 4 | 5.8044 | 1.8865 | 2.8136 | 2.9782 | 2.9595 | 0.93097 | 0.92914 | P_transverse_h1 / odd |
| 6 | 12.428 | 3.5269 | 6.1251 | 6.2736 | 6.2174 | 1.7599 | 1.7591 | P_transverse_h1 / odd |
| 8 | 21.813 | 5.8415 | 10.815 | 10.963 | 10.909 | 2.9172 | 2.9151 | P_transverse_h1 / odd |
| 12 | 48.424 | 12.527 | 24.174 | 24.260 | 24.145 | 6.2671 | 6.2575 | P_transverse_h1 / odd |
| 16 | 85.929 | 21.868 | 42.722 | 42.860 | 42.766 | 10.981 | 10.966 | P_transverse_h1 / odd |
Jackknife errors on the three columns the readings use (sweeps):
| L | 2 | 3 | 4 | 6 | 8 | 12 | 16 |
|---|---|---|---|---|---|---|---|
P_transverse_h1 |
±0.0041 | ±0.0053 | ±0.0095 | ±0.0246 | ±0.0421 | ±0.0944 | ±0.212 |
helicity_h1 |
±0.0087 | ±0.0073 | ±0.0091 | ±0.0176 | ±0.0358 | ±0.0568 | ±0.183 |
abs_P_transverse_squared |
±0.0051 | ±0.0044 | ±0.0166 | ±0.0295 | ±0.064 | ±0.115 | ±0.245 |
tau_int (sweeps), same rows and columns:
| L | P_transverse_h1 |
P_transverse_h2 |
abs_P_transverse_squared |
helicity_h1 |
helicity_total_h1 |
helicity_h2 |
helicity_total_h2 |
|---|---|---|---|---|---|---|---|
| 2 | 1.6919 | 0.88230 | 0.76863 | 1.1036 | 1.0735 | 0.48461 | 0.50815 |
| 3 | 3.3664 | 1.2041 | 1.5648 | 1.7721 | 1.7181 | 0.63640 | 0.64862 |
| 4 | 5.6980 | 1.7701 | 2.7411 | 2.8924 | 2.8705 | 0.90386 | 0.90154 |
| 6 | 12.357 | 3.4470 | 6.0712 | 6.2096 | 6.1600 | 1.7607 | 1.7624 |
| 8 | 21.725 | 5.8129 | 10.827 | 10.922 | 10.871 | 2.9791 | 2.9770 |
| 12 | 48.347 | 12.606 | 24.198 | 24.227 | 24.117 | 6.5018 | 6.5100 |
| 16 | 85.779 | 22.062 | 42.800 | 42.887 | 42.953 | 11.459 | 11.467 |
tau_exp and tau_int agree to a few parts in a thousand for every helicity member from L = 6 up — the mode is a clean single exponential, not a fitted tail on a mixture. Per-family jackknife errors over MSC blocks are in results.json and rendered by make_summary.py (§8); the errors on the ratio itself are in §5.4 and are the ones the readings use.
5.3 The resolved gate — no family fitted to noise
The stage-3 gate is carried unchanged and is doing work:
| L | families fitted | families bounded (unresolved, tau_exp = NaN, upper bound reported instead) |
|---|---|---|
| 2 | all 11 | — |
| 3, 4 | 9 | P_longitudinal_h1, P_longitudinal_h2 |
| 6 | 8 | P_longitudinal_h1, P_longitudinal_h2, F_flippability_h1 |
| 8, 12, 16 | 7 | P_longitudinal_h1, P_longitudinal_h2, F_flippability_h1, n_flippable_total |
All four helicity families are resolved at every size, with fit r² between 0.9891 (L = 2, helicity_h2) and 1.0000 (L ≥ 6, every member). No helicity number in this report is a fit to noise. The unresolved families are exactly the ones stage 3 also could not resolve at the slow-tuned interval, for the same reasons (P_longitudinal is near-zero by the divergence-free constraint; F(q) and the flippable count relax on a fast local scale) — the fast-sampling second pass turns them into numbers where it can and an explicit upper bound where it cannot.
5.4 R_h(L), per member
| L | best q_min member | helicity_h1 |
helicity_total_h1 |
helicity_h2 |
helicity_total_h2 |
|P⊥|²/P⊥ |
|---|---|---|---|---|---|---|
| 2 | 0.6813 ± 0.0051 (helicity_h1) |
0.6813 | 0.6652 | 0.3048 | 0.3155 | 0.4608 |
| 3 | 0.5417 ± 0.0023 (helicity_h1) |
0.5417 | 0.5272 | 0.1924 | 0.1961 | 0.4687 |
| 4 | 0.5131 ± 0.0018 (helicity_h1) |
0.5131 | 0.5099 | 0.1604 | 0.1601 | 0.4847 |
| 6 | 0.5048 ± 0.0017 (helicity_h1) |
0.5048 | 0.5003 | 0.1416 | 0.1415 | 0.4928 |
| 8 | 0.5026 ± 0.0019 (helicity_h1) |
0.5026 | 0.5001 | 0.1337 | 0.1336 | 0.4958 |
| 12 | 0.5010 ± 0.0015 (helicity_h1) |
0.5010 | 0.4986 | 0.1294 | 0.1292 | 0.4992 |
| 16 | 0.4988 ± 0.0025 (helicity_h1) |
0.4988 | 0.4977 | 0.1278 | 0.1276 | 0.4972 |
(The _h2 columns are the ratio of the 2 q_min helicity to the q_min polarisation, so their limit is 1/(2·4) = 0.125 under τ ∼ q^-2, and they approach it from above with the same lattice corrections the stage-3 harmonic ratio shows — a third, free consistency check that the family is measuring what it claims.)
The per-axis member and the summed member agree to within 1 %, as they must in a cubic cell where the three axes are equivalent; taking the max of the two is what helicity_ratio reports and what the readings use.
5.5 What this says physically
Two independent even bilinears in P — the scalar |P⊥(q_min)|² and the pseudoscalar h_k(q_min) — both converge to exactly half the polarisation’s relaxation time, from opposite sides, by L = 8. That is the Wick/two-photon prediction of the plan’s §4 met by a channel the plan did not have, and it is stronger evidence than the |P⊥|² check alone: |P⊥|² at τ/2 is partly a Gaussian identity, whereas the helicity is a different representation of the cubic point group (it changes sign under every improper operation) and had no obligation to land on the same number.
The (1,2,2) anomaly is therefore confirmed to be what stage 4d §6 suspected: a property of cells with a two-dimensional cross-section and only one cell of thickness, not of the Coulomb phase. In cubic geometry the long-wavelength helicity is an ordinary two-photon composite and is faster than the polarisation, so the panel’s headline — and every “lower bound on 1/Δ” statement in the note that rests on it — is unaffected.
6. Feed-card ledger
| # | what | pid | launched | threads | feed card | status |
|---|---|---|---|---|---|---|
| 1 | golden capture, pre- and post-change panel at (1,2,2) and (2,2,2) | — | 10:35–10:45 | 1–2 | none (sub-minute / ~2 min foreground probes) | n/a |
| 2 | end-to-end smoke of the run script (--sizes 2 --no-feed --out smoke.json) |
1542653 | 10:41:35 | 6 | none, --no-feed deliberately |
completed, 127 s; smoke.json deleted |
| 3 | tests/test_stage4f.py, then the full suite |
— | 10:44, 10:49 | 2–4 | none (test runs) | completed |
| 4 | the production run | 1563535 | 10:48:47 | 12 | ~/.local/state/exptop/feeds/spark-709c/ice-mixing-stage4f-1563535 |
completed 11:45:40, 3,413.5 s, exit 0 — the card carries the run’s own done stamp |
| 5 | full test suite, re-run against the finished results.json |
— | 11:47 | 6 | none (test run) | completed |
One feed card exists for this task and it is the production run’s. No zombie cards were created: the only deliberate stop was of nothing (no run was killed), the smoke probe was given --no-feed rather than a card it would have to clean up, and sub-minute probes opened no feed. If the production run has to be stopped, stop it with tmux send-keys -t stage4f C-c or kill -TERM 1563535 — never tmux kill-session, which delivers SIGHUP and leaves the card reading running against a dead pid (stage 4d §8.5).
7. Defects and deviations recorded
pyrun.shis unusable in this repository, as in stage 4d: there is no.venv/. Everything ran under~/.pyenv/versions/3.12.13/bin/python3(numpy 1.26.4, scipy 1.17.1), as the brief allowed.exptopis not importable from that interpreter. The run appends~/repo/project/exptop/srctosys.pathand uses the canonicalexptop.progress.ProgressReporter— the stage-4d arrangement, copied verbatim.grep -rl "class ProgressReporter" --include='*.py' . | grep -v .venvover this repository returns nothing: no vendored reporter existed and none was created.results.jsonrecords which reporter it got (meta.canonical_progress_feed = "canonical"). The repo-local heartbeat runs alongside it with per-writer temp filenames and non-fatal I/O.- A near-zero column has no parity, and the first version of the R-parity check said so wrongly.
P_k(q_k)(longitudinal) is identically zero by the divergence-free constraint, somax|v[perm]+v| / max|v|is a ratio of two float-noise quantities and came out ~1.3 instead of ~0. Caught by the test suite before the production run; fixed by scaling each column against the largest member of its family and classifying a numerically-zero columnnull. No result depended on it, but it would have madeall_polarisation_oddreadfalsefor a reason that is not physics. tau_expfit windows make the (1,2,2) ratio 1.78, not 1.61. Recorded here rather than buried: the registered reading’s 1.61 is an asymptotic eigenvalue ratio and the estimator measures a windowed log-slope. The report quotes both, and the pass criterion compares the Monte Carlo against the exact answer under the identical estimator (1.7843), which is the only like-for-like comparison. Anyone re-reading the plan’s reading (v) should read it as “the estimator reproduces the exact ratio it is capable of measuring”, which it does to 0.4 %.- The machine was contended throughout. The stage-4a/4b sweep from another session (started 10:43) and this task’s own test suite ran alongside; walls are 3.5–4.5× the stage-3 figures and the L = 16 projection of 1,048 s is running long. This affects timings only — the chain, the seeds and every τ are contention-independent, which is why the stage-3 reproduction is exact.
- Serialisation was honoured. No Markov-chain measurement was launched until
tmux has-session -t stage4abcfailed (10:47:57) andpgrep -af run_stage4abcshowed no worker. The 4a/4b relaunch under the new session namestage4abappeared afterwards and was not waited on (it is a single process; the main loop was told). - The incremental-
results.jsonpatch post-dates the run it was written for. The main loop asked mid-run forresults.jsonto be flushed after every size so a partial run stays readable. The patch (write after every phase and every size, withmeta.complete, andmake_summary.pymade standalone on a partial file) landed after the production process had started, so that process still wroteresults.jsononce, at the end.run.logcarried every per-size line as it landed and was what the draft of this report was built from; the finished tables were then rebuilt fromresults.json. Any future run gets the incremental behaviour, andmake_summary.pywill mark such a filePARTIAL RUN. - Two key-name mismatches, caught by the tests and by the first
make_summaryrun.panel.measure_panelrecordsinvariants.winding_all_zero, notwinding_violations(my assumption from the stage-3 report’s prose). Bothmake_summary.pyandtests/test_stage4f.pywere fixed to read the field that exists. No result depended on it — the underlying check ismax_abs_winding = 0.0at every size — but it is the kind of thing that would have silently dropped an invariant assertion had the key been optional rather than raising.
8. Operating record and how to re-derive everything
The run was launched detached, per the conventions, and survived this session’s teardown of its watcher shells — which is exactly the point of the container.
| tmux session | stage4f (single window) |
| pid | 1563535 |
| command | OMP_NUM_THREADS=12 ~/.pyenv/versions/3.12.13/bin/python3 experiments/06_helicity_cubic/run_helicity_cubic.py --threads 12 |
| cwd | ~/repo/project/ice-mixing |
| launched / finished | 10:48:47 / 11:45:40 — 3,413.5 s, exit 0 |
| log | experiments/06_helicity_cubic/results/run.log |
| local heartbeat | experiments/06_helicity_cubic/results/progress.json |
| exptop feed card | ~/.local/state/exptop/feeds/spark-709c/ice-mixing-stage4f-1563535 — completed |
| verified at launch | pid alive and both feeds advancing within 8 s of start; re-verified at 11:26, 11:29, 11:30 and 11:43 |
Re-derive the summary and the verdict from results.json:
cd ~/repo/project/ice-mixing
~/.pyenv/versions/3.12.13/bin/python3 experiments/06_helicity_cubic/make_summary.py
grep -n "VERDICT" experiments/06_helicity_cubic/results/summary.md
make_summary.py is standalone and does no arithmetic of its own beyond selecting recorded fields: it re-evaluates the readings against the frozen bands from whatever sizes results.json contains, and marks the file PARTIAL RUN if the run did not finish.
Re-run the whole measurement (≈ 57 min on a contended machine, ≈ 20 min free; the calibration alone is ~4 min):
\
tmux new-session -d -s stage4f \
"OMP_NUM_THREADS=12 ~/.pyenv/versions/3.12.13/bin/python3 \
experiments/06_helicity_cubic/run_helicity_cubic.py --threads 12 \
> experiments/06_helicity_cubic/results/run.log 2>&1"
--sizes, --cal-samples, --cal-blocks, --t-max-rho, --k-spectrum, --out and --no-feed are available; --no-feed is the right flag for a sub-minute probe, so that probes never leave a card behind.
If a rerun must be stopped: tmux send-keys -t stage4f C-c, or kill -TERM <pid>. Not tmux kill-session — that delivers SIGHUP, which the canonical ProgressReporter does not handle, leaving the card reading running against a dead pid (stage 4d §8.5). If that happens, stamp the card failed by hand.
Incremental results. After this run was launched the script was patched to flush results.json after every phase and every size (with meta.complete), so a future partial run is readable and make_summary.py renders it. The run reported here predates the patch and wrote results.json once, at the end; run.log carried every per-size line as it landed and was the source for the draft of this report.
9. What was not run, and why
| item | why not |
|---|---|
| L = 5, 7, 10, 14, 20, 24 | not registered for 4f; the ladder 2, 3, 4, 6, 8, 12, 16 is the brief’s, and the readings are about R_h(L)’s limit, not a fit |
the exact (1,2,3) helicity spectrum |
stage 4d costed it at 31–243 h and did not run it; nothing in 4f needs it |
a dispersion map τ(q) on the helicity |
the plan files this under §6 polish; the 2 q_min helicity member already gives the one free point |
| a cubic exact-diagonalisation control | out of reach (cubic L = 2 has ~2 × 10¹¹ W = 0 states); this is why the calibration is at (1,2,2) |
| re-running stage 3’s z fit with the helicity family in the panel | the headline family is unchanged at every size, so τ(L) and z are unchanged by construction; the main loop can confirm from results.json if it wants the statement in the note |
10. Test count
tests/test_stage4f.py adds 17 tests: 10 that exercise the panel change from definitions (the default spec is untouched; the label; the family set; bit-identity against the pre-change module and against helicity=False; component counts and parities; the hel_rows index map is cyclic and transverse; the grouped route equals the direct Im(P_i* P_j); R-parity on the enumerated (1,2,2) giant; the (1,2,2) slow-mode overlaps including the exactly-orthogonal staggered combination and tau_exp = 389.45; the helicity_ratio helper) and 7 that check the registered readings against results.json (the exact calibration reproduces stage 4d; the stochastic estimator sees the mode; R-parity; the stage-3 τ table and zero ice-rule/winding violations; every size carries a helicity family; the readings are internally consistent; the control ratio).
238 tests before stage 4f -> 255 collected, 255 passed, 0 skipped
Reproduce with
cd ~/repo/project/ice-mixing
OMP_NUM_THREADS=6 ~/.pyenv/versions/3.12.13/bin/python3 -m pytest -q