A report archived by the project, published as it was written. All reports · Markdown source

Subagent report: stage 4, items 4a / 4b / 4c (2026-09-04)

Provenance: Claude Code implementation/numerics subagent, model Opus 5 (1M context), claude-opus-5[1m], run 2026-09-04 on the GB10 (spark-709c), dispatched by the Fable main loop against the registration in docs/next-stage-plan-2026-09-04.md §5 (items 4a, 4b, 4c only; §2 and §3 read as context). The readings registered there are FROZEN: this report states each one PASS or FAIL as registered and does not reinterpret it. Item 4d was worked in parallel by a second subagent in experiments/05_thin_even/ and is not touched here. Git untouched by this subagent — the main loop commits.

Artefacts, all under ~/repo/project/ice-mixing/:

path contents
icemix/logconcave.py exact log-concavity, support-lattice and symmetry tests on an integer histogram on Z^d (NEW, additive)
icemix/signgauge.py exact bond-indicator pair statistics by two routes; the balanced-signed-graph test with three independent decision procedures (NEW, additive)
icemix/component.py bounded breadth-first closure of one hexagon-flip component, at arbitrary spin count (NEW, additive)
experiments/04_lorentzian_giant/run_stage4abc.py the run
experiments/04_lorentzian_giant/make_summary.py merges the part files into results/results.json, renders results/summary.md
experiments/04_lorentzian_giant/results/ part_4a.json, part_4b.json, part_4c.json, results.json, summary.md
tests/test_stage4abc.py 30 new tests

No existing icemix function was modified; the three new modules are purely additive. Every number below is in experiments/04_lorentzian_giant/results/results.json.


0. Bottom line

item reading verdict
4a (i) N(W) invariant under W -> -W and under the signed axis permutations the cell shape admits PASS at all six cells
4a (ii) axis log-concavity, and no internal zeros along any axis line PASS at all six cells
4a (iii) diagonal log-concavity along e_j +- e_k and e_x +- e_y +- e_z PASS at all six cells
4a (iv) the (111) marginal s = Wx+Wy+Wz log-concave with interval support PASS at all six cells
4b (i) at least one strictly positive pair covariance in the plain gauge HOLDS at every cell and every measure (prediction confirmed)
4b (ii) a diagonal +-1 gauge makes every pair covariance <= 0 FAIL at every cell and every measure
4b (iii) strong-Rayleigh necessary condition under conditioning NOT RUN — the registered trigger (a PASS at (ii)) never fired
4b NN two bonds sharing a site are negatively correlated PASS in measures A and B everywhere; FAILS in measure C at (1,1,2) — see §3.4
4c (i) zero non-frozen starts that are not certified connected to R PASS — 0 uncertified out of 27,000 starts (26,987 non-frozen)
4c (ii) a closed bounded BFS on an uncertified non-frozen start (a counterexample) none arose: no start was uncertified
4c (1,2,2) exhaustive census reproduces stage 1 exactly PASS

Headline: 4b (ii) FAILS, and it fails at the 16-spin cell with a three-bond certificate. Per the registration (docs/next-stage-plan-2026-09-04.md §5, 4b), “a FAIL at (ii) closes the Lorentzian lead”; per §7.4, item 3 (the matching lower bound Δ ≳ L⁻²) opens only if 4a and 4b both pass, so it stays closed. 4a passed cleanly and without qualification — see §2.5 for the one methodological trap that had to be disarmed before that PASS meant anything.

No counterexample to (NF⇒G) was found; the conjecture survives 27,000 fresh and adversarial starts at cubic L = 2 and L = 3, all of them either frozen (13) or certified into the giant (26,987).


1. Pre-flight (measured, then multiplied out)

Every computation below was timed on one unit before launch. Rates are on the GB10 (spark-709c, 20 cores); no GPU is used anywhere in 4a/4b/4c.

computation unit timed unit cost multiplied out decision
4a frontier DP with track_axes=(0,1,2) one cell 0.004 s at (1,1,1) rising to 10.9 s at (1,2,3) 6 cells ≈ 18 s run
4a materialised cross-check one cell 2,891,562 states in 44.9 s at (1,2,2) = 64.4k states/s; 9,765,378 in 94 s at (1,1,4) = 104k/s 5 cells ≈ 175 s run
4a (2,2,2) joint-winding DP one DP step 215,576 key-updates/s; the DP reached 8,661,950 keys at step 41 of 128 in 259 s and 4.22 GB growth 1.2114 keys/step in the tail ⇒ projected peak 7.14×10⁸ keys ≈ 347 GB, ≈ 3.7 h NOT RUN (memory ≫ the 121 GB machine; wall > 3 h)
4b pair statistics + signing, one cell (1,2,2) measure A enumeration 45 s + pair statistics 8.8 s + signing < 0.1 s 4 cells × 3 measures ≈ 94 s run
4b (iii) conditional check one (pair, conditioning) triple 98,280 triples in 0.1 s at (1,1,1) 1.94×10⁶ triples ≈ 20 s at (1,1,2) trigger did not fire
4c (1,2,2) exhaustive census whole 29 s 29 s run
4c cubic L=2 start one start sample_w0_dfs 0.003 s + certify_connected 0.029 s 17,000 starts = 545 core-s ÷ workers ≈ 91–314 s run
4c cubic L=3 start one start sample_w0_dfs 0.87 s + certify_connected 0.031 s 17,000 starts ≈ 9,350 core-s ÷ 6 ≈ 26 min — this projection was wrong by 3.8×, see §4.3 run, then resized
4c (1,2,3) exhaustive census DFS leaf rate ≈1.0×10⁵ states/s ≈3.1×10⁹-node tree ≈ 8.5 h, ≥16 GB for the state list, ≈9.4×10⁹ flip-graph edges NOT RUN — and blocked outright, see §4.5

Total projected ≈ 41 min, over the 10 min threshold, so the job was run detached in a named tmux session with a canonical exptop.progress.ProgressReporter feed (ice-mixing stage 4abc). Launch was verified each time: PID alive, feed advancing (heartbeat_at moving, phase/step/message updating). The actual course of the run — a code defect, a pre-flight miss, and the main loop’s contention notice — is in §5, with every launch, its PID, its feed card and its final status.

Grep for a vendored progress reporter, as required: grep -rl "class ProgressReporter" --include='*.py' . | grep -v .venv returns nothing — this repo has no fork. It does, however, have a home-grown feed; see defect D1.

Memory was checked before every launch. free -g reported 88–93 GB available of 121 GB throughout (an RSS-invisible resident, a spin-ice job from another session, and the parallel 4d subagent share the machine). Recorded peak RSS per run is in §5 and in results.json (items.<item>.resource); the largest was 4.216 GB, the (2,2,2) DP probe. On the main loop’s contention notice the 4c worker count was cut from 6 to 4 and OMP_NUM_THREADS/OPENBLAS_NUM_THREADS /MKL_NUM_THREADS were pinned to 4; 4a, 4b and 4c were run strictly one at a time thereafter.


2. Item 4a — sector log-concavity

2.1 What was computed, and the two-method gate

The exact joint winding histogram N(Wx, Wy, Wz) from icemix.enumerate.frontier_count(lat, track_axes=(0,1,2)) — a frontier DP that never materialises a state — at every registered cell up to the largest feasible one. At every cell where the state fits a 64-bit word and the count is inside the enumeration budget, the whole histogram (not just the total) was recomputed from the materialised DFS enumeration (enumerate_ice_states + winding) and compared entry by entry.

cell spins ice states (DP) DP peak keys DP s enum s sectors N(W=0) two-method histogram
(1,1,1) 16 90 72 0.003 0.001 25 12 exact agreement
(1,1,2) 32 3,618 368 0.020 0.036 69 388 exact agreement
(1,1,3) 48 181,122 904 0.065 2.284 137 15,024 exact agreement
(1,2,2) 64 2,891,562 42,160 3.356 65.515 217 221,628 exact agreement
(1,1,4) 64 9,765,378 1,680 0.096 59.343 229 637,988 exact agreement
(1,2,3) 96 3,076,306,362 123,432 9.017 — 453 196,465,480 n/a (see below)
(2,2,2) 128 — — — — — — not feasible (§2.6)

All totals and all W = 0 counts reproduce stage 1’s table exactly. At (1,2,3) the cross-check is unavailable for a hard reason, not a budget one: enumerate_ice_states packs a state into a single 64-bit word and raises for n_bonds = 96 (defect D3).

2.2 The trap that had to be disarmed first: the divisor artefact

The support of N is not all of Z³. Computed from the histogram (not assumed): the support is a coset of a sublattice L ⊂ Z³ of index 8 at every cell, with basis in row-echelon form

cell support-lattice basis index
(1,1,1) (2,2,0), (0,2,2), (0,0,2) 8
(1,1,2), (1,1,3), (1,1,4) (2,2,0), (0,2,0), (0,0,2) 8
(1,2,2), (1,2,3) (2,0,0), (0,2,0), (0,0,2) 8

So W ± e_j is simply not a lattice point: the literal reading “N(W)² ≥ N(W−e_j)N(W+e_j)” is satisfied vacuously (N = 0 on both sides) and every axis line would appear riddled with “internal zeros”. Reporting a PASS on that basis would be worthless and reporting a FAIL on the internal zeros would be a fabricated result. icemix.logconcave therefore computes the support lattice by integer row reduction and, for each direction v, the least positive t with t·v ∈ L (the primitive lattice vector in that direction); every line test below is on those points. The step is 2 for every direction at every cell.

This is the one place where the registered wording and the lattice disagree, and the resolution is recorded here rather than silently applied: the substantive reading is on the primitive lattice vector, and that is what “PASS” below means. The unrescaled reading is separately true and vacuous.

tests/test_stage4abc.py carries two negative controls (test_a_diagonal_violation_would_be_caught, test_an_internal_zero_would_be_caught) so the PASS is not a test that cannot fire.

2.3 The readings

Discriminant = min over lines and interior points of N(W)² − N(W−e)N(W+e), in exact Python integers, e the primitive lattice vector of that direction.

cell symmetry (invariant / candidate) W→−W axis LC axis disc face-diag LC face disc body-diag LC body disc interval support (111) marginal LC
(1,1,1) 48 / 48 yes PASS 52 PASS 3 PASS (no interior) PASS PASS
(1,1,2) 16 / 16 yes PASS 160 PASS 10 PASS 138 PASS PASS
(1,1,3) 16 / 16 yes PASS 324 PASS 21 PASS 56,184 PASS PASS
(1,1,4) 16 / 16 yes PASS 544 PASS 36 PASS 15,251,160 PASS PASS
(1,2,2) 16 / 16 yes PASS 592 PASS 10 PASS 523,840 PASS PASS
(1,2,3) 8 / 8 yes PASS 1,272 PASS 5,112 PASS 2,790 PASS PASS

(i) SYMMETRY gate — PASS. The candidate group at each cell is the set of signed axis permutations that preserve the cell’s extents: all 48 at the cubic (1,1,1), 16 at (1,1,2)/(1,1,3)/(1,1,4) (swap of the two equal axes × 8 sign patterns), 16 at (1,2,2), 8 at (1,2,3) (sign changes only). Which of them N actually obeys was measured, not assumed, and at every cell the answer is all of them. Inversion W → −W holds separately at every cell.

(ii) AXIS log-concavity — PASS. Every line, every cell; minimum discriminant strictly positive everywhere (52 at the smallest cell). No internal zeros: the support along every axis line is an interval.

(iii) DIAGONAL log-concavity — PASS. All six face diagonals e_j ± e_k and all four body diagonals e_x ± e_y ± e_z, every cell. The tightest case in the whole item is the face diagonals at (1,1,1) (discriminant 3), (1,1,2) (10) and (1,2,2) (10) — positive, but not by much. At (1,1,1) every body-diagonal line has at most two support points, so there is no interior term to test; that is recorded as “no interior”, not as a PASS with content.

(iv) The (111) marginal — PASS. s = Wx + Wy + Wz is log-concave with interval support at every cell, on a support of step 2. This is the direction the spin-ice repo’s Theorem D already covers (the layer stack there is this lattice), so it is the check of Theorem D’s prediction on this lattice, and the prediction holds. The distribution at (1,1,1) is exactly flat over its three interior values (9, 24, 24, 24, 9), so the discriminant there is 0 — log-concave with equality, not a violation.

2.4 Marginals of each Wj

All are log-concave with interval support and step 2. Exact counts:

cell Wx Wy Wz
(1,1,1) 1, 16, 56, 16, 1 same same
(1,1,2) 1, 24, 208, 856, 1440, 856, 208, 24, 1 same 1, 256, 3104, 256, 1
(1,1,3) 1, 36, 522, 3928, 16743, 41220, 56222, 41220, 16743, 3928, 522, 36, 1 same 1, 4096, 172928, 4096, 1
(1,1,4) 1, 48, 984, 11280, 79648, 361104, 1069400, 2068528, 2583392, … (symmetric) same 1, 65536, 9634304, 65536, 1
(1,2,2) 1, 32, 448, 3776, 22384, 97728, 302640, 620896, 795752, … (symmetric) 1, 288, 26440, 567048, 1704008, 567048, 26440, 288, 1 same as Wy
(1,2,3) 1, 48, 1044, 13800, 125706, 849912, 4479400, 18924864, 64433079, … (symmetric) 1, 420, 60762, 3769000, 92647455, 716696340, 1449958406, … (symmetric) 1, 4224, 4172416, 424729552, 2218493976, 424729552, 4172416, 4224, 1

The full lists, and the (111) marginals, are in results.json (items.4a.cells.<cell>.analysis.marginals).

2.5 What a 4a PASS does and does not mean

It means the joint winding-sector counts on the fully periodic torus are log-concave along every lattice line tested, in exact integer arithmetic, at every cell reachable by the DP — including (1,2,3), a 96-spin cell with 3.08×10⁹ ice states that no enumeration can touch. That is the falsifier the plan registered, and it did not fire.

It does not mean the generating polynomial is Lorentzian, and 4b now says it is not even pairwise-negatively-correlatable in any diagonal gauge. The two readings are independent: 4a is about the coefficient sequence of the flux grading, 4b about the bond variables themselves.

2.6 (2,2,2): not run, with the projection

The cubic L = 2 cell is the one that would test cubic geometry, and it is out of reach for the joint-winding DP. The bounded probe (registered budget 8×10⁶ keys) is in results.json under items.4a.dp_probe_222, with the full per-step curve:

347 GB does not fit a 121 GB machine and 3.7 h exceeds the 3 h rule, so it was not attempted. Stage 1 recorded the same cell as infeasible for the untracked DP at 10.5M keys; this is the quantified version for the tracked DP. A better bond schedule (minimising the frontier width rather than closing sites early) is the only obvious lever and was not explored.


3. Item 4b — gauge-signability of bond correlations

3.1 Method, and the two-method gate on every count

For each cell and each of the three measures, the exact integer statistics N, n_i = #{x_i = 1}, n_ij = #{x_i = x_j = 1} were computed by two independent routes and required to agree bit for bit:

They agree everywhere (pair_statistics raises otherwise). The covariance is the exact integer C_ij = N·n_ij − n_i·n_j, which is N²·Cov(x_i, x_j).

The three measures: A uniform on the full ice manifold, B uniform on W = 0, C uniform on the giant component of W = 0. Measure C is skipped at (1,1,1), where the W = 0 sector is wholly frozen (12 states, 12 singleton components) and there is no giant.

One exact structural check falls out and is confirmed at every cell and measure: complementing every arrow maps the ice manifold to itself and sends W → −W, so it fixes W = 0 and (stage 1) permutes components with the giant closed under it. Hence n_i = N/2 for every bond — verified in every row below.

3.2 The counts

cell measure N n_i = N/2 pairs C>0 C<0 C=0 reading (i): a positive pair reading (ii): signable
(1,1,1) A 90 yes 120 72 48 0 yes FAIL
(1,1,1) B 12 yes 120 48 72 0 yes FAIL
(1,1,1) C — — — — — — — skipped (sector wholly frozen)
(1,1,2) A 3,618 yes 496 272 224 0 yes FAIL
(1,1,2) B 388 yes 496 176 192 128 yes FAIL
(1,1,2) C 160 yes 496 224 256 16 yes FAIL
(1,1,3) A 181,122 yes 1,128 600 528 0 yes FAIL
(1,1,3) B 15,024 yes 1,128 360 384 384 yes FAIL
(1,1,3) C 3,360 yes 1,128 528 600 0 yes FAIL
(1,2,2) A 2,891,562 yes 2,016 992 1,024 0 yes FAIL
(1,2,2) B 221,628 yes 2,016 832 1,184 0 yes FAIL
(1,2,2) C 221,424 yes 2,016 832 1,184 0 yes FAIL

(i) PLAIN GAUGE — the registered prediction HOLDS. At least one strictly positive pair covariance in every measure at every cell, including (1,2,2), the only cell here with a genuine 2-D cross-section. In fact roughly half of all pairs are positive (49–60 % depending on cell and measure), so this is not a marginal effect.

(ii) SIGNABILITY — FAIL, everywhere. No diagonal signing s ∈ {±1}^bonds makes s_i s_j C_ij ≤ 0 for every pair, in any of the eleven cell/measure combinations tested.

3.3 Three independent decision procedures, and a hand-checkable certificate

The FAIL is not left resting on one implementation.

  1. Union–find with parity on the constraint graph (edge for every nonzero C_ij, requiring s_i s_j = −sign(C_ij)): unsatisfiable.
  2. An odd-cycle certificate from a BFS spanning forest — an independent traversal, not the union–find — extracted at every cell and measure. Every certificate found is a triangle.
  3. Exhaustive scan of all 2¹⁵ signings at (1,1,1) (both measures): none works. This agrees with (1) and (2); the run asserts the agreement.

The (1,1,1) measure-A certificate, in full, so a reader can check it by hand from N = 90 and n_i = 45:

pair geometry n_ij C = 90·n_ij − 45·45 constraint
(15, 0) share the B-site at (1,1,1) 15 1350 − 2025 = −675 s_15 = s_0
(0, 1) share the A-site at (0,0,0) 15 1350 − 2025 = −675 s_0 = s_1
(1, 15) share no site 25 2250 − 2025 = +225 s_1 ≠ s_15

The three constraints force s_15 = s_0 = s_1 and s_1 ≠ s_15. No sign gauge exists, and the obstruction lives on three bonds of a 16-spin cell. The two negative constraints are the local 2-of-4 hypergeometric negativity at the two shared sites; the positive one is a next-nearest bond pair. That is the whole content of the FAIL: the dipolar sign structure of the ice correlations produces an unbalanced triangle at the smallest possible scale, so nothing about growing the cell can rescue it.

Every certificate found, at every cell and measure, is a triangle — the obstruction is always three bonds, never a long cycle. In measure A they are [15, 0, 1] at (1,1,1), [31, 0, 1] at (1,1,2), [47, 0, 1] at (1,1,3) and [63, 0, 1] at (1,2,2); measures B and C give the same or a sibling triangle ([31, 0, 3], [47, 0, 3]). All eleven are in results.json under items.4b.cells.<cell>.measures.<M>.odd_cycle_certificate, each with the covariance sign and the required relation on every edge.

3.4 Nearest-neighbour sanity — and a correction to its premise

Two bonds meeting at a site are, in every state, two of the four incident bits of which exactly two are set. The brief asked for this to be asserted as a negative correlation. Result:

This is not a bug, and it is worth stating precisely because the premise as written is too strong. What the 2-of-4 constraint proves is a sum rule, not a per-pair sign: at each site the six pair-products sum to 1 in every state, so Σ n_ij = N over the six pairs, and with n_i = N/2 throughout, Σ C_ij = N² − 6·N²/4 = −N²/2 < 0. The average of the six is forced negative; the individual signs are not. On the (1,1,2) giant that average is realised as four pairs at C = −3360 (n_ij = 19) and two at C = +320 (n_ij = 42); 4·19 + 2·42 = 160 = N, exactly as the sum rule requires. The full ice manifold has enough symmetry to make all six negative; the giant component of a shattered quasi-1D cell does not. Stage 1 already flagged (1,1,c) cells as pathological, and this is another instance.

Recorded, not hidden: the run records the offending pairs rather than asserting and aborting, precisely so this could be reported.

3.5 Reading (iii) — not run, as registered

The registered trigger is “if (ii) PASSES at (1,1,1) or (1,1,2) in measure (A)”. It failed at both, so the strong-Rayleigh necessary condition under conditioning was not run. The machinery exists and is tested (icemix.signgauge.conditional_violations); a pre-flight at (1,1,1) with the trivial all-+1 gauge showed the full sweep of 98,280 (pair, conditioning) triples costs 0.1 s, and the (1,1,2) sweep of 1.94×10⁶ triples ≈ 20 s, so cost was never the obstacle — the trigger simply did not fire. This is recorded in results.json under items.4b.conditional_check.

3.6 What the 4b FAIL settles

Per the registration, “a FAIL at (ii) closes the Lorentzian lead”. It closes it in the strongest available form: not “we could not find a gauge at the sizes we could reach”, but “no gauge exists, at the smallest cell, and here is the three-bond reason”. The §3 tempering in the plan anticipated exactly this (“the uniform ice measure has dipolar correlations whose sign depends on the pair’s orientation, so bond indicators are not pairwise negatively correlated in the plain gauge”) and asked whether a complement gauge rescues it, as it did in the spin-ice repo. It does not, here.


4. Item 4c — the conjecture (NF⇒G)

4.1 (1,2,2) exhaustive, recomputed

Recomputed from scratch (not quoted from stage 1): W = 0 enumerated with zero_winding_only, the flip graph built, components taken.

quantity this run stage 1
W = 0 states 221,628 221,628
components 205 205
giant 221,424 221,424
non-giant components 204, all of size 1 204 singletons
frozen states 204, and every singleton is frozen 204, all frozen
frozen count = non-giant count yes yes
the reference state R is in the giant yes yes

PASS, exactly reproducing the stage-1 census (30.0 s). (NF⇒G) is therefore exhaustively true at (1,2,2): every non-frozen W = 0 state is in the giant, because every state outside the giant is a frozen singleton.

4.2 Cubic L = 2 and L = 3

Two samplers, as registered: sample_w0_dfs (randomised ice-rule DFS then loop-driven to W = 0 — fresh states with no memory of any component) and sample_w0_loops with small n_loops (contractible loop reversals from the frozen Klein state — near-frozen adversarial starts). Each non-frozen start went through certify_connected to the reference state R, and any failure was retried at a 16× budget with 6 restarts before being called uncertified.

Neither sampler is uniform, and sample_w0_dfs additionally rejects a fraction of its own attempts (defect D2), which biases it further. The frozen fractions below are informative, not measure-theoretic.

cell family starts frozen non-frozen certified uncertified frozen fraction median n_flip
(2,2,2) fresh DFS 10,000 0 10,000 10,000 0 0 23
(2,2,2) loops n=1 1,000 11 989 989 0 1.100 % 14
(2,2,2) loops n=2 1,000 0 1,000 1,000 0 0 20
(2,2,2) loops n=3 1,000 0 1,000 1,000 0 0 23
(2,2,2) loops n=4 1,000 0 1,000 1,000 0 0 23
(2,2,2) loops n=6 1,000 0 1,000 1,000 0 0 23
(2,2,2) loops n=8 1,000 0 1,000 1,000 0 0 24
(2,2,2) loops n=16 1,000 0 1,000 1,000 0 0 23
(2,2,2) all 17,000 11 16,989 16,989 0 0.0647 % 23
(3,3,3) fresh DFS 3,000 0 3,000 3,000 0 0 74
(3,3,3) loops n=1 1,000 1 999 999 0 0.100 % 16
(3,3,3) loops n=2 1,000 1 999 999 0 0.100 % 34
(3,3,3) loops n=3 1,000 0 1,000 1,000 0 0 47
(3,3,3) loops n=4 1,000 0 1,000 1,000 0 0 56
(3,3,3) loops n=6 1,000 0 1,000 1,000 0 0 66
(3,3,3) loops n=8 1,000 0 1,000 1,000 0 0 72
(3,3,3) loops n=16 1,000 0 1,000 1,000 0 0 77
(3,3,3) all 10,000 2 9,998 9,998 0 0.0200 % 68

Reading (i) — PASS. (frozen, certified, uncertified) = (11, 16989, 0) at L = 2 over 17,000 starts and (2, 9998, 0) at L = 3 over 10,000 starts. Not one non-frozen start failed to produce an explicit flip-path to R. This extends stage 2’s 780/780 at L = 2 by a factor of 22, and takes the conjecture to L = 3 (432 spins) for the first time.

Reading (ii) — no counterexample arose. The bounded BFS is armed but was never invoked, because there were no uncertified non-frozen starts. Had one arisen, icemix.component.bfs_component would have closed its component under a 10⁶-state / 1800 s cap and reported “closed, size N, R not found” (a counterexample) or “inconclusive” (never a FAIL). The machinery is tested against the exact component decomposition at (1,1,2), where it reproduces the 160-state giant, and against the 12 frozen singletons at (1,1,1).

Reading (iii) — frozen fractions. Under the fresh sampler the frozen fraction is 0 at both sizes: 0/10,000 at L = 2 and 0/3,000 at L = 3, so the sampler simply never lands on a frozen state (its minimum flippable count was 11 at L = 2 and 55 at L = 3 — it does not even come close). That is why the loop sampler is the one that matters: with n_loops = 1 it produces frozen states at 1.10 % (L = 2) and 0.10 % (L = 3), and by n_loops ≥ 3 the frozen fraction is 0 at both sizes and the median flippability has saturated. Alongside stage 1’s exact 0.092 % at (1,2,2), the picture is consistent with a frozen density that falls with size — but none of these three numbers is a uniform-measure estimate, and the two samplers disagree by more than the size dependence, so no rate should be read off them.

The n_loops sweep also shows the adversarial construction working as designed: one loop reversal off the frozen Klein state leaves a median of 14 (L = 2) / 16 (L = 3) flippable hexagons against the equilibrium 23 / 74, so these really are near-frozen starts — and all of them certify into the giant.

4.3 Measured cost, against the pre-flight

The L = 3 fresh sampler was the one pre-flight miss of this stage and it is recorded as such: a 5-sample timing gave 0.87 s per sample_w0_dfs, but the production mean over 3,000 starts is 3.32 s — 3.8× the pre-flight, with a sampler retry rate of 0.419 rejections per accepted start (defect D2) that the small sample had not seen. At the original 10,000-start target that would have been a ~3 h phase against a 26 min projection. It was caught at ~600 starts by reading the live feed, the run was stopped, and L = 3 was resized to 3,000 fresh starts to fit the registered “~1 h per size” — 2,723 s in the event. L = 2 cost 314 s for 17,000 starts (mean sample 0.0018 s).

Lesson recorded: five samples were not enough to pre-flight a rejection-sampling loop with a heavy tail. A pre-flight of a sampler that can reject should time ≥100 units, not 5, and should report the rejection rate.

4.4 What 4c does and does not establish

It is 27,000 more consistent observations, at the two smallest cubic cells, and no counterexample. It is not a proof, and the samplers’ non-uniformity means it is not even a bound on the density of a hypothetical non-frozen non-giant component: such a component would have to be missed by both samplers, and the loop sampler at n_loops = 1 is precisely the probe designed to find one. (NF⇒G) stands as a registered conjecture with exhaustive support at (1,2,2) and 27,000/27,000 at L = 2, 3.

4.5 (1,2,3) exhaustive census — not attempted, with three blockers

  1. Hard representation blocker (D3). (1,2,3) has 96 bonds. enumerate_ice_states raises for n_bonds > 64, and build_flip_graph / components / verify_reference_in_giant all assume a uint64 state word. The census is not expensive-but-possible; it is impossible without a new state representation.
  2. Wall clock. At the measured DFS leaf rate (≈1.0×10⁵ states/s from (1,1,4)’s 9,765,378 states in 98 s) the ≈3.08×10⁹-node tree projects to ≈8.5 h, well over the 3 h rule.
  3. Memory. 196,465,480 W = 0 states as a Python list then a sorted uint64 array is ≥16 GB before the graph, and the flip graph would carry ≈9.4×10⁹ edges (96 hexagons × 1.96×10⁸ states / 2) — far beyond what scipy.sparse can hold on a 121 GB machine.

The counts quoted (3,076,306,362 ice states; 196,465,480 at W = 0) are stage 1’s frontier-DP values, re-derived by this run’s own DP at (1,2,3) and agreeing exactly.


5. Run ledger — every launch, its feed card, its footprint

All on spark-709c. Feed cards live at ~/.local/state/exptop/feeds/spark-709c/ice-mixing-stage-4abc-<pid>. The final status of every card this stage created is listed; none is left saying “running”.

# pid started wall items card status peak RSS outcome
d1 1236398 09:14:46 37 s 4c dry run failed — sample_w0_dfs raised (node budget) — defect D2 found here; the reporter caught the exception and stamped the card correctly
d2 1238690 09:15:54 53 s 4c dry run completed — retry wrapper verified
d3 1240917 09:17:00 83 s 4b dry run completed — 4b logic verified
d4 1244041 09:18:29 162 s 4a dry run completed — 4a logic verified, small (2,2,2) probe budget
r1 1253551 09:22:58 719 s 4a, 4b, 4c failed 4.2 GB 4a (452 s) and 4b (94 s) completed and were banked; 4c crashed: NameError: hex_ops in the multiprocessing initialiser, left over from moving bfs_component into icemix/component.py after the dry run. The pool respawned dead workers indefinitely. Stopped with tmux kill-session.
d5 1316292 09:35:19 35 s 4c dry run completed — fix verified
r2 1326612 09:36:01 656 s 4c failed — stopped: the L=3 sampler measured ~6× the pre-flight rate (3 h projected against 26 min). See §4.3.
r3 1367612 09:47:39 158 s 4c (6 workers) failed — stopped on the main loop’s contention notice, to drop 6 workers → 4
r4 1374776 09:50:53 16 s 4c (4 workers, 4,000 L=3) failed — stopped 30 s in, to resize L=3 4,000 → 3,000 to fit ~1 h at 4 workers
r5 1375679 09:51:21 3,067 s 4c completed 0.286 GB (workers ≤ 0.208 GB) the 4c result reported in §4
r6 1546796 10:43:05 481 s 4a, 4b completed 4.216 GB the 4a/4b results reported in §2–3, re-run alone to stamp resource usage into the part files

Per-item wall clock inside r6: 4a 350 s (of which the (2,2,2) bounded DP probe is 259 s and the (1,1,4)+(1,2,2) enumerations 125 s), 4b 131 s (of which (1,2,2) alone is 129 s). Inside r5: (1,2,2) census 30 s, L=2 314 s for 17,000 starts on 4 workers, L=3 2,723 s for 10,000 starts on 4 workers.

Peak-RSS caveat, stated because the number would otherwise mislead: getrusage reports a cumulative high-water mark per process, so 4a and 4b share the 4.216 GB figure in r6 — that peak is 4a’s (2,2,2) probe, not 4b’s. Measured separately during pre-flight, the (1,2,2) 4b computation (2.89×10⁶ states × 64 bonds as a dense matrix plus the chunked XᵀX) peaked at 1.86 GB.

Four cards were left saying “running” with a dead PID (r1–r4) and are a recorded defect of this stage’s operation. Diagnosis, since the canonical reporter does install SIGTERM/SIGINT handlers that stamp a card terminal: all four were stopped with tmux kill-session, which delivers SIGHUP, and SIGHUP is not among the signals the reporter handles — the process dies before __exit__ runs. It was not SIGKILL and not a hung pool. All four have since been stamped failed with an explanatory note (by the main loop and again here), and their stale experiment.pid files removed. Two changes follow: run_stage4abc.py gained a --no-feed flag so sub-minute dry runs (d1–d5 above, which should never have created cards) do not, and the practice for stopping a run is now kill -TERM <pid> — never tmux kill-session — so the reporter’s own handler does the stamping.


6. Defects recorded

D1 — this project’s progress feed is invisible to exptop, and exptop is not importable from the interpreter the project uses. pyrun.sh info fails (“no .venv/ found”); the project runs on the pyenv 3.12.13 interpreter with numpy 1.26.4 / scipy 1.17.1 / pytest 9.0.2 installed system-wide, and exptop is not among them. Separately, experiments/01_falsifiers/run_falsifiers.py (and its stage-2/3 successors) define a private Progress class writing results/progress.json — a path outside the exptop feed root, so those runs were never discoverable by exptop/monitor.sh. It is not a ProgressReporter fork (the required grep finds no class ProgressReporter anywhere in the repo), but it has the same operational consequence. The stage-4 run script uses the canonical exptop.progress.ProgressReporter and puts ~/repo/project/exptop/src on sys.path to reach it. Suggested fix for the main loop: pip install -e ~/repo/project/exptop into this interpreter (or give the project a venv), then retire the three private Progress classes.

D2 — icemix.start.sample_w0_dfs raises on a non-negligible fraction of seeds at cubic cells. The randomised ice-rule DFS exhausts its 4×10⁶-node budget (RuntimeError: random_w0_state: node budget exceeded), and the subsequent loop drive can fail to reach W = 0. Measured rejection rate: 0.000 per accepted start at L = 2 and 0.419 at L = 3 (recorded per start in results.json as sampler_attempts, aggregated as sampler_retry_rate). Stage 2’s 780-start probe must have met the same behaviour. The stage-4 run wraps the call in a bounded retry (fresh RNG stream, ≤ 40 attempts) and records the retry count per start, because the rejection is one more source of non-uniformity in an already non-uniform sampler. Nothing in icemix was changed.

D3 — the enumeration and component machinery is hard-limited to 64 spins. enumerate_ice_states raises for n_bonds > 64, and build_flip_graph, components and verify_reference_in_giant all assume a uint64 state word and searchsorted over a materialised array. This is why the (1,2,3) exhaustive census (96 bonds) is not merely expensive but impossible with the current representation, and why the 4a cross-check stops at (1,1,4). The new icemix/component.py uses Python big integers and has no such limit, but it closes one component at a time and is not a substitute for a full census.

D4 (methodological, §2.2) — the winding support is a coset of an index-8 sublattice of Z³. Any log-concavity reading stated on W ± e_j without rescaling is vacuous, and any “internal zeros” reading stated that way is a fabricated FAIL. Recorded here because the registration’s wording does not say it and a later reader could easily re-derive the wrong answer.

D5 (§3.4) — “bonds sharing a site are negatively correlated” is not a theorem. The 2-of-4 constraint forces only the sum over the six pairs at a site to be negative. The per-pair statement holds on the full manifold and on W = 0 but fails on the giant component of (1,1,2).

D6 (mine, §5 r1) — a refactor broke the multiprocessing initialiser nine minutes into the run. Moving bfs_component out of the run script into icemix/component.py dropped hex_ops from the imports, but _c_init still called it; the dry run had passed before the refactor. multiprocessing turns that into an infinite worker-respawn loop rather than a clean crash, so the parent kept “running” and only the log showed the fault. Cost: the 4c phase of r1. Rule taken from it: re-run the dry run after every refactor, however cosmetic, before a detached launch — and, separately, a pool whose initialiser fails should abort the run, not respawn forever.

D7 (mine, §5) — four zombie feed cards. Stopping a run with tmux kill-session sends SIGHUP, which the canonical reporter does not handle (it handles SIGTERM and SIGINT), so the card was never stamped terminal and read as a hung job in exptop. Raised by the main loop; all four stamped failed with notes and their stale experiment.pid files removed. Two fixes adopted: --no-feed for sub-minute probes, and kill -TERM instead of tmux kill-session. A note for exptop itself: adding SIGHUP to the reporter’s handled signals would close this class of zombie card, since tmux/ssh teardown is the ordinary way a detached run dies.

D8 (mine, §4.3) — a five-sample pre-flight of a rejection sampler. sample_w0_dfs at L = 3 measured 0.87 s over 5 samples and 3.32 s over 3,000, because the small sample missed the retry tail (0.419 rejections per accepted start). The projection was out by 3.8×. Caught from the live feed at ~600 starts, not from a log. Rule: pre-flight a sampler that can reject with ≥100 units and report its rejection rate.


7. What was NOT run, and why

not run reason
4a at (2,2,2) measured projection 7.1×10⁸ DP keys ≈ 347 GB and ≈ 4.5 h — over the machine’s 121 GB and over the 3 h rule (§2.6). The per-step curve is banked in results.json so the projection is reproducible.
4a materialised cross-check at (1,2,3) hard 64-bit blocker (D3), not a budget
4b reading (iii) the registered trigger (a PASS at (ii) in measure A at (1,1,1) or (1,1,2)) never fired. Cost was measured anyway: 0.1 s and ≈20 s respectively.
4b measure C at (1,1,1) the W = 0 sector there is wholly frozen (12 states in 12 singleton components); there is no giant component
4c (1,2,3) exhaustive component census three independent blockers: the 64-bit representation (D3); ≈8.5 h projected DFS; and ≈9.4×10⁹ flip-graph edges plus ≥16 GB for the state list
4d worked in parallel by another subagent in experiments/05_thin_even/; untouched here
4e already closed from banked data in the plan §4

8. Unexpected (not part of any registered reading)

  1. The winding support is a sublattice coset of index 8 at every cell (§2.2). The registered log-concavity readings, taken literally on W ± e_j, are vacuously true, and the internal-zero reading taken literally would fail everywhere for a reason with no content. Anyone re-deriving 4a must rescale to the primitive lattice vector first. This is the single most likely way for a later reader to reach a wrong conclusion from the same data.
  2. The signability obstruction is local — three bonds — and it is local at every cell and every measure. Every one of the eleven odd-cycle certificates is a triangle, and the (1,1,1) one is two site-sharing pairs (negative, from the 2-of-4 constraint) closed by one next-nearest pair (positive). Growing the cell cannot repair that: whatever else changes, a 16-spin motif with an unbalanced triangle is present. This makes the 4b FAIL much stronger than “no gauge was found at the sizes we could reach”.
  3. The tightest 4a margins are on the face diagonals of the two smallest 3-D-ish cells — discriminant 3 at (1,1,1) and 10 at (1,2,2), against 10³–10⁷ elsewhere. If log-concavity fails anywhere on this lattice family, the face diagonals of small cells are where to look.
  4. Structural zero covariances appear only in the restricted measures. The full ice manifold has no zero pair covariance at any cell; W = 0 has 128 at (1,1,2) and 384 at (1,1,3), and the (1,1,2) giant has 16. Fixing the flux (and then the component) creates exact independences that the unconstrained measure does not have.
  5. Nearest-neighbour negativity is not a theorem and fails on the (1,1,2) giant: 32 of 96 site pairs are strictly positive (§3.4). The 2-of-4 constraint gives a sum rule, not a per-pair sign.
  6. The fresh W = 0 sampler is useless as a frozen-state probe. Its minimum flippable count over 13,000 cubic draws was 11 (L = 2) and 55 (L = 3) — it never goes near the frozen set. Everything informative about frozen states at cubic sizes comes from the loop sampler at n_loops = 1. Any future frozen-density estimate must not be built on the DFS sampler.
  7. The (1,1,1) (111) marginal is exactly flat across its interior (9, 24, 24, 24, 9), so its log-concavity discriminant is 0 — the boundary case, satisfied with equality rather than slack.
  8. (1,2,3) carries 453 winding sectors, not the 217 of (1,2,2) — the sector count is not a function of volume, and stage 1’s table did not record it.

9. Tests

tests/test_stage4abc.py adds 30 tests, all passing (1.19 s standalone), covering the three new modules:

Whole suite, run after every change including the icemix/__init__.py export: 248 passed, 7 skipped in 1120.20s (0:18:40), exit code 0, from 255 collected. The 255 is 182 from stages 1–3, plus this stage’s 30, plus 43 from the parallel 4d work (tests/test_stage4d.py 26, tests/test_stage4f.py 17); the 7 skips are in that parallel work, not in stage 4abc’s, whose 30 all pass.

Per-file collection at the end of this stage:

file tests
test_campaign.py 5
test_chain.py 19
test_crystal.py 9
test_enumerate.py 14
test_invariants.py 13
test_lattice.py 33
test_msc.py 12
test_observables.py 13
test_panel.py 24
test_spectrum.py 5
test_stage4abc.py 30
test_stage4d.py 26
test_stage4f.py 17
test_start.py 28
test_tiling.py 7
total 255

icemix/__init__.py now imports and exports the three new modules alongside the 4d subagent’s parity; import icemix and the three submodule imports were verified after the change.

One environment note for whoever runs this next: pytest -q suppresses the summary line under pytest 9.0.2 here — it prints the progress dots and nothing else, which makes a -q run look truncated. Run plain python3 -m pytest to get “N passed” back.