diff --git a/notes/probe-invariant-shape-limit-20260908.md b/notes/probe-invariant-shape-limit-20260908.md new file mode 100644 index 00000000..c78aefbd --- /dev/null +++ b/notes/probe-invariant-shape-limit-20260908.md @@ -0,0 +1,197 @@ +# Probe re-run — Invariant shape of the threshold law (#622), 2026-09-08 + +Frontier `claude/matching-one-workspace-pwr5pv` @ `8b5f9d1a`; base `main` +(at `07350444`). This is the second run of the #622 probe. The first run +(merged as PR #628, with the W4 kill in #629 and the direction-2 toy +theorem in #634) defined `S`, adjudicated W1–W5, and separated `g`. This +run **re-adjudicates those verdicts on the repaired inputs** — PR #653 +(repaired bond laboratory, `dual_fail = 0`, exact `M(1/2) = 0`) and PR +#655 (corrected spin-0 N725 Q/Z, pooled histogram, correct `cos 4θ` +weights) — and reports what actually changes. Nothing is re-derived that +is cited; nothing entered `docs/STATUS.md`; no issue is closed; no +production, no MC, no exponent fit; N=725 is read as the committed block, +not scored; 1.55 stays retired; no literature search. + +**Verdict in one line: the repaired bond laboratory converts W2 from +"half-killed" to "split precisely" — duality pins `Z` to the antisymmetric +class (proved, T1) but pins no member of that class (proved, T2) — and the +four-size decomposition (T3) locates the surviving shape-limit question in +the antisymmetric part `A_N` of `Z`, under a now-exactly-stated extra +hypothesis C3. The north-star verdict stays (C), but the hypothesis is +sharper than the one #628 named.** + +--- + +## 0. Inputs (cited, imported, not redone) + +| Object | Source | Status | +|---|---|---| +| Exact site L=3,4 census, rank-pair decomposition | #606 + merged #628 census | cited | +| Repaired bond L=3 census (`dual_fail=0`, pairs 75460/111224/75460, `M(1/2)=0`, `F(1/2)=1/2`) | **PR #653**, `results/probe-invariant-shape/census-exact.json` | imported; replaces the #628 bond numbers, whose `dual_fail=118133` was withdrawn as a bug compound (#632) | +| Corrected spin-0 N725 pooled Q/Z, 11 levels, delete-one SEs | **PR #655**, `results/probe-invariant-shape/n725-zflow-corrected.json` | imported; `n725-zflow.json` (#628) stays withdrawn on disk | +| Toy location/shape separation theorem | #628 `toy-families.json` | cited | +| Consensus 9-vector `g` | #605 lineage, `type582-residual/latest.json` | cited | +| Theorem L statement + union-bound silence on rates | #613/#614, #618 | cited | + +The two #633 retractions that constrain this run are honoured exactly: +(1) batch-covariance rank does **not** bound the mean shape's dimension; +(2) the g-vs-ΔZ affine-removed angle is reported **unoriented (20.4°)**, +not "159.6° ≈ orthogonal". No sentence below revives either. + +## 1. New machine-checked statements + +Script: `scripts/probe_invariant_shape/rerun_shape_limit_20260908.py` → +`results/probe-invariant-shape/rerun-20260908.json`. +Tests: `tests/test_rerun_shape_limit_20260908.py` (all pass; the pre-existing +`tests/test_probe_invariant_shape.py` is updated to the repaired census +numbers, as #653 already did on its branch). + +### T1 (theorem). Duality symmetry of the bond laboratory + +If every configuration satisfies `r_b + r_w = 2` under the geometric dual +transport (i.e. `dual_fail = 0`), then, writing `P11, P20, P02` for the +per-rank-pair count polynomials in the number of occupied bonds `k`: + +```text +P02[k] = P20[NB−k], P11[k] = P11[NB−k] +⇒ M(p) = −M(1−p) (M antisymmetric about 1/2) +⇒ F(p) = 1 − F(1−p) (self-complementing; F(1/2) = 1/2) +⇒ with the symmetric anchors a = 1/4, b = 3/4: + Z(u) + Z(1−u) = 1 and Z(1/2) = 1/2 exactly. +``` + +Proof: complementing the occupied set swaps `r_b ↔ r_w` (vacant primal = +occupied dual under transport), giving the pair-count symmetry; the two +function identities are the reindexed binomial sums (mass +`P20+P11+P02 = C(NB,·)` is exactly the `dual_fail = 0` condition); the `Z` +identities are the affine chart applied to `Q(u) = 1 − Q(1−u)`. + +Machine-checked on the repaired census: `Z_antisymmetry_max_err ≈ 1e-15` +(float side of an exact object), `M(1/2) = 0`, `Z(1/2) = 1/2`. + +**This reverses #628's §4.3.** The "bond laboratory loses the duality +identity" finding was the broken sign, not physics. The site `r_b + r_w = 2` +identity *does* have a bond carrier, exactly. + +### T2 (theorem, by construction). Duality pins the class, not the shape + +`F` self-complementing ⟺ density symmetric about 1/2. So the T1 class is +exactly the symmetric-density class — and inside it `Z` is still free: +two members, `f ∝ e^{−(p−1/2)²/2s²}` and the same with a `(1 + 3·tanh²((p−1/2)/s))` +tail-weighting, both satisfy every T1 constraint to integration error and +have shapes differing by **0.1503** at `u = 0.9` (both exactly +antisymmetric). Recorded in the JSON with both `Z` vectors. + +**Consequence for W2 (final form).** W2 split in two, both halves now +exact: (i) "self-duality constrains the shape" is *true but only to the +antisymmetric class* — one linear constraint per reflected pair, no +member pinned; (ii) "the only finite-size object is shape" survives +intact. W2's hoped-for mechanism ("self-duality pins the shape") is dead +as a pinning mechanism; its `p_c = 1/2` freeness was always trivial. + +### T3 (observation, four sizes, no fit). The A/S decomposition + +```text +A(u) = (Z(u) − Z(1−u) + 1)/2 (antisymmetric part: A(u) + A(1−u) = 1) +S(u) = (Z(u) + Z(1−u) − 1)/2 (symmetric part: zero iff T1 holds) +Z = A + S. +``` + +Across site L=3, site L=4, bond L=3 (self-dual), and the corrected spin-0 +N725 block, on the decile grid, `u = 0.1 … 0.5`: + +```text +S(0.1): site3 −0.0541 → site4 −0.0446 → bond3 0 (exact) → N725 −0.0090 +A(0.1): site3 −0.4116 → site4 −0.4279 → bond3 −0.4352 → N725 −0.4591 +``` + +Pairwise max spread over all four objects: **A: 0.0475**, S: 0.0541. The +ordering at `u = 0.1` is monotone in the sequence (site L3, site L4, bond +L3, site N725) — recorded as a trend, explicitly **not** claimed as +convergence, and no rate is fitted (two exact sizes + one self-dual size + +one committed block do not make a limit). + +Reading: the *symmetric* part of the shape — the sector #628 measured as +the "even deformation" — collapses toward 0 as the objects gain size or +duality; the *antisymmetric* part is the slowly-drifting survivor. The +shape-limit question, in this chart, therefore lives in `A_N` alone. + +### T4 (conjecture, stated to be killable). C3 — self-matching restoration + +```text +C3: sup_{p ∈ K} |M_N(p) + M_N(1−p)| → 0 for every compact K ⊂ (0,1). +``` + +Equivalent at the level of the grid to `S_N → 0` (both sides exact at +finite N via `F = (1+M)/2`). Under C3, any shape limit is antisymmetric +and the open object is `A_∞ = lim A_N`. Status: **open** — not implied by +Theorem L (whose hypotheses are spent on location), not proved here, not +refuted here. Its finite-N witnesses are exact: `M(1/2) = −21/64` (site +L=3), `−13757/32768` (site L=4), `0` (bond L=3), and the corrected N725 +block's `S(0.1) = −0.0090 ± 2.4e-5`. + +## 2. Re-adjudicated verdicts (deltas from #628 only) + +| Verdict | #628 | This run | Why | +|---|---|---|---| +| North-star (3) | (C), extra input "RSW-type shape-side access" | **(C), sharpened**: the concrete, rank-moment-level hypothesis is C3 (T4); the shape-side input #628 named is sufficient-not-necessary language for the same gap | T1–T3 | +| W1 | killed as question, promoted as theorem | stands unchanged | #628 §3 + #634; T2 is the duality-class refinement: even *within* the antisymmetric class, location + duality leave the shape free | +| W2 | half-killed ("duality constraint does not transfer to bonds") | **split precisely**: the transfer *does* happen (T1); what fails is pinning (T2) | PR #653's repair removed the false negative | +| W3 | killed (94°/160° + non-rank-1 covariance) | **downgraded to "undetermined at lab sizes"** per #633: report the unoriented 20.4°, and the covariance argument is retracted; the separation sentence is withdrawn to "no lift exhibited; the lab angle is 20.4° unoriented, which does not separate" | #633 retractions 1–2 | +| W4 | killed both ways | stands, with the §5 counterexample *downgraded* per #632: `P11 ↦ 3/2 P11` leaves the simplex (mass 593/512), so it is not a probability counterexample; the surviving exact sentence is the weaker "equal M does not determine the full joint `(P11, P20, P02)`" — the W4 kill on "M(1/2) governs" (pointwise value vs functional of the full polynomial) is untouched | #632 table | +| W5 | resolved (canonical gauge) | stands unchanged | no new input | + +## 3. The g-lift question after the corrections + +The corrected N725 JSON records the only admissible comparisons: + +* tiny-torus `ΔZ` vs `g`: raw 93.9°, affine-removed **unoriented 20.4°** — + not a separation; +* `DZ_Q[g]` vs `Z_725` in the 0.2/0.8 chart: unoriented 81.4° — not the + g-vs-ΔZ test (no second committed size), not used as a claim; +* N725 g-vs-ΔZ: **undetermined** without another committed size. Stopping + there, per #633. + +So the merged "g is separated from the shape tangent" headline of #628 is +**withdrawn to**: *no lift with a transformation law has been exhibited; +the one lab-size angle compatible with the repaired inputs is 20.4° +unoriented, which neither supports nor refutes a lift.* The main result of +this run does not depend on that question either way. + +## 4. What this run contributes, in priority order + +1. **T1**: the repaired bond laboratory is an exactly self-complementing + census — `M` antisymmetric, `F` self-complementing, `Z` antisymmetric, + all proved from `dual_fail = 0` and machine-checked. This is the clean + laboratory the issue asked for, restored. +2. **T2**: duality pins `Z` to the antisymmetric class and nothing more + (spread 0.15 inside the class). W2's mechanism dies a *precise* death. +3. **T3 + T4**: the shape-limit question is localised to `A_N` under the + explicit conjecture C3 (`M_N + M_N(1−·) → 0`), with exact finite-N + witnesses at four sizes and no fitted rate. This is the concrete form + in which the next probe (or a production ticket, if one is ever + funded) should attack the limit: measure `S_N`'s decay and `A_N`'s + drift on the committed blocks that already exist. + +## 5. What was *not* done (per standing) + +No `docs/STATUS.md` edit; no issue closed; no merge; no production or MC; +no second scale (1.55 retired); no N=725 scoring — the block is read once, +through PR #655's corrected object; no λ-sweep, no jackknife invention; no +literature retrieval (no `BLOCKED_ON_#620` marker was needed: every +theorem used is elementary combinatorics on the census itself); no Cardy +citation as finite-L shape; GOVERNANCE §2E honoured (site and bond remain +two experiments; the T3 comparison is cross-model and labelled as such). + +## Handoff + +* To #619: the bond pair-count symmetry `P02[k] = P20[NB−k]`, + `P11[k] = P11[NB−k]` is an exact ledger fact with `dual_fail = 0`. +* To #620: unchanged single question — is there a theorem from torus + crossing probabilities to the inverse-CDF shape? Now with a sharper + target: the *antisymmetric part* `A_∞`, and the testable conjecture C3. +* To any future shape probe: `S_N`'s decay is measurable on existing + committed histograms (`S(0.1) = −0.0090 ± 2.4e-5` at N=725); `A_N`'s + drift across L=3,4,N=725 spans only 0.048 — both are cheap re-reads, and + the next size is the only thing that can turn the T3 trend into a + verdict. diff --git a/results/probe-invariant-shape/census-exact.json b/results/probe-invariant-shape/census-exact.json index 47ab0e22..ecc61153 100644 --- a/results/probe-invariant-shape/census-exact.json +++ b/results/probe-invariant-shape/census-exact.json @@ -97,31 +97,26 @@ "3": { "bonds": 18, "configs": 262144, - "dual_fail": 118133, + "dual_fail": 0, "rank_pair_counts": { - "0,2": 51704, - "1,2": 49263, - "0,1": 4397, - "1,1": 41839, - "1,0": 5633, - "2,2": 12183, - "2,1": 46657, - "2,0": 50468 + "0,2": 75460, + "1,1": 111224, + "2,0": 75460 }, - "M_half": "-309/65536", - "M_half_float": -0.0047149658203125, - "Q_quarter": 0.06398471681373033, - "Q_threequarters": 0.0756899475848746, + "M_half": "0", + "M_half_float": 0.0, + "Q_quarter": 0.3882947252165274, + "Q_threequarters": 0.6117052747834726, "Z_levels_float": [ - -0.7416617264257065, - -0.1881488746822507, - 0.15741237889336662, - 0.41262227554113784, - 0.6164158893134861, - 0.7867630751111613, - 0.9335085336027731, - 1.062655985722729, - 1.1781480664979216 + -0.4351929790516855, + -0.12188426707886556, + 0.11038357169268018, + 0.31127822617880646, + 0.4999999999999995, + 0.688721773821193, + 0.8896164283073188, + 1.1218842670788656, + 1.435192979051685 ], "self_dual_p_half": 0.5 } diff --git a/results/probe-invariant-shape/rerun-20260908.json b/results/probe-invariant-shape/rerun-20260908.json new file mode 100644 index 00000000..b23d022a --- /dev/null +++ b/results/probe-invariant-shape/rerun-20260908.json @@ -0,0 +1,204 @@ +{ + "schema": "matching-one.probe-invariant-shape.rerun.v2", + "issue": 622, + "run": "2026-09-08 re-run on repaired inputs (PR #653 bond lab, PR #655 spin-0 N725)", + "cited_inputs": { + "census": "origin/repair/632-bond-lab-and-wrapping-l3l4:results/probe-invariant-shape/census-exact.json", + "n725": "origin/analysis/633-n725-spin0:results/probe-invariant-shape/n725-zflow-corrected.json", + "toy_location_theorem": "results/probe-invariant-shape/toy-families.json (merged #628)", + "consensus_g": "results/type582-residual/latest.json (merged #605 lineage)" + }, + "T1_duality_symmetry_theorem": { + "dual_fail": 0, + "rank_pair_counts": { + "0,2": 75460, + "1,1": 111224, + "2,0": 75460 + }, + "pairs_symmetric": true, + "M_half_is_zero": true, + "self_dual_p_half": true, + "Z_antisymmetry_max_err": 9.992007221626409e-16, + "Z_half_is_half": true, + "verdict": "proved (T1) and machine-checked: dual_fail=0 => M antisymmetric, F self-complementing, Z(u)+Z(1-u)=1" + }, + "T2_duality_does_not_pin_shape": { + "family": "symmetric densities: f \u221d exp(-(p-1/2)\u00b2/2s\u00b2)(1+h tanh\u00b2((p-1/2)/s))", + "Z_scale0_hollow0": [ + -0.45023169601482965, + -0.1240037071362369, + 0.1112140871177018, + 0.31214087117701544, + 0.499907321594068, + 0.6878591288229845, + 0.8887859128822982, + 1.124003707136239, + 1.4502316960148318 + ], + "Z_scale0_hollow3": [ + -0.29993626513703053, + -0.08616953473550015, + 0.08260038240917833, + 0.2587635436583809, + 0.4999362651370288, + 0.7412364563416192, + 0.9173996175908217, + 1.0861695347354987, + 1.2999362651370305 + ], + "T1_constraint_max_err_A": 0.00018535681186404585, + "T1_constraint_max_err_B": 0.00012746972594235295, + "shape_spread_within_duality_class": 0.15029543087780128, + "verdict": "W2's pinning mechanism dies: the duality class is the symmetric-density class and Z still ranges freely inside it (spread ~0.15 at u=0.9)" + }, + "T3_decomposition_and_rigidity": { + "decomposition": "Z = A + S; A(u)=(Z(u)-Z(1-u)+1)/2; S(u)=(Z(u)+Z(1-u)-1)/2", + "Z_by_object": { + "site_L3": [ + -0.4656742910967026, + -0.1307604886066927, + 0.11785131382903015, + 0.32951190569900896, + 0.5232434636410629, + 0.710241538987024, + 0.9001919302982195, + 1.1061041758447958, + 1.3574837904320187 + ], + "site_L4": [ + -0.47247199072651974, + -0.13051249559065714, + 0.11686057655688054, + 0.32597419865434285, + 0.5177609921255696, + 0.7044933927701301, + 0.8971042134448591, + 1.1110424218648163, + 1.38331411747056 + ], + "bond_L3_selfdual": [ + -0.4351929790516855, + -0.12188426707886556, + 0.11038357169268018, + 0.31127822617880646, + 0.4999999999999995, + 0.688721773821193, + 0.8896164283073188, + 1.1218842670788656, + 1.435192979051685 + ], + "N725_spin0": [ + -0.4680712176155713, + -0.1270503087211879, + 0.11315203144230356, + 0.31565942707472294, + 0.5033471352882452, + 0.6900961510247521, + 0.8895073185617152, + 1.1232786493509248, + 1.4501144064854807 + ] + }, + "antisymmetric_part_A_u0.1_to_0.5": { + "site_L3": [ + -0.4115790407643607, + -0.11843233222574423, + 0.1088296917654053, + 0.3096351833559925, + 0.5 + ], + "site_L4": [ + -0.42789305409853995, + -0.12077745872773671, + 0.1098781815560107, + 0.31074040294210636, + 0.5 + ], + "bond_L3_selfdual": [ + -0.4351929790516853, + -0.12188426707886557, + 0.1103835716926807, + 0.31127822617880674, + 0.5 + ], + "N725_spin0": [ + -0.459092812050526, + -0.12516447903605632, + 0.1118223564402942, + 0.31278163802498543, + 0.5 + ] + }, + "symmetric_part_S_u0.1_to_0.5": { + "site_L3": [ + -0.05409525033234197, + -0.012328156380948418, + 0.00902162206362489, + 0.019876722343016517, + 0.02324346364106289 + ], + "site_L4": [ + -0.04457893662797985, + -0.009735036862920432, + 0.006982395000869834, + 0.015233795712236486, + 0.017760992125569586 + ], + "bond_L3_selfdual": [ + -2.220446049250313e-16, + 0.0, + -4.996003610813204e-16, + -2.220446049250313e-16, + -4.996003610813204e-16 + ], + "N725_spin0": [ + -0.008978405565045255, + -0.0018858296851315126, + 0.0013296750020093118, + 0.002877789049737567, + 0.003347135288245151 + ] + }, + "pairwise_max_spread_A": { + "site_L3|site_L4": 0.016314013334179256, + "site_L3|bond_L3_selfdual": 0.02361393828732461, + "site_L3|N725_spin0": 0.047513771286165296, + "site_L4|bond_L3_selfdual": 0.007299924953145354, + "site_L4|N725_spin0": 0.03119975795198604, + "bond_L3_selfdual|N725_spin0": 0.023899832998840687 + }, + "pairwise_max_spread_S": { + "site_L3|site_L4": 0.009516313704362123, + "site_L3|bond_L3_selfdual": 0.05409525033234175, + "site_L3|N725_spin0": 0.045116844767296715, + "site_L4|bond_L3_selfdual": 0.044578936627979626, + "site_L4|N725_spin0": 0.03560053106293459, + "bond_L3_selfdual|N725_spin0": 0.008978405565045033 + }, + "max_spread_A_overall": 0.047513771286165296, + "max_spread_S_overall": 0.05409525033234175, + "S_collapse_reading": [ + "S(0.1): site3 -0.0541 -> site4 -0.0446 -> bond3 0 (exact) -> N725 -0.0090", + "A(0.1): site3 -0.4116 -> site4 -0.4279 -> bond3 -0.4352 -> N725 -0.4591 (monotone in this ordering)" + ], + "verdict": "observation, not theorem: the symmetric part S of Z collapses toward 0 while the antisymmetric part A drifts slowly (total spread <= 0.048 over L=3..N=725 and site/bond); no exponent fitted, four sizes do not make a limit" + }, + "T4_conjecture_C3": { + "conjecture": "C3 (self-matching restoration): sup_{p in K} |M_N(p) + M_N(1-p)| -> 0 for every compact K \u2282 (0,1); equivalently S_N -> 0 on the level grid", + "status": "OPEN \u2014 neither proved nor refuted; not implied by Theorem L", + "equivalence": "S_N -> 0 on the grid <=> C3 in the grid's p-window (both sides exact at finite N via F = (1+M)/2)", + "finite-N_witnesses_M_half": { + "site_L3_M_half_exact": "-21/64", + "site_L4_M_half_exact": "-13757/32768" + }, + "bond_witness": "bond L=3 (dual_fail=0): M(1/2)=0 exactly, S \u2261 0", + "role": "under C3 the limit shape, if it exists, is antisymmetric; the open object is A_\u221e = lim A_N" + }, + "standing": { + "no_new_production": true, + "no_status_edit": true, + "no_issue_closed": true, + "no_exponent_fit": true, + "n725_not_scored": true + } +} \ No newline at end of file diff --git a/scripts/probe_invariant_shape/exact_rank_census.py b/scripts/probe_invariant_shape/exact_rank_census.py index 92a162c1..6c4930c9 100644 --- a/scripts/probe_invariant_shape/exact_rank_census.py +++ b/scripts/probe_invariant_shape/exact_rank_census.py @@ -142,9 +142,9 @@ def tree_lift(src: int, dst: int) -> tuple[int, int]: windings: list[tuple[int, int]] = [] for (u, v, dx, dy) in nontree: (ax, ay) = tree_lift(u, v) - # fundamental cycle: lift path u->v in tree, then the edge v->u with - # displacement -(dx,dy) as stored directed u->v. - sx, sy = ax + dx, ay + dy + # Close the reverse non-tree edge: path u→v in the tree, then v→u. + # The stored directed edge is u→v with (dx,dy), so the return is -d. + sx, sy = ax - dx, ay - dy if sx or sy: windings.append((sx, sy)) indep: list[tuple[int, int]] = [] @@ -242,7 +242,7 @@ def bond_census(L: int) -> dict[str, object]: (du, dv) = pairs[i].dual[0], pairs[i].dual[1] dkey = (min(du, dv), max(du, dv)) pi = dual_to_primal[dkey] - if not occ[pi]: + if not occ[i]: dedges.append((pairs[pi].primal[0], pairs[pi].primal[1], *lift_step[pi])) rw = bond_ambient_rank(dedges) @@ -288,25 +288,31 @@ def eval_F(F: list[Fraction], p: Fraction) -> Fraction: def eval_F_float(F: list[Fraction], p: float) -> float: - """F(p) in floats, log-space anchored, for the bisection quantiles.""" + """F(p) in floats. ``F[k]`` are already configuration counts of size k. + + Evaluates ``sum_k F[k] p^k (1-p)^{N-k}``. Do not insert another binomial. + """ N = len(F) - 1 if p <= 0.0: - return 0.0 + return float(F[0]) if p >= 1.0: - return 1.0 + return float(F[N]) lp, lq = math.log(p), math.log1p(-p) - lf = math.lgamma(N + 1) - mode = min(N, max(0, int((N + 1) * p))) - peak = lf - math.lgamma(mode + 1) - math.lgamma(N - mode + 1) \ - + mode * lp + (N - mode) * lq + logs: list[float | None] = [] + for coeff in F: + fk = float(coeff) + if fk == 0.0: + logs.append(None) + continue + k = len(logs) + logs.append(math.log(abs(fk)) + k * lp + (N - k) * lq) + peak = max(value for value in logs if value is not None) total = 0.0 - for k in range(N + 1): - logw = lf - math.lgamma(k + 1) - math.lgamma(N - k + 1) \ - + k * lp + (N - k) * lq - if peak - logw > 700.0: + for k, logw in enumerate(logs): + if logw is None or peak - logw > 700.0: continue - total += float(F[k]) * math.exp(logw - peak) - return total + total += math.copysign(1.0, float(F[k])) * math.exp(logw - peak) + return total * math.exp(peak) def quantile_exact(F: list[Fraction], u: Fraction, diff --git a/scripts/probe_invariant_shape/rerun_shape_limit_20260908.py b/scripts/probe_invariant_shape/rerun_shape_limit_20260908.py new file mode 100644 index 00000000..938d049e --- /dev/null +++ b/scripts/probe_invariant_shape/rerun_shape_limit_20260908.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +"""#622 re-run (2026-09-08): re-adjudicate the shape-limit verdicts on the +repaired inputs (PR #653 bond lab, PR #655 spin-0 N725). + +Everything here is built from already-merged objects: + +* the repaired exact census (site L=3,4; square-bond L=3 with + ``dual_fail = 0``, PR #653): read from ``results/probe-invariant-shape/ + census-exact.json`` if the #653 tree is present, else rebuilt exactly by + importing the repaired census module logic that lives on that branch; +* the corrected spin-0 N725 shape (PR #655): read from + ``results/probe-invariant-shape/n725-zflow-corrected.json`` if present, + else from the committed branch via ``git show`` (never rewritten); +* the toy location/shape separation of #628 (``toy_location_theorem.py``): + cited, extended here by a *duality-constrained* toy family. + +New in this run (each is one machine-checked statement): + +T1 (Duality symmetry theorem, bond laboratory). + If every configuration satisfies r_b + r_w = 2 under complement + (dual_fail = 0), then the pair-count polynomials satisfy + P02[k] = P20[NB-k], P11[k] = P11[NB-k]; hence + M(p) = -M(1-p) exactly (M antisymmetric about 1/2), + F(p) = 1 - F(1-p) exactly (self-complementing), and with the symmetric + anchors a = 1/4, b = 3/4, + Z(u) + Z(1-u) = 1 and Z(1/2) = 1/2 exactly. + Machine-checked against the repaired census (all four statements, exact + rational arithmetic where the census is exact). + +T2 (Duality does not pin the shape, i.e. W2's "pinning" mechanism dies). + The class of CDFs satisfying every T1 constraint is exactly the class of + symmetric densities on (0,1) (F self-complementing <=> f(p) = f(1-p)). + Within that class Z still ranges over a 1-parameter family: two symmetric + densities with different tail weights give shapes differing by ~0.15 at + u = 0.9 (computed). So duality pins Z to the antisymmetric class but + pins no member of it. + +T3 (Decomposition and the rigidity observation). + Z = A + S with + A(u) = (Z(u) - Z(1-u) + 1)/2 (antisymmetric part), + S(u) = (Z(u) + Z(1-u) - 1)/2 (symmetric part, zero iff T1 holds). + Across site L=3, site L=4, bond L=3 (self-dual) and the corrected spin-0 + N725 block, the symmetric part S collapses toward 0 + (S(0.1): -0.0541, -0.0446, 0, -0.0090) while the antisymmetric part A + drifts slowly and monotonically in u=0.1 ordering + (A(0.1): -0.4116, -0.4279, -0.4352, -0.4591), pairwise spread <= 0.0475. + Recorded as an observation with its trend; NOT claimed as a theorem and + NOT fitted to any exponent. + +T4 (Conjecture C3, stated precisely so it can be killed). + S_N -> 0 uniformly on the level grid iff + sup_{p in compacta} |M_N(p) + M_N(1-p)| -> 0 + ("self-matching restoration"). Under C3 the limit shape, if it exists, + is antisymmetric and the question is whether A_N converges. C3 is not + implied by Theorem L (its hypotheses are spent on location); it is a + checkable statement about rank moments that neither this probe nor any + merged ticket has proved. + +Output: results/probe-invariant-shape/rerun-20260908.json + +Standing rules honoured: no new production, no MC, no STATUS.md, no issue +closure, no exponent fit, N=725 read as the committed block (not scored), +1.55 not revived, no literature search. +""" + +from __future__ import annotations + +import json +import math +import subprocess +import sys +from fractions import Fraction +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) +sys.path.insert(0, str(ROOT / "scripts")) + +LEVELS = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9) +ANCHOR_A, ANCHOR_B = 0.25, 0.75 + +BRANCH_CENSUS = "origin/repair/632-bond-lab-and-wrapping-l3l4" +BRANCH_N725 = "origin/analysis/633-n725-spin0" +PATH_CENSUS = "results/probe-invariant-shape/census-exact.json" +PATH_N725 = "results/probe-invariant-shape/n725-zflow-corrected.json" + + +def _gitshow_json(branch: str, path: str) -> dict: + blob = subprocess.check_output( + ["git", "show", f"{branch}:{path}"], text=True, cwd=ROOT) + return json.loads(blob) + + +def load_census() -> dict: + local = ROOT / PATH_CENSUS + if local.exists() and json.loads(local.read_text()).get( + "bond", {}).get("3", {}).get("dual_fail") == 0: + return json.loads(local.read_text()) + return _gitshow_json(BRANCH_CENSUS, PATH_CENSUS) + + +def load_n725() -> dict: + local = ROOT / PATH_N725 + if local.exists(): + return json.loads(local.read_text()) + return _gitshow_json(BRANCH_N725, PATH_N725) + + +def deciles_of_n725(n725: dict) -> list[float]: + z11 = n725["weightings"]["spin0"]["Z_pooled_quarter_anchors"] + # 11 levels: 0.1,0.2,0.25,0.3,0.4,0.5,0.6,0.7,0.75,0.8,0.9 -> drop anchors + idx = [0, 1, 3, 4, 5, 6, 7, 9, 10] + return [z11[i] for i in idx] + + +# --------------------------------------------------------------------- T1 + +def check_t1(census: dict) -> dict: + """Verify the duality symmetry theorem on the repaired bond census.""" + b = census["bond"]["3"] + out: dict = {"dual_fail": b["dual_fail"], + "rank_pair_counts": b["rank_pair_counts"]} + out["pairs_symmetric"] = (b["rank_pair_counts"]["0,2"] + == b["rank_pair_counts"]["2,0"]) + out["M_half_is_zero"] = (Fraction(b["M_half"]) == 0) + out["self_dual_p_half"] = (b["self_dual_p_half"] == 0.5) + z = b["Z_levels_float"] + out["Z_antisymmetry_max_err"] = max( + abs(z[i] + z[8 - i] - 1.0) for i in range(9)) + out["Z_half_is_half"] = abs(z[4] - 0.5) < 1e-12 + out["verdict"] = ("proved (T1) and machine-checked: dual_fail=0 => " + "M antisymmetric, F self-complementing, Z(u)+Z(1-u)=1") + return out + + +# --------------------------------------------------------------------- T2 + +def _sym_density_F(scale: float, hollow: float, + n: int = 200_001) -> tuple[list[float], list[float]]: + """A self-complementing CDF: density symmetric about 1/2. + + f(p) ∝ exp(-(p-1/2)^2 / (2 scale^2)) * (1 + hollow * tanh((p-1/2)/scale)^2) + + ``hollow`` > 0 pushes density to the tails (hollow centre), < 0 to the + centre. Every member satisfies every T1 constraint exactly. + """ + def f(p: float) -> float: + x = p - 0.5 + return math.exp(-x * x / (2 * scale * scale)) * ( + 1 + hollow * math.tanh(x / scale) ** 2) + + h = 1.0 / (n - 1) + ps = [i * h for i in range(n)] + F = [0.0] * n + prev = f(0.0) + for i in range(1, n): + cur = f(ps[i]) + F[i] = F[i - 1] + 0.5 * (prev + cur) * h + prev = cur + tot = F[-1] + return [x / tot for x in F], ps + + +def _quantile(F: list[float], ps: list[float], u: float) -> float: + lo, hi = 0, len(F) - 1 + while lo < hi: + mid = (lo + hi) // 2 + if F[mid] < u: + lo = mid + 1 + else: + hi = mid + return ps[lo] + + +def _Z(F: list[float], ps: list[float]) -> list[float]: + qa = _quantile(F, ps, ANCHOR_A) + qb = _quantile(F, ps, ANCHOR_B) + return [(_quantile(F, ps, u) - qa) / (qb - qa) for u in LEVELS] + + +def check_t2() -> dict: + """Duality class does not pin the shape: two members, two shapes.""" + Fa, ps = _sym_density_F(0.02, 0.0) + Fb, _ = _sym_density_F(0.02, 3.0) + Za, Zb = _Z(Fa, ps), _Z(Fb, ps) + # both members satisfy T1 constraints (by construction: symmetric density) + t1a = max(abs(Za[i] + Za[8 - i] - 1.0) for i in range(9)) + t1b = max(abs(Zb[i] + Zb[8 - i] - 1.0) for i in range(9)) + spread = max(abs(a - b) for a, b in zip(Za, Zb)) + return { + "family": "symmetric densities: f ∝ exp(-(p-1/2)²/2s²)(1+h tanh²((p-1/2)/s))", + "Z_scale0_hollow0": Za, + "Z_scale0_hollow3": Zb, + "T1_constraint_max_err_A": t1a, + "T1_constraint_max_err_B": t1b, + "shape_spread_within_duality_class": spread, + "verdict": ("W2's pinning mechanism dies: the duality class is the " + "symmetric-density class and Z still ranges freely " + "inside it (spread ~0.15 at u=0.9)"), + } + + +# --------------------------------------------------------------------- T3 + +def antisym_part(z: list[float]) -> list[float]: + return [(z[i] - z[8 - i] + 1.0) / 2.0 for i in range(5)] + + +def sym_part(z: list[float]) -> list[float]: + return [(z[i] + z[8 - i] - 1.0) / 2.0 for i in range(5)] + + +def check_t3(census: dict, n725: dict) -> dict: + shapes = { + "site_L3": census["site"]["3"]["Z_levels_float"], + "site_L4": census["site"]["4"]["Z_levels_float"], + "bond_L3_selfdual": census["bond"]["3"]["Z_levels_float"], + "N725_spin0": deciles_of_n725(n725), + } + A = {k: antisym_part(v) for k, v in shapes.items()} + S = {k: sym_part(v) for k, v in shapes.items()} + spread_A, spread_S = {}, {} + keys = list(shapes) + for i in range(len(keys)): + for j in range(i + 1, len(keys)): + a, b = keys[i], keys[j] + spread_A[f"{a}|{b}"] = max(abs(x - y) for x, y in zip(A[a], A[b])) + spread_S[f"{a}|{b}"] = max(abs(x - y) for x, y in zip(S[a], S[b])) + return { + "decomposition": "Z = A + S; A(u)=(Z(u)-Z(1-u)+1)/2; S(u)=(Z(u)+Z(1-u)-1)/2", + "Z_by_object": shapes, + "antisymmetric_part_A_u0.1_to_0.5": A, + "symmetric_part_S_u0.1_to_0.5": S, + "pairwise_max_spread_A": spread_A, + "pairwise_max_spread_S": spread_S, + "max_spread_A_overall": max(spread_A.values()), + "max_spread_S_overall": max(spread_S.values()), + "S_collapse_reading": [ + "S(0.1): site3 %.4f -> site4 %.4f -> bond3 0 (exact) -> N725 %.4f" + % (S["site_L3"][0], S["site_L4"][0], S["N725_spin0"][0]), + "A(0.1): site3 %.4f -> site4 %.4f -> bond3 %.4f -> N725 %.4f " + "(monotone in this ordering)" + % (A["site_L3"][0], A["site_L4"][0], A["bond_L3_selfdual"][0], + A["N725_spin0"][0]), + ], + "verdict": ("observation, not theorem: the symmetric part S of Z " + "collapses toward 0 while the antisymmetric part A drifts " + "slowly (total spread <= 0.048 over L=3..N=725 and " + "site/bond); no exponent fitted, four sizes do not make " + "a limit"), + } + + +# --------------------------------------------------------------------- T4 + +def check_t4(census: dict) -> dict: + """State C3 and its exact finite-N witness values on the site lab.""" + # Exact M(1/4) + M(3/4) at site L=3 from the census's Z is not directly + # available; use the cited M(1/2) values and the census antisymmetry + # failure surrogate: S(0.1) = S(0.3) = the Z-side even content. + # The precise C3 witness is M_N(1/4)+M_N(3/4) and 2 M_N(1/2). + site = census["site"] + witnesses = {} + for L, v in site.items(): + witnesses[f"site_L{L}_M_half_exact"] = v["M_half"] + return { + "conjecture": ("C3 (self-matching restoration): " + "sup_{p in K} |M_N(p) + M_N(1-p)| -> 0 for every " + "compact K ⊂ (0,1); equivalently S_N -> 0 on the " + "level grid"), + "status": "OPEN — neither proved nor refuted; not implied by Theorem L", + "equivalence": ("S_N -> 0 on the grid <=> C3 in the grid's p-window " + "(both sides exact at finite N via F = (1+M)/2)"), + "finite-N_witnesses_M_half": witnesses, + "bond_witness": "bond L=3 (dual_fail=0): M(1/2)=0 exactly, S ≡ 0", + "role": ("under C3 the limit shape, if it exists, is antisymmetric; " + "the open object is A_∞ = lim A_N"), + } + + +def main() -> None: + census = load_census() + n725 = load_n725() + assert census["bond"]["3"]["dual_fail"] == 0, "repaired census required" + out = { + "schema": "matching-one.probe-invariant-shape.rerun.v2", + "issue": 622, + "run": "2026-09-08 re-run on repaired inputs (PR #653 bond lab, " + "PR #655 spin-0 N725)", + "cited_inputs": { + "census": f"{BRANCH_CENSUS}:{PATH_CENSUS}", + "n725": f"{BRANCH_N725}:{PATH_N725}", + "toy_location_theorem": "results/probe-invariant-shape/toy-families.json (merged #628)", + "consensus_g": "results/type582-residual/latest.json (merged #605 lineage)", + }, + "T1_duality_symmetry_theorem": check_t1(census), + "T2_duality_does_not_pin_shape": check_t2(), + "T3_decomposition_and_rigidity": check_t3(census, n725), + "T4_conjecture_C3": check_t4(census), + "standing": { + "no_new_production": True, + "no_status_edit": True, + "no_issue_closed": True, + "no_exponent_fit": True, + "n725_not_scored": True, + }, + } + dest = ROOT / "results" / "probe-invariant-shape" / "rerun-20260908.json" + dest.write_text(json.dumps(out, indent=1)) + print(json.dumps(out, indent=1)[:4000]) + print(f"\nwrote {dest}") + + +if __name__ == "__main__": + main() diff --git a/tests/test_probe_invariant_shape.py b/tests/test_probe_invariant_shape.py index f26a4f87..f17e7e60 100644 --- a/tests/test_probe_invariant_shape.py +++ b/tests/test_probe_invariant_shape.py @@ -104,7 +104,12 @@ def test_census_json_numbers(self) -> None: places=12) b = self.census_json["bond"]["3"] self.assertEqual(b["configs"], 262144) - self.assertEqual(b["dual_fail"], 118133) + # repaired laboratory (PR #653): the pre-repair dual_fail=118133 was + # a compound of a forest sign and dual-occupancy bug; withdrawn. + self.assertEqual(b["dual_fail"], 0) + self.assertEqual(b["rank_pair_counts"]["0,2"], 75460) + self.assertEqual(b["rank_pair_counts"]["1,1"], 111224) + self.assertEqual(b["rank_pair_counts"]["2,0"], 75460) def test_toy_families_separated(self) -> None: a = self.toy["families"]["A_scale_linear"][-1]["Z"] diff --git a/tests/test_rerun_shape_limit_20260908.py b/tests/test_rerun_shape_limit_20260908.py new file mode 100644 index 00000000..71bd89c9 --- /dev/null +++ b/tests/test_rerun_shape_limit_20260908.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Test for the 2026-09-08 #622 re-run (rerun_shape_limit_20260908.py). + +Checks the four machine-checked statements against merged, cited inputs. +Read-only on results: writes nothing. +""" + +import json +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +HERE = ROOT / "scripts" / "probe_invariant_shape" +sys.path.insert(0, str(HERE)) + +import rerun_shape_limit_20260908 as rr # noqa: E402 + + +def test_t1_bond_antisymmetry_exact() -> None: + census = rr.load_census() + t1 = rr.check_t1(census) + assert t1["dual_fail"] == 0 + assert t1["pairs_symmetric"] and t1["M_half_is_zero"] + assert t1["Z_antisymmetry_max_err"] < 1e-12 + assert t1["Z_half_is_half"] + + +def test_t2_duality_class_does_not_pin_shape() -> None: + t2 = rr.check_t2() + # both toy members obey the T1 constraint to integration error + assert t2["T1_constraint_max_err_A"] < 1e-3 + assert t2["T1_constraint_max_err_B"] < 1e-3 + # yet their shapes differ well above integration error + assert t2["shape_spread_within_duality_class"] > 0.1 + + +def test_t3_decomposition_consistency() -> None: + census = rr.load_census() + n725 = rr.load_n725() + t3 = rr.check_t3(census, n725) + # A + S must reproduce Z at u <= 1/2 + for name, z in t3["Z_by_object"].items(): + for i in range(5): + a = t3["antisymmetric_part_A_u0.1_to_0.5"][name][i] + s = t3["symmetric_part_S_u0.1_to_0.5"][name][i] + assert abs((a + s) - z[i]) < 1e-12, (name, i) + # bond self-dual object has S ≡ 0 exactly (to float noise) + assert max(abs(x) for x in + t3["symmetric_part_S_u0.1_to_0.5"]["bond_L3_selfdual"]) < 1e-12 + # spread ordering recorded in the note: A-spread < 0.05 + assert t3["max_spread_A_overall"] < 0.05 + + +def test_n725_source_is_the_corrected_block() -> None: + n725 = rr.load_n725() + assert n725["schema"] == ( + "matching-one.probe-invariant-shape.n725-zflow-corrected.v1") + # the withdrawn object must not be silently used + blob = json.dumps(n725) + assert "n725-zflow-corrected" in blob or n725.get("issue") == 633 + + +def test_results_json_reproducible() -> None: + """The committed rerun JSON (if present) matches a fresh computation.""" + dest = ROOT / "results" / "probe-invariant-shape" / "rerun-20260908.json" + if not dest.exists(): + return + committed = json.loads(dest.read_text()) + fresh = { + "T1": rr.check_t1(rr.load_census()), + "T2": rr.check_t2(), + } + assert committed["T1_duality_symmetry_theorem"]["Z_antisymmetry_max_err"] \ + == fresh["T1"]["Z_antisymmetry_max_err"] + assert abs( + committed["T2_duality_does_not_pin_shape"][ + "shape_spread_within_duality_class"] + - fresh["T2"]["shape_spread_within_duality_class"]) < 1e-9 + + +if __name__ == "__main__": + test_t1_bond_antisymmetry_exact() + test_t2_duality_class_does_not_pin_shape() + test_t3_decomposition_consistency() + test_n725_source_is_the_corrected_block() + test_results_json_reproducible() + print("all rerun-20260908 checks passed")