From 384d1ee9668d6d077c64b71bb5cfaedd5c8c9cce Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Mon, 12 Oct 2026 16:29:54 -0400 Subject: [PATCH 01/17] Research notes (no kappa claim): E8 decoder floors and #615/#617 cold-end levers Co-Authored-By: Claude Opus 5.5 --- .../README.md | 25 ++ .../ade_screen.py | 40 +++ .../r1-cold14/REPORT.md | 18 ++ .../r1-cold14/aglp.py | 80 +++++ .../r1-cold14/aglp617.py | 80 +++++ .../r1-cold14/aglp_allwin.py | 82 ++++++ .../r1-cold14/broad.py | 24 ++ .../r1-cold14/broad2.py | 31 ++ .../r1-cold14/cands.py | 38 +++ .../r1-cold14/coal.py | 44 +++ .../r1-cold14/coalf.py | 45 +++ .../r1-cold14/coalfinal.py | 34 +++ .../r1-cold14/coldsurvey.py | 18 ++ .../r1-cold14/common.py | 99 +++++++ .../r1-cold14/e1probe.py | 37 +++ .../r1-cold14/expand617.py | 96 ++++++ .../r1-cold14/hypsurvey.py | 28 ++ .../r1-cold14/mip617.py | 85 ++++++ .../r1-cold14/movecoal.py | 48 +++ .../r1-cold14/nulls.py | 26 ++ .../r1-cold14/price.py | 7 + .../r1-cold14/probe.py | 19 ++ .../r1-cold14/probe2.py | 48 +++ .../r1-cold14/survey.py | 28 ++ .../s2-decoder/REPORT.md | 21 ++ .../s2-decoder/addports.py | 37 +++ .../s2-decoder/asym.py | 46 +++ .../s2-decoder/asym2.py | 9 + .../s2-decoder/asym_ls.py | 45 +++ .../s2-decoder/asym_nodes.py | 72 +++++ .../s2-decoder/binv.py | 11 + .../s2-decoder/bitp.py | 35 +++ .../s2-decoder/cert.py | 133 +++++++++ .../s2-decoder/covers.py | 25 ++ .../s2-decoder/cxp.py | 35 +++ .../s2-decoder/dec9.py | 75 +++++ .../s2-decoder/dec9sat.py | 80 +++++ .../s2-decoder/e8units.py | 275 ++++++++++++++++++ .../s2-decoder/flats.py | 38 +++ .../s2-decoder/geo.py | 44 +++ .../s2-decoder/hyper.py | 50 ++++ .../s2-decoder/minrank_sat.py | 133 +++++++++ .../s2-decoder/mixed.py | 41 +++ .../s2-decoder/mixed_d8.py | 26 ++ .../s2-decoder/p111c.py | 18 ++ .../s2-decoder/report.py | 60 ++++ .../s2-decoder/run62.py | 4 + .../s2-decoder/sa_bit.py | 27 ++ .../s2-decoder/sat_cheap.py | 58 ++++ .../s2-decoder/search62.py | 17 ++ .../s2-decoder/skel.py | 85 ++++++ .../s2-decoder/subsys.py | 52 ++++ .../s2-decoder/t1.py | 10 + .../s2-decoder/thmc111.py | 8 + .../s2-decoder/uniq.py | 22 ++ .../s2-decoder/xtab.py | 11 + 56 files changed, 2683 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/ade_screen.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/REPORT.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp617.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp_allwin.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/cands.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/coal.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalf.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalfinal.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/coldsurvey.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/common.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/e1probe.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/expand617.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/hypsurvey.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/mip617.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/movecoal.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/nulls.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/price.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r1-cold14/survey.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/REPORT.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/addports.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_ls.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_nodes.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/binv.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/bitp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/cert.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/covers.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/cxp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9sat.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/e8units.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/geo.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/hyper.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/minrank_sat.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed_d8.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/p111c.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/report.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/sa_bit.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_cheap.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/search62.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/skel.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/subsys.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/t1.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/thmc111.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/xtab.py diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md new file mode 100644 index 0000000000..43446a32da --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -0,0 +1,25 @@ +# Research notes (no κ claim): E8 decoder floors and #615/#617 cold-end levers + +Negative results and one open lead, recorded so others don't repeat them. Scripts are included as they were run (paths reference a local scratch dir and `~/imb-src` pinned sources; Python 3.11 with numpy, scipy, sympy, python-sat, ortools). SAT results come from CaDiCaL via python-sat **without proof logs**. Nothing here is packaged with a verify.sh. + +## 1. Unit screen (`ade_screen.py`) +Real ADE root systems as units (v lines, window h = rank, c = F2 rank of (J+G) mod 2, proxy x = c·h/v, ceiling via #595's `skel.X`): only E8 gives a useful ceiling. E7 (v 63, h 7, c 7) reaches X* ≈ 1.84e-3; A_n, D_n and E6 have x ≥ 1 and give no saving. X scales roughly as 1/h, consistent with the Leech (#598) and Niemeier (#606) losses. + +## 2. E8 decoder floors (`s2-decoder/`, details in `REPORT.md`) +- **P111 (shipped bit puncture 39,52,57,71,76,78,103,109,114):** the 9-total F2 decoder is unique (= B0 on the live lines); 8 totals is UNSAT (`uniq.py`). +- **#446 Theorem C floor = star floor** on all realised units checked: P120 bit 72 (all 511 reader sets span Q^8, `t1.py`); P111 bit 63 (exact SAT over all label-decoder decompositions: cost ≤ 62 UNSAT, 63 SAT; `run62.py`, `dec9sat.py`, `p111c.py`); P112 complex frame decoder 64 (`cxp.py`). Decoders differing from B0 off-pattern (rank ≥ 10) are not covered. +- Where slack would have to come from: a total whose readers lie in a hyperplane orthogonal to a punctured line can drop to that line (cost 6 for orthogonal punctured pairs). It does not get P111 below 63. +- Deeper punctures (annealing, 9–26 lines removed), sub-root-system units (D8, E7+A1, A8, …) and mixed units with non-root ports (≥ 14 lines must be removed; best x ≥ 0.67) are all worse. Search results, not exhaustive. Exact W(E8) hyperplane orbit counts: E7 120, D7 1080, E6+A1 3360, A7 8640 (`hyper.py`). +- Asymmetric sinks on P112: 0 of 28,560 target/sink pairs relax for free (floating-point rank, `asym.py`); one extra copy unit frees 2 sinks (matches #595 C.6). Pricing (argued): ≤ 2 × 4.4 helpers × 0.143% ≈ 1.3% b < 2.82% per copy unit, so net negative. + +## 3. Levers on #615 / #617 (`r1-cold14/`, details in `REPORT.md`) +Measured on #615's words (#615 re-verified locally: κ 0.001626010676080313): +- **"e2" entering ends are infeasible** at all three Lean (E,U) pairs of #593: the row-cone left-nullity equals the number of helpers already ended (12/24/12) on jp8 and atm3. Cold-mixing directions (542/543/554/555, 566/567/578/579) have no column-cone solution (`nulls.py`, `broad2.py`, `probe2.py`). +- Extra cold dim-8 hyperplane ends all lose (best −5.67e-9 b per call). E1 partners 438/440 can't be replaced by already-lifted registers. +- No PR583-type frame coalescences on jp8, with or without commuting gate moves (the same scan finds #583's six on atm3) (`coal*.py`, `movecoal.py`). +- #615's owner map + dispatch equals the aggregate LP optimum over {jp8, atm3}, even with unlimited windows (`aglp.py`). + +**Open lead on #617 (κ 0.001635183729255778):** #617's owner solve stopped at its time limit with remainders {1:24, 2:2, 3:2, 7:2}. The aggregate LP bound (`aglp617.py`) is **+119.3e-9 b** above #617's map at fractional dispatch jp8 60.9 / atm3 11.1. An integer packing at jp8×61 / atm3×11 that meets the bound would give about **+1.1e-7 κ**. `mip617.py` (free-dispatch MIP with Hall constraints) and `expand617.py` (to OWNERS/PLAN) are included; the integer packing is not yet solved or verified. + +## Credits +Builds on jeremykothe2 (#591–#601, #607–#617), DreamingOfClouds (#595 format limits, #604, #610), chafreaky (#583, #616), #446 (Theorems A–C), eumemic (#543) and the contributors listed in those PRs. Prepared by Joel Pulikkan (GamingPuzzled) with Claude Opus 5.5 (Anthropic) via Claude Code. Apache-2.0. diff --git a/research/e8-decoder-floors-and-cold14-notes/ade_screen.py b/research/e8-decoder-floors-and-cold14-notes/ade_screen.py new file mode 100644 index 0000000000..6e73b0d052 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/ade_screen.py @@ -0,0 +1,40 @@ +import sys, itertools, numpy as np +from fractions import Fraction as Fr +sys.path.insert(0, '/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from e8units import f2_rank +from skel import X + +def roots_A(n): # in R^{n+1} + R=[] + for i in range(n+1): + for j in range(i+1,n+1): + v=[0]*(n+1); v[i]=1; v[j]=-1; R.append(v) + return np.array(R) +def roots_D(n): + R=[] + for i in range(n): + for j in range(i+1,n): + for s in (1,-1): + v=[0]*n; v[i]=1; v[j]=s; R.append(v) + return np.array(R) +def roots_E8(): + R=[list(r) for r in roots_D(8)] + for signs in itertools.product((1,-1),repeat=8): + if signs[0]==1 and signs.count(-1)%2==0: R.append([s*0.5 for s in signs]) + return np.array(R) +def E7(): + R=roots_E8(); s=np.ones(8)*0.5 + return R[np.abs(R@s)<1e-9] +def E6(): + R=roots_E8(); a=np.ones(8)*0.5; b=np.zeros(8); b[6]=1;b[7]=1 # pick two orthogonal roots + b=R[[i for i in range(len(R)) if abs(R[i]@a)<1e-9][0]] + R7=R[np.abs(R@a)<1e-9]; return R7[np.abs(R7@b)<1e-9] +def stats(name,R): + G=np.rint(R@R.T).astype(int); v=int(len(R)); h=int(np.linalg.matrix_rank(G)) + B=(1+G)%2; c=int(f2_rank(B)) + x=Fr(c*h, v) + vals=[(s,X(s,h,1,x)) for s in (3,5,7,9)]; vals=[(s,float(r["hi"])) for s,r in vals if r]; best=max(vals,key=lambda t:t[1]) if vals else (0,0.0) + print(f"{name:6s} v={v:4d} h={h:2d} c={c:3d} x~c*h/v={float(x):.3f} X*={best[1]:.4e}@s{best[0]}") +for n in (4,5,6,7,8): stats(f"A{n}",roots_A(n)) +for n in (4,5,6,7,8,9,10,12): stats(f"D{n}",roots_D(n)) +stats("E6",E6()); stats("E7",E7()); stats("E8",roots_E8()) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/REPORT.md b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/REPORT.md new file mode 100644 index 0000000000..a1dadf10ee --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/REPORT.md @@ -0,0 +1,18 @@ +# R1 resume (record track) — no strict gain yet +Worktrees: ~/imb-r1 (branch research/cold14-e2, base pr615, WIP commit with tools in research/r1-wip-tools/), +~/imb-r1b (branch research/cold14-b3-r1, base pr617, unchanged). Sources: ~/imb-src/pr{568,569,571,575,583}. +Verify: PATH=$SP/venv/bin:$PATH sh research//verify.sh WORK ~/imb-src (system python3 is too old). #615 baseline PASS kappa 0.001626010676080313 (615 s). +Tools ($SP/r1/tools == research/r1-wip-tools): price.py (exact b via PR568 moment.certify), V.json/V617.json (mass-neutral 1e-9 b per rank-r child), +nulls.py/broad2.py/hypsurvey.py/cands.py (cone capacity), coal.py/coalf.py/movecoal.py (frame-coalescence scans), aglp*.py (aggregate LP), mip617.py + expand617.py (free-dispatch MIP -> OWNERS/PLAN). +## Findings (on #615 words; #617 keeps E1) +- e2 infeasible: for all 3 Lean pairs (E=[30],[6],[24] raw) left-nullity of row-cone matrix over same-E helpers == #already-ended (12/24/12) on jp8 and atm3. Cold-mixing directions exist only via cold helpers 542/543/554/555 (E30), 566/567/578/579 (E24) but their column cone is unfixable (col-null space has no vector on them); other violators (315,347, data ports) are blocked by monotonicity. +- Cold dim-8 (bot,U) extras: best single end net -5.67 (V units), all negative. +- E1 partners 438/440 cannot be replaced by already-lifted registers (nullity drops 12->11 per dropped partner). +- Frame coalescence (PR583-type) on jp8 joint-prefix word: 0 single-gate (incl. final frames), 0 with commuting moves W=6 (validation on atm3 reproduces PR583's 6). +- #615 owner/dispatch: aggregate LP (any remainders, even unlimited windows) == current solution -> no owner/dispatch gain on #615. +- #617 (kappa 0.001635183729255778): its MIP hit time limit with remainders {1:24,2:2,3:2,7:2}. My LP (any remainders) bound is +119.3e-9 b over #617's map at fractional dispatch jp8 60.9/atm3 11.1 -> an integer perfect packing at jp8x61/atm3x11 would be ~+115e-9 b (~+1.1e-7 kappa). THIS IS THE NEXT LEVER. +## Resume +cd $SP/r1/tools && ../../venv/bin/python mip617.py 3 1800 ../m617 # free-dispatch Hall-mask MIP (was running, 900 s) +mkdir ../m617-out && ../../venv/bin/python expand617.py ../m617.pkl ../m617-out +../../venv/bin/python price.py "$(cat ../m617-profile.json)" # compare to b of #617 = 0.0016378619344417451 +Then: copy OWNERS/PLAN into ~/imb-r1b/research/complex-571-cold14-b3/data, update SPEC.json (data hashes, dispatch, expected global_profile/provider_counts/kappa/idle remainders), WITNESS/EXPECTED (spec.py generate), MANIFEST, rerun verify.sh with --reuse-baseline/--reuse-local from $SP/r1/b3-base for iteration, then full fresh run. diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp.py new file mode 100644 index 0000000000..d791089261 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp.py @@ -0,0 +1,80 @@ +"""aggregate free-dispatch LP (first-order, mass-neutral values V) over providers; patterns with any remainder.""" +import json,sys,itertools +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import linprog +from scipy.sparse import coo_matrix +V={int(k):v for k,v in json.load(open('V.json')).items()} +PLAN=json.load(open('/Users/joelpulikkan/imb-r1/research/complex-571-cold14-e1/data/PLAN.json')) +reg=PLAN['provider_registry'] +provs=sys.argv[1].split(',') if len(sys.argv)>1 else ['jp8','atm3'] +fixed=json.loads(sys.argv[2]) if len(sys.argv)>2 else None # {time:provider} optional +inv={p:Counter(reg[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in reg[p]['actual_paid_profile'].items()) for p in provs} +WID=sorted({w for p in provs for w in inv[p]}) +print('values',val,'widths',WID) +def pats(cap,L): + out=[] + def rec(pre,i,tot): + out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)1e-7: rem[PLAN['windows'][key][0]-sum(q)]+=x[i] +print('remainders',{k:round(v,3) for k,v in sorted(rem.items())}) +cur=68*val.get('jp8',0)+4*val.get('atm3',0) +print('current solution value',cur,'LP gain over current (1e-9 b)',-res.fun-cur) +json.dump({'sel':{f'{t},{p}':x[i] for (t,p),i in sel.items()},'pv':{f'{k}|{list(q)}':x[i] for (k,q),i in pv.items() if x[i]>1e-9}},open('aglp-out.json','w')) +du=res.eqlin.marginals +d=defaultdict(dict) +for i,k in enumerate(ek): + if k[0]=='inv': d[k[2]][k[1]]=-du[i] +for w in WID: + vals=[d[w][t] for t in range(72)] + print('dual width',w,'min %.2f max %.2f mean %.2f'%(min(vals),max(vals),sum(vals)/72)) +print('per-time dual(9)-dual(8):',[round(d[9][t]-d[8][t],2) for t in range(72)]) +print('per-time dual(8)-dual(7):',[round(d[8][t]-d[7][t],2) for t in range(72)]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp617.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp617.py new file mode 100644 index 0000000000..342abdbe4e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp617.py @@ -0,0 +1,80 @@ +"""aggregate free-dispatch LP (first-order, mass-neutral values V) over providers; patterns with any remainder.""" +import json,sys,itertools +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import linprog +from scipy.sparse import coo_matrix +V={int(k):v for k,v in json.load(open('V617.json')).items()} +PLAN=json.load(open('/Users/joelpulikkan/imb-r1b/research/complex-571-cold14-b3/data/PLAN.json')) +reg=PLAN['provider_registry'] +provs=sys.argv[1].split(',') if len(sys.argv)>1 else ['jp8','atm3'] +fixed=json.loads(sys.argv[2]) if len(sys.argv)>2 else None # {time:provider} optional +inv={p:Counter(reg[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in reg[p]['actual_paid_profile'].items()) for p in provs} +WID=sorted({w for p in provs for w in inv[p]}) +print('values',val,'widths',WID) +def pats(cap,L): + out=[] + def rec(pre,i,tot): + out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)1e-7: rem[PLAN['windows'][key][0]-sum(q)]+=x[i] +print('remainders',{k:round(v,3) for k,v in sorted(rem.items())}) +cur=60*val.get('jp8',0)+12*val.get('atm3',0)+V[1]*24+V[2]*2+V[3]*2+V[7]*2 +print('current solution value',cur,'LP gain over current (1e-9 b)',-res.fun-cur) +json.dump({'sel':{f'{t},{p}':x[i] for (t,p),i in sel.items()},'pv':{f'{k}|{list(q)}':x[i] for (k,q),i in pv.items() if x[i]>1e-9}},open('aglp617-out.json','w')) +du=res.eqlin.marginals +d=defaultdict(dict) +for i,k in enumerate(ek): + if k[0]=='inv': d[k[2]][k[1]]=-du[i] +for w in WID: + vals=[d[w][t] for t in range(72)] + print('dual width',w,'min %.2f max %.2f mean %.2f'%(min(vals),max(vals),sum(vals)/72)) +print('per-time dual(9)-dual(8):',[round(d[9][t]-d[8][t],2) for t in range(72)]) +print('per-time dual(8)-dual(7):',[round(d[8][t]-d[7][t],2) for t in range(72)]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp_allwin.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp_allwin.py new file mode 100644 index 0000000000..36c9cae3ac --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/aglp_allwin.py @@ -0,0 +1,82 @@ +"""aggregate free-dispatch LP (first-order, mass-neutral values V) over providers; patterns with any remainder.""" +import json,sys,itertools +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import linprog +from scipy.sparse import coo_matrix +V={int(k):v for k,v in json.load(open('V.json')).items()} +PLAN=json.load(open('/Users/joelpulikkan/imb-r1/research/complex-571-cold14-e1/data/PLAN.json')) +reg=PLAN['provider_registry'] +provs=sys.argv[1].split(',') if len(sys.argv)>1 else ['jp8','atm3'] +ALLWIN=True +fixed=json.loads(sys.argv[2]) if len(sys.argv)>2 else None # {time:provider} optional +inv={p:Counter(reg[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in reg[p]['actual_paid_profile'].items()) for p in provs} +WID=sorted({w for p in provs for w in inv[p]}) +print('values',val,'widths',WID) +def pats(cap,L): + out=[] + def rec(pre,i,tot): + out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)1e-7: rem[PLAN['windows'][key][0]-sum(q)]+=x[i] +print('remainders',{k:round(v,3) for k,v in sorted(rem.items())}) +cur=68*val.get('jp8',0)+4*val.get('atm3',0) +print('current solution value',cur,'LP gain over current (1e-9 b)',-res.fun-cur) +json.dump({'sel':{f'{t},{p}':x[i] for (t,p),i in sel.items()},'pv':{f'{k}|{list(q)}':x[i] for (k,q),i in pv.items() if x[i]>1e-9}},open('aglp-out.json','w')) +du=res.eqlin.marginals +d=defaultdict(dict) +for i,k in enumerate(ek): + if k[0]=='inv': d[k[2]][k[1]]=-du[i] +for w in WID: + vals=[d[w][t] for t in range(72)] + print('dual width',w,'min %.2f max %.2f mean %.2f'%(min(vals),max(vals),sum(vals)/72)) +print('per-time dual(9)-dual(8):',[round(d[9][t]-d[8][t],2) for t in range(72)]) +print('per-time dual(8)-dual(7):',[round(d[8][t]-d[7][t],2) for t in range(72)]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad.py new file mode 100644 index 0000000000..910e4d5198 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad.py @@ -0,0 +1,24 @@ +import sys,sympy +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +tau=lambda x: x^(43 if bin(x&43).count('1')%2 else 0) +LEAN={(30,):(256,128,64,32,18,10,6,1),(45,):(256,128,64,33,16,9,5,2),(51,):(256,128,64,33,17,8,4,3)} +LEANRAW={span([tau(x) for x in E]):span([tau(x) for x in U]) for E,U in LEAN.items()} +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +for E,U in LEANRAW.items(): + QU={g for g in range(n) if inside(endB[g],U)} + # any helper whose start frame is inside E (cold or E itself) + H=sorted(h for h in range(2*v,n) if inside(start[h],E)) + ended=[h for h in H if h in hf] + colsD=sorted({g for h in H for g,x in Pinv[h].items() if x and g not in QU}) + S=set(colsD);rk=modrank([{g:x for g,x in Pinv[h].items() if g in S} for h in H]) + Hp=[h for h in H if not (start[h]==E and h not in hf)] + rk2=modrank([{g:x for g,x in Pinv[h].items() if g in S} for h in Hp]) + print(' without nonended E helpers: |H|',len(Hp),'nullity',len(Hp)-rk2) + Hc=[h for h in H if start[h]==()] + rk3=modrank([{g:x for g,x in Pinv[h].items() if g in S} for h in Hc]);print(' cold only',len(Hc),'nullity',len(Hc)-rk3,'cold ended',len([h for h in Hc if h in hf])) + print(E,'|H|',len(H),'ended',len(ended),'left-nullity',len(H)-rk) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad2.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad2.py new file mode 100644 index 0000000000..6ef94ddfac --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/broad2.py @@ -0,0 +1,31 @@ +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +first={} +for part in ('A','B'): + for e in w[part]: + for r in regs(e): first.setdefault(r,span(e[-1])) +tau=lambda x: x^(43 if bin(x&43).count('1')%2 else 0) +LEAN={(30,):(256,128,64,32,18,10,6,1),(45,):(256,128,64,33,16,9,5,2),(51,):(256,128,64,33,17,8,4,3)} +LEANRAW={span([tau(x) for x in E]):span([tau(x) for x in U]) for E,U in LEAN.items()} +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +for E,U in LEANRAW.items(): + QU={g for g in range(n) if inside(endB[g],U)} + Hc=[h for h in range(2*v,n) if start[h]==() and h not in hf and inside(E,first.get(h,span([511]*0) or E))] + HE=[h for h in range(2*v,n) if start[h]==E] + H=HE+Hc + S={g for h in H for g,x in Pinv[h].items() if x and g not in QU} + rk=modrank([{g:x for g,x in Pinv[h].items() if g in S} for h in H]) + rk2=modrank([{g:x for g,x in Pinv[h].items() if g in S} for h in [h for h in HE if h in hf]+Hc]) + print(E,'E-group',len(HE),'ended',len([h for h in HE if h in hf]),'mixable cold',len(Hc),'nullity',len(H)-rk,'nullity without nonended E',len(H)-len([h for h in HE if h not in hf])-rk2) + print(' cold mixable:',[(h,first.get(h),endB[h],h in hf) for h in Hc]) + if not Hc: continue + rb=nullspace_rows([Pinv[h] for h in H],S) + for pv,r in rb.items(): + hs={H[j]:str(c) for j,c in r.items()} + if any(H[j] in HE and H[j] not in hf for j in r): + print(' null vec',hs) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/cands.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/cands.py new file mode 100644 index 0000000000..7676aba354 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/cands.py @@ -0,0 +1,38 @@ +import sys +from common import * +V={0:0,1:-3.987,2:-6.531,3:-8.532,4:-10.181,5:-11.569,6:-12.748,7:-13.755,8:-14.614,9:-15.344} +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]); avals=[int(x) for x in sys.argv[3].split(',')] +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +H0=[h for h in range(2*v,n) if start[h]==() and h not in hf] +def dy(q): return q.denominator&(q.denominator-1)==0 +for a in avals: + U=span([x for x in range(1,512) if bin(x&a).count('1')%2==0]) + QU={g for g in range(n) if inside(endB[g],U)} + keepR={g for h in H0 for g,x in Pinv[h].items() if x and g not in QU} + rb=nullspace_rows([Pinv[h] for h in H0],keepR) + # column null: m over H0 with sum_j P[g][H0[j]] m_j = 0 for g not in QU -> left null of columns + rowsC=[g for g in range(n) if g not in QU] + cols=[{g:P[g][h] for g in rowsC if P[g].get(h)} for h in H0] + cb=nullspace_rows(cols,set(rowsC)) + print('U a=%d'%a,U,'rownull',len(rb),'colnull',len(cb)) + for k,r in sorted(rb.items()): + hk=H0[k] + if not inside(endB[hk],U): continue + row={} + for j,c in r.items(): + for g,x in Pinv[H0[j]].items(): + y=row.get(g,0)+c*x + if y:row[g]=y + else:row.pop(g,None) + # column: try m=e_k + colk={g:P[g][hk] for g in range(n) if P[g].get(hk)} + okcol=all(g in QU for g in colk if g!=hk) + partners=(set(row)|(set(colk) if okcol else set()))-{hk} + tg=[g for g in partners if v<=g<2*v] + cost=sum(V[8-len(endB[g])]+V[1]-V[9-len(endB[g])] if len(endB[g])<8 else 0 for g in partners) + gain=V[8-len(endB[hk])]-V[9-len(endB[hk])] + print(' k',hk,'endB',len(endB[hk]),'premix',{H0[j]:str(c) for j,c in r.items() if j!=k},'selfcoef',row.get(hk),'colOK',okcol,'partners',sorted((g,len(endB[g])) for g in partners),'targets',tg,'dyadic',all(dy(x) for x in row.values()),'net %.2f'%(gain+cost)) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coal.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coal.py new file mode 100644 index 0000000000..4ba14acf79 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coal.py @@ -0,0 +1,44 @@ +import sys,json,gzip,copy,time +from collections import Counter +GK='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold/vendor/pr558/vendor/pr552/complex-local/static44/gk' +sys.path.insert(0,GK) +import gk_word as G,gk_common as C +G.V=C.V=112;G.DATA=C.DATA=7168 +V={int(k):v for k,v in json.load(open('V.json')).items()} +w=json.loads(gzip.open(sys.argv[1]).read()) +regs=lambda e:[e[1]]+([e[2]] if e[0]=='add' else []) +base=G.profile(w) +val=lambda p:sum(V[r]*c for r,c in p.items()) +print('base',dict(base),val(base)) +# next frame per register occurrence +occ={} +seq=[('A',i,e) for i,e in enumerate(w['A'])]+[('B',i,e) for i,e in enumerate(w['B'])] +nxt={} +last={} +for k in range(len(seq)-1,-1,-1): + part,i,e=seq[k] + for r in regs(e): + nxt[(k,r)]=last.get(r) + last[r]=k +found=[] +t0=time.time() +for k,(part,i,e) in enumerate(seq): + F=G.basis(e[-1]) + cands=set() + nf=[] + for r in regs(e): + j=nxt[(k,r)] + if j is not None: nf.append(G.basis(seq[j][2][-1])) + for f in nf: cands.add(G.basis(list(F)+list(f))) + if len(nf)==2: cands.add(G.basis(list(F)+list(nf[0])+list(nf[1]))) + cands.discard(F) + for Fp in cands: + z=copy.deepcopy(w[part][i]);old=w[part][i];w[part][i]=z;z[-1]=list(Fp) + try: + p=G.profile(w);d=val(p)-val(base) + if d>1e-9: found.append((d,part,i,old,list(Fp),{r:p[r]-base[r] for r in set(p)|set(base) if p[r]!=base[r]})) + except AssertionError: pass + w[part][i]=old +print('scan s',time.time()-t0,'found',len(found)) +for f in sorted(found,reverse=True)[:40]: print(f) +json.dump(found,open('coal-found.json','w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalf.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalf.py new file mode 100644 index 0000000000..9b14ca2377 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalf.py @@ -0,0 +1,45 @@ +import sys,json,gzip,copy,time +from collections import Counter +GK='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold/vendor/pr558/vendor/pr552/complex-local/static44/gk' +sys.path.insert(0,GK) +import gk_word as G,gk_common as C +G.V=C.V=112;G.DATA=C.DATA=7168 +V={int(k):v for k,v in json.load(open('V.json')).items()} +w=json.loads(gzip.open(sys.argv[1]).read()) +regs=lambda e:[e[1]]+([e[2]] if e[0]=='add' else []) +base=G.profile(w) +val=lambda p:sum(V[r]*c for r,c in p.items()) +print('base',dict(base),val(base)) +# next frame per register occurrence +occ={} +seq=[('A',i,e) for i,e in enumerate(w['A'])]+[('B',i,e) for i,e in enumerate(w['B'])] +nxt={} +last={} +for k in range(len(seq)-1,-1,-1): + part,i,e=seq[k] + for r in regs(e): + nxt[(k,r)]=last.get(r) + last[r]=k +found=[] +t0=time.time() +for k,(part,i,e) in enumerate(seq): + F=G.basis(e[-1]) + cands=set() + nf=[] + for r in regs(e): + j=nxt[(k,r)] + if j is not None: nf.append(G.basis(seq[j][2][-1])) + else: nf.append(G.FULL if (r<112 or r>=224) else G.perp(w['ports'][r-112])) + for f in nf: cands.add(G.basis(list(F)+list(f))) + if len(nf)==2: cands.add(G.basis(list(F)+list(nf[0])+list(nf[1]))) + cands.discard(F) + for Fp in cands: + z=copy.deepcopy(w[part][i]);old=w[part][i];w[part][i]=z;z[-1]=list(Fp) + try: + p=G.profile(w);d=val(p)-val(base) + if d>1e-9: found.append((d,part,i,old,list(Fp),{r:p[r]-base[r] for r in set(p)|set(base) if p[r]!=base[r]})) + except AssertionError: pass + w[part][i]=old +print('scan s',time.time()-t0,'found',len(found)) +for f in sorted(found,reverse=True)[:40]: print(f) +json.dump(found,open('coal-found-final.json','w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalfinal.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalfinal.py new file mode 100644 index 0000000000..d0af182bb1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coalfinal.py @@ -0,0 +1,34 @@ +import sys,json,gzip,copy,time +from collections import Counter +GK='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold/vendor/pr558/vendor/pr552/complex-local/static44/gk' +sys.path.insert(0,GK) +import gk_word as G,gk_common as C +G.V=C.V=112;G.DATA=C.DATA=7168 +V={int(k):v for k,v in json.load(open('V.json')).items()} +w=json.loads(gzip.open(sys.argv[1]).read()) +regs=lambda e:[e[1]]+([e[2]] if e[0]=='add' else []) +base=G.profile(w);val=lambda p:sum(V[r]*c for r,c in p.items()) +P=w['ports'] +def finalframe(r): + if r<112 or r>=224: return G.FULL + return G.perp(P[r-112]) +seq=[('A',i,e) for i,e in enumerate(w['A'])]+[('B',i,e) for i,e in enumerate(w['B'])] +lastk={} +for k,(part,i,e) in enumerate(seq): + for r in regs(e): lastk[r]=k +found=[] +for k,(part,i,e) in enumerate(seq): + rs=regs(e) + if not any(lastk[r]==k for r in rs): continue + F=G.basis(e[-1]);fin=[finalframe(r) for r in rs if lastk[r]==k] + Fp=G.basis(list(F)+list(min(fin,key=len))) + if Fp==F: continue + old=w[part][i];w[part][i]=old[:-1]+[list(Fp)] + try: + p=G.profile(w);d=val(p)-val(base) + found.append((round(d,3),part,i,old,len(Fp),{r:p[r]-base[r] for r in set(p)|set(base) if p[r]!=base[r]})) + except AssertionError as ex: found.append(('ERR',part,i,old,str(ex)[:80])) + w[part][i]=old +print(len(found)) +c=Counter((x[0] if x[0]=='ERR' else 'ok') for x in found);print(c) +for f in found[:30]:print(f) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coldsurvey.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coldsurvey.py new file mode 100644 index 0000000000..b5849ab78c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/coldsurvey.py @@ -0,0 +1,18 @@ +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +res=Counter();cands=[] +for h in range(2*v,n): + if h in hf: continue + supp={g for g,x in Pinv[h].items() if x}|{g for g in range(n) if P[g].get(h)}|{h} + J=span([x for g in supp for x in endB[g]]) + kind='cold' if start[h]==() else 'E%d'%len(start[h]) + res[(kind,len(J))]+=1 + if len(J)<=8: cands.append((h,kind,len(J),endB[h],len(supp))) +print(sorted(res.items())) +for c in cands: print(c) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/common.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/common.py new file mode 100644 index 0000000000..3b29c24869 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/common.py @@ -0,0 +1,99 @@ +import json,gzip,sys,copy +from fractions import Fraction as Q +from collections import Counter,defaultdict +PKG='/Users/joelpulikkan/imb-r1/research/complex-571-cold14-e1' +sys.path.insert(0,PKG+'/code') +import build_cold_end as BCE +import hpe_audit_and_transport as ATC +span=BCE.span +def rd(p): + b=open(p,'rb').read() + return json.loads(gzip.decompress(b) if str(p).endswith('.gz') else b) +def regs(e): return [e[1]]+([e[2]] if e[0]=='add' else []) +def inside(a,b): return span(list(a)+list(b))==span(b) +def frames_endB(w): + """raw-coordinate frame of every register after A, B (before tail)""" + v,R=w['report']['v'],w['report']['R'];n=2*v+R + cur=[() for _ in range(n)] + for i,p in enumerate(w['ports']):cur[i]=span([p]) + for r,f in w.get('start_extra',{}).items():cur[int(r)]=span(f) + start=list(cur) + for part in ('A','B'): + for e in w[part]: + for r in regs(e):cur[r]=span(e[-1]) + return start,cur +def main_maps(w): + v,R=w['report']['v'],w['report']['R'];n=2*v+R + main=w['A']+w['cut']+w['B'] + m=[{r:Q(1)} for r in range(n)] + for e in main:ATC.apply(e,m) + dirty=[{} for _ in range(n)] + for r in range(v,2*v):dirty[r]={j:x for j,x in m[r].items() if j>=2*v} + for e in w['precondition']:ATC.apply(e,dirty) + comp=[['add',r,j,-q.numerator,q.denominator,[]] for r in range(v,2*v) for j,q in sorted(dirty[r].items())] + P,Pinv=ATC.state_maps(w['precondition']+comp+main,n) + return P,Pinv,comp +PR=2**61-1 +def modrank(rows): + """rank mod a large prime of a list of dict rows {col: Fraction}""" + piv={} + r=0 + for row in rows: + x={c:(q.numerator*pow(q.denominator,-1,PR))%PR for c,q in row.items() if q} + x={c:a for c,a in x.items() if a} + while x: + c=min(x) + if c in piv: + p=piv[c];f=x[c] + for cc,a in p.items(): + x[cc]=(x.get(cc,0)-f*a)%PR + if not x[cc]:del x[cc] + else: + inv=pow(x[c],-1,PR);piv[c]={cc:a*inv%PR for cc,a in x.items()};r+=1;break + return r +def nullspace_rows(rows, keep): + """left null space of the matrix whose rows are rows[i] (dict col->Q), restricted to columns in keep. + returns RREF basis vectors as dicts i->Q (indices into rows).""" + # augment: row i -> (restricted row, identity tag) + aug=[({c:x for c,x in r.items() if c in keep and x},{i:Q(1)}) for i,r in enumerate(rows)] + piv={} + null=[] + for a,t in aug: + a=dict(a);t=dict(t) + while a: + c=min(a) + if c in piv: + pa,pt=piv[c];f=a[c] + for cc,x in pa.items(): + y=a.get(cc,0)-f*x + if y:a[cc]=y + else:a.pop(cc,None) + for cc,x in pt.items(): + y=t.get(cc,0)-f*x + if y:t[cc]=y + else:t.pop(cc,None) + else: + f=a[c];piv[c]=({cc:x/f for cc,x in a.items()},{cc:x/f for cc,x in t.items()});break + if not a:null.append(t) + # RREF the null basis on tag coordinates (prefer high index pivots? use min) + basis={} + for t in null: + t=dict(t) + for c in sorted(basis): + if c in t: + f=t[c]; + for cc,x in basis[c].items(): + y=t.get(cc,0)-f*x + if y:t[cc]=y + else:t.pop(cc,None) + if not t:continue + c=max(t);f=t[c];t={cc:x/f for cc,x in t.items()} + for c2 in basis: + if c in basis[c2]: + g=basis[c2][c] + for cc,x in t.items(): + y=basis[c2].get(cc,0)-g*x + if y:basis[c2][cc]=y + else:basis[c2].pop(cc,None) + basis[c]=t + return basis # pivot index -> vector diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/e1probe.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/e1probe.py new file mode 100644 index 0000000000..acf505bf16 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/e1probe.py @@ -0,0 +1,37 @@ +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +P,Pinv,comp=main_maps(w) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +# frames after tail +cur=list(endB) +for i,f in w['early_cleanup']: + for r in regs(w['cleanup'][i]):cur[r]=span(f) +U=span([256,128,64,32,18,10,6,1]) +liftedU={g for g in range(n) if cur[g]==U} +print('registers at U after tails:',len(liftedU),sorted(liftedU)) +print('endB==U:',sorted(g for g in range(n) if endB[g]==U)) +for k in (608,610): + print(k,'row',{g:(str(x),len(endB[g]),g in liftedU) for g,x in Pinv[k].items()}) + print(k,'col',{g:(str(P[g][k]),len(endB[g])) for g in range(n) if P[g].get(k)}) +# per tail gate partner listing +tl=defaultdict(list) +for i,f in w['early_cleanup']: + e=w['cleanup'][i] + for r in regs(e): tl[r].append(i) +E=(30,) +H=[h for h in range(2*v,n) if start[h]==E] +first={} +for part in ('A','B'): + for e in w[part]: + for r in regs(e): first.setdefault(r,span(e[-1])) +Hc=[h for h in range(2*v,n) if start[h]==() and h not in hf and first.get(h)==E] +for drop in ([438],[440],[438,440]): + F=(liftedU-set(drop)) + for HH,name in ((H,'E-group'),(H+Hc,'E-group+cold')): + S={g for h in HH for g,x in Pinv[h].items() if x and g not in F} + rb=nullspace_rows([Pinv[h] for h in HH],S) + print('drop',drop,name,'nullity',len(rb),[{HH[j]:str(c) for j,c in r.items()} for r in rb.values() if any(HH[j] in (608,610) for j in r) or len(r)>1][:6]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/expand617.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/expand617.py new file mode 100644 index 0000000000..d71268dcc8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/expand617.py @@ -0,0 +1,96 @@ +"""expand an aggregated MIP solution to a literal owner map + PLAN (PR569 nominator expansion logic).""" +import json,sys,gzip,hashlib,random,pickle,copy +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import milp,LinearConstraint,Bounds +from scipy.sparse import coo_matrix,csr_matrix +from scipy.sparse.csgraph import maximum_flow +PKG='/Users/joelpulikkan/imb-r1b/research/complex-571-cold14-b3' +PLAN=json.load(open(PKG+'/data/PLAN.json'));reg=PLAN['provider_registry'] +S=pickle.load(open(sys.argv[1],'rb'));OUT=sys.argv[2] +groups,x,disp=S['groups'],S['x'],{int(k):v for k,v in S['disp'].items()} +WID=[4,5,7,8,9] +def expand(plist,flows,seed): + times=sorted({t for t,w in flows});counts=[Counter(p) for p in plist];rng=random.Random(seed) + for attempt in range(60): + used=[set() for _ in plist];result=[[] for _ in plist] + colors=sorted(WID,key=lambda w:(sum(w in p for p in plist),w)) + if attempt:rng.shuffle(colors) + ok=True + for width in colors: + active=[p for p in range(len(counts)) if counts[p][width]] + if not active:continue + rng.shuffle(active);start=1+len(times);sink=start+len(active);rr,cc,vv=[],[],[] + for ti,t in enumerate(times): + d=flows.get((t,width),0) + if d: + rr.append(0);cc.append(1+ti);vv.append(d) + for pi,port in enumerate(active): + if t not in used[port]:rr.append(1+ti);cc.append(start+pi);vv.append(1) + for pi,port in enumerate(active):rr.append(start+pi);cc.append(sink);vv.append(counts[port][width]) + f=maximum_flow(csr_matrix((vv,(rr,cc)),shape=(sink+1,sink+1),dtype=np.int64),0,sink) + if f.flow_value!=sum(flows.get((t,width),0) for t in times):ok=False;break + F=f.flow.tocoo() if hasattr(f.flow,'tocoo') else coo_matrix(f.flow) + for a,b,v in zip(F.row,F.col,F.data): + if v>0 and 1<=a3 else 'free_dispatch_exact_window_pattern_milp',gap=None, + source_plan_sha256=hashlib.sha256(open(PKG+'/data/PLAN.json','rb').read()).hexdigest(),source_owners_sha256=hashlib.sha256(open(PKG+'/data/OWNERS.json.gz','rb').read()).hexdigest(),assignment_sha256=hashlib.sha256(packed).hexdigest()) +open(OUT+'/PLAN.json','w').write(json.dumps(plan,indent=2)+'\n') +print('profile',dict(sorted(paid.items())),'remainders',dict(rem),'dispatch',Counter(disp.values())) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/hypsurvey.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/hypsurvey.py new file mode 100644 index 0000000000..d4088fe7dd --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/hypsurvey.py @@ -0,0 +1,28 @@ +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +H0=[h for h in range(2*v,n) if start[h]==() and h not in hf] +colsupp={h:[g for g in range(n) if P[g].get(h)] for h in H0} +out=[] +for a in range(1,512): + U=span([x for x in range(1,512) if bin(x&a).count('1')%2==0]) # hyperplane = a-perp + QU={g for g in range(n) if inside(endB[g],U)} + K=[h for h in H0 if h in QU] + if not K: continue + S=sorted({g for h in H0 for g,x in Pinv[h].items() if x and g not in QU}) + Sset=set(S) + rr=modrank([{g:x for g,x in Pinv[h].items() if g in Sset} for h in H0]) + Nr=len(H0)-rr + # column condition: m over H0 with P[g,:]m=0 for g not in QU: nullity = |H0| - rank of the (rows not in QU) restricted to H0 columns + rowsC=sorted({g for h in H0 for g in colsupp[h] if g not in QU}) + H0s=set(H0) + rc=modrank([{h:x for h,x in P[g].items() if h in H0s} for g in rowsC]) + Nc=len(H0)-rc + out.append((min(Nr,Nc),Nr,Nc,len(K),a,U)) +out.sort(reverse=True) +for o in out[:25]: print(o) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/mip617.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/mip617.py new file mode 100644 index 0000000000..4c9f5721a8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/mip617.py @@ -0,0 +1,85 @@ +"""free-dispatch exact-window pattern MIP (first-order mass-neutral values), Hall masks, then literal expansion.""" +import json,sys,gzip,hashlib,random,time,itertools +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import milp,LinearConstraint,Bounds +from scipy.sparse import coo_matrix,csr_matrix +from scipy.sparse.csgraph import maximum_flow +V={int(k):v for k,v in json.load(open('V617.json')).items()} +PKG='/Users/joelpulikkan/imb-r1b/research/complex-571-cold14-b3' +PLAN=json.load(open(PKG+'/data/PLAN.json')) +reg=PLAN['provider_registry'];provs=['jp8','atm3'] +TIGHT=int(sys.argv[1]);SECS=float(sys.argv[2]);OUT=sys.argv[3] +inv={p:Counter(reg[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in reg[p]['actual_paid_profile'].items()) for p in provs} +WID=[4,5,7,8,9] +def pats(cap,L): + out=[] + def rec(pre,i,tot): + r=cap-tot + if r<=TIGHT or (r==cap==48): out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)>b&1} + req=tuple(i for (tt,w),i in fl.items() if tt==t and w in cols) + pres=tuple(j for j,c in zip(pv,cnts) if any(c[w] for w in cols)) + if req and (req,pres) not in seen: + seen.add((req,pres));row={i:1 for i in req} + for j in pres:row[j]=row.get(j,0)-1 + con(row,-np.inf,0) + for w in WID: + row={i:1 for (t,ww),i in fl.items() if ww==w} + for j,c in zip(pv,cnts): + if c[w]:row[j]=row.get(j,0)-c[w] + if row:con(row,0,0) + groups[key]=dict(cap=cap,times=times,ps=ps,pv=pv,fl=fl) +for t in range(72): + for w in WID: + row=dict(invrow[t,w]) + for p in provs: + if inv[p][w]:row[sel[t,p]]=-inv[p][w] + con(row,0,0) +rr,cc,vv=[],[],[] +for i,row in enumerate(R): + for j,x in row.items():rr.append(i);cc.append(j);vv.append(x) +A=coo_matrix((vv,(rr,cc)),shape=(len(R),len(ub))).tocsr() +print('vars',len(ub),'cons',len(R),flush=True) +t0=time.time() +res=milp(np.array(cost),integrality=np.ones(len(ub)),bounds=Bounds(np.zeros(len(ub)),np.array(ub)),constraints=LinearConstraint(A,lo,hi),options={'time_limit':SECS,'mip_rel_gap':1e-9,'disp':False}) +print(res.status,res.message,'obj',-res.fun if res.x is not None else None,'secs',time.time()-t0,flush=True) +x=np.round(res.x).astype(int) +disp={t:p for (t,p),i in sel.items() if x[i]} +rem=Counter() +for key,g in groups.items(): + for q,i in zip(g['ps'],g['pv']): + if x[i]:rem[g['cap']-sum(q)]+=x[i] +print('dispatch',Counter(disp.values()),'remainders',dict(rem)) +json.dump(dict(disp=disp,x=x.tolist(),obj=-res.fun),open(OUT+'-sol.json','w')) +# profile +prof=Counter() +for t,p in disp.items():prof.update({int(r):c for r,c in reg[p]['actual_paid_profile'].items()}) +prof.update(rem) +print('profile',dict(sorted(prof.items())),'mass',sum(r*c for r,c in prof.items())) +json.dump({str(k):v for k,v in sorted(prof.items())},open(OUT+'-profile.json','w')) +import pickle;pickle.dump(dict(groups=groups,x=x,disp=disp),open(OUT+'.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/movecoal.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/movecoal.py new file mode 100644 index 0000000000..af903a995d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/movecoal.py @@ -0,0 +1,48 @@ +import sys,json,gzip,copy,time +from multiprocessing import Pool +GK='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold/vendor/pr558/vendor/pr552/complex-local/static44/gk' +sys.path.insert(0,GK) +import gk_word as G,gk_common as C +G.V=C.V=112;G.DATA=C.DATA=7168 +V={int(k):v for k,v in json.load(open('V.json')).items()} +w0=json.loads(gzip.open(sys.argv[1]).read()) +W=int(sys.argv[2]) +def regs(e):return(e[1],e[2])if e[0]=='add'else(e[1],) +def commute(x,y): + if x[0]=='add'and y[0]=='add':return x[1]!=y[2]and y[1]!=x[2] + return not set(regs(x))&set(regs(y)) +val=lambda p:sum(V[r]*c for r,c in p.items()) +base=G.profile(w0);bv=val(base) +def nextframe(L,j,r): + for k in range(j+1,len(L)): + if r in regs(L[k]):return G.basis(L[k][-1]) + return None +def work(args): + part,k=args + w=copy.deepcopy(w0);L=w[part];out=[] + for d in list(range(1,W+1))+[-x for x in range(1,W+1)]: + j=k+d + if not 0<=jk else range(j,k) + if not all(commute(L[k],L[q]) for q in passed):continue + z=copy.deepcopy(w);Z=z[part];Z.insert(j,Z.pop(k)) + e=Z[j];F=G.basis(e[-1]);nf=[f for f in (nextframe(Z,j,r) for r in regs(e)) if f] + cands={G.basis(list(F)+list(f)) for f in nf} + if len(nf)==2:cands.add(G.basis(list(F)+list(nf[0])+list(nf[1]))) + cands.discard(F) + for Fp in cands: + old=Z[j];Z[j]=old[:-1]+[list(Fp)] + try: + p=G.profile(z);dv=val(p)-bv + if dv>1e-9:out.append((dv,part,k,j,old,list(Fp),{r:p[r]-base[r] for r in set(p)|set(base) if p[r]!=base[r]})) + except AssertionError:pass + Z[j]=old + return out +if __name__=='__main__': + tasks=[(p,k) for p in ('A','B') for k in range(len(w0[p]))] + t=time.time() + with Pool(9) as pool:res=pool.map(work,tasks,chunksize=50) + found=[x for r in res for x in r] + print('scan',time.time()-t,'found',len(found)) + for f in sorted(found,reverse=True)[:40]:print(f) + json.dump(found,open('movecoal-'+sys.argv[3]+'.json','w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/nulls.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/nulls.py new file mode 100644 index 0000000000..5f3927d784 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/nulls.py @@ -0,0 +1,26 @@ +import sys,sympy +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +tau=lambda x: x^(43 if bin(x&43).count('1')%2 else 0) +LEAN={(30,):(256,128,64,32,18,10,6,1),(45,):(256,128,64,33,16,9,5,2),(51,):(256,128,64,33,17,8,4,3)} +LEANRAW={span([tau(x) for x in E]):span([tau(x) for x in U]) for E,U in LEAN.items()} +hf={int(k):span(f) for k,f in w['helper_final'].items()} +P,Pinv,comp=main_maps(w) +for E,U in LEANRAW.items(): + H=sorted(int(k) for k,f in w['start_extra'].items() if span(f)==E ) + QU={g for g in range(n) if inside(endB[g],U)} + K=[h for h in H if h in QU] + rowsC=sorted({g for g in range(n) if g not in QU for h in H if P[g].get(h)}) + C=sympy.Matrix([[P[g].get(h,0) for h in H] for g in rowsC]) if rowsC else sympy.zeros(0,len(H)) + colsD=sorted({g for h in H for g,x in Pinv[h].items() if x and g not in QU}) + D=sympy.Matrix([[Pinv[h].get(g,0) for g in colsD] for h in H]) + ncol=C.nullspace() if rowsC else [sympy.eye(len(H))[:,i] for i in range(len(H))] + nrow=D.T.nullspace() + print('E',E,'U',U,'|H|',len(H),'candidates K',K,'ended',[h for h in H if h in hf]) + print(' C rows',len(rowsC),'nullity',len(ncol),' D cols',len(colsD),colsD,'left-nullity',len(nrow)) + def supp(vs): return [[H[i] for i in range(len(H)) if v[i]!=0] for v in vs] + print(' col-null supports',supp(ncol)[:12]) + print(' row-null supports',supp(nrow)[:12]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/price.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/price.py new file mode 100644 index 0000000000..cf550736a3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/price.py @@ -0,0 +1,7 @@ +import sys,json,importlib.util,time +from fractions import Fraction as Q +S='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold' +s=importlib.util.spec_from_file_location('moment',S+'/engines/moment.py');M=importlib.util.module_from_spec(s);s.loader.exec_module(M) +def price(H,live=7168):return Q(M.certify({int(k):v for k,v in H.items()},73,live,False)['lower']) +if __name__=='__main__': + H=json.loads(sys.argv[1]);t=time.time();b=price(H);print(float(b),b,time.time()-t) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe.py new file mode 100644 index 0000000000..ecbe54f535 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe.py @@ -0,0 +1,19 @@ +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +P,Pinv,comp=main_maps(w) +U30=span([256,128,64,32,18,10,6,1]);U24=span([256,128,64,32,24,4,2,1]) +first={} +for part in ('A','B'): + for e in w[part]: + for r in regs(e): + first.setdefault(r,span(e[-1])) +for U,ks in ((U30,[508,532,509,533]),(U24,[484,556,485,557])): + for k in ks: + print('k',k,'start',start[k],'endB',endB[k],{g:str(x) for g,x in Pinv[k].items()}) + for g,x in Pinv[k].items(): + if not inside(endB[g],U): + print(' viol',g,'start',start[g],'first',first.get(g),'endB',endB[g],'row',{gg:str(xx) for gg,xx in Pinv[g].items()},'inQU?',[inside(endB[gg],U) for gg in Pinv[g]]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe2.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe2.py new file mode 100644 index 0000000000..c625f604f6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/probe2.py @@ -0,0 +1,48 @@ +import sys +from common import * +V={0:0,1:-3.987,2:-6.531,3:-8.532,4:-10.181,5:-11.569,6:-12.748,7:-13.755,8:-14.614,9:-15.344} +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +P,Pinv,comp=main_maps(w) +U30=span([256,128,64,32,18,10,6,1]);U24=span([256,128,64,32,24,4,2,1]) +for U,combos in ((U30,[(542,518,Q(1,2)),(543,519,Q(-1)),(554,530,Q(-1)),(555,531,Q(-1))]),(U24,[(566,494,Q(-1)),(567,495,Q(-1)),(578,506,Q(-1)),(579,507,Q(-1))])): + QU={g for g in range(n) if inside(endB[g],U)} + for k,j,c in combos: + row=dict(Pinv[k]) + for g,x in Pinv[j].items(): + y=row.get(g,0)+c*x + if y:row[g]=y + else:row.pop(g) + col={g:P[g][k] for g in range(n) if P[g].get(k)} + colj={g:P[g][j] for g in range(n) if P[g].get(j)} + bad=[g for g in row if g not in QU]; badc=[g for g in col if g!=k and g not in QU] + partners=(set(row)|set(col))-{k} + cost=sum(V[8-len(endB[g])]+V[1]-V[9-len(endB[g])] if len(endB[g])<8 else 0 for g in partners) + gain=V[8-len(endB[k])]-V[9-len(endB[k])] + print(k,j,'row',{g:(str(x),len(endB[g])) for g,x in row.items()},'bad',bad,'col',{g:str(x) for g,x in col.items()},'badc',badc,'net %.2f'%(gain+cost)) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +first={} +for part in ('A','B'): + for e in w[part]: + for r in regs(e): first.setdefault(r,span(e[-1])) +print('--- column fixes') +for E,U,ks in (((30,),U30,[542,543,554,555]),((24,),U24,[566,567,578,579])): + QU={g for g in range(n) if inside(endB[g],U)} + H=[h for h in range(2*v,n) if h not in hf and (start[h]==E or (start[h]==() and first.get(h)==E))] + rowsC=[g for g in range(n) if g not in QU] + cols=[{g:P[g][h] for g in rowsC if P[g].get(h)} for h in H] + cb=nullspace_rows(cols,set(rowsC)) + print(E,'H',len(H),'colnull',len(cb)) + for pv,m in cb.items(): + if any(H[j] in ks for j in m): + col={} + for j,c in m.items(): + for g in range(n): + x=P[g].get(H[j]) + if x: + y=col.get(g,0)+c*x + if y:col[g]=y + else:col.pop(g) + print(' m',{H[j]:str(c) for j,c in m.items()},'col support',{g:(str(x),len(endB[g])) for g,x in col.items()}) diff --git a/research/e8-decoder-floors-and-cold14-notes/r1-cold14/survey.py b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/survey.py new file mode 100644 index 0000000000..8f60de6768 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r1-cold14/survey.py @@ -0,0 +1,28 @@ +"""survey entering helpers: start frame, end frame, cone violations for Lean (E,U) pairs.""" +import sys +from common import * +hpe_word=rd(sys.argv[1]); recipe=rd(sys.argv[2]) +w=BCE.apply(copy.deepcopy(hpe_word),recipe) +v,R=w['report']['v'],w['report']['R'];n=2*v+R +start,endB=frames_endB(w) +tau=lambda x: x^(43 if bin(x&43).count('1')%2 else 0) +LEAN={(30,):(256,128,64,32,18,10,6,1),(45,):(256,128,64,33,16,9,5,2),(51,):(256,128,64,33,17,8,4,3)} +LEANRAW={span([tau(x) for x in E]):span([tau(x) for x in U]) for E,U in LEAN.items()} +print('raw lean pairs',LEANRAW) +hf={int(k):span(f) for k,f in w['helper_final'].items()} +ent=Counter();groups=defaultdict(list) +for k,f in w['start_extra'].items(): + E=span(f);h=int(k);ent[(len(E),E in LEANRAW, h in hf)]+=1 + if len(E)==1 and h not in hf: groups[E].append(h) +print(ent) +for E,hs in groups.items(): print('E',E,'lean' if E in LEANRAW else '', len(hs), hs) +P,Pinv,comp=main_maps(w) +premixed={r for e in recipe['premix'] for r in regs(e)} +for E,hs in groups.items(): + if E not in LEANRAW: continue + U=LEANRAW[E] + QU={g for g in range(n) if inside(endB[g],U)} + for h in hs: + rowv=[g for g,x in Pinv[h].items() if x and g not in QU] + colv=[g for g in range(n) if g!=h and P[g].get(h) and g not in QU] + print(h,'premixed' if h in premixed else '','row',len(rowv),rowv[:8],'col',len(colv),colv[:8],'endB',endB[h]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/REPORT.md b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/REPORT.md new file mode 100644 index 0000000000..35b8a72eaa --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/REPORT.md @@ -0,0 +1,21 @@ +# S2 report: cheaper decoder on the E8 unit (stopped early, budget) + +Work dir: $SP/s2/work (scripts; python = $SP/venv/bin/python). No package/verify.sh written yet. No kappa claim. + +## Results (own runs, exact unless noted) +1. **P111 (shipped bit puncture 39,52,57,71,76,78,103,109,114): 9-total F2 decoder unique (= B0|live) and c=8 UNSAT** (CaDiCaL, no proof log). `uniq.py

` -> `uniq111.log`. So every 9-total decoder on P111 is a label decoder. +2. **Theorem C floor = star floor on the realised units (label-decoder family, any number of totals):** + - P120 bit: all 511 reader sets R(p) span Q^8 (`t1.py`), so every 9-total decoder costs exactly 72 under pairing rank (= star). + - P111 bit: 9 deficient functionals (the punctured labels); pair costs 6 (44 ordered orthogonal punctured pairs), 7, 8 (`p111c.py`). Exact SAT over all decompositions I_9 = sum p_j q_j^T (n<=10 suffices for cost<=62): **cost <= 62 infeasible** (all cost-62 type combos UNSAT; calibration at 63 SAT) (`run62.py

62|63`, `dec9sat.py`). Theorem C min = 63 = star; no slack. + - P112 complex, frame decoder: ThmC 64 = star 64 (`cxp.py`; 2025 frames enumerated). + - Slack mechanism identified: bit side only when a total's readers lie in a hyperplane (drop to g = , cost 6 for orthogonal punctured pairs); complex side also via degenerate source spans. Realizability caveats: reader frame chains, copy terminal frame g != 0 (restoration), (B2) common cut. + - Random search over other 9-line punctures for ThmC <= 62: 1 tested before stop, none found (`search62.py`). +3. **Deep punctures with label (bit) decoders:** SA over p=9..26 (star, `sa_bit.py`) never below cst 63 (x 63/111 best). Sub-root-system units (`subsys.py`): D8 57/56, E7+A1 50/64, A8 63/36 etc. — all far worse; label decoders degrade with depth. Not an exhaustive result. +4. **Mixed units (bit, non-root Q-label):** hyperplane census by type (`hyper.py`, exact W(E8) orbits: E7 120, D7 1080, E6+A1 3360, A7 8640; 8 types). Adding a non-root port needs >= 14 punctured lines (`addports.py`, sampled normals); D8-axis construction (`mixed_d8.py`) gives x >= 0.67. No gain. +5. **Asymmetric sinks (complex P112):** no free sink relaxation (0 of 28,560 (t,w) pairs, float rank; `asym.py`). Rank-1 shared extra total reproduces C.6 (2 sinks / copy unit, `asym2.py`); per-target two-hyperplane covers abundant (target 75: 263,014 covers, 146 sinks, `covers.py`). Local search for >2 targets per rank-1 total (`asym_ls.py`) stopped without improvement over 2. Pricing: break-even needs (sinks/unit) x (helpers/sink) >= ~20 at 0.14% b; with 2 sinks/unit and <= 4.4 shipped helpers per target (496/112), max ~1.2% b vs 2.82% cost -> net negative (argued, optimistic bound). +6. SAT for 10-total P120 decoders with >=9 E7-hyperplane-cheap totals (`sat_cheap.py 10 1|0`): unresolved after ~40 min, killed. + +## Next steps to resume +- Run `search62.py` over many 9-line (and 10-12 line) punctures; if any ThmC<=62 found, build explicit drop-to- copy tree and check chain/restoration. +- Finish asym rank-1/rank-r group search (vectorise node enumeration) to bound sinks per copy unit. +- Package: README table + verify.sh from the scripts above. diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/addports.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/addports.py new file mode 100644 index 0000000000..35ba9572d7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/addports.py @@ -0,0 +1,37 @@ +"""Mixed units: can a non-root port be added? Bit side: Q-label lam needs an odd F2 label z with =0 for every +live line S with != 0. Enumerate hyperplane flats (rank-7 sets of roots) -> normals lam.""" +import numpy as np, itertools +from geo import * +from collections import Counter +from sympy import Matrix +# hyperplane flats: all subsets of lines spanning a rank-7 space, maximal: generate from 7 independent lines +rng = np.random.default_rng(1) +seen = {} +Lf = L.astype(float) +for trial in range(200000): + idx = rng.choice(NL, 7, replace=False) + if np.linalg.matrix_rank(Lf[idx]) < 7: continue + # normal vector + M = Matrix(L[idx].tolist()); ns = M.nullspace()[0] + lam = np.array([int(x) for x in (ns * np.lcm.reduce([int(t.q) for t in ns]))]) + g = np.gcd.reduce(np.abs(lam)); lam //= g + if lam[np.nonzero(lam)[0][0]] < 0: lam = -lam + key = tuple(lam) + if key in seen: continue + O = [i for i in range(NL) if L[i] @ lam == 0] + seen[key] = O +print('hyperplane normals found', len(seen)) +odd = [z for z in range(1, 512) if bin(z).count('1') % 2 == 1] +types = Counter() +best = {} +for key, O in seen.items(): + lam = np.array(key) + N = [i for i in range(NL) if L[i] @ lam != 0] + # min over odd z of |{S in N : =1}| (lines to puncture); if lam is itself a root line, exclude it + selfi = LIDX.get(tuple(lam * (2 // max(1, int(np.max(np.abs(lam)))))), None) if False else None + rootline = [i for i in N if abs(G[i, i]) and np.linalg.matrix_rank(np.vstack([L[i], lam]).astype(float)) == 1] + Nn = [i for i in N if i not in rootline] + m = min((sum(lab_dot(z, uint[S]) for S in Nn), z) for z in odd) + t = (len(O), len(Nn), bool(rootline), m[0]) + types[t] += 1 +print(sorted(types.items())) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym.py new file mode 100644 index 0000000000..74a50702a6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym.py @@ -0,0 +1,46 @@ +"""Asymmetric sinks on the complex P112 unit (frame decoder: 8 coordinate totals, cost 64). +A relaxed target t takes a sink w != u_t (w.u_t = 1); its decoder row is n_t = L c + alpha * beta with an extra total +beta_S = on sources Q. Feasible iff (L_t, beta_t) is not in span{(L_S, beta_S) : S in Z(w) \\ {t}}, Z(w) the +live lines with = 1. Cost of the extra total = rank_F2 of the readers' rows ()_{S in Q}.""" +import numpy as np, itertools, sys +from collections import defaultdict +from geo import * +LIVE = list(range(8, 120)) +Lf = L.astype(float) +W_ALL = list(range(1, 512)) +def Zset(w, t): + return [S for S in LIVE if S != t and lab_dot(w, uint[S])] +def free_relax(): + out = [] + for t in LIVE: + for w in W_ALL: + if w == uint[t] or not lab_dot(w, uint[t]): continue + Z = Zset(w, t) + r1 = np.linalg.matrix_rank(Lf[Z]); r2 = np.linalg.matrix_rank(np.vstack([Lf[Z], Lf[t]])) + if r2 > r1: out.append((t, w)) + return out +def feasible_pairs(Q, g): + Qs = set(Q) + beta = {S: float(L[S] @ g) if S in Qs else 0.0 for S in LIVE} + ext = {S: np.append(Lf[S], beta[S]) for S in LIVE} + res = [] + for t in LIVE: + for w in W_ALL: + if w == uint[t] or not lab_dot(w, uint[t]): continue + Z = Zset(w, t) + M = np.array([ext[S] for S in Z]) + r1 = np.linalg.matrix_rank(M); r2 = np.linalg.matrix_rank(np.vstack([M, ext[t]])) + if r2 > r1: + row = tuple(lab_dot(w, uint[S]) for S in Q) + res.append((t, w, row)) + return res +def best_rank1(res): + groups = defaultdict(set) + for t, w, row in res: + if any(row): groups[row].add(t) + if not groups: return 0, None + row, ts = max(groups.items(), key=lambda kv: len(kv[1])) + return len(ts), sorted(ts) +if __name__ == '__main__': + fr = free_relax() + print('free relaxations (no extra total):', len(fr), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym2.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym2.py new file mode 100644 index 0000000000..b09304ba55 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym2.py @@ -0,0 +1,9 @@ +from asym import * +import time +for e, d0 in [(0, 75), (0, 76), (0, 77), (0, 8), (0, 20), (0, 50), (0, 100)]: + g = L[d0] + Q = [S for S in LIVE if L[S] @ L[e] == 0 and L[S] @ g != 0] + t0 = time.time() + res = feasible_pairs(Q, g) + k, ts = best_rank1(res) + print('e', e, 'd0', d0, '|Q|', len(Q), 'feasible (t,w)', len(res), 'targets', len({r[0] for r in res}), 'best rank-1 group', k, ts, round(time.time()-t0,1), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_ls.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_ls.py new file mode 100644 index 0000000000..c41fd19037 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_ls.py @@ -0,0 +1,45 @@ +"""Local search over Q (support of the extra total, beta = on Q) maximising the number of relaxed targets that can +share ONE rank-1 extra total (identical sink rows on Q). Complex P112 unit, frame decoder.""" +import numpy as np, random, sys, time +from collections import defaultdict +from geo import * +LIVE = list(range(8, 120)); LIVEMASK = sum(1 << i for i in LIVE) +Lf = L.astype(float) +TW = [(t, w) for t in LIVE for w in range(1, 512) if w != uint[t] and lab_dot(w, uint[t])] +ZL = {tw: np.array(members(Rmask[tw[1]] & LIVEMASK & ~(1 << tw[0]))) for tw in TW} +PINV = {tw: np.linalg.pinv(Lf[ZL[tw]]) for tw in TW} +def evaluate(Q, g): + Qa = np.zeros(NL); Qa[list(Q)] = 1 + beta = (Lf @ g) * Qa + groups = defaultdict(set) + for (t, w) in TW: + Z = ZL[(t, w)] + h = PINV[(t, w)] @ beta[Z] + if np.abs(Lf[Z] @ h - beta[Z]).max() > 1e-7: continue + if abs(beta[t] - Lf[t] @ h) < 1e-7: continue + row = tuple(sorted(S for S in Q if (Rmask[w] >> S) & 1)) + if not row: continue + groups[row].add(t) + if not groups: return 0, None + row, ts = max(groups.items(), key=lambda kv: len(kv[1])) + return len(ts), (row, sorted(ts)) +if __name__ == '__main__': + d0, e, seed, iters = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4]) + g = Lf[d0]; rng = random.Random(seed) + Q = set(S for S in LIVE if L[S] @ L[e] == 0 and L[S] @ L[d0] != 0) + cand = [S for S in LIVE if L[S] @ L[d0] != 0] + best = evaluate(Q, g); cur = best[0] + print('start', best[0], best[1][1] if best[1] else None, flush=True) + t0 = time.time() + for it in range(iters): + Q2 = set(Q) + S = rng.choice(cand) + if S in Q2: Q2.remove(S) + else: Q2.add(S) + if not Q2: continue + r = evaluate(Q2, g) + if r[0] >= cur: + Q, cur = Q2, r[0] + if r[0] > best[0]: + best = r; print(it, 'best', r[0], r[1][1], 'Q', sorted(Q), round(time.time() - t0), flush=True) + print('final', best[0], best[1]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_nodes.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_nodes.py new file mode 100644 index 0000000000..ef8a141f19 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/asym_nodes.py @@ -0,0 +1,72 @@ +"""Nodes for rank-1 shared extra totals with beta = on Q (complex P112, frame decoder). +node = (t, w, n1, case, mu) with required-in set I and required-out set O for Q.""" +import pickle, numpy as np, sys +from fractions import Fraction as Fr +from geo import * +H = pickle.load(open('hyper.pkl', 'rb')) +NORMALS = [np.array(n, dtype=np.int64) for sz in H for (n, m) in H[sz]] +NMASK = [m for sz in H for (n, m) in H[sz]] +LIVE = list(range(8, 120)); LIVEMASK = sum(1 << i for i in LIVE) +def nodes_for_g(g, targets=LIVE): + g = np.array(g, dtype=np.int64) + pg = L @ g + out = [] + for t in targets: + for w in range(1, 512): + if w == uint[t] or not lab_dot(w, uint[t]): continue + Z = Rmask[w] & LIVEMASK & ~(1 << t) + Zl = members(Z) + for k, n1 in enumerate(NORMALS): + pn = L @ n1 + inH = [S for S in Zl if pn[S] == 0] + rest = [S for S in Zl if pn[S] != 0] + mus = {Fr(int(pg[S]), int(pn[S])) for S in rest} + if len(mus) != 1: continue + mu = mus.pop() + for case in ('A', 'B'): + # h = mu*n1 (A) or h = g - mu*n1 (B) + if case == 'A': + I = [S for S in rest] + Ob = [S for S in inH if pg[S] != 0] + if any(pg[S] == 0 for S in I): continue + hdot_t = mu * int(pn[t]) + else: + I = [S for S in inH if pg[S] != 0] + Ob = [S for S in rest if pg[S] != 0] + hdot_t = int(pg[t]) - mu * int(pn[t]) + if mu == 0 and case == 'A': continue + out.append(dict(t=t, w=w, k=k, case=case, mu=mu, I=frozenset(I), O=frozenset(Ob), hdot_t=hdot_t, + gt=int(pg[t]), Z=Z)) + return out +def compatible(a, b): + if a['t'] == b['t']: return False + if a['I'] & b['O'] or b['I'] & a['O']: return False + Q = a['I'] | b['I'] + for S in Q: + ra = 1 if (a['Z'] >> S & 1) or S == a['t'] else 0 + rb = 1 if (b['Z'] >> S & 1) or S == b['t'] else 0 + if ra != rb: return False + # diag: beta_t (= g.t if t in Q else 0) != h.t + for x in (a, b): + bt = x['gt'] if x['t'] in Q else 0 + if bt == x['hdot_t']: return False + return True +def node_ok_alone(a): + bt = a['gt'] if a['t'] in a['I'] else 0 + if bt == a['hdot_t']: return False + if not a['I']: return False # no extra total needed would be a free relaxation (none exist) + return True +if __name__ == '__main__': + import networkx as nx, time + d0 = int(sys.argv[1]) + t0 = time.time() + nodes = [a for a in nodes_for_g(L[d0]) if node_ok_alone(a)] + print('g = line', d0, 'nodes', len(nodes), 'targets', len({a['t'] for a in nodes}), round(time.time() - t0, 1), flush=True) + # dedupe by (t, I, O, row-relevant Z, diag) + Gx = nx.Graph(); Gx.add_nodes_from(range(len(nodes))) + for i in range(len(nodes)): + for j in range(i + 1, len(nodes)): + if compatible(nodes[i], nodes[j]): Gx.add_edge(i, j) + print('edges', Gx.number_of_edges(), flush=True) + cl, sz = nx.max_weight_clique(Gx, weight=None) + print('max rank-1 group', sz, sorted({nodes[i]['t'] for i in cl}), [sorted(nodes[i]['I']) for i in cl][:3], flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/binv.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/binv.py new file mode 100644 index 0000000000..17d70d2b14 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/binv.py @@ -0,0 +1,11 @@ +import numpy as np, sympy +from geo import * +def binv(P): + BD = sympy.Matrix(B0[np.ix_(P, P)].tolist()) + if BD.det() % 2 == 0: return None + return np.array(BD.inv_mod(2).tolist(), dtype=int) +if __name__ == '__main__': + P = [39, 52, 57, 71, 76, 78, 103, 109, 114] + Bi = binv(P); print(Bi) + orth = (G[np.ix_(P, P)] == 0).astype(int) + print('column weights', Bi.sum(0)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/bitp.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/bitp.py new file mode 100644 index 0000000000..d7e3394396 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/bitp.py @@ -0,0 +1,35 @@ +"""Bit-side punctured units with label decoders (M = B0 on the live lines): star cost = min-weight basis of F2^9 under +s(q) = dim span L[R(q) \\ P] (matroid greedy, exact); ThmC cost needs reader spans.""" +import numpy as np +from geo import * +RLIST = {p: np.array(members(Rmask[p])) for p in range(1, 512)} +def f2_indep_add(basis, x): + for b in basis: + x = min(x, x ^ b) + return x +def s_all(Pset): + live = np.ones(NL, bool); live[list(Pset)] = False + s = {} + for q in range(1, 512): + idx = RLIST[q][live[RLIST[q]]] + s[q] = rankQ_lines(list(idx)) + return s +def greedy_basis(w): + order = sorted(range(1, 512), key=lambda q: (w[q], q)) + basis, chosen = [], [] + for q in order: + x = q + for b in sorted(basis, reverse=True): + x = min(x, x ^ b) + if x: + basis.append(x); chosen.append(q) + if len(chosen) == 9: break + return chosen, sum(w[q] for q in chosen) +def star_cost(Pset): + s = s_all(Pset) + return greedy_basis(s) +if __name__ == '__main__': + print(star_cost(set())) + P111 = [39, 52, 57, 71, 76, 78, 103, 109, 114] + print(star_cost(set(P111))) + print(star_cost(set(range(8)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cert.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cert.py new file mode 100644 index 0000000000..351fbd107c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cert.py @@ -0,0 +1,133 @@ +"""Certified (rational interval) pressure-root tools. +g_q(r) = r*((m/r)^q - 1). Root of sum_i z_i g_q(r_i) = D (increasing in q). +All transcendental values are enclosed by rational intervals (Fraction endpoints, outward rounding).""" +from fractions import Fraction as Fr +import math + +DEN = 10**45 # outward rounding grid + + +def rd(x): # round down to grid + return Fr(math.floor(x * DEN), DEN) + + +def ru(x): + return Fr(math.ceil(x * DEN), DEN) + + +def ln_iv(x): + """enclosure of ln(x), x = Fraction > 0, via ln x = 2 atanh(z), z=(x-1)/(x+1); reduce by powers of 2.""" + x = Fr(x) + assert x > 0 + if x == 1: + return (Fr(0), Fr(0)) + # reduce: x = 2^e * y, y in [1/sqrt2, sqrt2] (use [2/3, 4/3]-ish via loop) + e = 0 + while x > Fr(4, 3): + x /= 2; e += 1 + while x < Fr(2, 3): + x *= 2; e -= 1 + lo2, hi2 = LN2 + lo, hi = _atanh2(x) + return (rd(lo + e * (lo2 if e >= 0 else hi2)), ru(hi + e * (hi2 if e >= 0 else lo2))) + + +def _atanh2(x): + z = (x - 1) / (x + 1) + az = abs(z) + s = Fr(0); t = z; k = 0 + N = 40 + for k in range(N): + s += t / (2 * k + 1) + t *= z * z + # tail bound: |sum_{k>=N} z^(2k+1)/(2k+1)| <= |z|^(2N+1)/((2N+1)(1-z^2)) + tail = az ** (2 * N + 1) / ((2 * N + 1) * (1 - z * z)) + s = 2 * s; tail = 2 * tail + return (rd(s - tail), ru(s + tail)) + + +def _ln2(): + # ln 2 = 2 atanh(1/3) + z = Fr(1, 3); s = Fr(0); t = z + N = 60 + for k in range(N): + s += t / (2 * k + 1); t *= z * z + tail = z ** (2 * N + 1) / ((2 * N + 1) * (1 - z * z)) + return (rd(2 * s - 2 * tail), ru(2 * s + 2 * tail)) + + +LN2 = _ln2() + + +def expm1_iv(lo, hi): + """enclosure of e^y - 1 for y in [lo,hi], 0 <= lo <= hi <= 1.""" + assert 0 <= lo <= hi <= 2 + N = 30 + + def ser(y): + s = Fr(0); t = Fr(1) + for i in range(1, N + 1): + t = t * y / i; s += t + return s + slo = ser(lo) # all terms >= 0 -> lower bound + shi = ser(hi) + 8 * hi ** (N + 1) / math.factorial(N + 1) # tail <= y^(N+1)/(N+1)! * e^y, e^y<=8 (y<=2) + return (rd(slo), ru(shi)) + + +def g_iv(r, m, q): + """enclosure of r*((m/r)^q - 1), q Fraction >= 0.""" + if r >= m: + return (Fr(0), Fr(0)) if r == m else None + l_lo, l_hi = ln_iv(Fr(m) / r) + lo, hi = expm1_iv(q * l_lo, q * l_hi) + return (r * lo, r * hi) + + +def F_iv(prof, m, q): + """prof: dict rank -> count (Fractions/ints allowed), returns enclosure of sum z g_q(r).""" + lo = Fr(0); hi = Fr(0) + for r, z in prof.items(): + if z == 0: + continue + a, b = g_iv(Fr(r), Fr(m), Fr(q)) + lo += z * a; hi += z * b + return (rd(lo), ru(hi)) + + +def root_bracket(prof, m, D, lo=Fr(0), hi=Fr(1, 10), iters=60): + """returns (qlo, qhi) with F(qlo) < D (certified via upper enclosure) and F(qhi) >= D (via lower enclosure): + the true root lies in [qlo, qhi]. qhi is a certified UPPER bound of the root.""" + D = Fr(D) + assert F_iv(prof, m, hi)[0] >= D, 'hi too small' + for _ in range(iters): + mid = rd((lo + hi) / 2) + flo, fhi = F_iv(prof, m, mid) + if flo >= D: + hi = mid + elif fhi < D: + lo = mid + else: + break + return lo, hi + + +def cost_iv(prof, m): + """enclosure of Cost = sum z r ln(m/r).""" + lo = Fr(0); hi = Fr(0) + for r, z in prof.items(): + if z == 0 or r >= m: + continue + a, b = ln_iv(Fr(m) / r) + lo += z * r * a; hi += z * r * b + return lo, hi + + +if __name__ == '__main__': + # self-test against floats + for x in (Fr(64, 7), Fr(73, 8), Fr(100, 3), Fr(3, 2), Fr(1, 5)): + lo, hi = ln_iv(x) + assert lo <= Fr(math.log(x)) + Fr(1, 10**12) and hi >= Fr(math.log(x)) - Fr(1, 10**12), x + assert hi - lo < Fr(1, 10**40), (x, float(hi - lo)) + print('ln ok', float(LN2[1] - LN2[0])) + lo, hi = expm1_iv(Fr(1, 100), Fr(1, 100)) + print('expm1(0.01)', float(lo), math.expm1(0.01), float(hi - lo)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/covers.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/covers.py new file mode 100644 index 0000000000..e5dce758dd --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/covers.py @@ -0,0 +1,25 @@ +import pickle, numpy as np, sys +from collections import Counter +from geo import * +H = pickle.load(open('hyper.pkl', 'rb')) +HYP = [(sz, n, m) for sz in H for (n, m) in H[sz]] +LIVE = list(range(8, 120)); LIVEMASK = sum(1 << i for i in LIVE) +Lf = L.astype(float) +def covers_for(t): + out = [] + for w in range(1, 512): + if w == uint[t] or not lab_dot(w, uint[t]): continue + Z = Rmask[w] & LIVEMASK & ~(1 << t) + for sz, n, m in HYP: + rest = Z & ~m + ri = members(rest) + if len(ri) == 0: continue + if np.linalg.matrix_rank(Lf[ri]) <= 7: + out.append((w, n, sz, bin(Z & m).count('1'), len(ri))) + return out +if __name__ == '__main__': + t = int(sys.argv[1]) + cv = covers_for(t) + print('target', t, 'covers', len(cv), 'distinct w', len({c[0] for c in cv})) + print(Counter((c[2], c[3], c[4]) for c in cv).most_common(20)) + pickle.dump(cv, open('covers_%d.pkl' % t, 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cxp.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cxp.py new file mode 100644 index 0000000000..cf54991de6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/cxp.py @@ -0,0 +1,35 @@ +"""Complex-side punctured units with frame decoders. For a frame K (8 pairwise orthogonal lines) the totals are +A_a = R_a = live lines non-orthogonal to f_a (plus f_a if live); ThmC cost rank_F2 B0[A,A], star cost dim span u_A.""" +import numpy as np, itertools +from geo import * +from e8units import f2_rank_rows +def f2rank_ints(vs): return f2_rank_rows(list(vs)) +def gram_rank_f2(idx): + # rank over F2 of B0[idx, idx] + rows = [sum(int(B0[i, j]) << k for k, j in enumerate(idx)) for i in idx] + return f2_rank_rows(rows) +# all frames +def frames(): + out = set() + adj0 = [[G[i, j] == 0 and i != j for j in range(NL)] for i in range(NL)] + def rec(cl, cand): + if len(cl) == 8: out.add(tuple(sorted(cl))); return + for c in cand: + if c > cl[-1]: + rec(cl + [c], [d for d in cand if d > c and G[c, d] == 0]) + for i in range(NL): + rec([i], [d for d in range(NL) if d > i and G[i, d] == 0]) + return sorted(out) +FRAMES = frames() +def cost(Pset, K): + th = st = 0 + for f in K: + A = [S for S in range(NL) if S not in Pset and (G[S, f] != 0)] + th += gram_rank_f2(A); st += f2rank_ints(uint[S] for S in A) + return th, st +def best(Pset): + return min((cost(Pset, K), K) for K in FRAMES) +if __name__ == '__main__': + print('frames', len(FRAMES)) + K0 = tuple(range(8)) + print('P120', cost(set(), K0), 'P112', cost(set(K0), K0)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9.py new file mode 100644 index 0000000000..653a13e7eb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9.py @@ -0,0 +1,75 @@ +"""Generalised label decoders on a 9-line puncture P whose labels are a basis D: Binv = B_D^{-1} = sum_j x_j y_j^T over F2 +(p_j = D x_j readers, q_j = D y_j sources... in D-coordinates). Term cost (pairing rank, Theorem C): + 8 generic; 7 if x_j or y_j is a unit vector; 6 if x_j = e_a, y_j = e_b, a != b, y_a orth y_b (lines). +Star cost: 7 if y_j unit else 8. Modes: 'thmc' (free), 'chain' (all x-unit terms share one a), 'star'.""" +import sys, numpy as np, sympy, itertools +from ortools.sat.python import cp_model +from geo import * +def binv(P): + BD = sympy.Matrix(B0[np.ix_(P, P)].tolist()) + if BD.det() % 2 == 0: return None + return np.array(BD.inv_mod(2).tolist(), dtype=int) +def solve(P, n=9, mode='thmc', tl=60): + Bi = binv(P); k = len(P) + orth = [[G[P[a], P[b]] == 0 and a != b for b in range(k)] for a in range(k)] + m = cp_model.CpModel() + X = [[m.NewBoolVar('') for j in range(n)] for r in range(k)] + Y = [[m.NewBoolVar('') for j in range(n)] for r in range(k)] + A = {} + for r in range(k): + for c in range(k): + ands = [] + for j in range(n): + a = m.NewBoolVar(''); m.AddMultiplicationEquality(a, [X[r][j], Y[c][j]]); ands.append(a) + if Bi[r, c]: m.AddBoolXOr(ands) + else: + t = m.NewBoolVar(''); m.AddBoolXOr(ands + [t]) # xor(ands) xor t = 1 -> xor(ands) = not t ; force t = 1 + m.Add(t == 1) + cost = [] + unitx_a = {} + for j in range(n): + sx = sum(X[r][j] for r in range(k)); sy = sum(Y[r][j] for r in range(k)) + used = m.NewBoolVar(''); m.Add(sx >= 1).OnlyEnforceIf(used); m.Add(sx == 0).OnlyEnforceIf(used.Not()) + m.Add(sy >= 1).OnlyEnforceIf(used); m.Add(sy == 0).OnlyEnforceIf(used.Not()) + ux = m.NewBoolVar(''); m.Add(sx == 1).OnlyEnforceIf(ux); m.Add(sx != 1).OnlyEnforceIf(ux.Not()) + uy = m.NewBoolVar(''); m.Add(sy == 1).OnlyEnforceIf(uy); m.Add(sy != 1).OnlyEnforceIf(uy.Not()) + disc = m.NewBoolVar('') + pairs = [] + for a in range(k): + for b in range(k): + if orth[a][b]: + pv = m.NewBoolVar(''); m.AddBoolAnd([X[a][j], Y[b][j], ux, uy]).OnlyEnforceIf(pv) + m.AddBoolOr([X[a][j].Not(), Y[b][j].Not(), ux.Not(), uy.Not()]).OnlyEnforceIf(pv.Not()) + pairs.append(pv) + m.AddMaxEquality(disc, pairs) if pairs else m.Add(disc == 0) + if mode == 'star': + # star cost: 7 if y unit else 8 + c = m.NewIntVar(0, 8, ''); m.Add(c == 8 * used - uy) + else: + one = m.NewBoolVar(''); m.AddMaxEquality(one, [ux, uy]) + c = m.NewIntVar(0, 8, ''); m.Add(c == 8 * used - one - disc) + cost.append(c) + for a in range(k): + v = m.NewBoolVar(''); m.AddBoolAnd([ux, X[a][j]]).OnlyEnforceIf(v) + m.AddBoolOr([ux.Not(), X[a][j].Not()]).OnlyEnforceIf(v.Not()) + unitx_a.setdefault(a, []).append((v, uy)) + if mode == 'chain': + # x-unit terms are read at unless they are also y-unit with cost 7 read at 0 (column terms, star). Require: + # non-star x-unit terms (not y-unit) and discounted terms all share one a. + sel = [m.NewBoolVar('') for a in range(k)] + m.Add(sum(sel) <= 1) + for a in range(k): + for (v, uy) in unitx_a[a]: + # v and not(uy) -> sel[a] ; discount also implies uy and v -> require sel[a] when the term is discounted + m.AddImplication(v, sel[a]).OnlyEnforceIf(uy.Not()) + # discounted terms: force their a selected + # (approximation: any x-unit & y-unit term with cost 6 needs sel; enforce via cost bookkeeping below) + m.Minimize(sum(cost)) + s = cp_model.CpSolver(); s.parameters.max_time_in_seconds = tl; s.parameters.num_workers = 4 + r = s.Solve(m) + return s.StatusName(r), s.ObjectiveValue(), s.BestObjectiveBound() +if __name__ == '__main__': + P = [39, 52, 57, 71, 76, 78, 103, 109, 114] + for mode in ('star', 'thmc'): + for n in (9, 10, 11): + print(mode, n, solve(P, n, mode, tl=60), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9sat.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9sat.py new file mode 100644 index 0000000000..478562cf59 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/dec9sat.py @@ -0,0 +1,80 @@ +"""Exact: on a 9-line puncture P whose labels form a basis D, is there a generalised label decoder +(I_9 = sum_j p_j q_j^T, i.e. Binv = sum_j x_j y_j^T in D-coordinates) with pairing-rank (Theorem C) cost <= budget? +Term types: '6' x=e_a, y=e_b (a != b, y_a orth y_b); '7r' x=e_a, y free; '7c' y=e_b, x free; '8' free. +For cost <= 62 only n=9 (a6 >= 1 + a8) and n=10 (a6 - a8 >= 8) are possible.""" +import sys, itertools, numpy as np, sympy, time +from pysat.solvers import Cadical153 +from geo import * +class F: + def __init__(s): s.n = 0; s.cl = [] + def new(s): s.n += 1; return s.n + def AND(s, a, b): + z = s.new(); s.cl += [[-z, a], [-z, b], [z, -a, -b]]; return z + def XOR(s, a, b): + z = s.new(); s.cl += [[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]; return z + def onehot(s, lits): + s.cl.append(list(lits)) + for i in range(len(lits)): + for j in range(i + 1, len(lits)): s.cl.append([-lits[i], -lits[j]]) +def binv(P): + BD = sympy.Matrix(B0[np.ix_(P, P)].tolist()) + if BD.det() % 2 == 0: return None + return np.array(BD.inv_mod(2).tolist(), dtype=int) +def feasible(Bi, orth, types): + k = len(Bi); f = F(); X = []; Y = [] + for t in types: + if t == '6': + sel = {(a, b): f.new() for a in range(k) for b in range(k) if orth[a][b]} + f.onehot(list(sel.values())) + x = [f.new() for _ in range(k)]; y = [f.new() for _ in range(k)] + for (a, b), s_ in sel.items(): + for r in range(k): + f.cl.append([-s_, x[r]] if r == a else [-s_, -x[r]]) + f.cl.append([-s_, y[r]] if r == b else [-s_, -y[r]]) + elif t in ('7r', '7c'): + u = [f.new() for _ in range(k)]; f.onehot(u) + v = [f.new() for _ in range(k)]; f.cl.append(v) + x, y = (u, v) if t == '7r' else (v, u) + else: + x = [f.new() for _ in range(k)]; y = [f.new() for _ in range(k)] + f.cl.append(x); f.cl.append(y) + X.append(x); Y.append(y) + for r in range(k): + for c in range(k): + ands = [f.AND(X[j][r], Y[j][c]) for j in range(len(types))] + acc = ands[0] + for a in ands[1:]: acc = f.XOR(acc, a) + f.cl.append([acc] if Bi[r, c] else [-acc]) + s = Cadical153(bootstrap_with=f.cl); r = s.solve() + return r +def combos(budget): + out = [] + for n in (9, 10): + for a6 in range(n + 1): + for a8 in range(n + 1 - a6): + a7 = n - a6 - a8 + if 6 * a6 + 7 * a7 + 8 * a8 <= budget: + for a7r in range(a7 + 1): + out.append(['6'] * a6 + ['7r'] * a7r + ['7c'] * (a7 - a7r) + ['8'] * a8) + return out +def min_cost(P, budget=62): + Bi = binv(P) + if Bi is None: return None + k = len(P); orth = [[G[P[a], P[b]] == 0 and a != b for b in range(k)] for a in range(k)] + for ty in combos(budget): + if feasible(Bi, orth, ty): return ty + return 'none<=%d' % budget +if __name__ == '__main__': + P = [int(x) for x in sys.argv[1].split(',')] + t = time.time(); print(min_cost(P), round(time.time() - t, 1)) +def combos_exact(budget): + cost = {'6': 6, '7r': 7, '7c': 7, '8': 8} + return [t for t in combos(budget) if sum(cost[x] for x in t) == budget] +def check_budget(P, budget=62, verbose=True): + Bi = binv(P) + k = len(P); orth = [[G[P[a], P[b]] == 0 and a != b for b in range(k)] for a in range(k)] + for ty in combos_exact(budget): + t = time.time(); r = feasible(Bi, orth, ty) + if verbose: print(''.join(x[0] + (x[1] if len(x) > 1 else '') + ' ' for x in ty), r, round(time.time() - t, 1), flush=True) + if r: return ty + return None diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/e8units.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/e8units.py new file mode 100644 index 0000000000..c4c1053bd1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/e8units.py @@ -0,0 +1,275 @@ +"""Geometry shared by the E8 / paired-cube checks of research/format-limits: exact linear algebra over F_2 and Q, +the 120 root lines of E8, and the 9-bit labels of a complex or bit unit. Everything is generated here; nothing is +read from a file. + +Conventions. A 'unit' on v ports has an integer Gram matrix G (diagonal 2 for E8) and F_2 labels u_T in F_2^c with +U U^T = (J + G) mod 2, i.e. = 1 + G_ST (mod 2). Vectors of F_2^n are Python integers (bit j = coordinate j) +or 0/1 numpy rows; both representations are used below and the function names say which. +Two coordinate systems of the same 120 E8 root lines are provided. standard_e8(): doubled standard coordinates +(roots +-2e_i +-2e_j and the half-integer (+-1)^8 with an even number of minus signs, G = L L^T/4); the totals +'coordinate a' used for the complex decoder are the 78 lines with a nonzero a-th coordinate. hamming_e8(): lines +numbered as in the Hamming-code construction: + lines 0..7 : the coordinate lines 2 e_a, + lines 8..119 : the 112 'code lines', for each of the 14 weight-4 words of the extended Hamming code [8,4,4] + (support i0 integer with bit j = row[j].""" + return sum(int(x) << j for j, x in enumerate(row)) + + +# --------------------------------------------------------------------------------------------------------------- +# exact rank over Q +def _reduce_int(r, pc, rr): + """r - (r[pc]/rr[pc]) rr, scaled to stay integral and divided by the content.""" + f, g = r[pc], rr[pc] + r = [g * x - f * y for x, y in zip(r, rr)] + c = math.gcd(*r) + return [x // c for x in r] if c > 1 else r + + +def rank_q(rows): + """exact rank over Q of a list of integer rows (fraction-free elimination).""" + red = [] # (pivot column, row) + for r in rows: + r = [int(x) for x in r] + for pc, rr in red: + if r[pc] != 0: + r = _reduce_int(r, pc, rr) + pc = next((j for j, x in enumerate(r) if x != 0), None) + if pc is not None: + red.append((pc, r)) + return len(red) + + +def row_basis_idx_q(rows): + """indices of a maximal independent set of rows over Q (greedy, in order).""" + chosen, red = [], [] + for i, r in enumerate(rows): + r = [int(x) for x in r] + for pc, rr in red: + if r[pc] != 0: + r = _reduce_int(r, pc, rr) + pc = next((j for j, x in enumerate(r) if x != 0), None) + if pc is not None: + red.append((pc, r)) + chosen.append(i) + return chosen + + +def gram_rank_q(L, Q, S): + """rank over Q of the Gram block L_S Q L_S^T (L integer v x h, Q integer h x h, S index list), computed on a + row basis B of L_S: rank(L_S Q L_S^T) = rank(B Q B^T) because L_S = C B with C of full column rank.""" + LS = [[int(x) for x in L[s]] for s in S] + idx = row_basis_idx_q(LS) + if not idx: + return 0 + B = [LS[i] for i in idx] + Qm = [[int(x) for x in row] for row in Q] + BQ = [[sum(b[k] * Qm[k][j] for k in range(len(b))) for j in range(len(Qm[0]))] for b in B] + BQBt = [[sum(x * y for x, y in zip(bq, b2)) for b2 in B] for bq in BQ] + return rank_q(BQBt) + + +# --------------------------------------------------------------------------------------------------------------- +# E8 +def hamming_e8(): + """(L, G): L = 120 x 8 integer root lines (norm 4), G = L L^T / 2 (diagonal 2, off-diagonal 0, +-1).""" + gen = np.array([[1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 0, 1]]) + code = {tuple((np.array(c) @ gen) % 2) for c in itertools.product([0, 1], repeat=4)} + w4 = sorted(c for c in code if sum(c) == 4) + assert len(w4) == 14 + L = [tuple(2 * (np.arange(8) == a).astype(int)) for a in range(8)] + for c in w4: + idx = [i for i in range(8) if c[i]] + for sg in itertools.product([1, -1], repeat=3): + x = np.zeros(8, dtype=int) + x[idx[0]] = 1 + for k in range(3): + x[idx[k + 1]] = sg[k] + L.append(tuple(x)) + L = np.array(L, dtype=np.int64) + G = L @ L.T + assert (G % 2 == 0).all() + G = G // 2 + assert len(L) == 120 and set(np.diag(G)) == {2} and set(G[~np.eye(120, dtype=bool)].tolist()) == {-1, 0, 1} + return L, G + + +def standard_e8(): + """(L, G): the 120 root lines of E8 in doubled standard coordinates (norm 8), G = L L^T / 4. Lines 0..55 are + +-2e_i +-2e_j (i> l & 1: continue + m, rr = closure_mask(basis + [l]) + new.add(m) + flats[r] = new + print('rank', r, len(new), round(time.time() - t, 1), flush=True) + return flats +def row_basis(mem): + b = [] + for i in mem: + if np.linalg.matrix_rank(Lf[b + [i]]) > len(b): b.append(i) + return b +if __name__ == '__main__': + fl = enumerate_flats(int(sys.argv[1]) if len(sys.argv) > 1 else 7) + pickle.dump(fl, open('flats.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/geo.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/geo.py new file mode 100644 index 0000000000..550452ffe9 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/geo.py @@ -0,0 +1,44 @@ +"""E8 geometry for S2: lines, Gram, F2 labels, W(E8) line permutations, exact ranks.""" +import numpy as np, itertools +from functools import lru_cache +from e8units import hamming_e8, e8_labels, f2_rank, rank_q +L, G = hamming_e8() +U9 = e8_labels(G).astype(np.int64) # 120x9, U U^T = (J+G) mod 2 +B0 = ((1 + G) % 2).astype(np.int64) +NL = 120 +uint = [int(''.join(map(str, r)), 2) for r in U9] # label as int (bit 8-j = coord j) +def lab_dot(a, b): return bin(a & b).count('1') & 1 +ALLP = list(range(1, 512)) +# R(p) as python int bitmask over 120 lines +Rmask = {p: sum(1 << T for T in range(NL) if lab_dot(uint[T], p)) for p in range(512)} +def members(mask): + out=[];i=0 + while mask: + if mask & 1: out.append(i) + mask >>= 1; i += 1 + return out +def rankQ_lines(idx): + if not idx: return 0 + return int(np.linalg.matrix_rank(L[idx].astype(float))) +def rankG(R, A): + """exact-enough rank over Q of G[R,A] = L_R L_A^T/2 (entries small ints; rank <= 8; verified exactly elsewhere)""" + if not R or not A: return 0 + return int(np.linalg.matrix_rank((L[R] @ L[A].T).astype(float))) +def rankG_exact(R, A): + if not R or not A: return 0 + return rank_q((L[R] @ L[A].T).tolist()) +# W(E8) generators: reflections in the 120 roots acting on lines +def line_index(): + d = {} + for i, r in enumerate(L): + d[tuple(r)] = i; d[tuple(-r)] = i + return d +LIDX = line_index() +def reflection_perm(r): + r = L[r]; nr = r @ r + perm = [] + for x in L: + y = x - (2 * (x @ r) // nr) * r if (2 * (x @ r)) % nr == 0 else None + perm.append(LIDX[tuple(y)]) + return perm +REFL = [reflection_perm(i) for i in range(NL)] diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/hyper.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/hyper.py new file mode 100644 index 0000000000..8bb6520124 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/hyper.py @@ -0,0 +1,50 @@ +"""All root hyperplanes of E8 by type, as W(E8)-orbits of one normal each (exact integer vectors, hamming coords).""" +import numpy as np, pickle, random +from fractions import Fraction +from math import gcd +from functools import reduce +from geo import * +from sympy import Matrix +def prim(v): + v = [int(x) for x in v]; g = reduce(gcd, [abs(x) for x in v if x] or [1]); v = [x // g for x in v] + i = next(i for i, x in enumerate(v) if x) + return tuple(-x for x in v) if v[i] < 0 else tuple(v) +def refl_vec(x, r): + # s_r(x) = x - 2 (x.r)/(r.r) r ; roots norm 4 in hamming coords + x = np.array(x, dtype=object); r = np.array(r, dtype=object) + num = 2 * int(x @ r); den = int(r @ r) + y = x * den - num * r + return prim(y) +def orth_mask(n): + n = np.array(n, dtype=np.int64) + return sum(1 << i for i in range(NL) if int(L[i] @ n) == 0) +def find_types(seed=1, trials=60000): + rng = random.Random(seed); reps = {} + for _ in range(trials): + idx = rng.sample(range(NL), 7) + M = Matrix(L[idx].tolist()) + if M.rank() < 7: continue + n = prim([x * reduce(lambda a, b: a * b // gcd(a, b), [t.q for t in M.nullspace()[0]]) for x in M.nullspace()[0]]) + sz = bin(orth_mask(n)).count('1') + if sz not in reps: reps[sz] = n + if len(reps) == 8: break + return reps +def orbit(n0): + seen = {n0}; frontier = [n0] + while frontier: + nf = [] + for n in frontier: + for r in range(NL): + m = refl_vec(n, L[r]) + if m not in seen: seen.add(m); nf.append(m) + frontier = nf + return sorted(seen) +if __name__ == '__main__': + reps = find_types() + print({k: v for k, v in sorted(reps.items())}) + H = {} + for sz in sorted(reps, reverse=True): + if sz < 28: continue + orb = orbit(reps[sz]); H[sz] = [(n, orth_mask(n)) for n in orb] + print(sz, len(orb), flush=True) + pickle.dump(H, open('hyper.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/minrank_sat.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/minrank_sat.py new file mode 100644 index 0000000000..99a4a8c833 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/minrank_sat.py @@ -0,0 +1,133 @@ +"""SAT encodings for the F_2 pairing of a decoder pattern. + +A pattern is a set of unordered 'off-cap' pairs {S, T} of ports and the question is whether there are words +u_T (and, in the asymmetric case, v_T) in F_2^c with + = 1 (unit diagonal) + = = 0 for every off-cap pair {S, T} +(symmetric case v = u). Such words exist iff the pairing matrix B = V U^T (B_TS = ) has unit diagonal, +vanishes on the pattern, and has F_2 rank at most c; the smallest c is the F_2 'minrank' of the pattern. + +Symmetry breaking. A set K of pairwise off-cap ports has B[K,K] = I forced, so U_K and V_K are invertible; the action +(U, V) -> (U P, V P^-T) of GL(c, F_2) leaves B fixed and can bring U_K to [I_|K| | 0] and V_K to [I_|K| | 0] +(for |K| = c this is U_K = V_K = I; for |K| < c the last columns of V_K are cleared with the remaining freedom of P). +In the symmetric case O(c, F_2) is transitive on orthonormal bases, so U_K = V_K = [I | 0] as well. + +solve_minrank() is the plain minrank question (gauge only when |K| = c). solve_asym() adds the gauge for any +|K| <= c and the extra constraints 'B is not symmetric' (some free pair has B_TS != B_ST) or 'B differs from B0 = +(J + G) mod 2' (some free pair), and optionally fixes U to given labels. The solver is Cadical 1.5.3 through pysat. +""" +import time + +import numpy as np +from pysat.solvers import Cadical153 + + +class CNF: + def __init__(self): + self.n = 0 + self.cl = [] + + def new(self): + self.n += 1 + return self.n + + def AND(self, a, b): + z = self.new() + self.cl += [[-z, a], [-z, b], [z, -a, -b]] + return z + + def XOR(self, a, b): + z = self.new() + self.cl += [[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]] + return z + + def xorlist(self, lits): + acc = lits[0] + for l in lits[1:]: + acc = self.XOR(acc, l) + return acc + + def parity(self, lits, val): + """add the constraint xor(lits) = val.""" + if not lits: + if val: + self.cl.append([]) + return + acc = self.xorlist(lits) + self.cl.append([acc] if val else [-acc]) + + +def build(v, offpairs, c, sym, clique): + f = CNF() + U = [[f.new() for _ in range(c)] for _ in range(v)] + V = U if sym else [[f.new() for _ in range(c)] for _ in range(v)] + for T in range(v): + f.parity([f.AND(U[T][j], V[T][j]) for j in range(c)], 1) + for (S, T) in offpairs: + f.parity([f.AND(U[S][j], V[T][j]) for j in range(c)], 0) + if not sym: + f.parity([f.AND(U[T][j], V[S][j]) for j in range(c)], 0) + if clique and len(clique) == c: # U_K V_K^T = I => WLOG U_K = V_K = I + for i, T in enumerate(clique): + for j in range(c): + f.cl.append([U[T][j]] if j == i else [-U[T][j]]) + if not sym: + f.cl.append([V[T][j]] if j == i else [-V[T][j]]) + return f + + +def solve_minrank(v, offpairs, c, sym, clique): + """('SAT'|'UNSAT', seconds, variables, clauses) for words in F_2^c on the pattern.""" + f = build(v, offpairs, c, sym, clique) + s = Cadical153(bootstrap_with=f.cl) + t = time.time() + r = s.solve() + return ('SAT' if r else 'UNSAT'), round(time.time() - t, 1), f.n, len(f.cl) + + +def solve_asym(v, pattern, B0, c, mode, K, U_fixed=None): + """Existence of source labels U and sink labels W (v x c over F_2), B = W U^T, with B_TT = 1, B_TS = 0 on the + ordered pairs of `pattern`, gauge U_K = W_K = [I | 0] on the pairwise off-cap set K, and + mode 'none' : no further condition, + mode 'nonsym' : B_TS != B_ST for some free pair (T, S), + mode 'neB0' : B_TS != B0_TS for some free pair. + U_fixed (v x c 0/1 array) additionally fixes U (then K's gauge is dropped). Returns a dict with the result and, + when satisfiable, the pairing B = W U^T and its F_2 rank.""" + f = CNF() + U = [[f.new() for _ in range(c)] for _ in range(v)] + W = [[f.new() for _ in range(c)] for _ in range(v)] + Bvar = {} + + def B(T, S): + if (T, S) not in Bvar: + Bvar[(T, S)] = f.xorlist([f.AND(W[T][j], U[S][j]) for j in range(c)]) + return Bvar[(T, S)] + for T in range(v): + f.cl.append([B(T, T)]) + for (T, S) in pattern: + f.cl.append([-B(T, S)]) + if U_fixed is not None: + for T in range(v): + for j in range(c): + f.cl.append([U[T][j]] if U_fixed[T][j] else [-U[T][j]]) + else: + for i, T in enumerate(K): + for j in range(c): + f.cl.append([U[T][j]] if j == i else [-U[T][j]]) + f.cl.append([W[T][j]] if j == i else [-W[T][j]]) + free = [(T, S) for T in range(v) for S in range(v) if T != S and (T, S) not in pattern] + if mode == 'nonsym': + f.cl.append([f.XOR(B(T, S), B(S, T)) for (T, S) in free if T < S]) + elif mode == 'neB0': + f.cl.append([B(T, S) if B0[T][S] == 0 else -B(T, S) for (T, S) in free]) + s = Cadical153(bootstrap_with=f.cl) + t = time.time() + r = s.solve() + out = dict(c=c, mode=mode, result='SAT' if r else 'UNSAT', sec=round(time.time() - t, 1), vars=f.n, clauses=len(f.cl)) + if r: + m = set(x for x in s.get_model() if x > 0) + Uv = np.array([[1 if U[T][j] in m else 0 for j in range(c)] for T in range(v)], dtype=np.int64) + Wv = np.array([[1 if W[T][j] in m else 0 for j in range(c)] for T in range(v)], dtype=np.int64) + out['B'] = (Wv @ Uv.T) % 2 + out['U'], out['W'] = Uv, Wv + return out diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed.py new file mode 100644 index 0000000000..c29e71d091 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed.py @@ -0,0 +1,41 @@ +"""General bit-unit evaluator: ports with Q-vectors lam (rows), F2 labels u (ints, c bits). Checks the label decoder +pattern (=0 when !=0, =1) and returns the min star cost over label decoders +(min-weight basis of the label space under s(q) = dim span{lam_S : =1}), and window h.""" +import numpy as np +def dot(a, b): return bin(a & b).count('1') & 1 +def check_pattern(lam, u): + Gm = lam @ lam.T + n = len(u) + for i in range(n): + if dot(u[i], u[i]) != 1: return False, ('diag', i) + for j in range(i + 1, n): + if Gm[i, j] != 0 and dot(u[i], u[j]) != 0: return False, ('pair', i, j) + return True, None +def star_cost(lam, u, cbits=9): + lamf = lam.astype(float) + s = {} + for q in range(1, 1 << cbits): + idx = [i for i in range(len(u)) if dot(u[i], q)] + s[q] = int(np.linalg.matrix_rank(lamf[idx])) if idx else 0 + # min weight basis of the span of the labels' dual... decoder: I_c = sum p_j q_j^T requires q's to span F2^c; + # but only the label span matters: restrict to q modulo span(u)^perp -> use greedy on all q, need rank = dim span(u) + span_dim = len(basis_of(u)) + order = sorted(s, key=lambda q: (s[q], q)) + # the decoder needs q_1..q_r whose restrictions to span(u) are a basis of its dual: i.e. the matrix [] + # has F2 rank r = dim span(u). Greedy on the vectors ()_T. + basis, chosen = [], [] + for q in order: + vec = sum(dot(u[i], q) << i for i in range(len(u))) + x = vec + for b in sorted(basis, reverse=True): + x = min(x, x ^ b) + if x: + basis.append(x); chosen.append(q) + if len(chosen) == span_dim: break + return sum(s[q] for q in chosen), chosen, span_dim +def basis_of(vs): + b = [] + for x in vs: + for y in sorted(b, reverse=True): x = min(x, x ^ y) + if x: b.append(x) + return b diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed_d8.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed_d8.py new file mode 100644 index 0000000000..108396ac90 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/mixed_d8.py @@ -0,0 +1,26 @@ +import numpy as np, itertools +from e8units import standard_e8, e8_labels +from mixed import * +L, G = standard_e8() +U = e8_labels(G); u = [int(''.join(map(str, r)), 2) for r in U] +Lq = L.astype(np.int64) +ok, why = check_pattern(Lq, u); print('E8 std ok', ok, star_cost(Lq, u)[0]) +half = list(range(56, 120)) +# punctured set: D8 roots involving e_0 .. ; add unit vectors +def try_unit(add_axes): + P = set(i for i in range(56) if any(L[i][a] != 0 for a in add_axes)) + live = [i for i in range(120) if i not in P] + lam = [Lq[i] for i in live]; lab = [u[i] for i in live] + for a in add_axes: + e = np.zeros(8, dtype=np.int64); e[a] = 2 + N = [k for k, i in enumerate(live) if e @ Lq[i] != 0] + # odd z orthogonal to labels of N + zs = [z for z in range(1, 512) if bin(z).count('1') % 2 and all(not dot(z, lab[k]) for k in N)] + if not zs: return None + lam.append(e); lab.append(zs[0]) + lam = np.array(lam); okp, why = check_pattern(lam, lab) + if not okp: return ('pattern fail', why) + c, ch, sd = star_cost(lam, lab) + return dict(v=len(lab), punct=len(P), cst=c, x=c / len(lab), labdim=sd) +for k in range(1, 9): + print(k, try_unit(list(range(k))), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/p111c.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/p111c.py new file mode 100644 index 0000000000..16e8403efe --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/p111c.py @@ -0,0 +1,18 @@ +import numpy as np +from bitp import * +P = [39, 52, 57, 71, 76, 78, 103, 109, 114] +Ps = set(P) +live = np.ones(NL, bool); live[P] = False +R = {p: [int(i) for i in RLIST[p][live[RLIST[p]]]] for p in range(1, 512)} +D = [p for p in range(1, 512) if rankQ_lines(R[p]) < 8] +print('deficient', D, 'labels of punctured', [uint[y] for y in P]) +print('G among punctured:'); print(G[np.ix_(P, P)]) +# cost table for pairs with a deficient side +from collections import Counter +cnt = Counter() +for d in D: + for q in range(1, 512): + cnt[('p=d', rankG(R[d], R[q]))] += 1 + cnt[('q=d', rankG(R[q], R[d]))] += 1 +print(cnt) +print('d-d costs'); print(np.array([[rankG(R[a], R[b]) for b in D] for a in D])) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/report.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/report.py new file mode 100644 index 0000000000..5593062527 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/report.py @@ -0,0 +1,60 @@ +"""Shared paths and PASS/FAIL reporting for the checks of research/format-limits. + +Every check prints one line per claim: 'PASS ' or 'FAIL ', and finish() exits +with status 1 if any claim failed. Paths: PKG is this package, DATA its data/ directory, REPO the repository root +(the package lives at REPO/research/format-limits; files of main are read from REPO and pinned in DEPENDS.json). +""" +import gzip +import json +import os +import pathlib +import sys + +HERE = pathlib.Path(__file__).resolve().parent +PKG = HERE.parent +DATA = PKG / 'data' +VENDOR = DATA / 'vendor' +REPO = pathlib.Path(os.environ.get('FORMAT_LIMITS_REPO', PKG.parent.parent)).resolve() + +_failed = [] + + +def check(cid, ok, detail=''): + print(('PASS ' if ok else 'FAIL ') + cid + (' ' + str(detail) if detail != '' else ''), flush=True) + if not ok: + _failed.append(cid) + return ok + + +def near(x, expected, places): + """x agrees with a value printed to `places` decimals: |x - expected| <= 0.6 * 10^-places (half a unit of the last + printed digit plus slack, so a float computed with a different library build cannot flip a rounding boundary).""" + return abs(float(x) - float(expected)) <= 0.6 * 10.0 ** (-places) + + +def near_all(xs, expected, places): + xs, expected = list(xs), list(expected) + return len(xs) == len(expected) and all( + (x == e) if isinstance(e, int) and not isinstance(e, bool) and p is None else near(x, e, p) + for x, e, p in zip(xs, expected, places)) + + +def finish(): + if _failed: + print('FAILED CLAIMS: ' + ' '.join(_failed), flush=True) + sys.exit(1) + + +def load_json(path): + path = pathlib.Path(path) + if path.suffix == '.gz': + return json.loads(gzip.decompress(path.read_bytes())) + return json.loads(path.read_text()) + + +def main_file(rel): + """a file of the repository's main branch (pinned by sha256 in DEPENDS.json).""" + p = REPO / rel + if not p.exists(): + sys.exit('missing file of main: %s (run from a checkout of the whole repository)' % rel) + return p diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62.py new file mode 100644 index 0000000000..7971fb2347 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62.py @@ -0,0 +1,4 @@ +import sys +from dec9sat import * +P = [int(x) for x in sys.argv[1].split(',')] +print('result', check_budget(P, int(sys.argv[2]))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sa_bit.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sa_bit.py new file mode 100644 index 0000000000..d55fb6a034 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sa_bit.py @@ -0,0 +1,27 @@ +import sys, random, math +from fractions import Fraction as Fr +from bitp import * +def ev(Pset): + live = [i for i in range(NL) if i not in Pset] + if rankQ_lines(live) < 8: return None + ch, c = greedy_basis(s_all(Pset)) + return c +def sa(p, iters, seed): + rng = random.Random(seed) + P = set(rng.sample(range(NL), p)) + c = ev(P); best = (Fr(c, NL - p), sorted(P), c) + T = 2.0 + for it in range(iters): + out = rng.choice(sorted(P)); inn = rng.choice([i for i in range(NL) if i not in P]) + Q = set(P); Q.remove(out); Q.add(inn) + cq = ev(Q) + if cq is None: continue + if cq <= c or rng.random() < math.exp(-(cq - c) / T): + P, c = Q, cq + if Fr(c, NL - p) < best[0]: best = (Fr(c, NL - p), sorted(P), c) + T = max(0.05, T * 0.999) + return best +if __name__ == '__main__': + p = int(sys.argv[1]); seed = int(sys.argv[2]); iters = int(sys.argv[3]) + b = sa(p, iters, seed) + print(p, seed, 'cst', b[2], 'v', NL - p, 'x', b[0], float(b[0]), b[1], flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_cheap.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_cheap.py new file mode 100644 index 0000000000..fe40ee28be --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_cheap.py @@ -0,0 +1,58 @@ +"""SAT: is there an F2 decoder of the bit E8 pattern on the live lines with n totals, of which at least n - nfull have +their sources (or readers) inside an E7 hyperplane ell^perp (pairing rank <= 7)?""" +import sys, time, itertools +import numpy as np +from pysat.solvers import Cadical153 +from pysat.card import CardEnc, EncType +from geo import * +class F: + def __init__(s): s.n = 0; s.cl = [] + def new(s): s.n += 1; return s.n + def AND(s, a, b): + z = s.new(); s.cl += [[-z, a], [-z, b], [z, -a, -b]]; return z + def XOR(s, a, b): + z = s.new(); s.cl += [[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]; return z + def xorlist(s, l): + acc = l[0] + for x in l[1:]: acc = s.XOR(acc, x) + return acc +def build(live, n, nfull, hyper='E7', fix0=True): + v = len(live); f = F() + al = [[f.new() for _ in range(v)] for _ in range(n)] + be = [[f.new() for _ in range(v)] for _ in range(n)] + for T in range(v): + for S in range(v): + if T == S or G[live[T], live[S]] != 0: + acc = f.xorlist([f.AND(al[j][T], be[j][S]) for j in range(n)]) + f.cl.append([acc] if T == S else [-acc]) + # hyperplanes: E7 type, normal = root line ell; members = live lines orthogonal to ell + Hs = [[k for k in range(v) if G[live[k], l] == 0] for l in range(NL)] + cheap = [] + for j in range(n): + sels = [] + for h, mem in enumerate(Hs): + memset = set(mem) + for side, vec in (('A', be[j]), ('R', al[j])): + if fix0 and j == 0 and not (side == 'A' and h == 0): continue + x = f.new(); sels.append(x) + for k in range(v): + if k not in memset: f.cl.append([-x, -vec[k]]) + c = f.new(); cheap.append(c) + f.cl.append([-c] + sels) + if fix0: f.cl.append([cheap[0]]) + enc = CardEnc.atleast(lits=cheap, bound=n - nfull, top_id=f.n, encoding=EncType.seqcounter) + f.n = max(f.n, enc.nv); f.cl += enc.clauses + return f, al, be +if __name__ == '__main__': + n, nfull = int(sys.argv[1]), int(sys.argv[2]) + P = [int(x) for x in sys.argv[3].split(',')] if len(sys.argv) > 3 and sys.argv[3] else [] + live = [i for i in range(NL) if i not in P] + f, al, be = build(live, n, nfull) + print('vars', f.n, 'clauses', len(f.cl), flush=True) + s = Cadical153(bootstrap_with=f.cl); t = time.time(); r = s.solve() + print('n', n, 'nfull', nfull, 'live', len(live), 'SAT' if r else 'UNSAT', round(time.time() - t, 1), flush=True) + if r: + m = set(x for x in s.get_model() if x > 0) + for j in range(n): + R = [live[T] for T in range(len(live)) if al[j][T] in m]; A = [live[S] for S in range(len(live)) if be[j][S] in m] + print(j, len(R), len(A), rankG_exact(R, A)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/search62.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/search62.py new file mode 100644 index 0000000000..43649a3e85 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/search62.py @@ -0,0 +1,17 @@ +import sys, random, time +from dec9sat import * +from e8units import f2_rank_rows +seed = int(sys.argv[1]); N = int(sys.argv[2]); rng = random.Random(seed) +found = 0 +for it in range(N): + # biased: grow a set preferring lines orthogonal to many chosen ones + P = [rng.randrange(NL)] + while len(P) < 9: + cand = [l for l in range(NL) if l not in P and f2_rank_rows([uint[x] for x in P + [l]]) == len(P) + 1] + w = [1 + 3 * sum(G[l, x] == 0 for x in P) for l in cand] + P.append(rng.choices(cand, weights=w)[0]) + northo = sum(G[a, b] == 0 for a in P for b in P if a < b) + r = check_budget(P, 62, verbose=False) + print(it, sorted(P), 'orth pairs', northo, 'cost<=62' if r else 'no', r if r else '', flush=True) + if r: found += 1 +print('found', found) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/skel.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/skel.py new file mode 100644 index 0000000000..e3c6c815b6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/skel.py @@ -0,0 +1,85 @@ +"""R-free skeleton bound for the B_K compressed-window format (per port, v=1; v cancels). +Slots (per replica / v): + active : s stages x 2 wires (X,Y banks of the active pair) x one climb of a = h-k, child cap a + copies : s stages x cst/v = x (mass), child cap beta_max <= h (use h; E8: beta = 8 = h exactly) + idle : 2 copies-per-port x idle stretches of the B_K schedule, slot rank r, child cap max(r, h) +D/v = (s-1) k - s x ; m = k + s (h-k). +Variants switch individual scope assumptions off (see NOTES).""" +from fractions import Fraction as Fr +from cert import root_bracket, cost_iv, F_iv +import sys + + +def idle_slots(K, a, split=True): + sl = [] + if K >= 1 and 2 * K - 2 > 0: + sl.append(a * (2 * K - 2)) # pair 0 tail + for p in range(1, K): + if split: + sl += [2 * p * a, a] # pre-stretch split by the bridge + else: + sl += [(2 * p + 1) * a] + if 2 * K - 2 * p - 2 > 0: + sl.append(a * (2 * K - 2 * p - 2)) + return sl + + +def profile(s, h, k, x, variant='thm'): + """returns (prof, m, D) per port. prof: rank -> (fractional) count; mass at cap c entered as count mass/c at rank c.""" + assert s % 2 == 1 and s >= 3 + K = (s - 1) // 2 + a = h - k + m = k + s * a + x = Fr(x) + prof = {} + + def add(cap, mass): + if mass == 0: + return + cap = Fr(cap) + if cap >= m: # child of rank m costs nothing + return + prof[cap] = prof.get(cap, 0) + Fr(mass) / cap + loc_cap = h if variant in ('loc_h', 'idlefree_loch') else a + add(loc_cap, 2 * s * a) # active data climbs + xx = Fr(0) if variant == 'copyfree' else x + add(h, s * xx) # copies + if variant not in ('idlefree', 'idlefree_loch'): + for r in idle_slots(K, a, split=(variant != 'nosplit')): + if variant == 'idle_onechild': + continue + cap = r if variant == 'nohostcap' else max(r, h) + add(cap, 2 * r) + if variant == 'idle_onechild': # all idle of a copy in one child (cross-stretch batching) + for tot in idle_per_pair(K, a): + add(max(tot, h), 2 * tot) + D = (s - 1) * k - s * xx + return prof, m, D + + +def idle_per_pair(K, a): + out = [] + if 2 * K - 2 > 0: + out.append(a * (2 * K - 2)) + for p in range(1, K): + out.append(a * (2 * K - 1)) + return out + + +def X(s, h, k, x, variant='thm'): + prof, m, D = profile(s, h, k, x, variant) + if D <= 0: + return None + lo, hi = root_bracket(prof, m, D, hi=Fr(1, 2)) + clo, chi = cost_iv(prof, m) + return dict(s=s, m=m, D=D, lo=lo, hi=hi, DoverCost=D / clo, cost=clo) + + +if __name__ == '__main__': + h, k, x = int(sys.argv[1]), int(sys.argv[2]), Fr(sys.argv[3]) + variant = sys.argv[4] if len(sys.argv) > 4 else 'thm' + for s in range(3, 32, 2): + r = X(s, h, k, x, variant) + if r is None: + print(s, 'D<=0'); continue + print('s=%2d m=%3d D/v=%6.3f root in [%.9e, %.9e] D/Cost=%.9e' % (s, r['m'], float(r['D']), r['lo'], r['hi'], float(r['DoverCost'])), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/subsys.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/subsys.py new file mode 100644 index 0000000000..983673376d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/subsys.py @@ -0,0 +1,52 @@ +import itertools +from bitp import * +def diagram(name): + """edges of a Dynkin diagram (disjoint union), returns (n, edges)""" + parts = name.split('+'); n = 0; E = [] + for part in parts: + mult = 1 + if '*' in part: mult, part = int(part.split('*')[0]), part.split('*')[1] + for _ in range(mult): + t, r = part[0], int(part[1:]) + if t == 'A': e = [(i, i + 1) for i in range(r - 1)] + elif t == 'D': e = [(i, i + 1) for i in range(r - 2)] + [(r - 3, r - 1)] + elif t == 'E': e = [(0, 2), (2, 3), (3, 4), (1, 3)] + [(i, i + 1) for i in range(4, r - 1)] + E += [(a + n, b + n) for a, b in e]; n += r + return n, E +def embed(name, start=0): + n, E = diagram(name) + adj = {i: set() for i in range(n)} + for a, b in E: adj[a].add(b); adj[b].add(a) + sol = [] + def rec(k): + if k == n: return True + for c in range(NL): + if c in sol: continue + ok = all((abs(G[c, sol[j]]) == 1) == (j in adj[k]) for j in range(k)) + if ok: + sol.append(c) + if rec(k + 1): return True + sol.pop() + return False + assert rec(0) + return sol +def closure(S): + S = set(S) + while True: + new = set() + for a in S: + for b in S: + new.add(REFL[a][b]) + if new <= S: return sorted(S) + S |= new +def report(name): + X = closure(embed(name)) + P = set(range(NL)) - set(X) + live = sorted(X) + h = rankQ_lines(live) + s = s_all(P); ch, c = greedy_basis(s) + return len(X), h, c, ch +if __name__ == '__main__': + import sys + for nm in sys.argv[1:]: + print(nm, report(nm), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/t1.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/t1.py new file mode 100644 index 0000000000..887aaccdd7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/t1.py @@ -0,0 +1,10 @@ +from geo import * +from collections import Counter +c = Counter() +for p in ALLP: + m = members(Rmask[p]); c[(len(m), rankQ_lines(m))] += 1 +print(sorted(c.items())) +# check reflections preserve G +import random +P = REFL[5]; assert all(abs(G[P[i],P[j]])==abs(G[i,j]) for i in range(120) for j in range(120)) +print('ok') diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/thmc111.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/thmc111.py new file mode 100644 index 0000000000..f59448396d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/thmc111.py @@ -0,0 +1,8 @@ +from bitp import * +P = {39, 52, 57, 71, 76, 78, 103, 109, 114} +live = np.ones(NL, bool); live[list(P)] = False +defic = [p for p in range(1, 512) if rankQ_lines(list(RLIST[p][live[RLIST[p]]])) < 8] +print('P111: reader sets R(p)\\P not spanning Q^8:', len(defic), defic[:20]) +s = s_all(P); ch, c = greedy_basis(s) +from collections import Counter +print('star greedy', c, Counter(s.values())) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq.py new file mode 100644 index 0000000000..083a8e708a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq.py @@ -0,0 +1,22 @@ +"""Is the 9-total F2 decoder of a punctured bit pattern unique (= B0 restricted)? And is c=8 UNSAT?""" +import sys, numpy as np +from geo import * +from minrank_sat import solve_asym, solve_minrank +from e8units import first_clique +def run(live): + v = len(live) + pat = {(i, j) for i in range(v) for j in range(v) if i != j and G[live[i], live[j]] != 0} + adj = [[i != j and G[live[i], live[j]] != 0 for j in range(v)] for i in range(v)] + K = first_clique(adj, 8) + B0l = B0[np.ix_(live, live)] + out = {} + if K is None: + out['clique'] = None; return out + for c, mode in ((9, 'neB0'), (8, 'none')): + r = solve_asym(v, pat, B0l, c, mode, K) + out[(c, mode)] = r['result'] + return out +if __name__ == '__main__': + P = [int(x) for x in sys.argv[1].split(',')] if len(sys.argv) > 1 and sys.argv[1] else [] + live = [i for i in range(NL) if i not in P] + print(len(live), run(live), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/xtab.py b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/xtab.py new file mode 100644 index 0000000000..f2a4162925 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/xtab.py @@ -0,0 +1,11 @@ +from fractions import Fraction as Fr +from skel import X +import sys +def best(h, cst, v, S=(3,5,7,9,11)): + out={} + for s in S: + r=X(s,h,1,Fr(cst,v)) + out[s]=float(r['hi']) if r else None + return out +for h,cst,v in [(8,63,111),(9,64,112),(8,72,120),(8,62,111),(8,54,102),(9,56,104),(8,7,63*1)]: + print(h,cst,v, {k:('%.4e'%x if x else None) for k,x in best(h,cst,v).items()}) From 419c7374c3bf5f2eee28f08b1e50c1d57934081c Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Mon, 12 Oct 2026 16:30:34 -0400 Subject: [PATCH 02/17] Add E8 extension-ring (Gaussian/Eisenstein/Hurwitz) and K12 unit screens Co-Authored-By: Claude Opus 5.5 --- .../README.md | 16 ++++ .../s1-extension-rings/REPORT.md | 57 ++++++++++++++ .../s1-extension-rings/algebra_lines.py | 77 +++++++++++++++++++ .../s1-extension-rings/costexact.py | 73 ++++++++++++++++++ .../s1-extension-rings/costlb.py | 73 ++++++++++++++++++ .../s1-extension-rings/explore1.py | 40 ++++++++++ .../s1-extension-rings/explore2.py | 9 +++ .../s1-extension-rings/explore3.py | 9 +++ .../s1-extension-rings/explore4.py | 11 +++ .../s1-extension-rings/fx.py | 25 ++++++ .../s1-extension-rings/gauss_geom.py | 53 +++++++++++++ .../s1-extension-rings/gmin.py | 13 ++++ .../s1-extension-rings/hurwitz_exact.py | 58 ++++++++++++++ .../s1-extension-rings/k12.py | 35 +++++++++ .../s1-extension-rings/klines.py | 51 ++++++++++++ .../s1-extension-rings/meetratio.py | 17 ++++ .../s1-extension-rings/minrank_any.py | 28 +++++++ 17 files changed, 645 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/REPORT.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/algebra_lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costexact.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costlb.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore1.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore4.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/fx.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gauss_geom.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/hurwitz_exact.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/k12.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/klines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/meetratio.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/minrank_any.py diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 43446a32da..cb5917503a 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -23,3 +23,19 @@ Measured on #615's words (#615 re-verified locally: κ 0.001626010676080313): ## Credits Builds on jeremykothe2 (#591–#601, #607–#617), DreamingOfClouds (#595 format limits, #604, #610), chafreaky (#583, #616), #446 (Theorems A–C), eumemic (#543) and the contributors listed in those PRs. Prepared by Joel Pulikkan (GamingPuzzled) with Claude Opus 5.5 (Anthropic) via Claude Code. Apache-2.0. + +## 4. E8 over extension rings and K12 (`s1-extension-rings/`, details in `REPORT.md`) +On the bit side ranks are counted over Q, so a K-line unit is a rank-k unit with k = [K:Q] and h = k·w_K (argued). Exact F2 minrank of the decoder pattern, asymmetric decoders allowed (SAT, no proof logs): + +| view | lines v | w | largest non-orthogonal set | minrank | v/w | +|---|---|---|---|---|---| +| Gaussian E8 | 60 | 4 | 12 | 15 (13, 14 UNSAT) | 15 | +| Eisenstein E8 (Witting) | 40 | 4 | 7 | 10 (7–9 UNSAT) | 10 | +| Hurwitz E8 | 10 | 2 | 5 | 5 | 5 | + +- In every extension view, minrank = v/w. Real E8's cross-field gain (minrank 9 < 120/8) disappears. Under the full-rank-totals premise that #595 uses for E8's 72, x_K = 1.0, so D ≤ 0: no saving. +- Copy-cost searches (CP-SAT, 900 s, not proven optimal): Gaussian best 60 (proven lower bound only x_K ≥ 0.15); Eisenstein best 40 (x_K ≥ 0.225). Hurwitz symmetric decoders: optimum 10 (x_K = 1). Hurwitz asymmetric decoders: open. +- **Open question:** a structural lower bound Σ_j dim span A_j ≥ ~0.71·v for these units (the threshold to beat bit E8's ceiling at w = 4, float screen). This has the same open status as E8's 72. +- G34 / Coxeter–Todd K12: 126 lines, w = 6, largest non-orthogonal set 15, so x_K ≥ 0.714 even at minrank 15; it loses. +- Complex side (argued): F4-Hermitian (Eisenstein) labels give an alternating F2 trace form, so there are no odd-norm residuals and the phase calculus fails. Gaussian labels reduce mod (1+i) to F2, so they bring no extension. +- Side finding (float screen only, not certified): the paper's original triple family has a higher X* than E8 (~7.4e-3 bit, ~7.2e-3 complex at h = 24, s = 3), but 630 orthogonal neighbours per target (vs 63 for E8) makes its helper cost the issue. X* alone does not rank unit families. diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/REPORT.md b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/REPORT.md new file mode 100644 index 0000000000..1a766e3655 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/REPORT.md @@ -0,0 +1,57 @@ +# S1: E8 over extension rings and other line systems (stopped early because the budget ran out; no kappa claim) + +Scripts are in $SP/s1/ and run with $SP/venv/bin/python -B from inside that directory. The package/ directory was not written. + +## Reduction (argued) +- **Bit side.** The cost is rank_Q of the frame changes. A Q(i)-line costs 2 Q-ranks, so a K-linear unit is a rank-k unit with k = [K:Q] and h = k·w_K. +- **The ceiling under this scaling.** X depends only on (s, w = h/k, x/k) (§A.2, general k). The window does "halve" (8 -> 4), but v falls by the same factor: 120 real lines become 60 Q(i)-lines. +- **What decides the comparison.** It comes down to (w_K, x_K = Σ rank_K / v), with the F2 decoder pattern taken from K-orthogonality. +- **Q(i) is free on the bit side.** No ring rule (Lemma λ) blocks Q(i) there: the payload is F2 and the frames are Q-linear. + +## Numbers (exact) +- **Gaussian E8** (J = i on coordinate pairs; preserves E8; checked) + - 60 lines, w = 4. + - Each line is non-orthogonal to 44 others. + - α (largest pairwise non-orthogonal set) = 12. + - F2 minrank (asymmetric allowed, SAT with clique gauge) = 15: c = 13 and 14 are UNSAT, c = 15 is SAT. 15 = v/w, the frame-partition decoder. + - Hyperplane flats: 60 of size 15, 480 of size 6, 960 of size 4. +- **Eisenstein E8** (fixed-point-free order-3 element from 4 orthogonal A2's; Witting configuration) + - 40 lines, w = 4, non-orthogonal degree 27, α = 7. + - F2 minrank = 10: c = 7, 8, 9 are UNSAT, c = 10 is SAT. 10 = v/w. +- **Hurwitz E8** (left multiplication by i, j) + - 10 lines in H^2, w = 2, each line orthogonal to exactly one other, α = 5, minrank 5 = v/w. +- **Copy cost.** Symmetric decoders, exact CP-SAT with cost = dim span A_j, searched 900 s (not proven optimal): + - Gaussian (c ≤ 20): best found 60, so x_K = 1.0. Proven lower bound only 9 (x_K ≥ 0.15). + - Eisenstein (c ≤ 16): best found 40, so x_K = 1.0. Lower bound 9 (x_K ≥ 0.225). + - Hurwitz: + - Symmetric optimum is 10 (x_K = 1.0, proven OPTIMAL). + - The asymmetric D_j-relaxation gives only 2, and that relaxation is too weak. + - The exact SAT run with the Theorem-C cost (costs 8/9/10) did not finish, so the Hurwitz asymmetric case is **open**. +- **Under the README's own E8 premise** (cst = minrank × full in-cap clique rank, the same premise as E8's 72): Gaussian, Eisenstein and Hurwitz all have x_K = minrank·w/v = **1.0**. Then D/k = (s−1)v − s·v < 0 at every s, so there is **no saving at all**. +- **Threshold to beat bit E8** (X* = 6.0005e-3, float screen fx.py): x_K < ~0.71 at w = 4, < ~0.86 at w = 2, < ~0.64 at w = 6. +- **Structural finding.** The cross-field gain of E8 (minrank 9 < v/w = 15) disappears over Q(i), Q(ω) and H: in every extension view minrank = v/w exactly. +- **G34 / Coxeter–Todd K12.** Built exactly as {x ∈ Z[ω]^6 : x_i ≡ x_j mod θ, Σ x_i ≡ 0 mod 3}: 756 minimal vectors, 126 lines. + - w = 6, non-orthogonal degree 80, α = 15. + - Even at minrank = α, x_K ≥ 90/126 = 0.714 > 0.64, so it loses under the premise. The minrank itself was not run. +- **Complex side** (argued). Rules out F4/Eisenstein F2-labels and Gaussian F2-labels: + - **F4 / Eisenstein labels.** A Hermitian form over F4 has an alternating F2 trace form (Tr h(x,x) = 0), so there are no odd-norm residuals. This breaks the phase calculus (orthonormal kernel basis; Lemma λ consequence). It is the first broken rule. + - **Gaussian labels.** Z[i]/(1+i) = F2, so there is no extension. + - **A Q(i)-valued decoder** (Hermitian Gram, 4 centres) forces F2 window = the Gaussian-pattern minrank = 15 on 60 ports, or ≥ 24 on the 120 real ports (clique of r, ir pairs). Not evaluated further. +- **Side finding (b), checked with the float screen only.** The upstream Alon triple family (v = C(h,3), cst = h² bit / h(h+1) complex) has a higher **ceiling** than E8: + - X* = 7.39e-3 (bit, h = 24) and 7.16e-3 (complex, h = 24), at s = 3. + - Generalised Alon sets (7-subsets, λ = 3) would push the ceiling higher still. + - So X* alone does not rank unit families; helper cost decides (per-target neighbour count z = 3·C(h−3,2) = 630 at h = 24, against 63 for E8). + - This was the pre-E8 family (h = 23/25/28 audits in docs/research). + - Task (c), the helper-floor estimate, was not done. + +## Reproduce +- explore4.py: v, degree, α for the three views. +- minrank_any.py {Gauss|Eisen|Hurwitz|K12} [cmin]: minrank SAT. +- costexact.py {Gauss|Eisen} cmax 1 secs: exact cost CP-SAT (needs flats_*.pkl, built by costlb.py). +- k12.py: G34. +- fx.py: float X screen (certify with skel.X before publishing). + +## Open and next steps +1. A real lower bound Σ_j dim span A_j ≥ 0.71·v (Gaussian) for decoders with more than minrank totals. CP-SAT bounds stay at 9; a structural argument is needed. Same open status as E8's 72. +2. Finish the Hurwitz asymmetric SAT (hurwitz_exact.py, cost ≤ 8/9/10). +3. Certify the triple-family X* with skel.X, and estimate helpers via d1 cover numbers. The triple cover number is 2 by the argued two-hyperplane cover. diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/algebra_lines.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/algebra_lines.py new file mode 100644 index 0000000000..6ecaf65779 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/algebra_lines.py @@ -0,0 +1,77 @@ +"""E8 roots grouped into lines over an algebra A acting on R^8 (A spanned over Q by integer matrices preserving E8). +line = A-span of a root (a real subspace of dim = dim A); two lines are A-orthogonal iff = 0 for all basis a.""" +import sys, itertools, numpy as np +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from e8units import standard_e8, rank_q +import networkx as nx + +def e8_roots(): + L,G=standard_e8(); return np.vstack([L,-L]) # doubled coords, norm 8 + +def left_quat(): + """left multiplication by i, j, k on each H-block of R^8 = H^2 (coords 1,i,j,k).""" + Li=np.array([[0,-1,0,0],[1,0,0,0],[0,0,0,-1],[0,0,1,0]]) + Lj=np.array([[0,0,-1,0],[0,0,0,1],[1,0,0,0],[0,-1,0,0]]) + Lk=Li@Lj + blk=lambda M: np.kron(np.eye(2,dtype=np.int64),M).astype(np.int64) + return blk(Li),blk(Lj),blk(Lk) + +def a2_rotation(roots): + """a fixed-point-free order-3 element of W(E8): product of the Coxeter elements of 4 orthogonal A2's.""" + n8=lambda r: r@r + def refl(a): + return np.eye(8,dtype=np.int64)*8 - 2*np.outer(a,a) # 8*s_a (norm 8): s_a = I - 2aa^T/8 + R=[tuple(r) for r in roots] + chosen=[] + def ok(a,b): + return all(np.dot(a,x)==0 and np.dot(b,x)==0 for p in chosen for x in p) + for a in roots: + if len(chosen)==4: break + if any(np.dot(a,x)!=0 for p in chosen for x in p): continue + for b in roots: + if np.dot(a,b)==-4 and ok(a,b): + chosen.append((a,b)); break + assert len(chosen)==4 + g=np.eye(8,dtype=object) + for a,b in chosen: + sa=np.eye(8,dtype=object)*8-2*np.outer(a,a).astype(object); sb=np.eye(8,dtype=object)*8-2*np.outer(b,b).astype(object) + g=g.dot(sa).dot(sb) + D=8**8//4 + assert all(x%D==0 for x in g.flatten()); g=(g//D).astype(np.int64) # g = 4 * (order-3 isometry) + I=np.eye(8,dtype=np.int64) + assert (g@g@g==64*I).all() and (g@g+4*g+16*I==0).all() + S=set(map(tuple,roots)); assert all(x%4==0 for r in roots for x in g@r) and all(tuple(g@r//4) in S for r in roots) + return g + +def lines_over(roots, basis): + """basis: list of integer matrices (incl. identity) spanning the algebra. Returns representative roots, the + real span dimension d = len(basis), and the A-nonorthogonality matrix.""" + seen={}; reps=[] + d=len(basis) + for r in roots: + key=None + span=[list(B@r) for B in basis] + assert rank_q(span)==d + # canonical key: set of roots in the span + found=False + for i,rep in enumerate(reps): + if rank_q([list(B@rep) for B in basis]+[list(r)])==d: + found=True; break + if not found: reps.append(r) + reps=np.array(reps) + v=len(reps) + NO=np.zeros((v,v),bool) + for B in basis: + NO|=(reps@B@reps.T)!=0 + np.fill_diagonal(NO,False) + return reps,NO + +def clique_number(NO): + v=len(NO); g=nx.Graph(); g.add_nodes_from(range(v)) + g.add_edges_from((i,j) for i in range(v) for j in range(i+1,v) if NO[i,j]) + K=max(nx.find_cliques(g),key=len); return len(K),sorted(K) + +def arank(reps,basis,idx): + rows=[list(B@reps[i]) for i in idx for B in basis] + r=rank_q(rows) if rows else 0 + assert r%len(basis)==0; return r//len(basis) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costexact.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costexact.py new file mode 100644 index 0000000000..7de27f4ca6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costexact.py @@ -0,0 +1,73 @@ +"""CP-SAT: minimise a valid lower bound of sum_j beta_j / d over decoders B = W U^T on the A-orthogonality pattern. +cost_j >= f(D_j), D_j = A_j cap R_j: f = 0 (empty), 1 (one line), 2 (2..L2 lines), 3 (in a hyperplane flat), w otherwise, +using the exact flat census. usage: costlb.py """ +import sys, time, pickle; sys.path.insert(0,sys.path[0]) +from algebra_lines import * +from ortools.sat.python import cp_model +name, cmax, sym, secs = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), float(sys.argv[4]) +roots=e8_roots(); I8=np.eye(8,dtype=np.int64); Li,Lj,Lk=left_quat() +basis={'Gauss':[I8,Li],'Eisen':[I8,a2_rotation(roots)],'Hurwitz':[I8,Li,Lj,Lk]}[name] +reps,NO=lines_over(roots,basis); v=len(reps); w=arank(reps,basis,range(v)) +# flats: hyperplanes = closures of rank w-1; rank-2 flats; computed exactly +def closure(idx): + r=arank(reps,basis,idx); return frozenset(j for j in range(v) if j in idx or arank(reps,basis,list(idx)+[j])==r), r +cache='flats_%s.pkl'%name +try: fl=pickle.load(open(cache,'rb')) +except Exception: + fl={1:{frozenset([i]) for i in range(v)}} + for r in range(2,w): + fl[r]=set() + for F in fl[r-1]: + bas=[] + for i in sorted(F): + if arank(reps,basis,bas+[i])>len(bas): bas.append(i) + if len(bas)==r-1: break + for j in range(v): + if j not in F: fl[r].add(closure(bas+[j])[0]) + pickle.dump(fl,open(cache,'wb')) +L={r:max(len(F) for F in fl[r]) for r in fl} +print(name,'v',v,'w',w,'max flat sizes',L,flush=True) +m=cp_model.CpModel() +u=[[m.NewBoolVar('') for j in range(cmax)] for T in range(v)] +W=u if sym else [[m.NewBoolVar('') for j in range(cmax)] for T in range(v)] +def AND(a,b): + if a is b: return a + z=m.NewBoolVar(''); m.AddBoolAnd([a,b]).OnlyEnforceIf(z); m.AddBoolOr([a.Not(),b.Not()]).OnlyEnforceIf(z.Not()); return z +d=[[AND(W[T][j],u[T][j]) for j in range(cmax)] for T in range(v)] +for T in range(v): m.AddBoolXOr(d[T]) # B_TT = 1 +for T in range(v): + for S in range(v): + if NO[T,S] and (not sym or T= sum_{l} lev_{j,l}; lev_1 = nonempty, lev_2 = >=2 lines, lev_r (3..w-1) = not inside a rank-(r-1) flat +obj=[] +# precompute: for levels r>=3 use flats of rank r-1 with more than L[r-2] lines (smaller ones are covered by count) +for j in range(cmax): + n=sum(d[T][j] for T in range(v)) + l1=m.NewBoolVar(''); m.Add(n>=1).OnlyEnforceIf(l1); m.Add(n==0).OnlyEnforceIf(l1.Not()); obj.append(l1) + if w>=2: + l2=m.NewBoolVar(''); m.Add(n>=2).OnlyEnforceIf(l2); m.Add(n<=1).OnlyEnforceIf(l2.Not()); obj.append(l2) + prevL=1 + for r in range(3,w+1): + # lev_r = 1 unless D_j inside some rank-(r-1) flat; sets of size <= L[r-2] are allowed free at this level (relaxation) + lr=m.NewBoolVar(''); obj.append(lr) + small=m.NewConstant(0) + big=list(fl[r-1]) + sel=[m.NewBoolVar('') for F in big] + for s_,F in zip(sel,big): + for T in range(v): + if T not in F: m.AddImplication(s_, d[T][j].Not()) + m.AddBoolOr(sel+[small,lr]) +obj_expr=sum(obj) +# symmetry: totals ordered by count +cnt=[sum(d[T][j] for T in range(v)) for j in range(cmax)] +for j in range(cmax-1): m.Add(cnt[j]>=cnt[j+1]) +m.Minimize(obj_expr) +sv=cp_model.CpSolver(); sv.parameters.max_time_in_seconds=secs; sv.parameters.num_workers=8 +t=time.time(); st=sv.Solve(m) +print(name,'cmax',cmax,'sym',sym,sv.StatusName(st),'obj',sv.ObjectiveValue() if st in (cp_model.OPTIMAL,cp_model.FEASIBLE) else None,'bound',sv.BestObjectiveBound(),'x_K lb',sv.BestObjectiveBound()/v,round(time.time()-t,1),flush=True) +if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + U=np.array([[sv.Value(u[T][j]) for j in range(cmax)] for T in range(v)]); Wv=np.array([[sv.Value(W[T][j]) for j in range(cmax)] for T in range(v)]) + tot=[ (arank(reps,basis,[T for T in range(v) if Wv[T,j] and U[T,j]]), int(U[:,j].sum())) for j in range(cmax)] + print('totals (rank of D_j, |A_j|):',tot) + np.savez('costlb_%s_%d_%d.npz'%(name,cmax,sym),U=U,W=Wv) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costlb.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costlb.py new file mode 100644 index 0000000000..da68b13dad --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/costlb.py @@ -0,0 +1,73 @@ +"""CP-SAT: minimise a valid lower bound of sum_j beta_j / d over decoders B = W U^T on the A-orthogonality pattern. +cost_j >= f(D_j), D_j = A_j cap R_j: f = 0 (empty), 1 (one line), 2 (2..L2 lines), 3 (in a hyperplane flat), w otherwise, +using the exact flat census. usage: costlb.py """ +import sys, time, pickle; sys.path.insert(0,sys.path[0]) +from algebra_lines import * +from ortools.sat.python import cp_model +name, cmax, sym, secs = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), float(sys.argv[4]) +roots=e8_roots(); I8=np.eye(8,dtype=np.int64); Li,Lj,Lk=left_quat() +basis={'Gauss':[I8,Li],'Eisen':[I8,a2_rotation(roots)],'Hurwitz':[I8,Li,Lj,Lk]}[name] +reps,NO=lines_over(roots,basis); v=len(reps); w=arank(reps,basis,range(v)) +# flats: hyperplanes = closures of rank w-1; rank-2 flats; computed exactly +def closure(idx): + r=arank(reps,basis,idx); return frozenset(j for j in range(v) if j in idx or arank(reps,basis,list(idx)+[j])==r), r +cache='flats_%s.pkl'%name +try: fl=pickle.load(open(cache,'rb')) +except Exception: + fl={1:{frozenset([i]) for i in range(v)}} + for r in range(2,w): + fl[r]=set() + for F in fl[r-1]: + bas=[] + for i in sorted(F): + if arank(reps,basis,bas+[i])>len(bas): bas.append(i) + if len(bas)==r-1: break + for j in range(v): + if j not in F: fl[r].add(closure(bas+[j])[0]) + pickle.dump(fl,open(cache,'wb')) +L={r:max(len(F) for F in fl[r]) for r in fl} +print(name,'v',v,'w',w,'max flat sizes',L,flush=True) +m=cp_model.CpModel() +u=[[m.NewBoolVar('') for j in range(cmax)] for T in range(v)] +W=u if sym else [[m.NewBoolVar('') for j in range(cmax)] for T in range(v)] +def AND(a,b): + if a is b: return a + z=m.NewBoolVar(''); m.AddBoolAnd([a,b]).OnlyEnforceIf(z); m.AddBoolOr([a.Not(),b.Not()]).OnlyEnforceIf(z.Not()); return z +d=[[AND(W[T][j],u[T][j]) for j in range(cmax)] for T in range(v)] +for T in range(v): m.AddBoolXOr(d[T]) # B_TT = 1 +for T in range(v): + for S in range(v): + if NO[T,S] and (not sym or T= sum_{l} lev_{j,l}; lev_1 = nonempty, lev_2 = >=2 lines, lev_r (3..w-1) = not inside a rank-(r-1) flat +obj=[] +# precompute: for levels r>=3 use flats of rank r-1 with more than L[r-2] lines (smaller ones are covered by count) +for j in range(cmax): + n=sum(d[T][j] for T in range(v)) + l1=m.NewBoolVar(''); m.Add(n>=1).OnlyEnforceIf(l1); m.Add(n==0).OnlyEnforceIf(l1.Not()); obj.append(l1) + if w>=2: + l2=m.NewBoolVar(''); m.Add(n>=2).OnlyEnforceIf(l2); m.Add(n<=1).OnlyEnforceIf(l2.Not()); obj.append(l2) + prevL=1 + for r in range(3,w+1): + # lev_r = 1 unless D_j inside some rank-(r-1) flat; sets of size <= L[r-2] are allowed free at this level (relaxation) + lr=m.NewBoolVar(''); obj.append(lr) + small=m.NewBoolVar(''); m.Add(n<=L[r-2]).OnlyEnforceIf(small); m.Add(n>L[r-2]).OnlyEnforceIf(small.Not()) + big=[F for F in fl[r-1] if len(F)>L[r-2]] + sel=[m.NewBoolVar('') for F in big] + for s_,F in zip(sel,big): + for T in range(v): + if T not in F: m.AddImplication(s_, d[T][j].Not()) + m.AddBoolOr(sel+[small,lr]) +obj_expr=sum(obj) +# symmetry: totals ordered by count +cnt=[sum(d[T][j] for T in range(v)) for j in range(cmax)] +for j in range(cmax-1): m.Add(cnt[j]>=cnt[j+1]) +m.Minimize(obj_expr) +sv=cp_model.CpSolver(); sv.parameters.max_time_in_seconds=secs; sv.parameters.num_workers=8 +t=time.time(); st=sv.Solve(m) +print(name,'cmax',cmax,'sym',sym,sv.StatusName(st),'obj',sv.ObjectiveValue() if st in (cp_model.OPTIMAL,cp_model.FEASIBLE) else None,'bound',sv.BestObjectiveBound(),'x_K lb',sv.BestObjectiveBound()/v,round(time.time()-t,1),flush=True) +if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + U=np.array([[sv.Value(u[T][j]) for j in range(cmax)] for T in range(v)]); Wv=np.array([[sv.Value(W[T][j]) for j in range(cmax)] for T in range(v)]) + tot=[ (arank(reps,basis,[T for T in range(v) if Wv[T,j] and U[T,j]]), int(U[:,j].sum())) for j in range(cmax)] + print('totals (rank of D_j, |A_j|):',tot) + np.savez('costlb_%s_%d_%d.npz'%(name,cmax,sym),U=U,W=Wv) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore1.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore1.py new file mode 100644 index 0000000000..d14a9f1a44 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore1.py @@ -0,0 +1,40 @@ +import sys, itertools, numpy as np +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from e8units import standard_e8 +import networkx as nx +L,G=standard_e8() # doubled coords, norm 8 +roots=np.vstack([L,-L]) +J=np.zeros((8,8),int) +for a in range(4): + J[2*a+1,2*a]=1; J[2*a,2*a+1]=-1 # J e_{2a}=e_{2a+1}, J e_{2a+1}=-e_{2a} +assert (J@J==-np.eye(8)).all() +S=set(map(tuple,roots)) +assert all(tuple(J@r) in S for r in roots) +# complex lines: orbits under +seen=set(); lines=[] +for r in roots: + t=tuple(r) + if t in seen: continue + orb=[r, J@r, -r, -(J@r)] + for o in orb: seen.add(tuple(o)) + lines.append(r) +lines=np.array(lines); v=len(lines); print('complex lines',v) +# Hermitian non-orth: !=0 or !=0 +R=lines@lines.T; I=lines@(J@lines.T) +nonorth=(R!=0)|(I!=0) +np.fill_diagonal(nonorth,False) +print('deg nonorth',set(nonorth.sum(1))) +g=nx.Graph(); g.add_nodes_from(range(v)) +for i in range(v): + for j in range(i+1,v): + if nonorth[i,j]: g.add_edge(i,j) +w=max(len(c) for c in nx.find_cliques(g)) +print('clique number of nonorth graph',w) +from minrank_sat import solve_minrank +K=max(nx.find_cliques(g),key=len) +pairs=[(i,j) for i in range(v) for j in range(i+1,v) if nonorth[i,j]] +for c in range(12,17): + for sym in (True,False): + r=solve_minrank(v,pairs,c,sym,K if len(K)==c else None) + print('c',c,'sym',sym,r,flush=True) + if r[0]=='SAT': break diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore2.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore2.py new file mode 100644 index 0000000000..0d6321fe9e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore2.py @@ -0,0 +1,9 @@ +import sys, numpy as np +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +exec(open(sys.argv[0].replace('explore2','explore1')).read().split('from minrank_sat')[0]) +from e8units import f2_rank +B0=(~nonorth).astype(int); +print('rank of [orth or diag] matrix', f2_rank(B0)) +from collections import Counter +Hv=Counter((int(a),int(b)) for a,b in zip(R.flatten(),I.flatten())) +print(Hv) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore3.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore3.py new file mode 100644 index 0000000000..d74229a58b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore3.py @@ -0,0 +1,9 @@ +import sys,time; sys.path.insert(0,sys.path[0]) +from gauss_geom import * +lines,J=gaussian_e8() +t=time.time() +F=flats(lines,J,3) +from collections import Counter +for r in F: print(r,len(F[r]),Counter(len(f) for f in F[r])) +print(time.time()-t) +import pickle; pickle.dump(F,open('gflats.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore4.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore4.py new file mode 100644 index 0000000000..0ca29b147b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/explore4.py @@ -0,0 +1,11 @@ +import sys; sys.path.insert(0,sys.path[0]) +from algebra_lines import * +roots=e8_roots(); I8=np.eye(8,dtype=np.int64) +Li,Lj,Lk=left_quat() +S=set(map(tuple,roots)) +for nm,M in (('i',Li),('j',Lj)): assert all(tuple(M@r) in S for r in roots), nm +g=a2_rotation(roots) +for name,basis in (('Gauss',[I8,Li]),('Eisen',[I8,g]),('Hurwitz',[I8,Li,Lj,Lk])): + reps,NO=lines_over(roots,basis) + w,K=clique_number(NO) + print(name,'v',len(reps),'deg nonorth',sorted(set(NO.sum(1))),'clique',w, 'rank of clique',arank(reps,basis,K)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/fx.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/fx.py new file mode 100644 index 0000000000..396859bc46 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/fx.py @@ -0,0 +1,25 @@ +"""float screen of skel.X (same profile), for exploration only""" +import sys, math +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from skel import profile +from fractions import Fraction as Fr +def Xf(s,h,k,x): + prof,m,D=profile(s,h,k,Fr(x)) + if D<=0: return None + m=float(m); D=float(D) + F=lambda q: sum(float(z)*float(r)*((m/float(r))**q-1) for r,z in prof.items()) + lo,hi=0.0,0.5 + for _ in range(100): + mid=(lo+hi)/2 + if F(mid)>=D: hi=mid + else: lo=mid + return hi +def Xstar(h,k,x,S=range(3,40,2)): + best=(None,0.0) + for s in S: + r=Xf(s,h,k,x) + if r and r>best[1]: best=(s,r) + return best +if __name__=='__main__': + for h in (3,4,5,6,7,8,9): + print(h,[ (str(x),Xstar(h,1,x)) for x in (Fr(2,5),Fr(1,2),Fr(3,5),Fr(2,3),Fr(3,4),Fr(4,5),Fr(9,10))]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gauss_geom.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gauss_geom.py new file mode 100644 index 0000000000..7caaaeddc5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gauss_geom.py @@ -0,0 +1,53 @@ +"""Gaussian E8: the 60 Q(i)-lines (J-orbits of E8 roots). Exact integer arithmetic. +A Q(i)-line through root r is the real plane ; complex rank of a line set = real rank / 2.""" +import sys, itertools, numpy as np +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from e8units import standard_e8, rank_q + +def gaussian_e8(): + L,G=standard_e8() + roots=np.vstack([L,-L]) + J=np.zeros((8,8),dtype=np.int64) + for a in range(4): + J[2*a+1,2*a]=1; J[2*a,2*a+1]=-1 + assert (J@J==-np.eye(8,dtype=np.int64)).all() + S=set(map(tuple,roots)); assert all(tuple(J@r) in S for r in roots) + seen=set(); lines=[] + for r in roots: + if tuple(r) in seen: continue + for o in (r,J@r,-r,-(J@r)): seen.add(tuple(o)) + lines.append(r) + lines=np.array(lines) + assert len(lines)==60 + return lines,J + +def nonorth(lines,J): + R=lines@lines.T + I=lines@J@lines.T + M=(R!=0)|(I!=0); np.fill_diagonal(M,False); return M + +def crank(lines,J,idx): + rows=[] + for i in idx: rows+= [list(lines[i]), list(J@lines[i])] + r=rank_q(rows) if rows else 0 + assert r%2==0; return r//2 + +def flats(lines,J,maxrank=3): + v=len(lines); out={1:{frozenset([i]) for i in range(v)}} + def closure(idx): + r=crank(lines,J,idx) + return frozenset(j for j in range(v) if j in idx or crank(lines,J,list(idx)+[j])==r), r + for r in range(2,maxrank+1): + out[r]=set() + for Fl in out[r-1]: + base=list(Fl) + # pick an independent basis of size r-1 from Fl + bas=[] + for i in base: + if crank(lines,J,bas+[i])>len(bas): bas.append(i) + if len(bas)==r-1: break + for j in range(v): + if j in Fl: continue + C,rr=closure(bas+[j]); assert rr==r + out[r].add(C) + return out diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin.py new file mode 100644 index 0000000000..160132e868 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin.py @@ -0,0 +1,13 @@ +import sys,time; sys.path.insert(0,sys.path[0]) +from gauss_geom import * +import networkx as nx +from minrank_sat import solve_asym +lines,J=gaussian_e8(); NO=nonorth(lines,J); v=60 +g=nx.Graph([(i,j) for i in range(v) for j in range(i+1,v) if NO[i,j]]) +K=sorted(max(nx.find_cliques(g),key=len)) +pat={(i,j) for i in range(v) for j in range(v) if NO[i,j]} +c=int(sys.argv[1]) +r=solve_asym(v,pat,None,c,'none',K) +print(c,len(K),r['result'],r['sec'],flush=True) +if r['result']=='SAT': + import numpy as np; np.save('gauss_B_c%d.npy'%c,r['B']) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/hurwitz_exact.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/hurwitz_exact.py new file mode 100644 index 0000000000..887fee6982 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/hurwitz_exact.py @@ -0,0 +1,58 @@ +"""Hurwitz view of E8: 10 quaternionic lines in H^2 (w = 2), each orthogonal to exactly one other. +Exact: the least Theorem-C copy floor sum_j rank G[R_j, A_j] (H-rank units) over ALL F_2 decoders B = W U^T on the +pattern (B_TT = 1, B_TS = 0 on non-orthogonal pairs), asymmetric allowed, any number of totals. +For w = 2 the rank of the Gram block between line sets R, A is exactly 0 if R x A has no equal or non-orthogonal pair, +2 if |R| >= 2 and |A| >= 2, and 1 otherwise (checked below from exact ranks). Totals of rank 0 only touch free entries +and are dropped, so a decoder of cost <= C has at most C totals. SAT (CaDiCaL via pysat) with a cardinality bound: +cost <= C is UNSAT for C = 9 and SAT for C = 10.""" +import sys; sys.path.insert(0,sys.path[0]) +from minrank_any import systems +from minrank_sat import CNF +from pysat.card import CardEnc, EncType +from pysat.solvers import Cadical153 +import itertools +S=systems()['Hurwitz']; v=S.v; NO=S.NO +assert S.w==2 and set(NO.sum(1))=={8} +for R in itertools.chain.from_iterable(itertools.combinations(range(v),k) for k in (1,2,3)): + for A in itertools.chain.from_iterable(itertools.combinations(range(v),k) for k in (1,2,3)): + import numpy as np + rk=S.rank(R) and None +def gram_rank(R,A): + # exact rank of the real Gram block between the H-spans, / 4 + from e8units import rank_q + BR=[S.basis[b]@S.V[i] for i in R for b in range(4)]; BA=[S.basis[b]@S.V[i] for i in A for b in range(4)] + M=[[int(x@S.Q@y) for y in BA] for x in BR] + r=rank_q(M); assert r%4==0; return r//4 +def formula(R,A): + if not any(T==S_ or NO[T,S_] for T in R for S_ in A): return 0 + return 2 if len(R)>=2 and len(A)>=2 else 1 +import random +rnd=random.Random(1); checked=0 +for _ in range(400): + R=rnd.sample(range(v),rnd.randint(1,4)); A=rnd.sample(range(v),rnd.randint(1,4)) + assert gram_rank(R,A)==formula(R,A); checked+=1 +print('rank formula checked on',checked,'random pairs') +def feasible(C): + f=CNF(); c=C + U=[[f.new() for j in range(c)] for T in range(v)]; W=[[f.new() for j in range(c)] for T in range(v)] + P={(T,S_,j):f.AND(W[T][j],U[S_][j]) for T in range(v) for S_ in range(v) for j in range(c)} + for T in range(v): f.parity([P[T,T,j] for j in range(c)],1) + for T in range(v): + for S_ in range(v): + if NO[T,S_]: f.parity([P[T,S_,j] for j in range(c)],0) + costlits=[] + for j in range(c): + # r1 >= any relevant pair; r2 >= (|R|>=2 and |A|>=2) + r1=f.new(); r2=f.new() + for T in range(v): + for S_ in range(v): + if T==S_ or NO[T,S_]: f.cl.append([-P[T,S_,j],r1]) + for T1,T2 in itertools.combinations(range(v),2): + for S1,S2 in itertools.combinations(range(v),2): + f.cl.append([-W[T1][j],-W[T2][j],-U[S1][j],-U[S2][j],r2]) + costlits+=[r1,r2] + enc=CardEnc.atmost(lits=costlits,bound=C,top_id=f.n,encoding=EncType.seqcounter) + s=Cadical153(bootstrap_with=f.cl+enc.clauses) + return s.solve() +for C in (8,9,10): + print('cost <=',C,'SAT' if feasible(C) else 'UNSAT',flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/k12.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/k12.py new file mode 100644 index 0000000000..43f22a9e79 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/k12.py @@ -0,0 +1,35 @@ +"""Coxeter-Todd K12 = {x in Z[w]^6 : x_i = x_j mod theta, sum x_i = 0 mod 3}; 756 minimal vectors (norm 6) = 126 lines (G34). +Units of Z[w]: +-w^a. Exact enumeration of the minimal vectors.""" +import sys, itertools; sys.path.insert(0,sys.path[0]) +import numpy as np +from klines import * +UNITS=[(1,0),(0,1),(-1,-1),(-1,0),(0,-1),(1,1)] # 1, w, w^2, -1, -w, -w^2 +def mul(x,y): + a,b=x; c,d=y; return (a*c-b*d, a*d+b*c-b*d) +THETA=(1,2) # theta = w - w^2 = w - (-1-w) = 1 + 2w (norm 3) +def norm(x): a,b=x; return a*a-a*b+b*b +def k12_min(): + out=set() + # type 1: theta*u1 at i, theta*u2 at j with u1 = -u2 mod theta (opposite sign classes) + for i,j in itertools.combinations(range(6),2): + for a in range(6): + for b in range(6): + if (a<3)==(b<3): continue + x=[(0,0)]*6; x[i]=mul(THETA,UNITS[a]); x[j]=mul(THETA,UNITS[b]); out.add(tuple(x)) + # type 2: eps * w^{a_i}, sum w^{a_i} = 0 mod 3 + for eps in (1,-1): + for e in itertools.product(range(3),repeat=6): + n=[e.count(0),e.count(1),e.count(2)] + if not (n[0]%3==n[1]%3==n[2]%3): continue + out.add(tuple(UNITS[k if eps==1 else k+3] for k in e)) + out=sorted(out) + assert len(out)==756 and all(sum(norm(c) for c in x)==6 for x in out) + return out +def k12_system(): + vs=k12_min() + S=eis_system('K12', vs, 6) + reps=dedup_lines(S.V, S.basis) + return System('G34(K12)', reps, S.Q, S.basis) +if __name__=='__main__': + S=k12_system(); print(S.name,'v',S.v,'w',S.w,'nonorth deg',set(S.NO.sum(1))) + a,K=S.alpha(); print('alpha',a,'rank of clique',S.rank(K)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/klines.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/klines.py new file mode 100644 index 0000000000..ef7cb552a0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/klines.py @@ -0,0 +1,51 @@ +"""Generic line systems over an algebra A (imaginary quadratic field or quaternions) in a real model. +A system = (V, Q, basis): V integer vectors (one per line), Q integer symmetric positive-definite Gram (real model), +basis = integer matrices spanning A (identity first), acting on the left. A-lines L = A.v. Two lines are A-orthogonal iff +v^T Q B u = 0 for every B in the basis. A-rank of a set = real rank of {B v} / dim A. Everything exact.""" +import itertools, sys +import numpy as np +sys.path.insert(0,'/Users/joelpulikkan/imb-main/research/format-limits/scripts') +from e8units import rank_q +import networkx as nx + +class System: + def __init__(self, name, V, Q, basis): + self.name=name; self.V=np.array(V,dtype=np.int64); self.Q=np.array(Q,dtype=np.int64); self.basis=[np.array(B,dtype=np.int64) for B in basis] + self.d=len(self.basis); self.v=len(self.V) + NO=np.zeros((self.v,self.v),bool) + for B in self.basis: NO|=(self.V@self.Q@B@self.V.T)!=0 + np.fill_diagonal(NO,False); self.NO=NO + self.w=self.rank(range(self.v)) + def rank(self,idx): + rows=[list(B@self.V[i]) for i in idx for B in self.basis] + if not rows: return 0 + r=rank_q(rows); assert r%self.d==0; return r//self.d + def graph(self): + g=nx.Graph(); g.add_nodes_from(range(self.v)) + g.add_edges_from((i,j) for i in range(self.v) for j in range(i+1,self.v) if self.NO[i,j]); return g + def alpha(self): + """max pairwise A-nonorthogonal set (clique of the nonorth graph); exact (networkx max clique enumeration).""" + K=max(nx.find_cliques(self.graph()),key=len); return len(K),sorted(K) + +def dedup_lines(vecs, basis, Q=None): + """group vectors into A-lines: u ~ v iff rank{B u, B v} = d. Keeps the first vector of each line.""" + reps=[]; keys={} + d=len(basis) + for x in vecs: + x=np.array(x,dtype=np.int64) + orb=[] + # line membership test via exact rank + found=False + for r in reps: + if rank_q([list(B@r) for B in basis]+[list(x)])==d: found=True; break + if not found: reps.append(x) + return reps + +# --------------------------------------------------------------------------------------------------------------- +# Eisenstein integers in real coordinates (a, b) <-> a + b w, w^2 + w + 1 = 0. 2*Re(x conj y) form per coordinate. +EIS_Q=np.array([[2,-1],[-1,2]]); EIS_W=np.array([[0,-1],[1,-1]]) # multiplication by w: (a,b) -> (-b, a-b) +def eis_system(name, vecs_complex, n): + """vecs_complex: list of tuples of (a,b) pairs, length n.""" + V=[[c for ab in x for c in ab] for x in vecs_complex] + Q=np.kron(np.eye(n,dtype=np.int64),EIS_Q); W=np.kron(np.eye(n,dtype=np.int64),EIS_W) + return System(name, V, Q, [np.eye(2*n,dtype=np.int64), W]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/meetratio.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/meetratio.py new file mode 100644 index 0000000000..9eca6d40a6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/meetratio.py @@ -0,0 +1,17 @@ +import sys,pickle; sys.path.insert(0,sys.path[0]) +from algebra_lines import * +name=sys.argv[1] +roots=e8_roots(); I8=np.eye(8,dtype=np.int64); Li,Lj,Lk=left_quat() +basis={'Gauss':[I8,Li],'Eisen':[I8,a2_rotation(roots)]}[name] +reps,NO=lines_over(roots,basis); v=len(reps) +fl=pickle.load(open('flats_%s.pkl'%name,'rb')) +w=4; fl[4]={frozenset(range(v))} +big=[F for F in fl[3] if len(F)==max(len(G) for G in fl[3])] +print('hyperplane flats of max size',len(big),len(big[0])) +best=0 +for r in fl: + for F in fl[r]: + meet=sum(1 for X in big if F & X) + best=max(best,meet/r) + +print('max meet/rank',best) diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/minrank_any.py b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/minrank_any.py new file mode 100644 index 0000000000..747319ad0d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/minrank_any.py @@ -0,0 +1,28 @@ +"""F2 minrank (asymmetric B = W U^T allowed) of the A-orthogonality decoder pattern, by SAT with the clique gauge.""" +import sys,time; sys.path.insert(0,sys.path[0]) +from klines import * +from minrank_sat import solve_asym +def systems(): + from algebra_lines import e8_roots, left_quat, a2_rotation, lines_over + roots=e8_roots(); I8=np.eye(8,dtype=np.int64); Li,Lj,Lk=left_quat() + out={} + for nm,basis in (('Gauss',[I8,Li]),('Eisen',[I8,a2_rotation(roots)]),('Hurwitz',[I8,Li,Lj,Lk]),('E8',[I8])): + reps,_=lines_over(roots,basis); out[nm]=System(nm,reps,I8,basis) + return out +def minrank(S, cmin=None, cmax=None): + a,K=S.alpha() + pat={(i,j) for i in range(S.v) for j in range(S.v) if S.NO[i,j]} + c=cmin or a; res=[] + while True: + r=solve_asym(S.v,pat,None,c,'none',K); res.append((c,r['result'],r['sec'])) + print(S.name,c,r['result'],r['sec'],flush=True) + if r['result']=='SAT': return c,res,r + c+=1 + if cmax and c>cmax: return None,res,None +if __name__=='__main__': + nm=sys.argv[1] + if nm=='K12': + from k12 import k12_system; S=k12_system() + else: S=systems()[nm] + print(nm,'v',S.v,'w',S.w,'alpha',S.alpha()[0]) + minrank(S, int(sys.argv[2]) if len(sys.argv)>2 else None, int(sys.argv[3]) if len(sys.argv)>3 else None) From 3759bebcd9e221099c8bf8cea4221c05fb7d51e0 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Mon, 12 Oct 2026 22:31:07 -0400 Subject: [PATCH 03/17] Add unit-family notes (triple, Lorentzian I_{n,1}) and correct the E6 screen row (#630) Co-Authored-By: Claude Opus 5.5 --- .../README.md | 10 +- .../ade_screen_fixed.py | 131 ++ .../s3-unit-families/README.md | 288 +++ .../s3-unit-families/expected.json | 1916 +++++++++++++++++ .../scripts/ade_screen_fixed.py | 131 ++ .../s3-unit-families/scripts/check.py | 104 + .../s3-unit-families/scripts/cover.py | 147 ++ .../s3-unit-families/scripts/est_model.py | 135 ++ .../s3-unit-families/scripts/lorentz.py | 87 + .../scripts/lorentz_minrank.py | 20 + .../s3-unit-families/scripts/rank.py | 115 + .../s3-unit-families/scripts/triple.py | 173 ++ .../s3-unit-families/scripts/units.py | 210 ++ .../s3-unit-families/verify.sh | 21 + 14 files changed, 3487 insertions(+), 1 deletion(-) create mode 100644 research/e8-decoder-floors-and-cold14-notes/ade_screen_fixed.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/expected.json create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/ade_screen_fixed.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/check.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/cover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/est_model.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz_minrank.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/rank.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/triple.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/units.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s3-unit-families/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index cb5917503a..845b7a4327 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -2,7 +2,10 @@ Negative results and one open lead, recorded so others don't repeat them. Scripts are included as they were run (paths reference a local scratch dir and `~/imb-src` pinned sources; Python 3.11 with numpy, scipy, sympy, python-sat, ortools). SAT results come from CaDiCaL via python-sat **without proof logs**. Nothing here is packaged with a verify.sh. -## 1. Unit screen (`ade_screen.py`) +## 1. Unit screen (`ade_screen.py`; **superseded by `s3-unit-families/` §1**) + +> **Erratum.** As #630 (vectorsss) points out, the "E6" row was D6 (30 lines). True E6 (the complement of an A2: v 36, h 6) has exact F2 minrank 7 and label copy cost 35 (x = 35/36), so X ≤ 5.69e-5: it is still useless. The c column here was rank_F2(J+G), which is only an upper bound; the exact minranks are in `s3-unit-families/`. E7 and E8 are unchanged. + Real ADE root systems as units (v lines, window h = rank, c = F2 rank of (J+G) mod 2, proxy x = c·h/v, ceiling via #595's `skel.X`): only E8 gives a useful ceiling. E7 (v 63, h 7, c 7) reaches X* ≈ 1.84e-3; A_n, D_n and E6 have x ≥ 1 and give no saving. X scales roughly as 1/h, consistent with the Leech (#598) and Niemeier (#606) losses. ## 2. E8 decoder floors (`s2-decoder/`, details in `REPORT.md`) @@ -39,3 +42,8 @@ On the bit side ranks are counted over Q, so a K-line unit is a rank-k unit with - G34 / Coxeter–Todd K12: 126 lines, w = 6, largest non-orthogonal set 15, so x_K ≥ 0.714 even at minrank 15; it loses. - Complex side (argued): F4-Hermitian (Eisenstein) labels give an alternating F2 trace form, so there are no odd-norm residuals and the phase calculus fails. Gaussian labels reduce mod (1+i) to F2, so they bring no extension. - Side finding (float screen only, not certified): the paper's original triple family has a higher X* than E8 (~7.4e-3 bit, ~7.2e-3 complex at h = 24, s = 3), but 630 orthogonal neighbours per target (vs 63 for E8) makes its helper cost the issue. X* alone does not rank unit families. + +## 5. Beyond E8: triple and Lorentzian families (`s3-unit-families/`, own README and `verify.sh`, 114 PASS) +- Certified ceilings above E8's: the paper's triple family T_h (bit cst h(h−1), max X* 7.7735e-3 at h = 21; complex cst h², max 7.4444e-3 at h = 22) and the Lorentzian units of #634: I_{11,1} (minrank exactly 11, cst 110, x = 1/2, X* 7.1715e-3) and I_{13,1} (cst 156, x = 3/7, X* 8.6247e-3). +- Helper-adjusted (model, calibrated to #555 within 0.3%, not a bound): no candidate reaches E8's estimate, even at E8's 3.95 helpers per port. To break even, a design would need ≤ 2.6–3.4 helpers per port for I_{n,1} (≤ 2.5 for bit triples). The best lead is I_{11,1}; next steps are its punctures (the analogue of E8's 120 → 111) and a real helper design. +- #595 §D helper floors don't separate the families: the triple family has cover number 2 (exact certificates up to h = 24). diff --git a/research/e8-decoder-floors-and-cold14-notes/ade_screen_fixed.py b/research/e8-decoder-floors-and-cold14-notes/ade_screen_fixed.py new file mode 100644 index 0000000000..f9a9bc338b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/ade_screen_fixed.py @@ -0,0 +1,131 @@ +"""Corrected ADE unit screen (replaces #620's ade_screen.py; erratum found by #630). + +Per unit (real root lines, window h = Gram rank, k = 1): + v, h exact + c0 = rank_F2(J + G) the label decoder B0 used by #620 (an UPPER bound on the F2 minrank of the pattern) + omega largest pairwise non-orthogonal set (identity block of the pattern) -> minrank >= omega + c = minrank exact F2 minrank of the pattern (asymmetric decoders allowed, SAT; c-1 UNSAT, c SAT) + x_proxy = c*h/v #620's full-rank-totals premise (each total charged the window), now with the exact minrank + cst_lab star cost attainable by label decoders on the B0 labels: min over bases {p_j} of F2^c0 of + sum_j dim_Q span{lines T : u_T . p_j = 1} (matroid greedy, exact); an UPPER bound on cst_min + X* certified ceiling of #595 (skel.X, rational intervals) maximised over odd s in 3..15 + +What changed against #620: + * E6 was built as the complement of two ORTHOGONAL roots (= D6, 30 lines); it is now the complement of an A2 (36 lines). + * the column 'c' was rank_F2(J+G) of one decoder; it is now the exact minrank (both are printed). + * an attainable label-decoder star cost is added next to the proxy. +Usage: python ade_screen_fixed.py REPO_ROOT [--json out.json] +""" +import json +import sys +import time +from fractions import Fraction as Fr + +import numpy as np + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X, profile # noqa: E402 (#595, certified ceiling) +from cert import cost_iv # noqa: E402 +from minrank_sat import solve_asym # noqa: E402 (#595 SAT encoding, GL(c) gauge on a clique) +from e8units import sym_factor_f2 # noqa: E402 + +import units as U # noqa: E402 + +S_RANGE = range(3, 17, 2) +TIMEOUT = 600 # seconds per SAT query (A8, D9 hit it: minrank left as a range) + + +def xstar(h, x): + best = None + for s in S_RANGE: + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi'], r['lo']) + return best + + +def minrank(G, omega_set, c0): + lo, hi, log = U.minrank_f2(G, omega_set, c0, solve_asym, timeout=TIMEOUT) + return (lo if lo == hi else '%d..%d' % (lo, hi)), log, lo + + +def label_cost(L, G): + B0 = (1 + G) % 2 + Uf = sym_factor_f2(B0).astype(np.int64) + c0 = Uf.shape[1] + costs = [] + for p in range(1, 1 << c0): + pv = np.array([(p >> j) & 1 for j in range(c0)]) + R = np.nonzero((Uf @ pv) % 2)[0] + costs.append((U.rank_q(L[R].tolist()), p)) + costs.sort() + basis, tot = {}, 0 + for cst, p in costs: + x = p + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + if x: + tot += cst + if len(basis) == c0: + break + return tot, c0 + + +def unit(name, L, G=None): + if G is None: + G = U.gram_doubled(L) + v = len(L) + h = U.rank_q(G.tolist()) + c0 = U.f2_rank((1 + G) % 2) + om = U.nonorth_clique(G) + c, log, clo = minrank(G, om, c0) + cst_lab, _ = label_cost(L, G) + xp = Fr(clo * h, v) # proxy at the certified lower end of the minrank + xl = Fr(cst_lab, v) + row = dict(unit=name, v=v, h=h, c0=c0, omega=len(om), minrank=c, sat_log={k: v_ for k, v_ in log.items()}, + x_proxy=str(xp), cst_lab=cst_lab, x_lab=str(xl)) + for key, x in (('proxy', xp), ('lab', xl)): + b = xstar(h, x) + row['Xstar_' + key] = None if b is None else dict(s=b[0], hi=str(b[1]), hi_float=float(b[1])) + if row['Xstar_lab'] is None and xl < 1: + # positive deficit only at s > 1/(1 - x): bound the ceiling by D_s / Cost_s (#595 A.2) for odd s up to 801 + best = None + for s_ in range(3, 802, 2): + prof, m, D = profile(s_, h, 1, xl) + if D <= 0: + continue + b = D / cost_iv(prof, m)[0] + if best is None or b > best[1]: + best = (s_, b) + row['DoverCost_lab'] = dict(s=best[0], hi=str(best[1]), hi_float=float(best[1])) + print(' %s: x_lab < 1, deficit > 0 only for s > %s; max over odd s <= 801 of D_s/Cost_s = %.4e at s = %d' + % (name, 1 / (1 - xl), float(best[1]), best[0])) + return row + + +def main(): + rows = [] + cases = [('A%d' % n, U.roots_A(n)) for n in (4, 5, 6, 7, 8)] + cases += [('D%d' % n, U.roots_D(n)) for n in (4, 5, 6, 7, 8, 9, 10, 12)] + cases += [('D6(archived "E6")', U.roots_D6_archived()), ('E6', U.roots_E6()), ('E7', U.roots_E7()), ('E8', U.roots_E8())] + for name, L in cases: + t = time.time() + r = unit(name, L) + rows.append(r) + f = lambda k: ('%.4e@s%d' % (r[k]['hi_float'], r[k]['s'])) if r[k] else 'D<=0' + print('%-18s v=%4d h=%2d c0=%2d omega=%2d minrank=%-5s x_proxy=%-7s cst_lab=%4d x_lab=%-8s X*proxy=%-14s X*lab=%-14s (%.0fs)' + % (name, r['v'], r['h'], r['c0'], r['omega'], r['minrank'], r['x_proxy'], r['cst_lab'], r['x_lab'], + f('Xstar_proxy'), f('Xstar_lab'), time.time() - t), flush=True) + if '--json' in sys.argv: + json.dump(rows, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/README.md b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/README.md new file mode 100644 index 0000000000..72ac4ba772 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/README.md @@ -0,0 +1,288 @@ +# Beyond E8 (no κ claim): ADE screen erratum, certified triple-family and Lorentzian ceilings, and a helper-adjusted ranking + +These notes follow up #620 (GamingPuzzled). They make **no κ claim** and ship no word. + +Status labels: + +- **checked**: `verify.sh` recomputes the item; it is exact or certified unless marked *model*. +- **argued**: a proof written in this README. +- **model**: an estimate. It is not a bound. + +```sh +sh verify.sh /path/to/integer-mult-bounds-main /fresh/out # PY=python3.11 ...; about 40 min, steps in parallel +``` + +Requirements: Python ≥ 3.11 with numpy, networkx and python-sat. The scripts import #595's `skel.py`, `cert.py`, `minrank_sat.py` and +`e8units.py` from `research/format-limits/scripts` of main. `check.py` compares every recomputed field with +`expected.json`: + +- integers, fractions and certified interval ends must be equal; +- model floats must agree to a relative 10⁻⁹. + +SAT answers come from CaDiCaL through python-sat, **without proof logs**. + +Conventions are those of #595 (§A): + +- a unit is v lines with a Gram matrix G and window h (k = 1); +- decoder pattern over F2: B_TT = 1, and B_TS = 0 wherever G_TS ≠ 0; +- c is the number of totals; +- star cost cst = Σ_j β_j, where β_j is the dimension of the gathered span (the copy drops f = V → 0); +- x = cst/v; +- X* is the certified root of skel.X, maximised over odd s ∈ [3, 15]. + +## 1. Erratum to #620's ADE screen (`ade_screen_fixed.py`, checked) + +#630 (vectorsss) is right: #620's `E6()` took the complement of two **orthogonal** roots. That gives 30 lines of type D6. +E6 is the complement of an A2 (a·b = 1), which has 36 lines. Recomputing the whole table found two more problems with #620's +screen. + +- #620's column "c" was rank_F2(J + G) of one decoder. That is only an **upper** bound on the minrank. The column is now + the exact F2 minrank, with asymmetric decoders allowed: SAT from the clique bound ω upwards, under the GL(c) gauge on the clique. +- #620 charged every total the full window (x = c·h/v). The table now also gives the star cost that label decoders on + the B0 labels actually attain, cst_lab: a matroid-greedy minimum over bases of F2^c of Σ dim span(gathered set). It is + an upper bound on cst_min. + +| unit | v | h | rank_F2(J+G) | ω | **minrank** | x_proxy = c·h/v | cst_lab (x_lab) | X* (proxy / lab) | +|---|---|---|---|---|---|---|---|---| +| A4 | 10 | 4 | 5 | 4 | 5 | 2 | 15 (3/2) | – / – | +| A5 | 15 | 5 | 5 | 5 | 5 | 5/3 | 20 (4/3) | – / – | +| A6 | 21 | 6 | 7 | 6 | 7 | 2 | 35 (5/3) | – / – | +| A7 | 28 | 7 | 7 | 7 | 7 | 7/4 | 42 (3/2) | – / – | +| A8 | 36 | 8 | 9 | 8 | 8..9 (c = 8 timed out after 600 s) | ≥ 16/9 | 63 (7/4) | – / – | +| D4 | 12 | 4 | 3 | 3 | 3 | 1 | 12 (1) | – / – | +| D5 | 20 | 5 | 5 | 4 | 5 | 5/4 | 20 (1) | – / – | +| D6 | 30 | 6 | 5 | 5 | 5 | 1 | 30 (1) | – / – | +| D7 | 42 | 7 | 7 | 6 | 7 | 7/6 | 42 (1) | – / – | +| D8 | 56 | 8 | 7 | 7 | 7 | 1 | 56 (1) | – / – | +| D9 | 72 | 9 | 9 | 8 | 8..9 (timeout) | ≥ 1 (c ≥ 8: 8·9/72) | 72 (1) | – / – | +| D10 | 90 | 10 | 9 | 9 | 9 | 1 | 90 (1) | – / – | +| D12 | 132 | 12 | 11 | 11 | 11 | 1 | 132 (1) | – / – | +| D6 (#620's "E6") | 30 | 6 | 5 | 5 | 5 | 1 | 30 (1) | – / – | +| **E6 (corrected)** | 36 | 6 | 7 | 5 | **7** (5 and 6 UNSAT) | 7/6 | **35 (35/36)** | – / ≤ 5.69·10⁻⁵ (see below) | +| E7 | 63 | 7 | 7 | 7 | 7 | 7/9 | 49 (7/9) | 1.8468·10⁻³ (s = 11) | +| E8 | 120 | 8 | 9 | 8 | 9 | 3/5 | 72 (3/5) | 5.1598·10⁻³ (s = 5) | + +("–" means D_s = (s − 1) − s·x ≤ 0 for every s ≤ 15.) + +Findings: + +- **E6 (corrected).** + - The minrank is 7. ω = 5 (c = 5 and 6 are UNSAT), and #630's rank_F2(J+G) = 7 is attained. + - The proxy deficit is negative, as #630 says. + - The attainable label-decoder cost is 35 < 36, though: one total spans only 5 dimensions. That makes x = 35/36 < 1, so D_s > 0 for + s > 36. + - The ceiling there is negligible. max over odd s ≤ 801 of D_s/Cost_s is 5.69·10⁻⁵ (at s = 81), and D_s/Cost_s is an upper + bound on X (#595 §A.2). + - So the conclusion "E6 gives no useful saving" stands, but the reason is the tiny D_s/Cost_s, not D ≤ 0 at every s. +- **A_n and D_n.** x ≥ 1 under both the proxy (at the lower end of the minrank) and the attained label cost, so there is no saving + in this screen. The D_n label decoders attain exactly x = 1 (D = −1). +- **E7 and E8.** These rows are unchanged from #620: X* = 1.8468·10⁻³ (E7) and 5.1598·10⁻³ (E8 120/72). +- **Scope.** The cst_lab column is an attained upper bound for label decoders on the B0 labels. A lower bound over + all decoders is proved only for E8 (#595 C.6, S2). The "no saving" verdicts for A_n, D_n and E6 rest on #620's + full-rank-totals premise; this table only recomputes them. + +## 2. The triple family T_h (`triple.py`, checked) + +This is the upstream Alon-type unit (`upstream/build/sections/03-motifs.tex`). + +**Bit side.** Lines t_T, T ∈ C([h], 3), with the form I − J/9, so G_ST = |S∩T| − 1. + +- The pattern requires B_TS = 0 where |S∩T| ∈ {0, 2}. +- The paper's centre decoder C_i is B0 = M Mᵀ mod 2, with c = h totals. + +**Complex side.** Labels are t_T ∈ F2^h with the dot product. + +- The pattern requires B_TS = 0 where |S∩T| = 1. +- The decoder is the h + 1 centres C_i and C_*, with B_ST = (|S∩T| − 1)/2 over ℚ(i). + +**Copy cost (derived, not a proxy).** Total C_i gathers the C(h−1, 2) lines T ∋ i, and its readers are the same set. + +| | bit | complex | +|---|---|---| +| span of a centre C_i | dimension h − 1 over ℚ (the functional 2x_i − Σ_{j≠i} x_j vanishes on it) | V_i = {x : Σ_{j≠i} x_j = 0}, dimension h − 1 over F2 | +| #446 Theorem C pairing rank of C_i | h − 1 (computed) | h − 1 for even h; h − 2 for odd h, because 1 + e_i ∈ V_i ∩ V_i^⊥ | +| C_* | – | costs h | +| **cst, attained** | **h(h − 1)** | **h²** | +| cst, Theorem C floor of the centre decoder | h(h − 1) | h² for even h; h(h − 1) for odd h | + +**Minrank (bit).** + +- For 8 ≤ h ≤ 11 the minrank is h (SAT). +- For h ≡ 0 (mod 4) it is h by the clique argument: ⌊h/4⌋ disjoint 4-sets give 4⌊h/4⌋ pairwise even-intersection triples. +- h = 6 and 7 give 6. +- Label decoders on the B0 labels have every gathered span of dimension ≥ h − 1 (`lab_min`, checked for h ≤ 11). So the centre decoder is + optimal among them. + +Certified ceilings (selected; every h from 6 to 32 is in `triple.json`): + +| h | v | bit x = 6/(h−2) | **bit X*** | complex x = h²/C(h,3) | **complex X*** | +|---|---|---|---|---|---| +| 9 | 84 | 6/7 (window 8: the form is degenerate at h = 9) | 7.2457·10⁻⁴ (s = 15) | 27/28 | – (Theorem C floor: 6.358·10⁻⁴) | +| 12 | 220 | 3/5 | 3.3282·10⁻³ | 36/55 | 2.4187·10⁻³ | +| 15 | 455 | 6/13 | 6.4061·10⁻³ | 0.4945 | 5.3738·10⁻³ | +| 16 | 560 | 3/7 | 6.9611·10⁻³ | 0.4571 | 6.1233·10⁻³ | +| 18 | 816 | 3/8 | 7.5613·10⁻³ | 0.3971 | 6.9874·10⁻³ | +| 20 | 1140 | 1/3 | 7.7612·10⁻³ | 0.3509 | 7.3512·10⁻³ | +| **21** | 1330 | 6/19 | **7.7735·10⁻³ (max)** | 0.3316 | 7.4224·10⁻³ | +| **22** | 1540 | 3/10 | 7.7474·10⁻³ | 0.3143 | **7.4444·10⁻³ (max)** | +| 24 | 2024 | 3/11 | 7.6184·10⁻³ | 0.2846 | 7.3882·10⁻³ | +| 30 | 4060 | 3/14 | 6.9743·10⁻³ | 0.2217 | 6.8600·10⁻³ | + +All maxima are at s = 3. + +- **Bit side.** The triple family's ceiling exceeds E8's realised bit unit (6.0005·10⁻³) for every h from 15 to 32. +- **Complex side.** It exceeds E8's realised complex unit (5.1863·10⁻³) for every h from 15 to 32. At the + attained cost the odd-h rows are slightly lower; at their Theorem C floor they equal the bit values. +- **S1's float screen.** It gave 7.4 / 7.2·10⁻³ at h = 24 with the proxy cst = h² / h(h+1). The derived cst is h(h−1) / h², and + the certified values are 7.6184 / 7.3882·10⁻³. + +## 3. Lorentzian I_{n,1} (`lorentz.py`, `lorentz_minrank.py`, checked) + +The units are #634's triples e_A + e_{n+1} and pairs e_i − e_j, n odd. + +- The binary labels (the 3-set, or the complement of the pair) fit the #595 pattern exactly: ⟨u_S, u_T⟩ = 1 + G_ST mod 2 (checked). +- The window is the Lorentz Gram rank n. #634 note 3 proves that n − 1 is impossible for n = 11, 13. + +| unit | v | h | c (minrank) | ω | readers z per line | cst_lab (per total) | x | **X*** | +|---|---|---|---|---|---|---|---|---| +| I_{9,1} = E8 (control, quotient by the isotropic w) | 120 | 8 | 9 | 8 | 63 | 72 (8 each) | 3/5 | 5.1598·10⁻³ | +| I_{11,1} | 220 | 11 | **11** (10 UNSAT) | 10 | 115 (triples), 129 (pairs) | **110 (10 each)** | 1/2 | **7.1715·10⁻³** (s = 3) | +| I_{13,1} | 364 | 13 | ≤ 13 (ω = 12) | 12 | 183, 231 | **156 (12 each)** | 3/7 | **8.6247·10⁻³** (s = 3) | + +Unlike E8, each total spans only h − 1 dimensions. That is where x < 3/5 comes from. Both ceilings exceed E8's. + +## 4. Helper floors: the #595 §D method (`cover.py`, checked; one bound argued) + +The method computes cover numbers at the source end: the fewest proper hyperplane sections of a source's readers T(s) that cover T(s). + +| unit | readers | rank | cover number | evidence | +|---|---|---|---|---| +| E8 bit (control) | 63 | 7 | 3 | no 2-cover; best pair union 52; 3-cover found (= #595 D1) | +| T_h, h = 8 … 24 | 3·C(h−3, 2) | h − 1 | **2** | exact certificate: two rank-(h−2) subsets with union T(s) | +| I_{11,1} / I_{13,1}, pair-type sources | 129 / 231 | h − 1 | 2 | certificate | +| I_{11,1} / I_{13,1}, triple-type sources | 115 / 183 | h − 1 | ≤ 3 (no 2-cover found in sampling) | 3-cover certificate | + +**Triple family, max section (argued; matches the search for h = 10, 12, 16, 20, 24).** + +- **Setup.** For S = {1, 2, 3} and O = [h] \ S (n′ = h − 3), a section is {(a, {x, y}) : c_a + c_x + c_y = 0} for a functional c. +- **Constant c on O.** At most two of the three a-classes are covered, so the section has ≤ 2C(n′, 2) readers. If all three were + covered, c would vanish on T(s). +- **Non-constant c on O.** Each a-class meets a "sum graph" with at most C(n′−1, 2) edges (n′ ≥ 6). That gives ≤ 3C(h−4, 2) readers, + and c = e_z attains it. +- **Result.** For h ≥ 9 the max section is 3C(h−4, 2), the leftover is 3(h − 4), and the #595 floors are: + - sink-only: R ≥ v − ⌊v/(3(h−4))⌋, about 0.98–0.99 helpers per port; + - strict: 0. +- **Comparison.** E8 bit has 212 / 138, which is 1.91 / 1.24 per port. + +**Conclusion of §4.** The design-free floors do **not** penalise these families; they are lower per port than E8's. +The §D method cannot rank them. Their helper cost is in the work per port: 630 readers per source for T_24 bit, against 63 +for E8. + +## 5. Helper-adjusted ranking (`est_model.py`, `rank.py`; model) + +**Model.** + +- **Base.** #595's per-port slot profile at window h. +- **Data runs.** Cut with the shipped run histograms: #555 x/y for bit, #569 four-cold for complex (#595 a4). +- **Helpers.** + - ρ = R/v helpers per port, each climbing h per stage. + - Cut with the shipped helper-run histograms (#555; #568 implied). + - Hosted into the largest B_K idle slots first. A hosted slot of rank r leaves a remainder of rank r/a, as in #555's 42/28/14 → 6/4/2. + - Mass beyond the hostable idle mass stays as extra children. +- **Root.** The same pressure root, at m = 1 + s(h−1) and D = (s−1) − s·x, with the best s. +- **Calibration.** + - Bit: the model reproduces #555 to 0.3% (factor 1.0032). + - Complex: the factor is 1.129, because #568's hosting is better than the rule above. +- **Piece-size scaling to larger h.** + - abs: shipped absolute ranks (pessimistic). + - rel: ranks scale with the window (optimistic). +- **Loss-chain factors.** #634's a ≈ (1/m)(D/W)/L_ent is reported per candidate in `rank.json`. It matches the root to < 1% at E8. + +**Columns.** + +- **ρ\*** is the break-even helpers per port: the ρ at which a candidate's estimate equals the E8 word of the same side in the same + model (bit 1.8058·10⁻³ at ρ = 439/111 = 3.95; complex 1.6108·10⁻³ at ρ = 496/112 = 4.43). +- **ρ_dim** transfers E8's shipped intensity per reader per window dimension: ρ_E8 · (z/(h−1)) / (z_E8/(h_E8−1)). + +| candidate | h | v | x | readers z | X* (certified) | **ρ\*** abs / rel | ρ_dim | a_est at ρ = ρ_E8, abs / rel | **a_est at ρ_dim**, abs / rel | +|---|---|---|---|---|---|---|---|---|---| +| E8 bit 111/63 (#555) | 8 | 111 | 0.5676 | 63 | 6.0005e-3 | 3.95 (ref) | 3.95 | 1.806e-3 | **1.806e-3** | +| E8 complex 112/64 (#568) | 9 | 112 | 0.5714 | 52 | 5.1863e-3 | 4.43 (ref) | 4.43 | 1.611e-3 | **1.611e-3** | +| **I_{11,1} bit** | 11 | 220 | 0.5000 | 118.5 | 7.1715e-3 | 2.66 / 3.34 | 5.21 | 1.480e-3 / 1.620e-3 | **1.225e-3 / 1.339e-3** | +| I_{13,1} bit | 13 | 364 | 0.4286 | 193.3 | 8.6247e-3 | 2.64 / 3.43 | 7.08 | 1.438e-3 / 1.647e-3 | 0.942e-3 / 1.079e-3 | +| T_13 bit | 13 | 286 | 0.5455 | 135 | 4.3818e-3 | 1.03 / 1.75 | 4.94 | 1.046e-3 / 1.198e-3 | 0.904e-3 / 1.026e-3 | +| T_12 bit | 12 | 220 | 0.6000 | 108 | 3.3282e-3 | 0.55 / 0.98 | 4.31 | 0.954e-3 / 1.070e-3 | 0.900e-3 / 1.009e-3 | +| T_14 bit | 14 | 364 | 0.5000 | 165 | 5.5852e-3 | 1.27 / 2.08 | 5.58 | 1.092e-3 / 1.275e-3 | 0.866e-3 / 1.001e-3 | +| T_16 bit | 16 | 560 | 0.4286 | 234 | 6.9611e-3 | 1.46 / 2.41 | 6.86 | 1.109e-3 / 1.340e-3 | 0.745e-3 / 0.898e-3 | +| T_20 bit | 20 | 1140 | 0.3333 | 408 | 7.7612e-3 | 1.20 / 2.43 | 9.44 | 1.029e-3 / 1.326e-3 | 0.533e-3 / 0.679e-3 | +| T_24 bit | 24 | 2024 | 0.2727 | 630 | 7.6184e-3 | 0.80 / 2.19 | 12.04 | 0.918e-3 / 1.248e-3 | 0.387e-3 / 0.517e-3 | +| T_16 complex | 16 | 560 | 0.4571 | 325 | 6.1233e-3 | 2.14 / 2.92 | 14.76 | 1.087e-3 / 1.270e-3 | 0.424e-3 / 0.490e-3 | +| T_22 complex | 22 | 1540 | 0.3143 | 1026 | 7.4444e-3 | 1.92 / 3.07 | 33.29 | 0.995e-3 / 1.265e-3 | 0.182e-3 / 0.229e-3 | +| T_24 complex | 24 | 2024 | 0.2846 | 1393 | 7.3882e-3 | 1.67 / 2.93 | 41.26 | 0.943e-3 / 1.230e-3 | 0.141e-3 / 0.181e-3 | + +The full list (T_10 … T_30, both sides) is in `rank.json`. + +**Answer to "is there a family that beats E8 on both the ceiling and the helper-adjusted estimate?" — no candidate in +this screen does.** + +- **The ceiling.** Many candidates beat E8's ceiling: T_h for h ≥ 15 on both sides, I_{11,1} and I_{13,1}. +- **The helper-adjusted estimate.** Even at E8's own helper intensity per port (3.95), every candidate stays below E8: + - the best is I_{13,1} rel at 1.647·10⁻³, against 1.806·10⁻³; + - the break-even ρ\* is 2.6–3.4 helpers per port for I_{n,1} and ≤ 2.5 for the bit triples. +- **Why the break-even is implausible.** These units have 1.3–1.9× (I_{n,1}) and up to 3× (T_24) more readers per window + dimension than E8, which ships 3.95. +- **Loss-chain view (#634).** + - 1/m falls from 1/50 (E8, s = 7) to 1/91 (I_{11,1}, s = 9) and 1/162 (T_24 bit, s = 7). + - L_ent rises from 3.19 to 3.79–4.46. + - The smaller copy share x improves D/W by at most about 1.5×. +- **Ranking by a_est at ρ_dim.** + 1. I_{11,1}: 1.23–1.34·10⁻³ + 2. I_{13,1}: 0.94–1.08·10⁻³ + 3. T_12–T_14 bit: about 0.9–1.0·10⁻³ + 4. larger triples, falling as 1/h + 5. complex triples: their readers grow like h³ +- **Closest lead.** I_{11,1} beats E8 only if a design reaches ≤ 2.7–3.3 helpers per port on 118 readers per line. If + its minrank had been 10, cst would have been 100 and X* 9.14·10⁻³. The minrank is 11 (c = 10 UNSAT). + +**Model caveats.** + +- The helper piece statistics and hosting rule are transferred from the E8 words. +- ρ for a new family is unknown; ρ\* is the honest output. +- s is free on both sides. +- None of this bounds κ. + +## 6. Not done + +- **Q3: a rigorous lower bound on the copy cost for the Gaussian and Eisenstein E8 views.** Not attempted beyond observing that the + counting bounds available here are weak: + - covering lines by flats gives 4; + - the minrank gives 15 ≤ 0.25v; + - the clique-matroid argument gives 12. + S1's question (Σ_j dim span A_j ≥ 0.71v?) stays open. +- The exact minrank of A8 and D9 (SAT timeouts at c = 8). Not needed: x ≥ 1 either way. +- Exact max section and cover number for the triple-type sources of I_{n,1}. +- Punctures of I_{n,1}, which might help as 120 → 111 did for E8. + +## Files + +- `scripts/units.py`: exact line systems and ranks, the clique bound, and the time-limited gauged minrank SAT. +- `scripts/ade_screen_fixed.py`: §1; it replaces #620's `ade_screen.py`. +- `scripts/triple.py`: §2. +- `scripts/lorentz.py`, `scripts/lorentz_minrank.py`: §3. +- `scripts/cover.py`: §4. +- `scripts/est_model.py`, `scripts/rank.py`: §5. +- `scripts/check.py`, `expected.json`, `verify.sh`: recomputation. + +## Credits + +Builds on: + +- DreamingOfClouds: #595 format limits (skel/cert/minrank_sat, §A, §D, a4 histograms) and #446 Theorems A–C; +- vectorsss: #630, the E6 correction; +- ShikeChen01: #634, the Lorentzian family and the loss chain; +- the upstream paper (03-motifs); +- jeremykothe2: #568; +- #555 and #569, whose shipped statistics are used; +- #620 (GamingPuzzled). + +Prepared by Joel Pulikkan (GamingPuzzled) with Claude Opus 5.5 (Anthropic) via Claude Code. Apache-2.0. diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/expected.json b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/expected.json new file mode 100644 index 0000000000..aa07c6745b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/expected.json @@ -0,0 +1,1916 @@ +{ + "ade": { + "A4": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 5, + "cst_lab": 15, + "h": 4, + "minrank": 5, + "omega": 4, + "v": 10, + "x_lab": "3/2" + }, + "A5": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 5, + "cst_lab": 20, + "h": 5, + "minrank": 5, + "omega": 5, + "v": 15, + "x_lab": "4/3" + }, + "A6": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 7, + "cst_lab": 35, + "h": 6, + "minrank": 7, + "omega": 6, + "v": 21, + "x_lab": "5/3" + }, + "A7": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 7, + "cst_lab": 42, + "h": 7, + "minrank": 7, + "omega": 7, + "v": 28, + "x_lab": "3/2" + }, + "A8": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 9, + "cst_lab": 63, + "h": 8, + "minrank": "8..9", + "omega": 8, + "v": 36, + "x_lab": "7/4" + }, + "D10": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 9, + "cst_lab": 90, + "h": 10, + "minrank": 9, + "omega": 9, + "v": 90, + "x_lab": "1" + }, + "D12": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 11, + "cst_lab": 132, + "h": 12, + "minrank": 11, + "omega": 11, + "v": 132, + "x_lab": "1" + }, + "D4": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 3, + "cst_lab": 12, + "h": 4, + "minrank": 3, + "omega": 3, + "v": 12, + "x_lab": "1" + }, + "D5": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 5, + "cst_lab": 20, + "h": 5, + "minrank": 5, + "omega": 4, + "v": 20, + "x_lab": "1" + }, + "D6": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 5, + "cst_lab": 30, + "h": 6, + "minrank": 5, + "omega": 5, + "v": 30, + "x_lab": "1" + }, + "D6(archived \"E6\")": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 5, + "cst_lab": 30, + "h": 6, + "minrank": 5, + "omega": 5, + "v": 30, + "x_lab": "1" + }, + "D7": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 7, + "cst_lab": 42, + "h": 7, + "minrank": 7, + "omega": 6, + "v": 42, + "x_lab": "1" + }, + "D8": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 7, + "cst_lab": 56, + "h": 8, + "minrank": 7, + "omega": 7, + "v": 56, + "x_lab": "1" + }, + "D9": { + "DoverCost_lab": null, + "Xstar_lab": null, + "c0": 9, + "cst_lab": 72, + "h": 9, + "minrank": "8..9", + "omega": 8, + "v": 72, + "x_lab": "1" + }, + "E6": { + "DoverCost_lab": { + "hi": "1000000000000000000000000000000000000000000000/17583496298980754515651120025120377029171980162981", + "s": 81 + }, + "Xstar_lab": null, + "c0": 7, + "cst_lab": 35, + "h": 6, + "minrank": 7, + "omega": 5, + "v": 36, + "x_lab": "35/36" + }, + "E7": { + "DoverCost_lab": null, + "Xstar_lab": { + "hi": "923423603180445516994123700271757115842773/500000000000000000000000000000000000000000000", + "s": 11 + }, + "c0": 7, + "cst_lab": 49, + "h": 7, + "minrank": 7, + "omega": 7, + "v": 63, + "x_lab": "7/9" + }, + "E8": { + "DoverCost_lab": null, + "Xstar_lab": { + "hi": "5159798631791548059744023646544519579038021/1000000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": 9, + "cst_lab": 72, + "h": 8, + "minrank": 9, + "omega": 8, + "v": 120, + "x_lab": "3/5" + } + }, + "cover": { + "E8|0": { + "best_section_found": 36, + "cover_lb": 2, + "cover_ub": 3, + "h": 8, + "rank_readers": 7, + "readers": 63 + }, + "L11|0": { + "best_section_found": 87, + "cover_lb": 2, + "cover_ub": 3, + "h": 11, + "rank_readers": 10, + "readers": 115 + }, + "L11|165": { + "best_section_found": 120, + "cover_lb": 2, + "cover_ub": 2, + "h": 11, + "rank_readers": 10, + "readers": 129 + }, + "L13|0": { + "best_section_found": 147, + "cover_lb": 2, + "cover_ub": 3, + "h": 13, + "rank_readers": 12, + "readers": 183 + }, + "L13|286": { + "best_section_found": 220, + "cover_lb": 2, + "cover_ub": 2, + "h": 13, + "rank_readers": 12, + "readers": 231 + }, + "T10|0": { + "best_section_found": 45, + "cover_lb": 2, + "cover_ub": 2, + "h": 10, + "rank_readers": 9, + "readers": 63 + }, + "T12|0": { + "best_section_found": 84, + "cover_lb": 2, + "cover_ub": 2, + "h": 12, + "rank_readers": 11, + "readers": 108 + }, + "T16|0": { + "best_section_found": 198, + "cover_lb": 2, + "cover_ub": 2, + "h": 16, + "rank_readers": 15, + "readers": 234 + }, + "T20|0": { + "best_section_found": 360, + "cover_lb": 2, + "cover_ub": 2, + "h": 20, + "rank_readers": 19, + "readers": 408 + }, + "T24|0": { + "best_section_found": 570, + "cover_lb": 2, + "cover_ub": 2, + "h": 24, + "rank_readers": 23, + "readers": 630 + }, + "T8|0": { + "best_section_found": 20, + "cover_lb": 2, + "cover_ub": 2, + "h": 8, + "rank_readers": 7, + "readers": 30 + } + }, + "lorentz": { + "11": { + "Xstar": { + "hi": "1792880663455379886862817162551664296188391/250000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 11, + "cst_lab": 110, + "omega": 10, + "per_total": [ + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10 + ], + "v": 220, + "window": 11, + "x": "1/2", + "z": [ + 115, + 129 + ] + }, + "13": { + "Xstar": { + "hi": "8624695507724970504337402843475501867942509/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 13, + "cst_lab": 156, + "omega": 12, + "per_total": [ + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12 + ], + "v": 364, + "window": 13, + "x": "3/7", + "z": [ + 183, + 231 + ] + }, + "9": { + "Xstar": { + "hi": "5159798631791548059744023646544519579038021/1000000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": 9, + "cst_lab": 72, + "omega": 8, + "per_total": [ + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8 + ], + "v": 120, + "window": 8, + "x": "3/5", + "z": [ + 63 + ] + } + }, + "lorentz_minrank": { + "11": { + "c0": 11, + "hi": 11, + "lo": 11, + "log": { + "10": "UNSAT" + }, + "omega": 10 + } + }, + "rank": { + "E8 bit 111/63 (#555)": { + "Xs": 5, + "Xstar": 0.00600049473312529, + "a_dim_abs": 0.0018058295009581778, + "a_dim_rel": 0.0018058295009581778, + "a_read_abs": 0.0018058295009581778, + "a_read_rel": 0.0018058295009581778, + "a_same_abs": 0.0018058295009581778, + "a_same_rel": 0.0018058295009581778, + "cst": 63, + "h": 8, + "rho_star_abs": 3.954954954679124, + "rho_star_rel": 3.954954954679124, + "v": 111, + "x": 0.5675675675675675, + "z": 63.0 + }, + "E8 bit 120/72": { + "Xs": 5, + "Xstar": 0.005159798631791548, + "a_dim_abs": 0.0016034717449358912, + "a_dim_rel": 0.0016034717449358912, + "a_read_abs": 0.0016034717449358912, + "a_read_rel": 0.0016034717449358912, + "a_same_abs": 0.0016034717449358912, + "a_same_rel": 0.0016034717449358912, + "cst": 72, + "h": 8, + "rho_star_abs": 3.2809337786602555, + "rho_star_rel": 3.2809337786602555, + "v": 120, + "x": 0.6, + "z": 63.0 + }, + "E8 complex 112/64 (#568)": { + "Xs": 5, + "Xstar": 0.005186252252886545, + "a_dim_abs": 0.0016107569812721682, + "a_dim_rel": 0.0016107569812721682, + "a_read_abs": 0.0016107569812721682, + "a_read_rel": 0.0016107569812721682, + "a_same_abs": 0.0016107569812721682, + "a_same_rel": 0.0016107569812721682, + "cst": 64, + "h": 9, + "rho_star_abs": 4.428571428434225, + "rho_star_rel": 4.428571428434225, + "v": 112, + "x": 0.5714285714285714, + "z": 52.0 + }, + "I_{11,1} bit": { + "Xs": 3, + "Xstar": 0.007171522653821519, + "a_dim_abs": 0.0012246258053555746, + "a_dim_rel": 0.0013387673129528718, + "a_read_abs": 0.0009392956976872076, + "a_read_rel": 0.0010222574941126607, + "a_same_abs": 0.0014795179684754338, + "a_same_rel": 0.001620305554277553, + "cst": 110, + "h": 11, + "rho_star_abs": 2.661677615469671, + "rho_star_rel": 3.343132941881777, + "v": 220, + "x": 0.5, + "z": 118.5 + }, + "I_{13,1} bit": { + "Xs": 3, + "Xstar": 0.00862469550772497, + "a_dim_abs": 0.0009421714307318571, + "a_dim_rel": 0.0010785736492618507, + "a_read_abs": 0.000605362942552331, + "a_read_rel": 0.0006919920072509971, + "a_same_abs": 0.00143769279819993, + "a_same_rel": 0.0016469528589961638, + "cst": 156, + "h": 13, + "rho_star_abs": 2.6371141389972763, + "rho_star_rel": 3.433550539557473, + "v": 364, + "x": 0.42857142857142855, + "z": 193.28571428571428 + }, + "T_10 bit": { + "Xs": 9, + "Xstar": 0.0015532374018115726, + "a_dim_abs": 0.0006366782737705969, + "a_dim_rel": 0.0006670208710166347, + "a_read_abs": 0.0005648600231426096, + "a_read_rel": 0.0005916051335760184, + "a_same_abs": 0.0005648600231426096, + "a_same_rel": 0.0005916051335760184, + "cst": 90, + "h": 10, + "rho_star_abs": 0.0, + "rho_star_rel": 0.0, + "v": 120, + "x": 0.75, + "z": 63.0 + }, + "T_11 bit": { + "Xs": 5, + "Xstar": 0.0024323916899852945, + "a_dim_abs": 0.0008255146093040117, + "a_dim_rel": 0.0009039827913448415, + "a_read_abs": 0.0006934655057453908, + "a_read_rel": 0.0007535652717616766, + "a_same_abs": 0.0007902736647929714, + "a_same_rel": 0.0008639705203513044, + "cst": 110, + "h": 11, + "rho_star_abs": 0.0, + "rho_star_rel": 0.14618176319345366, + "v": 165, + "x": 0.6666666666666666, + "z": 84.0 + }, + "T_12 bit": { + "Xs": 5, + "Xstar": 0.003328195617920844, + "a_dim_abs": 0.0008997028737231361, + "a_dim_rel": 0.0010087127548971155, + "a_read_abs": 0.0006771271392320587, + "a_read_rel": 0.000747282541655889, + "a_same_abs": 0.0009540840873135277, + "a_same_rel": 0.0010697568404938681, + "cst": 132, + "h": 12, + "rho_star_abs": 0.5452559926197864, + "rho_star_rel": 0.976033717233804, + "v": 220, + "x": 0.6, + "z": 108.0 + }, + "T_12 complex": { + "Xs": 5, + "Xstar": 0.0024186870696829414, + "a_dim_abs": 0.0006281113047948817, + "a_dim_rel": 0.0006734081685599144, + "a_read_abs": 0.0004934908042433177, + "a_read_rel": 0.0005275648784861581, + "a_same_abs": 0.0008059951535310397, + "a_same_rel": 0.0008648149595573777, + "cst": 144, + "h": 12, + "rho_star_abs": 0.48710020128055476, + "rho_star_rel": 0.6897237952216528, + "v": 220, + "x": 0.6545454545454545, + "z": 111.0 + }, + "T_13 bit": { + "Xs": 3, + "Xstar": 0.004381759860146903, + "a_dim_abs": 0.0009036809025035332, + "a_dim_rel": 0.0010264161026451776, + "a_read_abs": 0.0006080025550507143, + "a_read_rel": 0.0006887166295234427, + "a_same_abs": 0.00104557348670486, + "a_same_rel": 0.0011976316369566441, + "cst": 156, + "h": 13, + "rho_star_abs": 1.02635551666026, + "rho_star_rel": 1.7492625691374997, + "v": 286, + "x": 0.5454545454545454, + "z": 135.0 + }, + "T_14 bit": { + "Xs": 3, + "Xstar": 0.005585241543794565, + "a_dim_abs": 0.0008655227738654691, + "a_dim_rel": 0.0010005620795299343, + "a_read_abs": 0.0005329883240333378, + "a_read_rel": 0.0006147499988792014, + "a_same_abs": 0.001091738055930747, + "a_same_rel": 0.001275022866536123, + "cst": 182, + "h": 14, + "rho_star_abs": 1.2655638638534583, + "rho_star_rel": 2.084736299002543, + "v": 364, + "x": 0.5, + "z": 165.0 + }, + "T_14 complex": { + "Xs": 3, + "Xstar": 0.004293631356651573, + "a_dim_abs": 0.0005483091957474773, + "a_dim_rel": 0.0006129532971435715, + "a_read_abs": 0.00036255701361225895, + "a_read_rel": 0.00040443259957845306, + "a_same_abs": 0.0010234719625590752, + "a_same_rel": 0.0011551969513878845, + "cst": 196, + "h": 14, + "rho_star_abs": 1.7584143653948558, + "rho_star_rel": 2.303974145979737, + "v": 364, + "x": 0.5384615384615384, + "z": 198.0 + }, + "T_16 bit": { + "Xs": 3, + "Xstar": 0.006961145145311995, + "a_dim_abs": 0.0007452933227853547, + "a_dim_rel": 0.0008983077741330437, + "a_read_abs": 0.0003966910615280629, + "a_read_rel": 0.00047508518050712675, + "a_same_abs": 0.0011091266842283764, + "a_same_rel": 0.0013403348998162832, + "cst": 240, + "h": 16, + "rho_star_abs": 1.4634474871854763, + "rho_star_rel": 2.407953140209429, + "v": 560, + "x": 0.42857142857142855, + "z": 234.0 + }, + "T_16 complex": { + "Xs": 3, + "Xstar": 0.006123263792854414, + "a_dim_abs": 0.00042377794371244247, + "a_dim_rel": 0.0004898786258478099, + "a_read_abs": 0.00024031529461724212, + "a_read_rel": 0.00027781609507599884, + "a_same_abs": 0.0010870303652881837, + "a_same_rel": 0.0012695930715339167, + "cst": 256, + "h": 16, + "rho_star_abs": 2.142810977966292, + "rho_star_rel": 2.924762014299631, + "v": 560, + "x": 0.45714285714285713, + "z": 325.0 + }, + "T_18 bit": { + "Xs": 3, + "Xstar": 0.007561304679511506, + "a_dim_abs": 0.0006309954656744363, + "a_dim_rel": 0.0007835103298075171, + "a_read_abs": 0.00029383722494590747, + "a_read_rel": 0.00036345237440677647, + "a_same_abs": 0.0010783101326397855, + "a_same_rel": 0.0013418062817776315, + "cst": 306, + "h": 18, + "rho_star_abs": 1.368834213280934, + "rho_star_rel": 2.4796230376523454, + "v": 816, + "x": 0.375, + "z": 315.0 + }, + "T_18 complex": { + "Xs": 3, + "Xstar": 0.006987429549461068, + "a_dim_abs": 0.0003181181271738347, + "a_dim_rel": 0.0003808654851440009, + "a_read_abs": 0.00015774463573908155, + "a_read_rel": 0.00018848056558206242, + "a_same_abs": 0.0010800778491285182, + "a_same_rel": 0.0012989589307333856, + "cst": 324, + "h": 18, + "rho_star_abs": 2.1875754409848014, + "rho_star_rel": 3.1392038152262103, + "v": 816, + "x": 0.39705882352941174, + "z": 500.0 + }, + "T_20 bit": { + "Xs": 3, + "Xstar": 0.0077611939844754885, + "a_dim_abs": 0.0005332995281648943, + "a_dim_rel": 0.0006787099642924436, + "a_read_abs": 0.00022024900488504246, + "a_read_rel": 0.0002797644201233015, + "a_same_abs": 0.0010288039557595971, + "a_same_rel": 0.001325574314045184, + "cst": 380, + "h": 20, + "rho_star_abs": 1.1988456732069608, + "rho_star_rel": 2.429817240772536, + "v": 1140, + "x": 0.3333333333333333, + "z": 408.0 + }, + "T_20 complex": { + "Xs": 3, + "Xstar": 0.007351183769407273, + "a_dim_abs": 0.00023908863409453362, + "a_dim_rel": 0.00029449766676582914, + "a_read_abs": 0.00010522230157651156, + "a_read_rel": 0.00012939200383095016, + "a_same_abs": 0.0010433886289098093, + "a_same_rel": 0.0012872933425278842, + "cst": 400, + "h": 20, + "rho_star_abs": 2.0844154812948545, + "rho_star_rel": 3.1535716298094485, + "v": 1140, + "x": 0.3508771929824561, + "z": 731.0 + }, + "T_22 bit": { + "Xs": 3, + "Xstar": 0.007747442341172933, + "a_dim_abs": 0.0004526916133157033, + "a_dim_rel": 0.0005912195595351862, + "a_read_abs": 0.00016803400556938305, + "a_read_rel": 0.0002180636537321941, + "a_same_abs": 0.0009734733602727194, + "a_same_rel": 0.001290865418594146, + "cst": 462, + "h": 22, + "rho_star_abs": 0.9919938849634491, + "rho_star_rel": 2.3212470892758574, + "v": 1540, + "x": 0.3, + "z": 513.0 + }, + "T_22 complex": { + "Xs": 3, + "Xstar": 0.007444442957394497, + "a_dim_abs": 0.00018201365840515342, + "a_dim_rel": 0.00022938261676260345, + "a_read_abs": 7.189964043107868e-05, + "a_read_rel": 9.059494947906635e-05, + "a_same_abs": 0.0009950241720641595, + "a_same_rel": 0.0012650966137252916, + "cst": 484, + "h": 22, + "rho_star_abs": 1.9187685724318726, + "rho_star_rel": 3.0672522520035272, + "v": 1540, + "x": 0.3142857142857143, + "z": 1026.0 + }, + "T_24 bit": { + "Xs": 3, + "Xstar": 0.007618412475718811, + "a_dim_abs": 0.00038697123547943907, + "a_dim_rel": 0.0005173002697658811, + "a_read_abs": 0.00013032317121934275, + "a_read_rel": 0.00017285017348586576, + "a_same_abs": 0.0009180763062131824, + "a_same_rel": 0.0012476664622146996, + "cst": 552, + "h": 24, + "rho_star_abs": 0.7990600730408914, + "rho_star_rel": 2.185761201326386, + "v": 2024, + "x": 0.2727272727272727, + "z": 630.0 + }, + "T_24 complex": { + "Xs": 3, + "Xstar": 0.007388222422275112, + "a_dim_abs": 0.00014064176962379717, + "a_dim_rel": 0.00018076803466452233, + "a_read_abs": 5.0458669755174115e-05, + "a_read_rel": 6.48413396110639e-05, + "a_same_abs": 0.0009433375657405344, + "a_same_rel": 0.0012302261372722726, + "cst": 576, + "h": 24, + "rho_star_abs": 1.6675037080858601, + "rho_star_rel": 2.930999599993811, + "v": 2024, + "x": 0.2845849802371542, + "z": 1393.0 + }, + "T_26 bit": { + "Xs": 3, + "Xstar": 0.007428231018537684, + "a_dim_abs": 0.0003333773974749822, + "a_dim_rel": 0.00045483199889902295, + "a_read_abs": 0.0001026889042022319, + "a_read_rel": 0.00013905065899780917, + "a_same_abs": 0.0008651482519676222, + "a_same_rel": 0.0012012355585059723, + "cst": 650, + "h": 26, + "rho_star_abs": 0.6150653007352958, + "rho_star_rel": 2.040078298159642, + "v": 2600, + "x": 0.25, + "z": 759.0 + }, + "T_26 complex": { + "Xs": 3, + "Xstar": 0.007249283806134319, + "a_dim_abs": 0.00011029490561668976, + "a_dim_rel": 0.0001447741189583302, + "a_read_abs": 3.624882766282049e-05, + "a_read_rel": 4.751118139800312e-05, + "a_same_abs": 0.0008922422561732215, + "a_same_rel": 0.0011895040631954342, + "cst": 676, + "h": 26, + "rho_star_abs": 1.3607824246719247, + "rho_star_rel": 2.7717935470718658, + "v": 2600, + "x": 0.26, + "z": 1840.0 + }, + "T_28 bit": { + "Xs": 3, + "Xstar": 0.0072076119088542445, + "a_dim_abs": 0.00028944394817983555, + "a_dim_rel": 0.000402115662153537, + "a_read_abs": 8.211037410852214e-05, + "a_read_rel": 0.00011325612669387032, + "a_same_abs": 0.0008157057167079721, + "a_same_rel": 0.0011543833755217059, + "cst": 756, + "h": 28, + "rho_star_abs": 0.49403276352677494, + "rho_star_rel": 1.8931100377812982, + "v": 3276, + "x": 0.23076923076923078, + "z": 900.0 + }, + "T_28 complex": { + "Xs": 3, + "Xstar": 0.007065766446158325, + "a_dim_abs": 8.774277335209955e-05, + "a_dim_rel": 0.00011741486045123343, + "a_read_abs": 2.6606929081828828e-05, + "a_read_rel": 3.5559762278825425e-05, + "a_same_abs": 0.0008435227641570476, + "a_same_rel": 0.0011466627081791326, + "cst": 784, + "h": 28, + "rho_star_abs": 1.1542831762199057, + "rho_star_rel": 2.604334763236693, + "v": 3276, + "x": 0.23931623931623933, + "z": 2375.0 + }, + "T_30 bit": { + "Xs": 3, + "Xstar": 0.006974330753293903, + "a_dim_abs": 0.00025316737186053704, + "a_dim_rel": 0.00035750594180493134, + "a_read_abs": 6.654007199535274e-05, + "a_read_rel": 9.331882840159467e-05, + "a_same_abs": 0.000770036303954073, + "a_same_rel": 0.001108605043605795, + "cst": 870, + "h": 30, + "rho_star_abs": 0.3769432929402683, + "rho_star_rel": 1.6355415471480228, + "v": 4060, + "x": 0.21428571428571427, + "z": 1053.0 + }, + "T_30 complex": { + "Xs": 3, + "Xstar": 0.0068600041595340226, + "a_dim_abs": 7.07404153900592e-05, + "a_dim_rel": 9.631573881733644e-05, + "a_read_abs": 1.991384474990046e-05, + "a_read_rel": 2.708365352329143e-05, + "a_same_abs": 0.0007979115595605061, + "a_same_rel": 0.0011037594235652427, + "cst": 900, + "h": 30, + "rho_star_abs": 0.9186809969833121, + "rho_star_rel": 2.43670740055677, + "v": 4060, + "x": 0.22167487684729065, + "z": 3006.0 + } + }, + "triple": { + "bit|10": { + "X_att": { + "hi": "776618700905786288335441724939300911501049/500000000000000000000000000000000000000000000", + "s": 9 + }, + "X_thmC": { + "hi": "776618700905786288335441724939300911501049/500000000000000000000000000000000000000000000", + "s": 9 + }, + "c0": 10, + "c_centre": 10, + "clique_lb": 8, + "cst_att": 90, + "cst_thmC": 90, + "lab_min": 9, + "minrank": 10, + "span": 10, + "v": 120, + "window": 10, + "z": 63 + }, + "bit|11": { + "X_att": { + "hi": "9729566759941177950254953543662850279361/4000000000000000000000000000000000000000000", + "s": 5 + }, + "X_thmC": { + "hi": "9729566759941177950254953543662850279361/4000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": 11, + "c_centre": 11, + "clique_lb": 8, + "cst_att": 110, + "cst_thmC": 110, + "lab_min": 10, + "minrank": 11, + "span": 11, + "v": 165, + "window": 11, + "z": 84 + }, + "bit|12": { + "X_att": { + "hi": "1664097808960422102478315942164499574573709/500000000000000000000000000000000000000000000", + "s": 5 + }, + "X_thmC": { + "hi": "1664097808960422102478315942164499574573709/500000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": 12, + "c_centre": 12, + "clique_lb": 12, + "cst_att": 132, + "cst_thmC": 132, + "lab_min": null, + "span": 12, + "v": 220, + "window": 12, + "z": 108 + }, + "bit|13": { + "X_att": { + "hi": "876351972029380636708406626667056116275489/200000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "876351972029380636708406626667056116275489/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 13, + "c_centre": 13, + "clique_lb": 12, + "cst_att": 156, + "cst_thmC": 156, + "lab_min": null, + "span": 13, + "v": 286, + "window": 13, + "z": 135 + }, + "bit|14": { + "X_att": { + "hi": "2792620771897282852189170476719937141751869/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "2792620771897282852189170476719937141751869/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 14, + "c_centre": 14, + "clique_lb": 12, + "cst_att": 182, + "cst_thmC": 182, + "lab_min": null, + "span": 14, + "v": 364, + "window": 14, + "z": 165 + }, + "bit|15": { + "X_att": { + "hi": "6406069772985404330023495589330195798538623/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "6406069772985404330023495589330195798538623/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 15, + "c_centre": 15, + "clique_lb": 12, + "cst_att": 210, + "cst_thmC": 210, + "lab_min": null, + "span": 15, + "v": 455, + "window": 15, + "z": 198 + }, + "bit|16": { + "X_att": { + "hi": "1392229029062399051075438194402522640302777/200000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1392229029062399051075438194402522640302777/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 16, + "c_centre": 16, + "clique_lb": 16, + "cst_att": 240, + "cst_thmC": 240, + "lab_min": null, + "span": 16, + "v": 560, + "window": 16, + "z": 234 + }, + "bit|17": { + "X_att": { + "hi": "1465693281711998724459178333745512645691633/200000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1465693281711998724459178333745512645691633/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 17, + "c_centre": 17, + "clique_lb": 16, + "cst_att": 272, + "cst_thmC": 272, + "lab_min": null, + "span": 17, + "v": 680, + "window": 17, + "z": 273 + }, + "bit|18": { + "X_att": { + "hi": "7561304679511505685840511148398945806547997/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7561304679511505685840511148398945806547997/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 18, + "c_centre": 18, + "clique_lb": 16, + "cst_att": 306, + "cst_thmC": 306, + "lab_min": null, + "span": 18, + "v": 816, + "window": 18, + "z": 315 + }, + "bit|19": { + "X_att": { + "hi": "3848388541781380504935849984349260921590029/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3848388541781380504935849984349260921590029/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 19, + "c_centre": 19, + "clique_lb": 16, + "cst_att": 342, + "cst_thmC": 342, + "lab_min": null, + "span": 19, + "v": 969, + "window": 19, + "z": 360 + }, + "bit|20": { + "X_att": { + "hi": "7761193984475488541474597070646268548443911/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7761193984475488541474597070646268548443911/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 20, + "c_centre": 20, + "clique_lb": 20, + "cst_att": 380, + "cst_thmC": 380, + "lab_min": null, + "span": 20, + "v": 1140, + "window": 20, + "z": 408 + }, + "bit|21": { + "X_att": { + "hi": "3886742179208512283986043378547492466168477/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3886742179208512283986043378547492466168477/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 21, + "c_centre": 21, + "clique_lb": 20, + "cst_att": 420, + "cst_thmC": 420, + "lab_min": null, + "span": 21, + "v": 1330, + "window": 21, + "z": 459 + }, + "bit|22": { + "X_att": { + "hi": "968430292646616679760190882220172170491423/125000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "968430292646616679760190882220172170491423/125000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 22, + "c_centre": 22, + "clique_lb": 20, + "cst_att": 462, + "cst_thmC": 462, + "lab_min": null, + "span": 22, + "v": 1540, + "window": 22, + "z": 513 + }, + "bit|23": { + "X_att": { + "hi": "7693231728662683170660852027822329546324907/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7693231728662683170660852027822329546324907/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 23, + "c_centre": 23, + "clique_lb": 20, + "cst_att": 506, + "cst_thmC": 506, + "lab_min": null, + "span": 23, + "v": 1771, + "window": 23, + "z": 570 + }, + "bit|24": { + "X_att": { + "hi": "76184124757188113191697098613985872361809/10000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "76184124757188113191697098613985872361809/10000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 24, + "c_centre": 24, + "clique_lb": 24, + "cst_att": 552, + "cst_thmC": 552, + "lab_min": null, + "span": 24, + "v": 2024, + "window": 24, + "z": 630 + }, + "bit|25": { + "X_att": { + "hi": "376432642736300557446960102225830269162543/50000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "376432642736300557446960102225830269162543/50000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 25, + "c_centre": 25, + "clique_lb": 24, + "cst_att": 600, + "cst_thmC": 600, + "lab_min": null, + "span": 25, + "v": 2300, + "window": 25, + "z": 693 + }, + "bit|26": { + "X_att": { + "hi": "7428231018537683957858597239010123303160069/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7428231018537683957858597239010123303160069/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 26, + "c_centre": 26, + "clique_lb": 24, + "cst_att": 650, + "cst_thmC": 650, + "lab_min": null, + "span": 26, + "v": 2600, + "window": 26, + "z": 759 + }, + "bit|27": { + "X_att": { + "hi": "3660196538785299904400494241940577921923249/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3660196538785299904400494241940577921923249/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 27, + "c_centre": 27, + "clique_lb": 24, + "cst_att": 702, + "cst_thmC": 702, + "lab_min": null, + "span": 27, + "v": 2925, + "window": 27, + "z": 828 + }, + "bit|28": { + "X_att": { + "hi": "7207611908854244520128418116655666381120681/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7207611908854244520128418116655666381120681/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 28, + "c_centre": 28, + "clique_lb": 28, + "cst_att": 756, + "cst_thmC": 756, + "lab_min": null, + "span": 28, + "v": 3276, + "window": 28, + "z": 900 + }, + "bit|29": { + "X_att": { + "hi": "1772944080991738203222085612509317797957919/250000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1772944080991738203222085612509317797957919/250000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 29, + "c_centre": 29, + "clique_lb": 28, + "cst_att": 812, + "cst_thmC": 812, + "lab_min": null, + "span": 29, + "v": 3654, + "window": 29, + "z": 975 + }, + "bit|30": { + "X_att": { + "hi": "3487165376646951661307460845762307144468649/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3487165376646951661307460845762307144468649/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 30, + "c_centre": 30, + "clique_lb": 28, + "cst_att": 870, + "cst_thmC": 870, + "lab_min": null, + "span": 30, + "v": 4060, + "window": 30, + "z": 1053 + }, + "bit|31": { + "X_att": { + "hi": "6856379355605141806551117156232066918164489/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "6856379355605141806551117156232066918164489/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 31, + "c_centre": 31, + "clique_lb": 28, + "cst_att": 930, + "cst_thmC": 930, + "lab_min": null, + "span": 31, + "v": 4495, + "window": 31, + "z": 1134 + }, + "bit|32": { + "X_att": { + "hi": "1684691068896542322057274798652315439539961/250000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1684691068896542322057274798652315439539961/250000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": 32, + "c_centre": 32, + "clique_lb": 32, + "cst_att": 992, + "cst_thmC": 992, + "lab_min": null, + "span": 32, + "v": 4960, + "window": 32, + "z": 1218 + }, + "bit|6": { + "X_att": null, + "X_thmC": null, + "c0": 6, + "c_centre": 6, + "clique_lb": 4, + "cst_att": 30, + "cst_thmC": 30, + "lab_min": 5, + "minrank": 6, + "span": 6, + "v": 20, + "window": 6, + "z": 9 + }, + "bit|7": { + "X_att": null, + "X_thmC": null, + "c0": 7, + "c_centre": 7, + "clique_lb": 4, + "cst_att": 42, + "cst_thmC": 42, + "lab_min": 6, + "minrank": 6, + "span": 7, + "v": 35, + "window": 7, + "z": 18 + }, + "bit|8": { + "X_att": null, + "X_thmC": null, + "c0": 8, + "c_centre": 8, + "clique_lb": 8, + "cst_att": 56, + "cst_thmC": 56, + "lab_min": 7, + "minrank": 8, + "span": 8, + "v": 56, + "window": 8, + "z": 30 + }, + "bit|9": { + "X_att": { + "hi": "90571224493574240162196975489905526046641/125000000000000000000000000000000000000000000", + "s": 15 + }, + "X_thmC": { + "hi": "90571224493574240162196975489905526046641/125000000000000000000000000000000000000000000", + "s": 15 + }, + "c0": 9, + "c_centre": 9, + "clique_lb": 8, + "cst_att": 72, + "cst_thmC": 72, + "lab_min": 8, + "minrank": 9, + "span": 9, + "v": 84, + "window": 8, + "z": 45 + }, + "complex|10": { + "X_att": { + "hi": "46529923804317346175407621444719552528113/62500000000000000000000000000000000000000000", + "s": 13 + }, + "X_thmC": { + "hi": "46529923804317346175407621444719552528113/62500000000000000000000000000000000000000000", + "s": 13 + }, + "c0": null, + "c_centre": 11, + "clique_lb": null, + "cst_att": 100, + "cst_thmC": 100, + "lab_min": null, + "span": 10, + "v": 120, + "window": 10, + "z": 56 + }, + "complex|11": { + "X_att": { + "hi": "1569351408934127525135471259432051738258449/1000000000000000000000000000000000000000000000", + "s": 9 + }, + "X_thmC": { + "hi": "9729566759941177950254953543662850279361/4000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": null, + "c_centre": 12, + "clique_lb": null, + "cst_att": 121, + "cst_thmC": 110, + "lab_min": null, + "span": 11, + "v": 165, + "window": 11, + "z": 80 + }, + "complex|12": { + "X_att": { + "hi": "1209343534841470684318420758529555314453317/500000000000000000000000000000000000000000000", + "s": 5 + }, + "X_thmC": { + "hi": "1209343534841470684318420758529555314453317/500000000000000000000000000000000000000000000", + "s": 5 + }, + "c0": null, + "c_centre": 13, + "clique_lb": null, + "cst_att": 144, + "cst_thmC": 144, + "lab_min": null, + "span": 12, + "v": 220, + "window": 12, + "z": 111 + }, + "complex|13": { + "X_att": { + "hi": "199769727269112531541778166044309728022199/62500000000000000000000000000000000000000000", + "s": 5 + }, + "X_thmC": { + "hi": "876351972029380636708406626667056116275489/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 14, + "clique_lb": null, + "cst_att": 169, + "cst_thmC": 156, + "lab_min": null, + "span": 13, + "v": 286, + "window": 13, + "z": 150 + }, + "complex|14": { + "X_att": { + "hi": "858726271330314573637654262938667670823633/200000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "858726271330314573637654262938667670823633/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 15, + "clique_lb": null, + "cst_att": 196, + "cst_thmC": 196, + "lab_min": null, + "span": 14, + "v": 364, + "window": 14, + "z": 198 + }, + "complex|15": { + "X_att": { + "hi": "2686895853961696186903984617799778789049013/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "6406069772985404330023495589330195798538623/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 16, + "clique_lb": null, + "cst_att": 225, + "cst_thmC": 210, + "lab_min": null, + "span": 15, + "v": 455, + "window": 15, + "z": 256 + }, + "complex|16": { + "X_att": { + "hi": "612326379285441438349302245569560909643769/100000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "612326379285441438349302245569560909643769/100000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 17, + "clique_lb": null, + "cst_att": 256, + "cst_thmC": 256, + "lab_min": null, + "span": 16, + "v": 560, + "window": 16, + "z": 325 + }, + "complex|17": { + "X_att": { + "hi": "1659784821813878644657103134107956066145561/250000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1465693281711998724459178333745512645691633/200000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 18, + "clique_lb": null, + "cst_att": 289, + "cst_thmC": 272, + "lab_min": null, + "span": 17, + "v": 680, + "window": 17, + "z": 406 + }, + "complex|18": { + "X_att": { + "hi": "3493714774730533826480050052509795932564883/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3493714774730533826480050052509795932564883/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 19, + "clique_lb": null, + "cst_att": 324, + "cst_thmC": 324, + "lab_min": null, + "span": 18, + "v": 816, + "window": 18, + "z": 500 + }, + "complex|19": { + "X_att": { + "hi": "7213972525897024631880150025153852766379711/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3848388541781380504935849984349260921590029/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 20, + "clique_lb": null, + "cst_att": 361, + "cst_thmC": 342, + "lab_min": null, + "span": 19, + "v": 969, + "window": 19, + "z": 608 + }, + "complex|20": { + "X_att": { + "hi": "7351183769407273419643766843023513501975683/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7351183769407273419643766843023513501975683/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 21, + "clique_lb": null, + "cst_att": 400, + "cst_thmC": 400, + "lab_min": null, + "span": 20, + "v": 1140, + "window": 20, + "z": 731 + }, + "complex|21": { + "X_att": { + "hi": "371117624979200571438964217918510257732123/50000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3886742179208512283986043378547492466168477/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 22, + "clique_lb": null, + "cst_att": 441, + "cst_thmC": 420, + "lab_min": null, + "span": 21, + "v": 1330, + "window": 21, + "z": 870 + }, + "complex|22": { + "X_att": { + "hi": "3722221478697248456951607309406426793429999/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3722221478697248456951607309406426793429999/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 23, + "clique_lb": null, + "cst_att": 484, + "cst_thmC": 484, + "lab_min": null, + "span": 22, + "v": 1540, + "window": 22, + "z": 1026 + }, + "complex|23": { + "X_att": { + "hi": "1857490263160947621505492133309189739520661/250000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7693231728662683170660852027822329546324907/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 24, + "clique_lb": null, + "cst_att": 529, + "cst_thmC": 506, + "lab_min": null, + "span": 23, + "v": 1771, + "window": 23, + "z": 1200 + }, + "complex|24": { + "X_att": { + "hi": "3694111211137556145811533525602499139495193/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3694111211137556145811533525602499139495193/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 25, + "clique_lb": null, + "cst_att": 576, + "cst_thmC": 576, + "lab_min": null, + "span": 24, + "v": 2024, + "window": 24, + "z": 1393 + }, + "complex|25": { + "X_att": { + "hi": "7326228893872904681522362224654898454900829/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "376432642736300557446960102225830269162543/50000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 26, + "clique_lb": null, + "cst_att": 625, + "cst_thmC": 600, + "lab_min": null, + "span": 25, + "v": 2300, + "window": 25, + "z": 1606 + }, + "complex|26": { + "X_att": { + "hi": "7249283806134318615399525498332877759821711/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "7249283806134318615399525498332877759821711/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 27, + "clique_lb": null, + "cst_att": 676, + "cst_thmC": 676, + "lab_min": null, + "span": 26, + "v": 2600, + "window": 26, + "z": 1840 + }, + "complex|27": { + "X_att": { + "hi": "7161430130975224035011494905234030738938597/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3660196538785299904400494241940577921923249/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 28, + "clique_lb": null, + "cst_att": 729, + "cst_thmC": 702, + "lab_min": null, + "span": 27, + "v": 2925, + "window": 27, + "z": 2096 + }, + "complex|28": { + "X_att": { + "hi": "3532883223079162464397295906337603810243307/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3532883223079162464397295906337603810243307/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 29, + "clique_lb": null, + "cst_att": 784, + "cst_thmC": 784, + "lab_min": null, + "span": 28, + "v": 3276, + "window": 28, + "z": 2375 + }, + "complex|29": { + "X_att": { + "hi": "6964677485548873524245916399877387448213993/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "1772944080991738203222085612509317797957919/250000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 30, + "clique_lb": null, + "cst_att": 841, + "cst_thmC": 812, + "lab_min": null, + "span": 29, + "v": 3654, + "window": 29, + "z": 2678 + }, + "complex|30": { + "X_att": { + "hi": "6860004159534022552535681427343661198392509/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "6860004159534022552535681427343661198392509/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 31, + "clique_lb": null, + "cst_att": 900, + "cst_thmC": 900, + "lab_min": null, + "span": 30, + "v": 4060, + "window": 30, + "z": 3006 + }, + "complex|31": { + "X_att": { + "hi": "6753170162846582752491642054337717127054927/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "6856379355605141806551117156232066918164489/1000000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 32, + "clique_lb": null, + "cst_att": 961, + "cst_thmC": 930, + "lab_min": null, + "span": 31, + "v": 4495, + "window": 31, + "z": 3360 + }, + "complex|32": { + "X_att": { + "hi": "3322638549104997085850699001241537189343943/500000000000000000000000000000000000000000000", + "s": 3 + }, + "X_thmC": { + "hi": "3322638549104997085850699001241537189343943/500000000000000000000000000000000000000000000", + "s": 3 + }, + "c0": null, + "c_centre": 33, + "clique_lb": null, + "cst_att": 1024, + "cst_thmC": 1024, + "lab_min": null, + "span": 32, + "v": 4960, + "window": 32, + "z": 3741 + }, + "complex|6": { + "X_att": null, + "X_thmC": null, + "c0": null, + "c_centre": 7, + "clique_lb": null, + "cst_att": 36, + "cst_thmC": 36, + "lab_min": null, + "span": 6, + "v": 20, + "window": 6, + "z": 10 + }, + "complex|7": { + "X_att": null, + "X_thmC": null, + "c0": null, + "c_centre": 8, + "clique_lb": null, + "cst_att": 49, + "cst_thmC": 42, + "lab_min": null, + "span": 7, + "v": 35, + "window": 7, + "z": 16 + }, + "complex|8": { + "X_att": null, + "X_thmC": null, + "c0": null, + "c_centre": 9, + "clique_lb": null, + "cst_att": 64, + "cst_thmC": 64, + "lab_min": null, + "span": 8, + "v": 56, + "window": 8, + "z": 25 + }, + "complex|9": { + "X_att": null, + "X_thmC": { + "hi": "317912890061411348291420253531214257236569/500000000000000000000000000000000000000000000", + "s": 15 + }, + "c0": null, + "c_centre": 10, + "clique_lb": null, + "cst_att": 81, + "cst_thmC": 72, + "lab_min": null, + "span": 9, + "v": 84, + "window": 9, + "z": 38 + } + } +} \ No newline at end of file diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/ade_screen_fixed.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/ade_screen_fixed.py new file mode 100644 index 0000000000..f9a9bc338b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/ade_screen_fixed.py @@ -0,0 +1,131 @@ +"""Corrected ADE unit screen (replaces #620's ade_screen.py; erratum found by #630). + +Per unit (real root lines, window h = Gram rank, k = 1): + v, h exact + c0 = rank_F2(J + G) the label decoder B0 used by #620 (an UPPER bound on the F2 minrank of the pattern) + omega largest pairwise non-orthogonal set (identity block of the pattern) -> minrank >= omega + c = minrank exact F2 minrank of the pattern (asymmetric decoders allowed, SAT; c-1 UNSAT, c SAT) + x_proxy = c*h/v #620's full-rank-totals premise (each total charged the window), now with the exact minrank + cst_lab star cost attainable by label decoders on the B0 labels: min over bases {p_j} of F2^c0 of + sum_j dim_Q span{lines T : u_T . p_j = 1} (matroid greedy, exact); an UPPER bound on cst_min + X* certified ceiling of #595 (skel.X, rational intervals) maximised over odd s in 3..15 + +What changed against #620: + * E6 was built as the complement of two ORTHOGONAL roots (= D6, 30 lines); it is now the complement of an A2 (36 lines). + * the column 'c' was rank_F2(J+G) of one decoder; it is now the exact minrank (both are printed). + * an attainable label-decoder star cost is added next to the proxy. +Usage: python ade_screen_fixed.py REPO_ROOT [--json out.json] +""" +import json +import sys +import time +from fractions import Fraction as Fr + +import numpy as np + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X, profile # noqa: E402 (#595, certified ceiling) +from cert import cost_iv # noqa: E402 +from minrank_sat import solve_asym # noqa: E402 (#595 SAT encoding, GL(c) gauge on a clique) +from e8units import sym_factor_f2 # noqa: E402 + +import units as U # noqa: E402 + +S_RANGE = range(3, 17, 2) +TIMEOUT = 600 # seconds per SAT query (A8, D9 hit it: minrank left as a range) + + +def xstar(h, x): + best = None + for s in S_RANGE: + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi'], r['lo']) + return best + + +def minrank(G, omega_set, c0): + lo, hi, log = U.minrank_f2(G, omega_set, c0, solve_asym, timeout=TIMEOUT) + return (lo if lo == hi else '%d..%d' % (lo, hi)), log, lo + + +def label_cost(L, G): + B0 = (1 + G) % 2 + Uf = sym_factor_f2(B0).astype(np.int64) + c0 = Uf.shape[1] + costs = [] + for p in range(1, 1 << c0): + pv = np.array([(p >> j) & 1 for j in range(c0)]) + R = np.nonzero((Uf @ pv) % 2)[0] + costs.append((U.rank_q(L[R].tolist()), p)) + costs.sort() + basis, tot = {}, 0 + for cst, p in costs: + x = p + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + if x: + tot += cst + if len(basis) == c0: + break + return tot, c0 + + +def unit(name, L, G=None): + if G is None: + G = U.gram_doubled(L) + v = len(L) + h = U.rank_q(G.tolist()) + c0 = U.f2_rank((1 + G) % 2) + om = U.nonorth_clique(G) + c, log, clo = minrank(G, om, c0) + cst_lab, _ = label_cost(L, G) + xp = Fr(clo * h, v) # proxy at the certified lower end of the minrank + xl = Fr(cst_lab, v) + row = dict(unit=name, v=v, h=h, c0=c0, omega=len(om), minrank=c, sat_log={k: v_ for k, v_ in log.items()}, + x_proxy=str(xp), cst_lab=cst_lab, x_lab=str(xl)) + for key, x in (('proxy', xp), ('lab', xl)): + b = xstar(h, x) + row['Xstar_' + key] = None if b is None else dict(s=b[0], hi=str(b[1]), hi_float=float(b[1])) + if row['Xstar_lab'] is None and xl < 1: + # positive deficit only at s > 1/(1 - x): bound the ceiling by D_s / Cost_s (#595 A.2) for odd s up to 801 + best = None + for s_ in range(3, 802, 2): + prof, m, D = profile(s_, h, 1, xl) + if D <= 0: + continue + b = D / cost_iv(prof, m)[0] + if best is None or b > best[1]: + best = (s_, b) + row['DoverCost_lab'] = dict(s=best[0], hi=str(best[1]), hi_float=float(best[1])) + print(' %s: x_lab < 1, deficit > 0 only for s > %s; max over odd s <= 801 of D_s/Cost_s = %.4e at s = %d' + % (name, 1 / (1 - xl), float(best[1]), best[0])) + return row + + +def main(): + rows = [] + cases = [('A%d' % n, U.roots_A(n)) for n in (4, 5, 6, 7, 8)] + cases += [('D%d' % n, U.roots_D(n)) for n in (4, 5, 6, 7, 8, 9, 10, 12)] + cases += [('D6(archived "E6")', U.roots_D6_archived()), ('E6', U.roots_E6()), ('E7', U.roots_E7()), ('E8', U.roots_E8())] + for name, L in cases: + t = time.time() + r = unit(name, L) + rows.append(r) + f = lambda k: ('%.4e@s%d' % (r[k]['hi_float'], r[k]['s'])) if r[k] else 'D<=0' + print('%-18s v=%4d h=%2d c0=%2d omega=%2d minrank=%-5s x_proxy=%-7s cst_lab=%4d x_lab=%-8s X*proxy=%-14s X*lab=%-14s (%.0fs)' + % (name, r['v'], r['h'], r['c0'], r['omega'], r['minrank'], r['x_proxy'], r['cst_lab'], r['x_lab'], + f('Xstar_proxy'), f('Xstar_lab'), time.time() - t), flush=True) + if '--json' in sys.argv: + json.dump(rows, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/check.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/check.py new file mode 100644 index 0000000000..ee0706f08d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/check.py @@ -0,0 +1,104 @@ +"""Compare a fresh run (OUT/*.json) with the shipped expected values (expected.json); one PASS/FAIL line per claim. + +Exact fields (integers, fractions, certified interval ends) must be equal. Model estimates (rank.json) are compared +with relative tolerance 1e-9. A minrank that the shipped run left as a range 'lo..hi' (SAT timeout) is accepted when +the fresh value lies in that range. +Usage: python check.py OUT expected.json (python check.py --freeze OUT expected.json writes it) +""" +import json +import pathlib +import sys + +FIELDS = { + 'ade': (['unit'], ['v', 'h', 'c0', 'omega', 'cst_lab', 'x_lab', 'Xstar_lab', 'DoverCost_lab']), + 'triple': (['side', 'h'], ['v', 'window', 'span', 'c_centre', 'c0', 'clique_lb', 'z', 'cst_att', 'cst_thmC', + 'X_att', 'X_thmC', 'lab_min']), + 'lorentz': (['n'], ['v', 'window', 'c0', 'omega', 'z', 'cst_lab', 'per_total', 'x', 'Xstar']), + 'cover': (['unit', 'source'], ['readers', 'rank_readers', 'h', 'cover_lb', 'cover_ub', 'best_section_found']), + 'lorentz_minrank': (['n'], ['omega', 'c0', 'lo', 'hi', 'log']), + 'rank': (['name'], ['h', 'v', 'cst', 'x', 'z', 'Xstar', 'Xs', 'rho_star_abs', 'rho_star_rel', 'a_same_abs', + 'a_same_rel', 'a_dim_abs', 'a_dim_rel', 'a_read_abs', 'a_read_rel']), +} +TOL = {'rank'} + + +def strip(row, fields): + out = {} + for f in fields: + v = row.get(f) + if isinstance(v, dict) and 'hi' in v: + v = {k: v[k] for k in ('s', 'hi') if k in v} + out[f] = v + return out + + +def load(out): + d = {} + for name in FIELDS: + p = pathlib.Path(out) / (name + '.json') + if p.exists(): + d[name] = json.loads(p.read_text()) + return d + + +def key(row, kf): + return '|'.join(str(row.get(k)) for k in kf) + + +def freeze(out, exp): + d = load(out) + E = {} + for name, rows in d.items(): + kf, ff = FIELDS[name] + E[name] = {key(r, kf): dict(strip(r, ff), **({'minrank': r['minrank']} if 'minrank' in r else {})) for r in rows} + pathlib.Path(exp).write_text(json.dumps(E, indent=1, sort_keys=True, default=str)) + + +def close(a, b, tol): + if isinstance(a, float) and isinstance(b, (int, float)) and tol: + return abs(a - b) <= 1e-9 * max(1.0, abs(b)) if abs(b) < 1 else abs(a / b - 1) <= 1e-9 + if isinstance(a, dict) and isinstance(b, dict): + return a.keys() == b.keys() and all(close(a[k], b[k], tol) for k in a) + if isinstance(a, list) and isinstance(b, list): + return len(a) == len(b) and all(close(x, y, tol) for x, y in zip(a, b)) + return a == b + + +def minrank_ok(fresh, exp): + if isinstance(exp, str) and '..' in exp: + lo, hi = map(int, exp.split('..')) + if isinstance(fresh, str): + flo, fhi = map(int, fresh.split('..')) + return lo <= flo and fhi <= hi + return lo <= int(fresh) <= hi + return fresh == exp + + +def check(out, exp): + E = json.loads(pathlib.Path(exp).read_text()) + d = load(out) + bad = 0 + for name, rows in E.items(): + if name not in d: + print('FAIL %s missing' % name); bad += 1; continue + kf, ff = FIELDS[name] + fresh = {key(r, kf): r for r in d[name]} + for k, ev in rows.items(): + r = fresh.get(k) + ok = r is not None + if ok: + sr = strip(r, ff) + ok = all(close(sr[f], ev[f], name in TOL) for f in ff) + if 'minrank' in ev: + ok = ok and minrank_ok(r.get('minrank'), ev['minrank']) + print(('PASS ' if ok else 'FAIL ') + '%s %s' % (name, k), flush=True) + bad += not ok + print('%d FAIL' % bad) + return bad + + +if __name__ == '__main__': + if sys.argv[1] == '--freeze': + freeze(sys.argv[2], sys.argv[3]) + else: + sys.exit(1 if check(sys.argv[1], sys.argv[2]) else 0) diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/cover.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/cover.py new file mode 100644 index 0000000000..041baf1e42 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/cover.py @@ -0,0 +1,147 @@ +"""#595 section D cover numbers for candidate units (source end; the terminal end is the same geometry for these +self-dual line systems). + +For a source s, the readers T(s) are the lines orthogonal to s. A register crossing from into serves the +readers in w^perp, a 'hyperplane section' of T(s). The cover number is the least number of proper sections covering T(s). + * lower bound 2 <=> rank_Q T(s) = h - 1 (then no single proper section covers T(s)); exact + * upper bound 2 <=> two subsets of T(s), each of rank <= h - 2, with union T(s) (certificate); exact check + (the witnesses are found by sampling hyperplanes through h - 2 random readers plus structured functionals; + a found pair is re-checked exactly) + * the best single section found (a LOWER bound on the largest section; sampled, not exhaustive) +#595's single-cut floors (d_geometry.single_cut_bounds): sink-only R >= (c - 1) v - floor(v / leftover), strict +R >= max(c v - 2 v, 0). With cover number 2 the strict floor is 0 and the sink-only floor is at most v. +Control: E8 bit (cover number 3, best pair union 52 of 63: #595 D1). +Usage: python cover.py REPO_ROOT unit[,unit...] [samples] [--json out] unit in {E8, T, L} +""" +import itertools +import sys + +import numpy as np + +sys.dont_write_bytecode = True +import units as U # noqa: E402 + + +def load(name): + if name == 'E8': + L = U.roots_E8(); G = U.gram_doubled(L); return L, G, [0] + if name.startswith('T'): + L, G, _ = U.triple_family(int(name[1:])); return L, G, [0] + if name.startswith('L'): + n = int(name[1:]) + L, G, _ = U.lorentz_family(n) + from math import comb + return L, G, [0, comb(n, 3)] # one triple-type and one pair-type source + raise ValueError(name) + + +def sections_for(L, G, s, h, samples, rng): + T = np.nonzero(G[s] == 0)[0] + T = T[T != s] + X = L[T].astype(float) + rT = U.rank_q(L[T].tolist()) + secs = set() + # structured functionals: coordinate patterns c with c . t = 0 + n = L.shape[1] + cands = [] + for i in range(n): + e = np.zeros(n); e[i] = 1; cands.append(e) + for i, j in itertools.combinations(range(n), 2): + for sg in (1, -1): + e = np.zeros(n); e[i] = 1; e[j] = sg; cands.append(e) + for i, j, k in itertools.combinations(range(n), 3): + for sg in itertools.product((1, -1), repeat=2): + e = np.zeros(n); e[i] = 1; e[j] = sg[0]; e[k] = sg[1]; cands.append(e) + for i, j in itertools.combinations(range(n), 2): # triple family: 1 off {i, j}, -2 on {i, j} (two-class sections) + e = np.ones(n); e[i] = e[j] = -2; cands.append(e) + for i in range(n - 1): + for wgt in (1, 2, 3): + e = np.ones(n); e[-1] = wgt; e[i] -= 1; cands.append(e) + e = np.ones(n); e[-1] = wgt; e[i] += 1; cands.append(e) + for c in cands: + m = np.abs(X @ c) < 1e-9 + if 0 < m.sum() < len(T): + secs.add(tuple(np.nonzero(m)[0])) + # random hyperplanes of span(T) through h - 2 readers + for _ in range(samples): + idx = rng.choice(len(T), rT - 1, replace=False) + M = X[idx] + if np.linalg.matrix_rank(M) < rT - 1: + continue + # normal of the hyperplane inside span(T): project onto span(T) via SVD of X + _, _, Vt = np.linalg.svd(np.vstack([M, np.zeros((0, n))])) + null = Vt[rT - 1:] # functionals vanishing on M + # choose a functional that does not vanish on all of T + best = None + for c in null: + m = np.abs(X @ c) < 1e-7 + if m.sum() < len(T): + best = m; break + if best is not None: + secs.add(tuple(np.nonzero(best)[0])) + return T, rT, secs + + +def run(name, samples=20000, seed=1): + L, G, srcs = load(name) + h = U.rank_q(G.tolist()) + rng = np.random.default_rng(seed) + out = [] + for s in srcs: + T, rT, secs = sections_for(L, G, s, h, samples, rng) + # exact check of every kept section: rank <= h - 2 (a proper section) + secs = [S for S in secs if U.rank_q(L[T[list(S)]].tolist()) <= h - 2] + masks = [sum(1 << int(k) for k in S) for S in secs] + full = (1 << len(T)) - 1 + best1 = max(len(S) for S in secs) + order = sorted(range(len(masks)), key=lambda i: -len(secs[i])) + bestpair, cover2 = 0, None + for a in range(len(order)): + ma = masks[order[a]] + for b in range(a, len(order)): + u = bin(ma | masks[order[b]]).count('1') + if u > bestpair: + bestpair = u + if u == len(T): + cover2 = (secs[order[a]], secs[order[b]]) + break + if cover2: + break + lb = 2 if rT == h - 1 else 1 + res = dict(unit=name, source=int(s), readers=len(T), rank_readers=rT, h=h, cover_lb=lb, + cover_ub=2 if cover2 else None, best_section_found=best1, best_pair_union=bestpair, + n_sections=len(secs)) + if not cover2: # look for a 3-cover among the largest sections + top = order[:400] + found = None + for a in range(len(top)): + for b in range(a, len(top)): + rest = full & ~(masks[top[a]] | masks[top[b]]) + for c in range(len(masks)): + if rest & ~masks[c] == 0: + found = (secs[top[a]], secs[top[b]], secs[c]); break + if found: + break + if found: + break + if found: + assert set().union(*found) == set(range(len(T))) + res['cover_ub'] = 3 + res['cover3_sizes'] = tuple(len(x) for x in found) + if cover2: + A, B = cover2 + assert U.rank_q(L[T[list(A)]].tolist()) <= h - 2 and U.rank_q(L[T[list(B)]].tolist()) <= h - 2 + assert set(A) | set(B) == set(range(len(T))) + res['cover2_sizes'] = (len(A), len(B)) + print(res, flush=True) + out.append(res) + return out + + +if __name__ == '__main__': + import json + allres = [] + for u in sys.argv[2].split(','): + allres += run(u, int(sys.argv[3]) if len(sys.argv) > 3 else 20000) + if '--json' in sys.argv: + json.dump(allres, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/est_model.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/est_model.py new file mode 100644 index 0000000000..2ba54d9815 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/est_model.py @@ -0,0 +1,135 @@ +"""Helper-adjusted estimate of the achievable saving a for a unit family (MODEL, not a bound). + +The #595 ceiling X(s, h, x) is helper-free. On the realised E8 words the gap to the ceiling is 87-93% helper cost +(#595 A.5). This model puts the helpers back with the piece statistics of the shipped words and solves the same +pressure root, so that unit families can be compared at the same design maturity: + + per port and replica (v = 1), stage count s = 2K + 1, a = h - 1, m = 1 + s a, D = (s - 1) - s x (#595 A.1, k = 1) + data 2 s a mass, cut into pieces with the per-port run histogram of the shipped word + (bit: #555 x/y runs; complex: #569 four-cold data runs, as replayed by #595 a4_gap.py) + copies s x mass in children of rank h - 1 (#555: 9 x 7, #568: 8 x 8) + helpers rho = R / v helpers per port, each climbing h per stage, cut with the shipped helper run histogram + (bit: #555, complex: #568's implied helper histogram, both from #595 a4_gap.py) + idle the B_K idle slots (skel.idle_slots, 2 per port), hosting: the helper mass is hosted into the largest idle + slots first; a hosted slot of rank r leaves a remainder child of rank r / a (the #555 pattern: 42, 28, 14 -> + 6, 4, 2); helper mass beyond the hostable mass stays as extra paid children (#595 step D semantics) +Piece-size scaling to another window h: + 'abs' pieces keep the shipped absolute ranks (their number grows like h) -- pessimistic for large h + 'rel' pieces scale with the window (rank x a/a_ref; same number of stops per register) -- optimistic +Calibration: the model is run on the E8 words themselves (bit h 8, x 63/111, rho 439/111; complex h 9, x 64/112, +rho 496/112) and the ratio to the published value (1.7331e-3 / 1.5987e-3, #595 A.5) is reported; the same factor is +applied to every candidate of that side (a_cal). +Also printed: #634's loss-chain factors 1/m, D/W (W = 2K v, whole hosting), L_ent = ln m - E ln r on the model profile. +""" +import math +import sys +from fractions import Fraction as Fr + +REPO = sys.argv[1] if len(sys.argv) > 1 else None +if REPO: + sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import idle_slots # noqa: E402 + +# shipped piece statistics (checked by #595 a4_gap.py: A4.runs.#555, A4.data.#568, A4.inputs.#568) +BIT = dict(h=8, v=111, R=439, + data={1: 10 + 317, 2: 209 + 92, 3: 10, 4: 3, 5: 92, 6: 10, 7: 9}, # x + y runs per stage + helpers={1: 994, 2: 1041, 3: 70, 4: 44, 5: 10}, published=Fr(17331, 10 ** 7), x=Fr(63, 111), s=9) +CX = dict(h=9, v=112, R=496, + data={1: 718, 2: 74, 3: 60, 4: 92, 5: 42, 6: 28}, # per call, v = 112 + helpers={1: 115952, 2: 26320, 3: 28064, 4: 7072, 5: 1008, 6: 2016}, published=Fr(15987, 10 ** 7), + x=Fr(64, 112), s=9) + + +def _norm(hist, mass): + tot = sum(r * z for r, z in hist.items()) + return {Fr(r): Fr(z) * mass / tot for r, z in hist.items()} + + +def profile(side, h, x, rho, s, mode): + ref = BIT if side == 'bit' else CX + a, K = h - 1, (s - 1) // 2 + m = 1 + s * a + D = (s - 1) - s * Fr(x) + scale = Fr(a, ref['h'] - 1) if mode == 'rel' else Fr(1) + prof = {} + + def add(r, n): + r = Fr(r) + if n <= 0 or r >= m: + return + prof[r] = prof.get(r, 0) + n + # data: 2 s a mass with the reference run shape + for r, n in _norm(ref['data'], 2 * s * a).items(): + rr = min(r * scale, a) + add(rr, n * r / rr) # keep the mass of each class + # copies + add(a, Fr(s) * Fr(x) / a) + # helpers + hmass = Fr(rho) * s * h + hsc = Fr(h, ref['h']) if mode == 'rel' else Fr(1) + for r, n in _norm(ref['helpers'], hmass).items(): + rr = min(r * hsc, h) + add(rr, n * r / rr) + # idle with hosting + slots = sorted([r for r in idle_slots(K, a) for _ in range(2)], reverse=True) + left = hmass + for r in slots: + rem = Fr(r, a) # remainder piece of a hosted slot + cap = r - rem + if left >= cap: + left -= cap + add(rem, 1) + elif left > 0: + add(r - left, 1) # partially hosted slot: one paid child of the rest + left = 0 + else: + add(r, 1) + return prof, m, D + + +def root(prof, m, D, iters=200): + m = float(m); D = float(D) + F = lambda q: sum(float(z) * float(r) * ((m / float(r)) ** q - 1) for r, z in prof.items()) + lo, hi = 0.0, 0.5 + for _ in range(iters): + mid = (lo + hi) / 2 + if F(mid) >= D: + hi = mid + else: + lo = mid + return hi + + +def chain(prof, m, D, s): + """#634 loss chain on the model profile: a ~ (1/m)(D/W)/L_ent with W = (mass + D)/m data-equivalent wires per port + (= 2K when every helper is hosted) and L_ent = ln m - E ln r (rank-weighted).""" + mass = sum(float(r * z) for r, z in prof.items()) + W = (mass + float(D)) / float(m) + Eln = sum(float(r * z) * math.log(float(r)) for r, z in prof.items()) / mass + Lent = math.log(float(m)) - Eln + return dict(inv_m=1 / float(m), W=W, DW=float(D) / W, Lent=Lent, first=float(D) / W / float(m) / Lent) + + +def estimate(side, h, x, rho, mode, S=range(3, 26, 2)): + best = None + for s in S: + prof, m, D = profile(side, h, x, rho, s, mode) + if D <= 0: + continue + a = root(prof, m, D) + if best is None or a > best[1]: + best = (s, a, chain(prof, m, D, s)) + return best + + +def calibration(side, mode): + ref = BIT if side == 'bit' else CX + prof, m, D = profile(side, ref['h'], ref['x'], Fr(ref['R'], ref['v']), ref['s'], mode) + return float(ref['published']) / root(prof, m, D) + + +if __name__ == '__main__': + for side in ('bit', 'complex'): + for mode in ('abs', 'rel'): + print(side, mode, 'calibration factor (published / model at s = 9): %.4f' % calibration(side, mode)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz.py new file mode 100644 index 0000000000..0e78709fa0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz.py @@ -0,0 +1,87 @@ +"""Lorentzian I_{n,1} units (#634 note 3: triples e_A + e_{n+1} and pairs e_i - e_j, n odd) as bit units. + +Per n, exactly: v; window = rank of the Lorentz Gram (#634 proves the fitting minrank over char != 2 is n for n = 11, 13, +i.e. no rank-(n-1) labelling as for E8 = I_{9,1}); the binary labels (3-set / complement of a pair) satisfy + = 1 + G_ST mod 2 (checked), so B0 = U U^T is a decoder of the #595 pattern with c0 = rank_F2(U) totals; +omega (pairwise non-orthogonal clique, exact) is a lower bound on the F_2 minrank; z = orthogonal neighbours per line; +cst_lab = attainable star cost of the label decoders on these labels (matroid-greedy over the 2^n - 1 functionals); +X* certified (skel.X) at x = cst_lab / v. n = 9 is the E8 control (window 8 after the isotropic quotient). +Usage: python lorentz.py REPO_ROOT [n ...] [--json out] +""" +import sys +from fractions import Fraction as Fr + +import numpy as np + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X # noqa: E402 + +import units as U # noqa: E402 + + +def greedy_cost(L, lab, rad=None): + """rad: isotropic radical vector (n = 9); spans are then measured in the quotient by .""" + n = lab.shape[1] + costs = [] + for p in range(1, 1 << n): + pv = np.array([(p >> j) & 1 for j in range(n)]) + R = np.nonzero((lab @ pv) % 2)[0] + costs.append(((U.rank_q(L[R].tolist() + ([rad] if rad is not None else [])) - (1 if rad is not None else 0)) if len(R) else 0, p)) + costs.sort() + basis, tot, used = {}, 0, [] + for cst, p in costs: + x = p + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + if x: + tot += cst; used.append(cst) + if len(basis) == n: + break + return tot, sorted(used) + + +def main(): + args = sys.argv[2:] + jout = None + if '--json' in args: + jout = args[args.index('--json') + 1] + args = args[:args.index('--json')] + ns = [int(a) for a in args] or [9, 11, 13] + rows = [] + for n in ns: + L, G, lab = U.lorentz_family(n) + v = len(L) + assert (((lab @ lab.T) % 2) == ((1 + G) % 2)).all(), 'labels do not fit the pattern' + h = U.rank_q(G.tolist()) + c0 = U.f2_rank(lab) + om = U.nonorth_clique(G) + z = sorted(set(int((G[i] == 0).sum()) for i in range(v))) + rad = [1] * n + [3] if n == 9 else None + cst, used = greedy_cost(L, lab, rad) + # for n = 9 the label space is degenerate (w = (1,..,1,3) is isotropic): ranks of spans include the radical, + # the quotient (E8) costs dim span - 1 per total that contains the radical; report the Gram ranks too + x = Fr(cst, v) + best = None + for s in range(3, 17, 2): + r = X(s, h, 1, x) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi']) + print('I_{%d,1}: v=%d window(Gram rank)=%d c0=rank_F2(labels)=%d omega=%d z=%s cst_lab=%d (per total %s) x=%s=%.4f X*=%s' + % (n, v, h, c0, len(om), z, cst, used, x, float(x), + ('%.5e@s%d' % (float(best[1]), best[0])) if best else 'D<=0'), flush=True) + rows.append(dict(n=n, v=v, window=h, c0=c0, omega=len(om), z=z, cst_lab=cst, per_total=used, x=str(x), + Xstar=None if best is None else dict(s=best[0], hi=str(best[1])))) + if jout: + import json + json.dump(rows, open(jout, 'w'), indent=1) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz_minrank.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz_minrank.py new file mode 100644 index 0000000000..500d459c1c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/lorentz_minrank.py @@ -0,0 +1,20 @@ +"""F_2 minrank of the #595 pattern on I_{n,1} (n = 11: is it 10 = omega or 11 = c0?). SAT with the clique gauge, +time-limited; prints the outcome per c. Usage: python lorentz_minrank.py REPO_ROOT n timeout_s [--json out]""" +import sys +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from minrank_sat import solve_asym # noqa: E402 +import units as U # noqa: E402 + +n, T = int(sys.argv[2]), int(sys.argv[3]) +L, G, lab = U.lorentz_family(n) +om = U.nonorth_clique(G) +c0 = U.f2_rank(lab) +print('I_{%d,1}: omega=%d c0=%d' % (n, len(om), c0), flush=True) +lo, hi, log = U.minrank_f2(G, om, c0, solve_asym, timeout=T) +print(lo, hi, log, flush=True) +if '--json' in sys.argv: + import json + json.dump([dict(n=n, omega=len(om), c0=c0, lo=lo, hi=hi, log={k: v[0] for k, v in log.items()})], + open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1) diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/rank.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/rank.py new file mode 100644 index 0000000000..500b2b215f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/rank.py @@ -0,0 +1,115 @@ +"""Rank candidate unit families by a helper-adjusted estimate of the achievable saving (MODEL; see est_model.py). + +Per candidate (side, h, v, cst, z): + X* certified #595 ceiling (skel.X, best odd s in 3..15) at x = cst / v [exact interval upper end] + rho* break-even helpers per port: the rho at which the calibrated model estimate (best s) equals the calibrated + model estimate of the E8 word of the same side at its shipped intensity and best s (bit #555 at + rho = 439/111, complex #568 at rho = 496/112): same model, same s freedom on both sides + a_est calibrated model estimate at three helper-intensity hypotheses (E8's shipped intensity transferred): + rho_same = rho_E8 (helpers per port unchanged) + rho_dim = rho_E8 * (z/(h-1)) / (z_E8/(h_E8-1)) (readers per window dimension) <- central + rho_read = rho_E8 * z / z_E8 (readers per port) + in both piece-scaling modes ('abs' pessimistic, 'rel' optimistic). +Candidates: E8 references; triple family T_h (bit cst = h(h-1), complex cst = h^2: centre decoders, checked by +triple.py); Lorentzian I_{11,1}, I_{13,1} (label decoders, cst 110 / 156, checked by lorentz.py). +Usage: python rank.py REPO_ROOT [--json out] +""" +import json +import sys +from fractions import Fraction as Fr +from math import comb + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X # noqa: E402 + +import est_model as E # noqa: E402 + +REF = {'bit': dict(h=8, rho=Fr(439, 111), z=63, a=1.7331e-3), + 'complex': dict(h=9, rho=Fr(496, 112), z=52, a=1.5987e-3)} + + +def xstar(h, x): + best = None + for s in range(3, 17, 2): + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi']) + return best + + +def candidates(): + C = [dict(name='E8 bit 111/63 (#555)', side='bit', h=8, v=111, cst=63, z=63), + dict(name='E8 bit 120/72', side='bit', h=8, v=120, cst=72, z=63), + dict(name='E8 complex 112/64 (#568)', side='complex', h=9, v=112, cst=64, z=52), + dict(name='I_{11,1} bit', side='bit', h=11, v=220, cst=110, z=Fr(165 * 115 + 55 * 129, 220)), + dict(name='I_{13,1} bit', side='bit', h=13, v=364, cst=156, z=Fr(286 * 183 + 78 * 231, 364))] + for h in (10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28, 30): + C.append(dict(name='T_%d bit' % h, side='bit', h=h, v=comb(h, 3), cst=h * (h - 1), z=3 * comb(h - 3, 2))) + for h in (12, 14, 16, 18, 20, 22, 24, 26, 28, 30): + C.append(dict(name='T_%d complex' % h, side='complex', h=h, v=comb(h, 3), cst=h * h, + z=comb(h - 3, 3) + 3 * (h - 3))) + return C + + +def cal_est(side, h, x, rho, mode, cal): + b = E.estimate(side, h, x, rho, mode) + return (None, None, None) if b is None else (b[0], b[1] * cal, b[2]) + + +def breakeven(side, h, x, mode, cal, target): + lo, hi = Fr(0), Fr(400) + f = lambda r: (cal_est(side, h, x, r, mode, cal)[1] or 0.0) + if f(lo) < target: + return 0.0 + if f(hi) >= target: + return float(hi) + for _ in range(40): + mid = (lo + hi) / 2 + if f(mid) >= target: + lo = mid + else: + hi = mid + return float(lo) + + +def main(): + rows = [] + cals = {(sd, md): E.calibration(sd, md) for sd in ('bit', 'complex') for md in ('abs', 'rel')} + print('calibration factors', {k: round(v, 4) for k, v in cals.items()}) + target = {sd: cal_est(sd, REF[sd]['h'], Fr(63, 111) if sd == 'bit' else Fr(64, 112), REF[sd]['rho'], 'abs', + cals[(sd, 'abs')])[1] for sd in ('bit', 'complex')} + print('E8 targets (calibrated model, best s):', {k: '%.4e' % v for k, v in target.items()}) + for c in candidates(): + sd, h = c['side'], c['h'] + x = Fr(c['cst'], c['v']) + ref = REF[sd] + xs = xstar(h, x) + rh = {'same': ref['rho'], + 'dim': ref['rho'] * Fr(c['z']) / (h - 1) / Fr(ref['z'], ref['h'] - 1), + 'read': ref['rho'] * Fr(c['z']) / ref['z']} + row = dict(name=c['name'], side=sd, h=h, v=c['v'], cst=c['cst'], x=float(x), z=float(c['z']), + Xstar=float(xs[1]) if xs else None, Xs=xs[0] if xs else None, + rho={k: float(v) for k, v in rh.items()}) + for md in ('abs', 'rel'): + row['rho_star_' + md] = breakeven(sd, h, x, md, cals[(sd, md)], target[sd]) + for k, r in rh.items(): + s_, a_, ch = cal_est(sd, h, x, r, md, cals[(sd, md)]) + row['a_%s_%s' % (k, md)] = a_ + row['s_%s_%s' % (k, md)] = s_ + if k == 'dim' and md == 'abs' and ch: + row['chain_dim_abs'] = {kk: round(vv, 5) for kk, vv in ch.items()} + rows.append(row) + f = lambda v: '%.3e' % v if v else ' - ' + print('%-26s h=%2d v=%5d x=%.4f z=%7.1f X*=%s | rho*: abs %6.2f rel %6.2f | rho same/dim/read %5.2f/%6.2f/%7.2f | ' + 'a_dim abs %s rel %s | a_same abs %s | a_read abs %s' + % (row['name'], h, c['v'], row['x'], row['z'], f(row['Xstar']), row['rho_star_abs'], row['rho_star_rel'], + row['rho']['same'], row['rho']['dim'], row['rho']['read'], f(row['a_dim_abs']), f(row['a_dim_rel']), + f(row['a_same_abs']), f(row['a_read_abs'])), flush=True) + if '--json' in sys.argv: + json.dump(rows, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/triple.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/triple.py new file mode 100644 index 0000000000..da28fda4a3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/triple.py @@ -0,0 +1,173 @@ +"""Triple family T_h (upstream 03-motifs.tex, the Alon-type unit): decoder, copy cost and certified ceiling. + +Bit side. Lines t_T (T a 3-subset of [h]) in Q^h with the form I - J/9: G_ST = |S n T| - 1. Pattern: B_TS = 0 where +|S n T| in {0, 2}, free where |S n T| = 1. Label decoder B0 = M M^T mod 2 (M = incidence; the h centres C_i of the +paper): B0_ST = |S n T| mod 2. +Complex side. Lines t_T in F_2^h with the dot product (t_T . t_T = 1). Pattern: B_TS = 0 where |S n T| = 1 (odd), +free where |S n T| in {0, 2}. Decoder over a field of characteristic 0: the paper's h + 1 centres C_i, C_*, with +B_ST = (|S n T| - 1)/2. + +Per h the script computes, exactly: + window rank of the form on span{t_T} (Q for bit, F_2 for complex) + c totals of the centre decoder; for bit also rank_F2(B0) and the clique lower bound 4*floor(h/4) + (4 triples inside a 4-set are pairwise at even intersection; odd-weight pairwise-orthogonal vectors of + F_2^h are independent, so the clique number is <= h) + cst_att star cost of the centre decoder = sum_i dim span(gathered lines) (attained: copy f = V -> 0) + cst_thmC #446 Theorem C floor of the centre decoder = sum_i rank of the pairing span(gathered) x span(readers) + X*(att) certified #595 ceiling skel.X at x = cst_att / v, best odd s in 3..15 +For small h (bit, h <= MAXEXACT) also: + minrank exact F_2 minrank of the pattern (SAT, #595's encoder), and + lab_min min over nonzero p in F_2^h of dim_Q span{t_T : |T n supp p| odd} (every label decoder on the B0 labels + has each total gathering such a set, so cst >= c * lab_min for those decoders) +Usage: python triple.py REPO_ROOT HMIN HMAX [--exact MAXEXACT] [--json out] +""" +import itertools +import json +import sys +from fractions import Fraction as Fr +from math import comb + +import numpy as np + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X # noqa: E402 +from minrank_sat import solve_asym # noqa: E402 + +import units as U # noqa: E402 + +S_RANGE = range(3, 17, 2) + + +def xstar(h, x): + best = None + for s in S_RANGE: + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi']) + return best + + +def q_pair_rank(rows, Qform): + """rank over Q of the Gram matrix of span(rows) under Qform (integer).""" + idx = [] + red = [] + import math + for i, r in enumerate(rows): + rr0 = [int(x) for x in r] + rr = rr0 + for pc, b in red: + if rr[pc]: + f, g = rr[pc], b[pc] + rr = [g * x - f * y for x, y in zip(rr, b)] + c = math.gcd(*rr) + if c > 1: + rr = [x // c for x in rr] + pc = next((j for j, x in enumerate(rr) if x), None) + if pc is not None: + red.append((pc, rr)); idx.append(i) + B = np.array([rows[i] for i in idx], dtype=object) + Gm = B.dot(np.array(Qform, dtype=object)).dot(B.T) + return len(idx), U.rank_q(Gm.tolist()) + + +def f2_dim(rows): + return U.f2_rank(np.array(rows)) + + +def f2_pair_rank(rows): + """dim and dot-product rank of span(rows) over F_2.""" + rows = np.array(rows, dtype=np.int64) % 2 + # basis + basis = [] + for r in rows: + x = int(''.join(str(int(b)) for b in r[::-1]), 2) + for b in basis: + x = min(x, x ^ b) + if x: + basis.append(x) + n = rows.shape[1] + Bm = np.array([[(b >> j) & 1 for j in range(n)] for b in basis], dtype=np.int64) + return len(basis), U.f2_rank((Bm @ Bm.T) % 2) + + +def bit_row(h, exact): + M, G, T = U.triple_family(h) + v = len(T) + Qf = 9 * np.eye(h, dtype=np.int64) - 1 # 9 (I - J/9); ranks are unchanged by the factor 9 + dimall, window = q_pair_rank(M.tolist(), Qf.tolist()) + c0 = U.f2_rank(M @ M.T % 2) + att, thm = 0, 0 + per = [] + for i in range(h): + R = [k for k, t in enumerate(T) if i in t] + d, pr = q_pair_rank(M[R].tolist(), Qf.tolist()) + att += d; thm += pr; per.append((d, pr)) + row = dict(side='bit', h=h, v=v, window=window, span=dimall, c_centre=h, c0=c0, clique_lb=4 * (h // 4), + z=3 * comb(h - 3, 2), cst_att=att, cst_thmC=thm, per_total=sorted(set(per))) + if exact and h <= exact: + off = [(i, j) for i in range(v) for j in range(i + 1, v) if G[i, j] != 0] + # explicit clique: disjoint 4-sets' triples + cl = [] + for b in range(h // 4): + pts = range(4 * b, 4 * b + 4) + cl += [T.index(tuple(t)) for t in itertools.combinations(pts, 3)] + lo, hi, log = U.minrank_f2(G, cl, c0, solve_asym, timeout=600) + c = lo if lo == hi else '%d..%d' % (lo, hi) + row['minrank'] = c + row['minrank_log'] = log + lab = None + for p in range(1, 1 << h): + P = [j for j in range(h) if p >> j & 1] + R = [k for k, t in enumerate(T) if len(set(t) & set(P)) % 2] + d = U.rank_q(M[R].tolist()) + lab = d if lab is None else min(lab, d) + row['lab_min'] = lab + return row + + +def cx_row(h): + M, G, T = U.triple_family(h) + v = len(T) + dimall, window = f2_pair_rank(M.tolist()) + # centre decoder rank over Q of (M M^T - J)/2 = rank of [M | 1] (Gram-type), exact + Bc = (M @ M.T - 1) + cdec = U.rank_q(np.hstack([M, np.ones((v, 1), dtype=np.int64)]).tolist()) + att, thm = 0, 0 + per = [] + for i in range(h): + R = [k for k, t in enumerate(T) if i in t] + d, pr = f2_pair_rank(M[R].tolist()) + att += d; thm += pr; per.append((d, pr)) + d, pr = f2_pair_rank(M.tolist()) # C_* gathers every line + att += d; thm += pr; per.append((d, pr)) + return dict(side='complex', h=h, v=v, window=window, span=dimall, c_centre=h + 1, rank_centre_matrix=cdec, + z=comb(h - 3, 3) + 3 * (h - 3), cst_att=att, cst_thmC=thm, per_total=sorted(set(per))) + + +def main(): + hmin, hmax = int(sys.argv[2]), int(sys.argv[3]) + exact = int(sys.argv[sys.argv.index('--exact') + 1]) if '--exact' in sys.argv else 0 + rows = [] + for h in range(hmin, hmax + 1): + for r in (bit_row(h, exact), cx_row(h)): + x_att, x_thm = Fr(r['cst_att'], r['v']), Fr(r['cst_thmC'], r['v']) + wnd = r['window'] + # the window entering skel.X is the rank of the label space (Theorem B uses the window form) + for key, x in (('att', x_att), ('thmC', x_thm)): + b = xstar(wnd, x) + r['X_' + key] = None if b is None else dict(s=b[0], hi=str(b[1]), f=float(b[1])) + r['x_att'], r['x_thmC'] = str(x_att), str(x_thm) + rows.append(r) + fx = lambda k: ('%.5e@s%d' % (r[k]['f'], r[k]['s'])) if r[k] else 'D<=0' + extra = (' minrank=%s lab_min=%s' % (r.get('minrank'), r.get('lab_min'))) if 'minrank' in r else '' + print('%-7s h=%2d v=%5d window=%2d c=%2d cst_att=%4d cst_thmC=%4d x_att=%.4f X*att=%s X*thmC=%s z=%d per=%s%s' + % (r['side'], h, r['v'], wnd, r['c_centre'], r['cst_att'], r['cst_thmC'], float(x_att), fx('X_att'), + fx('X_thmC'), r['z'], r['per_total'], extra), flush=True) + if '--json' in sys.argv: + json.dump(rows, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/units.py b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/units.py new file mode 100644 index 0000000000..358c72b4b5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/scripts/units.py @@ -0,0 +1,210 @@ +"""Exact line systems used by the S3 screens (integer coordinates, exact Gram matrices). + +Every unit is returned as (L, G, name) with L an integer v x n matrix of line representatives and G the exact integer +Gram matrix of the unit's bilinear form (diagonal 2). The F2 decoder pattern of #595 is: B_TT = 1 and B_TS = 0 whenever +G_TS != 0 (non-orthogonal lines); the entries at orthogonal pairs are free. + +Families + * ADE root systems A_n, D_n, E6, E7, E8 (doubled coordinates, G = L L^T / 4). + E6 is the root lines orthogonal to an A2 = , a = (1/2)^8, b = e1 + e2 (a.b = 1), as in #630; + the archived #620 screen used b = e1 - e2 (a.b = 0), which gives D6. + * Triple family T_h (upstream 03-motifs.tex, bit construction): the C(h,3) indicator vectors t_T of 3-subsets of [h] + with the form I - J/9, so G_ST = |S n T| - 1 (diagonal 2). + * Lorentzian I_{n,1} (n odd, #634 note 3): e_A + e_{n+1} (|A| = 3) and e_i - e_j, Lorentz form diag(1^n, -1). +""" +import itertools + +import numpy as np + + +def roots_A(n): + R = [] + for i in range(n + 1): + for j in range(i + 1, n + 1): + r = [0] * (n + 1); r[i] = 2; r[j] = -2; R.append(r) + return np.array(R, dtype=np.int64) + + +def roots_D(n): + R = [] + for i in range(n): + for j in range(i + 1, n): + for s in (1, -1): + r = [0] * n; r[i] = 2; r[j] = 2 * s; R.append(r) + return np.array(R, dtype=np.int64) + + +def roots_E8(): + """120 lines, doubled coordinates: +-2e_i +-2e_j (one sign class per line) and (+-1)^8 with an even number of + minus signs and first coordinate +1.""" + R = [list(r) for r in roots_D(8)] + for sg in itertools.product((1, -1), repeat=8): + if sg[0] == 1 and sg.count(-1) % 2 == 0: + R.append(list(sg)) + return np.array(R, dtype=np.int64) + + +def perp(R, w): + return R[(R @ np.asarray(w, dtype=np.int64)) == 0] + + +A_VEC = np.ones(8, dtype=np.int64) # (1/2)^8 doubled +B_E6 = np.array([2, 2, 0, 0, 0, 0, 0, 0]) # e1 + e2 doubled; a.b = 4/4 = 1 -> A2 with a +B_D6 = np.array([2, -2, 0, 0, 0, 0, 0, 0]) # e1 - e2 doubled; a.b = 0 -> A1 + A1 (archived "E6") + + +def roots_E7(): + return perp(roots_E8(), A_VEC) + + +def roots_E6(): + return perp(perp(roots_E8(), A_VEC), B_E6) + + +def roots_D6_archived(): + return perp(perp(roots_E8(), A_VEC), B_D6) + + +def gram_doubled(L): + G4 = L @ L.T + assert (G4 % 4 == 0).all() + G = G4 // 4 + assert (np.diag(G) == 2).all() + return G + + +def triple_family(h): + """lines t_T, T in C([h],3); G_ST = |S n T| - 1 (form I - J/9).""" + T = list(itertools.combinations(range(h), 3)) + M = np.zeros((len(T), h), dtype=np.int64) + for i, t in enumerate(T): + M[i, list(t)] = 1 + G = M @ M.T - 1 + return M, G, T + + +def lorentz_family(n): + """I_{n,1}: e_A + e_{n+1} (|A| = 3) and e_i - e_j; Lorentz Gram; binary labels: A, resp. [n] minus {i, j}.""" + vecs, labels = [], [] + for A in itertools.combinations(range(n), 3): + v = [0] * (n + 1) + for a in A: + v[a] = 1 + v[n] = 1 + vecs.append(v); labels.append([1 if i in A else 0 for i in range(n)]) + for i, j in itertools.combinations(range(n), 2): + v = [0] * (n + 1); v[i] = 1; v[j] = -1 + vecs.append(v); labels.append([0 if k in (i, j) else 1 for k in range(n)]) + L = np.array(vecs, dtype=np.int64) + Q = np.diag([1] * n + [-1]).astype(np.int64) + G = L @ Q @ L.T + return L, G, np.array(labels, dtype=np.int64) + + +# ------------------------------------------------------------------ exact linear algebra +def rank_q(rows): + """exact rank over Q (fraction-free Bareiss-style elimination on Python ints).""" + import math + red = [] + for r in rows: + r = [int(x) for x in r] + for pc, rr in red: + if r[pc]: + f, g = r[pc], rr[pc] + r = [g * x - f * y for x, y in zip(r, rr)] + c = math.gcd(*r) + if c > 1: + r = [x // c for x in r] + pc = next((j for j, x in enumerate(r) if x), None) + if pc is not None: + red.append((pc, r)) + return len(red) + + +def rank_mod_p(M, p): + """rank of an integer matrix over F_p (p prime).""" + A = (np.array(M, dtype=object) % p).tolist() + rk, rows, cols = 0, len(A), len(A[0]) if A else 0 + for c in range(cols): + piv = next((i for i in range(rk, rows) if A[i][c] % p), None) + if piv is None: + continue + A[rk], A[piv] = A[piv], A[rk] + inv = pow(A[rk][c], p - 2, p) + A[rk] = [(x * inv) % p for x in A[rk]] + for i in range(rows): + if i != rk and A[i][c] % p: + f = A[i][c] + A[i] = [(x - f * y) % p for x, y in zip(A[i], A[rk])] + rk += 1 + return rk + + +def f2_rank(M): + rows = [int(''.join('1' if x & 1 else '0' for x in row[::-1]) or '0', 2) for row in np.asarray(M)] + basis = {} + for x in rows: + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + return len(basis) + + +def gram_rank(G, idx): + """rank over Q of the principal block G[idx, idx] (= pairing rank of span(idx) with itself).""" + if len(idx) == 0: + return 0 + return rank_q(G[np.ix_(idx, idx)].tolist()) + + +def nonorth_clique(G): + """exact maximum set of pairwise non-orthogonal lines (identity block of the pattern => minrank lower bound).""" + import networkx as nx + v = len(G) + H = nx.Graph() + H.add_nodes_from(range(v)) + H.add_edges_from((i, j) for i in range(v) for j in range(i + 1, v) if G[i, j] != 0) + best, _ = nx.max_weight_clique(H, weight=None) + return sorted(best) + + +def _query(args): + solver, v, pattern, c, clique = args + out = solver(v, pattern, None, c, 'none', clique) + if out['result'] == 'SAT': + B = out['B'] + assert all(B[i, i] == 1 for i in range(v)) and all(B[i, j] == 0 for i, j in pattern) + return out['result'], out['sec'] + + +def minrank_f2(G, clique, cmax, solver=None, timeout=300): + """exact F_2 minrank of the pattern of G (B_TT = 1, B_TS = 0 where G_TS != 0), asymmetric decoders allowed. + Asks SAT for c = |clique|, |clique| + 1, ... < cmax; each query fixes the GL(c, F_2) gauge U_K = W_K = [I | 0] on + the clique K (solve_asym of #595's minrank_sat.py; a SAT answer is re-checked on the returned B). A query that does + not finish within `timeout` seconds is recorded as 'TIMEOUT' and the search stops there. + Returns (lo, hi, log): the minrank lies in [lo, hi]; lo == hi when it is determined (hi = cmax is attained by a + known decoder).""" + import multiprocessing as mp + v = len(G) + pattern = [(i, j) for i in range(v) for j in range(v) if i != j and G[i, j] != 0] + log = {} + lo = len(clique) + ctx = mp.get_context('fork') + for c in range(len(clique), cmax): + with ctx.Pool(1) as pool: + res = pool.apply_async(_query, ((solver, v, pattern, c, list(clique)),)) + try: + r, sec = res.get(timeout) + except mp.TimeoutError: + log[c] = ('TIMEOUT', timeout) + pool.terminate() + return lo, cmax, log + log[c] = (r, sec) + if r == 'SAT': + return c, c, log + lo = c + 1 + return cmax, cmax, log diff --git a/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/verify.sh new file mode 100644 index 0000000000..eebeb5ee68 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s3-unit-families/verify.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Recompute every number of this package and compare with expected.json. +# Usage: sh verify.sh REPO_ROOT OUT_DIR +# REPO_ROOT: a checkout of CrocSwap/integer-mult-bounds main (reads research/format-limits/scripts: skel.py, cert.py, +# minrank_sat.py, e8units.py of #595); OUT_DIR: fresh output directory. +# Python >= 3.11 with numpy, networkx, python-sat on PATH as `python3` (or set PY). About 40 min on 10 cores +# (the steps run in parallel; the ADE step spends up to 600 s in each of its two SAT timeouts). +set -eu +REPO=$(cd "$1" && pwd); OUT=$2 +PY=${PY:-python3} +HERE=$(cd "$(dirname "$0")" && pwd) +mkdir -p "$OUT" +cd "$HERE/scripts" +$PY -B ade_screen_fixed.py "$REPO" --json "$OUT/ade.json" > "$OUT/ade.log" 2>&1 & +$PY -B triple.py "$REPO" 6 32 --exact 11 --json "$OUT/triple.json" > "$OUT/triple.log" 2>&1 & +$PY -B lorentz.py "$REPO" 9 11 13 --json "$OUT/lorentz.json" > "$OUT/lorentz.log" 2>&1 & +$PY -B lorentz_minrank.py "$REPO" 11 600 --json "$OUT/lorentz_minrank.json" > "$OUT/lorentz_minrank.log" 2>&1 & +$PY -B cover.py "$REPO" E8,T8,T10,T12,T16,T20,T24,L11,L13 20000 --json "$OUT/cover.json" > "$OUT/cover.log" 2>&1 & +$PY -B rank.py "$REPO" --json "$OUT/rank.json" > "$OUT/rank.log" 2>&1 & +wait +$PY -B check.py "$OUT" "$HERE/expected.json" From e9cf03bbdf7094b5a83f6dbd1d74bfd77a05fb87 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Mon, 12 Oct 2026 22:36:27 -0400 Subject: [PATCH 04/17] Add dispatch and cold-end pricing notes from the #639 lineage Co-Authored-By: Claude Opus 5.5 --- .../README.md | 5 + .../r2-dispatch/REPORT.md | 15 +++ .../r2-dispatch/V639.json | 1 + .../r2-dispatch/a1.py | 15 +++ .../r2-dispatch/alt.py | 9 ++ .../r2-dispatch/alt2.py | 16 +++ .../r2-dispatch/aug.py | 107 ++++++++++++++++++ .../r2-dispatch/cpagg.py | 87 ++++++++++++++ .../r2-dispatch/cpfeas.py | 87 ++++++++++++++ .../r2-dispatch/dbg.py | 8 ++ .../r2-dispatch/disp.py | 18 +++ .../r2-dispatch/evalst.py | 20 ++++ .../r2-dispatch/expand_fn.py | 52 +++++++++ .../r2-dispatch/lns.py | 69 +++++++++++ .../r2-dispatch/lns2.py | 85 ++++++++++++++ .../r2-dispatch/lns3.py | 86 ++++++++++++++ .../r2-dispatch/lns4.py | 92 +++++++++++++++ .../r2-dispatch/load.py | 7 ++ .../r2-dispatch/mip.py | 90 +++++++++++++++ .../r2-dispatch/mkV.py | 11 ++ .../r2-dispatch/price.py | 7 ++ .../r2-dispatch/submip.py | 104 +++++++++++++++++ 22 files changed, 991 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/REPORT.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/V639.json create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/a1.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/alt.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/alt2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/aug.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpagg.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpfeas.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/dbg.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/disp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/evalst.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/expand_fn.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns4.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/load.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mip.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mkV.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/price.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r2-dispatch/submip.py diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 845b7a4327..fe1c5e4966 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -47,3 +47,8 @@ On the bit side ranks are counted over Q, so a K-line unit is a rank-k unit with - Certified ceilings above E8's: the paper's triple family T_h (bit cst h(h−1), max X* 7.7735e-3 at h = 21; complex cst h², max 7.4444e-3 at h = 22) and the Lorentzian units of #634: I_{11,1} (minrank exactly 11, cst 110, x = 1/2, X* 7.1715e-3) and I_{13,1} (cst 156, x = 3/7, X* 8.6247e-3). - Helper-adjusted (model, calibrated to #555 within 0.3%, not a bound): no candidate reaches E8's estimate, even at E8's 3.95 helpers per port. To break even, a design would need ≤ 2.6–3.4 helpers per port for I_{n,1} (≤ 2.5 for bit triples). The best lead is I_{11,1}; next steps are its punctures (the analogue of E8's 120 → 111) and a real helper design. - #595 §D helper floors don't separate the families: the triple family has cover number 2 (exact certificates up to h = 24). + +## 6. Dispatch and cold-end pricing on the #639 lineage (`r2-dispatch/`, details in `REPORT.md`) +- Exact prices at concentrated idle (PR568 `moment.certify`): #639 b = 0.0016391439889600812; 72×jp8 b = 0.0016392558 (not admissible, since the global controls need two dispatched providers); 71 jp8 + 1 st2 (#635's jp8 word as the second provider) b = 0.0016392519, i.e. **+1.08e-7 b** from removing the forced ck call. #643 (jeremykothe2) later realised this twin dispatch independently. +- Exact sub-MIP obstruction: with jp8 at t = 5 and every window not touching t ≤ 7 held fixed, the optimum leaves 64 idle-3 owners, so no perfect packing exists in that neighbourhood. Once ck's 9+7 pairs are gone, the early windows are the tight ones. +- Mass-neutral V pricing per end per call (V639.json): u4-type premix-only dim-8 ends ({1:+2, 2:−1, 4:+1, 5:−1}, width −1) ≈ −0.025, essentially neutral, consistent with #637's 23 LP-negative candidates. **Dim-7 factored end pairs (#627 type) ≈ +2.25 per pair: the positive direction.** This should carry over to the gauge44 lineage (#645 onward) and was not searched here. diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/REPORT.md b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/REPORT.md new file mode 100644 index 0000000000..bc0cea6222 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/REPORT.md @@ -0,0 +1,15 @@ +# R2 progress (record track) +Worktree ~/imb-r2 (branch research/cold14-r2, base pr639 cf2f6d77). Tools ~/imb-harness/r2 (load.py, price.py, disp.py, alt*.py, lns*.py, aug.py, mip.py, V639.json). +## Findings so far +- #639 global profile = sum provider profiles + idle; idle = 387072 - total widths. jp8(u4) widths 3890, ck 3854, st2-jp8 (#635 word) 3894. +- Concentrated-idle bound: 72 jp8 -> b 0.0016392558 (+1.118e-7 b over #639 0.0016391440). BUT global assembly controls need 2 dispatched providers (README: ck pinned at t=5 for that reason). +- 2nd-provider options (concentrated bound): st2 jp8 word (#635) x1: b 0.0016392519 (+1.08e-7); jp8 minus one u4 end: 0.0016392548. +- Literal packing: LNS (CP-SAT) for 72 jp8 stalls at ~24 partial owners (b 0.0016389 < base). Perfect packing not found yet. +- Running: mip.py (aggregate free-dispatch exact-window MIP, Hall masks) LP bound over {jp8,ck,st2}. +## Update (handback) +- V639.json: mass-neutral b values (1e-9 b) vs #639 profile. Provider net vs jp8(u4): ck -247.4 profile (+36 width -> net ~-101 => the 1.1e-7 gap); st2 +16.37 profile, +4 width (~-16.3 idle) => ~neutral. +- Lever 1 accounting: u4-type end {1:+2,2:-1,4:+1,5:-1}, width -1 => net -0.025 per end per call (neutral/negative). Dim-7 factored end pair ({1:+2,2:+2,5:-2}, width -4) => +2.25 per pair per call: further dim-7 ends would be the real lever 1. +- Sub-MIP (submip.py, windows touching t<=7, jp8 at t=5, rest fixed): optimum forces 64 owners (9,9,9,9,9) idle 3 in window 5,8 => perfect packing not available locally. +- Launched (may still be running, ~/imb-harness/r2): c1.log = cpagg.py jp8,ck,st2 free dispatch CP-SAT hinted by #639 (only hint solution found so far); f72.log = cpfeas.py pure feasibility of perfect 72-jp8 packing; mip2.log = HiGHS free-dispatch MIP. +- Package skeleton: ~/imb-r2/research/complex-571-cold14-r2 (copy of witness638 + data/recipes/k492-st2-cold_end.json.gz = #635 jp8 recipe, sha 90952fde..). Not wired up; no commit. +Resume: cd ~/imb-harness/r2; cat c1.log f72.log mip2.log. If a dispatch {jp8 x(72-k), st2 x k} with idle profile beating b 0.0016391439889600812 is found: expand (expand_fn.py), wire st2 provider into SPEC/WITNESS (build entry = #635 SPEC build.providers.jp8, providers entry = #635 SPEC providers.jp8, registry = st2-PLAN.json jp8 with directory providers/st2), drop ck/ck_504, fix extras/check_native_owners.py + check_u4_portable provider lists, drop PR638 owner-replay steps from verify.sh, run verify. diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/V639.json b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/V639.json new file mode 100644 index 0000000000..10c5172220 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/V639.json @@ -0,0 +1 @@ +{"1": 0.0, "2": 1.460005797133559, "3": 3.469932932242822, "4": 5.836723075555035, "5": 8.46874739837603, "6": 11.312062510844147, "7": 14.331037175716805, "8": 17.500354290769017, "9": 20.801089049900753, "10": 24.21855529378407, "11": 27.7410230271743, "12": 31.358906530427365, "13": 35.064225132528996, "14": 38.85023095040961, "15": 42.71114356550622, "16": 46.641955786833186, "17": 50.63828817463157, "18": 54.696277919861934, "19": 58.81249249872061, "20": 62.98386155943487, "21": 67.20762246873477, "22": 71.48127625626488, "23": 75.80255158749605, "24": 80.16937501554928, "25": 84.57984620087221, "26": 89.0322171031147, "27": 93.52487437982572, "28": 98.05632439702877, "29": 102.62518038446753, "30": 107.23015136515151, "31": 111.87003256303241, "32": 116.54369705005661, "33": 121.25008843867404, "34": 125.98821446119275, "35": 130.75714130539652, "36": 135.55598859825827, "37": 140.38392494763218, "38": 145.2401639664379, "39": 150.1239607157839, "40": 155.03460851326403, "41": 159.9714360607355, "42": 164.93380485257933, "43": 169.92110683102177, "44": 174.93276225976217, "45": 179.9682177910782, "46": 185.0269447048926, "47": 190.1084373010941, "48": 195.21221142879605} \ No newline at end of file diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/a1.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/a1.py new file mode 100644 index 0000000000..c9460855a5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/a1.py @@ -0,0 +1,15 @@ +from load import * +print(len(rows), rows[0]) +occ=defaultdict(list) +for r in rows: occ[(r['owner_replica'],r['child'],r['port'])].append(r) +fill={k:sum(r['width'] for r in v) for k,v in occ.items()} +part=[(k,fill[k],P['windows'][f'{k[0]},{k[1]}'][0]) for k in fill if fill[k]4 else 0 # owners that must end with exactly 4 used (one 4-role) +random.seed(int(sys.argv[5]) if len(sys.argv)>5 else 0) +W=P['windows'];CAP={};TIM={} +for k,(c,ts) in W.items(): + a,b=map(int,k.split(',')) + for p in range(112):CAP[a,b,p]=c;TIM[a,b,p]=set(ts) +calls=[c['provider'] for c in P['provider_calls']] +def wid(t,h):return NEWW[h] if t==T else reg[calls[t]]['widths'][h] +own={} # (t,h)->owner +oldw=reg[calls[T]]['widths'] +pool=defaultdict(list) +for h in range(492):pool[NEWW[h]].append(h) +pend=[] +for r in sorted(rows,key=lambda r:(r['time'],r['helper'])): + o=(r['owner_replica'],r['child'],r['port']) + if r['time']!=T:own[r['time'],r['helper']]=o + elif pool[r['width']]:own[T,pool[r['width']].pop()]=o + else:pend.append((o,r['width'])) +newroles=[(T,h) for w,v in pool.items() for h in v] +# pair the leftover old slots with new roles (widths bigger): greedy by sorting +pend.sort(key=lambda x:x[1]);newroles.sort(key=lambda r:NEWW[r[1]]) +assert len(pend)==len(newroles) +for (o,w),r in zip(pend,newroles):own[r]=o +at=defaultdict(dict) +for r,o in own.items():at[o][r[0]]=r +fill={o:sum(wid(*r) for r in d.values()) for o,d in at.items()} +exc={o:fill[o]-CAP[o] for o in fill if fill[o]>CAP[o]} +print('excess',sum(exc.values()),len(exc)) +# partial owners +part=[o for o in CAP if 0=9 or any(wid(*c)==left for c in cands)==False else next(c for c in cands if wid(*c)==left) + plan.append(r);tot+=wid(*r) +for r in plan: + d=own[r];move(r,Z) +print('Z filled',fill[Z],CAP[Z]) +# extra 4-owners: move a 4-role into an empty 48 owner +empties=[o for o in CAP if CAP[o]==48 and fill.get(o,0)==0] +for k in range(EXTRA4): + for _ in range(10000): + r=random.choice([r for r,o in own.items() if wid(*r)==4]) + e=[o for o in empties if r[0] in TIM[o]] + if e:move(r,e[0]);empties.remove(e[0]);break +bal={o:fill.get(o,0)-CAP[o] for o in at if at[o]} +# owners that must be exactly 4 used are not CAP-balanced: treat target fill +target={o:(4 if fill.get(o,0)==4 and CAP[o]==48 and len(at[o])==1 else CAP[o]) for o in at if at[o]} +bal={o:fill[o]-target[o] for o in target} +src=[o for o,b in bal.items() if b>0];snk={o for o,b in bal.items() if b<0} +print('sources',sum(bal[o] for o in src),'sinks',-sum(bal[o] for o in snk)) +idx=defaultdict(list) # (t,width)->roles +for r in own:idx[r[0],wid(*r)].append(r) +def bfs(s): + prev={s:None};q=deque([s]) + while q: + x=q.popleft() + rs=list(at[x].values());random.shuffle(rs) + for r in rs: + w=wid(*r) + for y in idx.get((r[0],w-1),()): + Y=own[y] + if Y in prev:continue + prev[Y]=(x,r,y) + if Y in snk and bal[Y]<0:return Y,prev + q.append(Y) + return None,prev +def swap(r,y): + X,Y=own[r],own[y];t=r[0] + own[r],own[y]=Y,X;at[X][t]=y;at[Y][t]=r + fill[X]+=wid(*y)-wid(*r);fill[Y]+=wid(*r)-wid(*y) +npath=0 +while any(bal[o]>0 for o in src): + s=next(o for o in src if bal[o]>0) + Y,prev=bfs(s) + if Y is None:print('stuck at',s);break + path=[];cur=Y + while prev[cur] is not None:x,r,y=prev[cur];path.append((r,y));cur=x + for r,y in reversed(path):swap(r,y) + bal[s]-=1;bal[Y]+=1;npath+=1 +print('paths',npath,'remaining',sum(b for b in bal.values() if b>0)) +assert all(fill[o]<=CAP[o] for o in fill) +pickle.dump({r:o for r,o in own.items()},open(OUT,'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpagg.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpagg.py new file mode 100644 index 0000000000..f2a6742c13 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpagg.py @@ -0,0 +1,87 @@ +"""Aggregate exact-window pattern model (Hall masks) in CP-SAT with free dispatch over provs, >=MINALT non-jp8, +hinted with PR639's literal map. Objective: maximize sum of mass-neutral V (1e-9 b, scaled x1000).""" +import json,sys,time,pickle,gzip +from collections import Counter,defaultdict +from ortools.sat.python import cp_model +from load import P,reg,rows +V={int(k):v for k,v in json.load(open('V639.json')).items()};V[0]=0.0 +st2=json.load(open('st2-PLAN.json'))['provider_registry']['jp8'] +REG={'jp8':reg['jp8'],'ck':reg['ck'],'st2':st2} +provs=sys.argv[1].split(',');TIGHT=int(sys.argv[2]);SECS=float(sys.argv[3]);OUT=sys.argv[4];MINALT=int(sys.argv[5]) +FIX=json.loads(sys.argv[6]) if len(sys.argv)>6 else {} # {time:provider} +SC=1000 +inv={p:Counter(REG[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in REG[p]['actual_paid_profile'].items()) for p in provs} +WID=[4,5,7,8,9] +# hint from PR639 +own=defaultdict(list) +for r in rows:own[r['owner_replica'],r['child'],r['port']].append((r['time'],r['width'])) +hpat=defaultdict(Counter);hflow=defaultdict(Counter) +for k in P['windows']: + a,b=map(int,k.split(',')) + for p in range(112): + L=own.get((a,b,p),[]);hpat[k][tuple(sorted(w for _,w in L))]+=1 + for t,w in L:hflow[k][t,w]+=1 +def pats(cap,L,extra): + out=set() + def rec(pre,i,tot): + r=cap-tot + if r==0 or r<=TIGHT or r==cap: out.add(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)=MINALT) +invrow=defaultdict(list);groups={} +for key,(cap,times) in P['windows'].items(): + ps=pats(cap,len(times),list(hpat[key]));cnts=[Counter(q) for q in ps] + pv=[] + for q in ps: + v=m.NewIntVar(0,112,'');pv.append(v);obj.append(int(round(V[cap-sum(q)]*SC))*v);m.AddHint(v,hpat[key][q]) + m.Add(sum(pv)==112) + fl={} + for t in times: + for w in WID: + if not any(c[w] for c in cnts):continue + v=m.NewIntVar(0,112,'');fl[t,w]=v;invrow[t,w].append(v);m.AddHint(v,hflow[key][t,w]) + seen=set() + for mask in range(1,32): + cols={w for b,w in enumerate(WID) if mask>>b&1} + req=tuple((t,w) for (tt,w) in fl if tt==t and w in cols) + pres=tuple(j for j,c in enumerate(cnts) if any(c[w] for w in cols)) + if req and (req,pres) not in seen: + seen.add((req,pres));m.Add(sum(fl[k] for k in req)<=sum(pv[j] for j in pres)) + for w in WID: + fs=[v for (t,ww),v in fl.items() if ww==w] + m.Add(sum(fs)==sum(c[w]*pv[j] for j,c in enumerate(cnts) if c[w])) + groups[key]=dict(cap=cap,times=times,ps=ps,pv=pv,fl=fl) +for t in range(72): + for w in WID: + m.Add(sum(invrow[t,w])==sum(inv[p][w]*sel[t,p] for p in provs)) +m.Maximize(sum(obj)) +s=cp_model.CpSolver();s.parameters.max_time_in_seconds=SECS;s.parameters.num_workers=10;s.parameters.log_search_progress=False +base=71*val['jp8']+(val['ck'] if 'ck' in val else 0)+V[36]+2229*V[48] +class CB(cp_model.CpSolverSolutionCallback): + def __init__(s):super().__init__();s.t0=time.time() + def on_solution_callback(s):print('sol %.1fs obj %.3f gain_vs_639 %.3f bound %.3f'%(time.time()-s.t0,s.ObjectiveValue()/SC,s.ObjectiveValue()/SC-base,s.BestObjectiveBound()/SC),flush=True) +st=s.Solve(m,CB()) +print(s.StatusName(st),'obj',s.ObjectiveValue()/SC,'bound',s.BestObjectiveBound()/SC,'base639',base,flush=True) +if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + disp={t:p for (t,p),v in sel.items() if s.Value(v)} + rem=Counter() + x={} + for key,g in groups.items(): + g2=dict(cap=g['cap'],times=g['times'],ps=g['ps'],pv=[s.Value(v) for v in g['pv']],fl={k:s.Value(v) for k,v in g['fl'].items()}) + x[key]=g2 + for q,n in zip(g['ps'],g2['pv']): + if n and g['cap']-sum(q) and not(g['cap']==48 and not q):rem[g['cap']-sum(q)]+=n + print('dispatch',Counter(disp.values()),{t:p for t,p in disp.items() if p!='jp8'},'remainders',dict(rem)) + pickle.dump(dict(groups=x,disp=disp),open(OUT+'.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpfeas.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpfeas.py new file mode 100644 index 0000000000..d96d2d7629 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/cpfeas.py @@ -0,0 +1,87 @@ +"""Aggregate exact-window pattern model (Hall masks) in CP-SAT with free dispatch over provs, >=MINALT non-jp8, +hinted with PR639's literal map. Objective: maximize sum of mass-neutral V (1e-9 b, scaled x1000).""" +import json,sys,time,pickle,gzip +from collections import Counter,defaultdict +from ortools.sat.python import cp_model +from load import P,reg,rows +V={int(k):v for k,v in json.load(open('V639.json')).items()};V[0]=0.0 +st2=json.load(open('st2-PLAN.json'))['provider_registry']['jp8'] +REG={'jp8':reg['jp8'],'ck':reg['ck'],'st2':st2} +provs=sys.argv[1].split(',');TIGHT=int(sys.argv[2]);SECS=float(sys.argv[3]);OUT=sys.argv[4];MINALT=int(sys.argv[5]) +FIX=json.loads(sys.argv[6]) if len(sys.argv)>6 else {} # {time:provider} +SC=1000;HINT=__import__('os').environ.get('HINT','1')=='1' +inv={p:Counter(REG[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in REG[p]['actual_paid_profile'].items()) for p in provs} +WID=[4,5,7,8,9] +# hint from PR639 +own=defaultdict(list) +for r in rows:own[r['owner_replica'],r['child'],r['port']].append((r['time'],r['width'])) +hpat=defaultdict(Counter);hflow=defaultdict(Counter) +for k in P['windows']: + a,b=map(int,k.split(',')) + for p in range(112): + L=own.get((a,b,p),[]);hpat[k][tuple(sorted(w for _,w in L))]+=1 + for t,w in L:hflow[k][t,w]+=1 +def pats(cap,L,extra): + out=set() + def rec(pre,i,tot): + r=cap-tot + if r==0 or r<=TIGHT or (r==cap and cap==48): out.add(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)=MINALT) +invrow=defaultdict(list);groups={} +for key,(cap,times) in P['windows'].items(): + ps=pats(cap,len(times),[q for q in hpat[key] if (cap-sum(q))<=TIGHT or not q] if not HINT else list(hpat[key]));cnts=[Counter(q) for q in ps] + pv=[] + for q in ps: + v=m.NewIntVar(0,112,'');pv.append(v);obj.append(int(round(V[cap-sum(q)]*SC))*v);m.AddHint(v,hpat[key][q]) if HINT else None + m.Add(sum(pv)==112) + fl={} + for t in times: + for w in WID: + if not any(c[w] for c in cnts):continue + v=m.NewIntVar(0,112,'');fl[t,w]=v;invrow[t,w].append(v);m.AddHint(v,hflow[key][t,w]) if HINT else None + seen=set() + for mask in range(1,32): + cols={w for b,w in enumerate(WID) if mask>>b&1} + req=tuple((t,w) for (tt,w) in fl if tt==t and w in cols) + pres=tuple(j for j,c in enumerate(cnts) if any(c[w] for w in cols)) + if req and (req,pres) not in seen: + seen.add((req,pres));m.Add(sum(fl[k] for k in req)<=sum(pv[j] for j in pres)) + for w in WID: + fs=[v for (t,ww),v in fl.items() if ww==w] + m.Add(sum(fs)==sum(c[w]*pv[j] for j,c in enumerate(cnts) if c[w])) + groups[key]=dict(cap=cap,times=times,ps=ps,pv=pv,fl=fl) +for t in range(72): + for w in WID: + m.Add(sum(invrow[t,w])==sum(inv[p][w]*sel[t,p] for p in provs)) +m.Maximize(sum(obj)) +s=cp_model.CpSolver();s.parameters.max_time_in_seconds=SECS;s.parameters.num_workers=int(__import__("os").environ.get("NW","10"));s.parameters.log_search_progress=False +base=71*val['jp8']+(val['ck'] if 'ck' in val else 0)+V[36]+2229*V[48] +class CB(cp_model.CpSolverSolutionCallback): + def __init__(s):super().__init__();s.t0=time.time() + def on_solution_callback(s):print('sol %.1fs obj %.3f gain_vs_639 %.3f bound %.3f'%(time.time()-s.t0,s.ObjectiveValue()/SC,s.ObjectiveValue()/SC-base,s.BestObjectiveBound()/SC),flush=True) +st=s.Solve(m,CB()) +print(s.StatusName(st),'obj',s.ObjectiveValue()/SC,'bound',s.BestObjectiveBound()/SC,'base639',base,flush=True) +if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + disp={t:p for (t,p),v in sel.items() if s.Value(v)} + rem=Counter() + x={} + for key,g in groups.items(): + g2=dict(cap=g['cap'],times=g['times'],ps=g['ps'],pv=[s.Value(v) for v in g['pv']],fl={k:s.Value(v) for k,v in g['fl'].items()}) + x[key]=g2 + for q,n in zip(g['ps'],g2['pv']): + if n and g['cap']-sum(q) and not(g['cap']==48 and not q):rem[g['cap']-sum(q)]+=n + print('dispatch',Counter(disp.values()),{t:p for t,p in disp.items() if p!='jp8'},'remainders',dict(rem)) + pickle.dump(dict(groups=x,disp=disp),open(OUT+'.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/dbg.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/dbg.py new file mode 100644 index 0000000000..d792c348a9 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/dbg.py @@ -0,0 +1,8 @@ +import sys +sys.argv=['aug.py','5','-','/dev/null'] +src=open('aug.py').read().split("npath=0")[0] +exec(src) +s=(2,2,43);print(at[s],[wid(*r) for r in at[s].values()],bal[s]) +for r in at[s].values():print(r,wid(*r),len(idx.get((r[0],wid(*r)-1),()))) +Y,prev=bfs(s);print(Y,len(prev)) +print([ (o,bal[o],[ (r,wid(*r)) for r in at[o].values()]) for o in snk]) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/disp.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/disp.py new file mode 100644 index 0000000000..32710655d3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/disp.py @@ -0,0 +1,18 @@ +import json,sys +from collections import Counter +from fractions import Fraction as Q +from price import price +P=json.load(open('/Users/joelpulikkan/imb-r2/research/pr638-improvement-20261013/witness638/data/PLAN.json')) +reg=P['provider_registry'] +W={p:sum(reg[p]['widths']) for p in reg} +print(W) +base=Counter({int(k):v for k,v in P['profile'].items()}) +print('base b',float(price(base))) +for nck in range(0,6): + prof=Counter() + for p,n in (('jp8',72-nck),('ck',nck)): + for r,c in reg[p]['actual_paid_profile'].items(): prof[int(r)]+=n*c + idle=107028+(W['jp8']-W['ck'])*(nck-1) + prof[48]+=idle//48 + if idle%48: prof[idle%48]+=1 + b=price(prof);print(nck,idle,float(b)) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/evalst.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/evalst.py new file mode 100644 index 0000000000..a3eaaa9814 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/evalst.py @@ -0,0 +1,20 @@ +import sys,json,pickle +from load import * +from price import price +st=pickle.load(open(sys.argv[1],'rb'));T5=int(sys.argv[3]) if len(sys.argv)>3 else 5 +jw=json.load(open(sys.argv[2])) if len(sys.argv)>2 and sys.argv[2]!='-' else reg['jp8']['widths'] +prof5=json.load(open(sys.argv[4])) if len(sys.argv)>4 else reg['jp8']['actual_paid_profile'] +W=P['windows'];fill=Counter() +for (t,h),o in st.items(): + fill[o]+= jw[h] if t==T5 else reg[P['provider_calls'][t]['provider']]['widths'][h] +idle=Counter() +for k,(c,ts) in W.items(): + a,b=map(int,k.split(',')) + for p in range(112): + f=fill.get((a,b,p),0);assert f<=c + if f0 and 1<=a1:m.AddAtMostOne(v) +s=cp_model.CpSolver();s.parameters.max_time_in_seconds=TL;s.parameters.num_workers=10 +st=s.Solve(m);print('status',s.StatusName(st)) +if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + assign=[(freer[i],O[j]) for (i,j),v in x.items() if s.Value(v)] + out=[dict(time=t,helper=h,width=w,owner=list(o)) for (t,h,w),o in assign]+[dict(time=t,helper=h,width=w,owner=list(o)) for t,h,w,o in fixedrows] + json.dump(out,open(f'sol-{seed}.json','w')) + print('saved',len(out)) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns2.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns2.py new file mode 100644 index 0000000000..a0cfec0962 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns2.py @@ -0,0 +1,85 @@ +"""LNS: dispatch all-jp8 (call 5 ck->jp8); minimise #partial owners with CP-SAT neighbourhoods.""" +import sys,random,json,gzip,pickle,os +from load import * +from ortools.sat.python import cp_model +seed=int(sys.argv[1]);ITERS=int(sys.argv[2]);NB=int(sys.argv[3]);TL=float(sys.argv[4]);ST=sys.argv[5] +random.seed(seed) +W=P['windows'] +CAP={};TIM={} +allown=[] +for k,(c,ts) in W.items(): + o,ch=map(int,k.split(',')) + for p in range(112): allown.append((o,ch,p));CAP[o,ch,p]=c;TIM[o,ch,p]=set(ts) +FAM={o:fam(*o) for o in allown} +jw=reg['jp8']['widths'] +if os.path.exists(ST): + asg=pickle.load(open(ST,'rb')) +else: + asg={} # (t,h) -> owner + byw=defaultdict(list) + for h in range(492):byw[jw[h]].append(h) + for r in sorted(rows,key=lambda r:(r['time'],r['helper'])): + o=(r['owner_replica'],r['child'],r['port']) + if r['time']!=5: asg[r['time'],r['helper']]=o + elif byw[r['width']]: asg[5,byw[r['width']].pop()]=o + for w,v in byw.items(): + for h in v: asg[5,h]=None +def width(role):return reg['jp8']['widths'][role[1]] if role[0]==5 else reg[P['provider_calls'][role[0]]['provider']]['widths'][role[1]] +def state(): + occ=defaultdict(list) + for r,o in asg.items(): + if o is not None:occ[o].append(r) + return occ +def score(occ): + fill={o:sum(width(r) for r in v) for o,v in occ.items()} + part=[o for o,f in fill.items() if f1:m.AddAtMostOne(v) + m.Minimize(sum(pen)) + s=cp_model.CpSolver();s.parameters.max_time_in_seconds=TL;s.parameters.num_workers=10 + st=s.Solve(m) + if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + new=dict(asg) + for (i,j),v in x.items(): + if s.Value(v):new[freer[i]]=O[j] + old=(un,len(part)) + asg_bak=asg;asg=new + un2,part2,_=score(state()) + if (un2,len(part2))<=old: pickle.dump(asg,open(ST,'wb')) + else: asg=asg_bak + print(it,'owners',len(O),'roles',len(freer),s.StatusName(st),'obj',s.ObjectiveValue(),'->',un2,len(part2),flush=True) + else: print(it,s.StatusName(st),flush=True) +occ=state();un,part,fill=score(occ) +print('final',un,len(part),sorted(Counter(CAP[o]-fill[o] for o in part).items())) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns3.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns3.py new file mode 100644 index 0000000000..5a4f83c821 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns3.py @@ -0,0 +1,86 @@ +"""LNS: dispatch all-jp8 (call 5 ck->jp8); minimise #partial owners with CP-SAT neighbourhoods.""" +import sys,random,json,gzip,pickle,os +from load import * +from ortools.sat.python import cp_model +seed=int(sys.argv[1]);ITERS=int(sys.argv[2]);NB=int(sys.argv[3]);TL=float(sys.argv[4]);ST=sys.argv[5] +random.seed(seed) +W=P['windows'] +CAP={};TIM={} +allown=[] +for k,(c,ts) in W.items(): + o,ch=map(int,k.split(',')) + for p in range(112): allown.append((o,ch,p));CAP[o,ch,p]=c;TIM[o,ch,p]=set(ts) +FAM={o:fam(*o) for o in allown} +jw=json.load(open(sys.argv[6])) if len(sys.argv)>6 else reg['jp8']['widths'] +if os.path.exists(ST): + asg=pickle.load(open(ST,'rb')) +else: + asg={} # (t,h) -> owner + byw=defaultdict(list) + for h in range(492):byw[jw[h]].append(h) + for r in sorted(rows,key=lambda r:(r['time'],r['helper'])): + o=(r['owner_replica'],r['child'],r['port']) + if r['time']!=5: asg[r['time'],r['helper']]=o + elif byw[r['width']]: asg[5,byw[r['width']].pop()]=o + for w,v in byw.items(): + for h in v: asg[5,h]=None +def width(role):return jw[role[1]] if role[0]==5 else reg[P['provider_calls'][role[0]]['provider']]['widths'][role[1]] +def state(): + occ=defaultdict(list) + for r,o in asg.items(): + if o is not None:occ[o].append(r) + return occ +def score(occ): + fill={o:sum(width(r) for r in v) for o,v in occ.items()} + part=[o for o in allown if fill.get(o,0)0 or CAP[o]<48)] + un=sum(1 for o in asg.values() if o is None) + return un,part,fill +occ=state();un,part,fill=score(occ) +print('start unassigned',un,'partial',len(part),flush=True) +for it in range(ITERS): + occ=state();un,part,fill=score(occ) + # neighbourhood: partial owners + owners of unassigned-time + random others sharing times + O=set(part) + unr=[r for r,o in asg.items() if o is None] + seedt={r[0] for r in unr}|{t for o in part for t in TIM[o]} + cand=[o for o in allown if TIM[o]&seedt and o not in O] + full=[o for o in cand if o in occ];emp=[o for o in cand if o not in occ] + O|=set(random.sample(full,min(NB,len(full)))) + O|=set(random.sample(emp,min(NB//6,len(emp)))) + O=sorted(O);Oi={o:j for j,o in enumerate(O)} + freer=[r for r,o in asg.items() if o is None or o in Oi] + used=set((r[0],FAM[o]) for r,o in asg.items() if o is not None and o not in Oi) + m=cp_model.CpModel();x={} + byown=defaultdict(list);byfam=defaultdict(list) + for i,r in enumerate(freer): + c=[] + for j,o in enumerate(O): + if r[0] in TIM[o] and (r[0],FAM[o]) not in used and width(r)<=CAP[o]: + v=m.NewBoolVar('');x[i,j]=v;c.append(v);byown[j].append((i,v));byfam[r[0],FAM[o]].append(v) + if asg[r]==o:m.AddHint(v,1) + m.AddExactlyOne(c) + pen=[] + for j,o in enumerate(O): + s=sum(width(freer[i])*v for i,v in byown[j]) + f=m.NewBoolVar('');e=m.NewBoolVar('') + m.Add(s<=CAP[o]);m.Add(s==CAP[o]).OnlyEnforceIf(f);m.Add(s==0).OnlyEnforceIf(e) + if CAP[o]<48: m.Add(e==0) + p=m.NewBoolVar('');m.AddBoolOr([f,e,p]);pen.append(p) + for k,v in byfam.items(): + if len(v)>1:m.AddAtMostOne(v) + m.Minimize(sum(pen)) + s=cp_model.CpSolver();s.parameters.max_time_in_seconds=TL;s.parameters.num_workers=10 + st=s.Solve(m) + if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + new=dict(asg) + for (i,j),v in x.items(): + if s.Value(v):new[freer[i]]=O[j] + old=(un,len(part)) + asg_bak=asg;asg=new + un2,part2,_=score(state()) + if (un2,len(part2))<=old: pickle.dump(asg,open(ST,'wb')) + else: asg=asg_bak + print(it,'owners',len(O),'roles',len(freer),s.StatusName(st),'obj',s.ObjectiveValue(),'->',un2,len(part2),flush=True) + else: print(it,s.StatusName(st),flush=True) +occ=state();un,part,fill=score(occ) +print('final',un,len(part),sorted(Counter(CAP[o]-fill.get(o,0) for o in part).items())) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns4.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns4.py new file mode 100644 index 0000000000..19edbfcaeb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/lns4.py @@ -0,0 +1,92 @@ +"""LNS: dispatch all-jp8 (call 5 ck->jp8); minimise #partial owners with CP-SAT neighbourhoods.""" +import sys,random,json,gzip,pickle,os +from load import * +from ortools.sat.python import cp_model +seed=int(sys.argv[1]);ITERS=int(sys.argv[2]);NB=int(sys.argv[3]);TL=float(sys.argv[4]);ST=sys.argv[5] +random.seed(seed) +W=P['windows'] +CAP={};TIM={} +allown=[] +for k,(c,ts) in W.items(): + o,ch=map(int,k.split(',')) + for p in range(112): allown.append((o,ch,p));CAP[o,ch,p]=c;TIM[o,ch,p]=set(ts) +FAM={o:fam(*o) for o in allown} +jw=json.load(open(sys.argv[6])) if len(sys.argv)>6 else reg['jp8']['widths'] +if os.path.exists(ST): + asg=pickle.load(open(ST,'rb')) +else: + asg={} # (t,h) -> owner + byw=defaultdict(list) + for h in range(492):byw[jw[h]].append(h) + for r in sorted(rows,key=lambda r:(r['time'],r['helper'])): + o=(r['owner_replica'],r['child'],r['port']) + if r['time']!=5: asg[r['time'],r['helper']]=o + elif byw[r['width']]: asg[5,byw[r['width']].pop()]=o + for w,v in byw.items(): + for h in v: asg[5,h]=None +def width(role):return jw[role[1]] if role[0]==5 else reg[P['provider_calls'][role[0]]['provider']]['widths'][role[1]] +def state(): + occ=defaultdict(list) + for r,o in asg.items(): + if o is not None:occ[o].append(r) + return occ +def score(occ): + fill={o:sum(width(r) for r in v) for o,v in occ.items()} + part=[o for o in allown if fill.get(o,0)0 or CAP[o]<48)] + un=sum(1 for o in asg.values() if o is None) + return un,part,fill +occ=state();un,part,fill=score(occ) +print('start unassigned',un,'partial',len(part),flush=True) +for it in range(ITERS): + occ=state();un,part,fill=score(occ) + # neighbourhood: partial owners + owners of unassigned-time + random others sharing times + O=set(part) + unr=[r for r,o in asg.items() if o is None] + seedt={r[0] for r in unr}|{t for o in part for t in TIM[o]} + # structured: owners holding roles at the same times as problem owners' roles + bytime=defaultdict(list) + for r,o in asg.items(): + if o is not None: bytime[r[0]].append(o) + probt=[r[0] for o in part for r in occ[o]]+[r[0] for r in unr] + per=max(1,NB//max(1,len(probt))) + for t in probt: + O|=set(random.sample(bytime[t],min(per,len(bytime[t])))) + emp=[o for o in allown if o not in occ and CAP[o]==48 and TIM[o]&seedt] + O|=set(random.sample(emp,min(NB//10,len(emp)))) + O=sorted(O);Oi={o:j for j,o in enumerate(O)} + freer=[r for r,o in asg.items() if o is None or o in Oi] + used=set((r[0],FAM[o]) for r,o in asg.items() if o is not None and o not in Oi) + m=cp_model.CpModel();x={} + byown=defaultdict(list);byfam=defaultdict(list) + for i,r in enumerate(freer): + c=[] + for j,o in enumerate(O): + if r[0] in TIM[o] and (r[0],FAM[o]) not in used and width(r)<=CAP[o]: + v=m.NewBoolVar('');x[i,j]=v;c.append(v);byown[j].append((i,v));byfam[r[0],FAM[o]].append(v) + if asg[r]==o:m.AddHint(v,1) + m.AddExactlyOne(c) + pen=[] + for j,o in enumerate(O): + s=sum(width(freer[i])*v for i,v in byown[j]) + f=m.NewBoolVar('');e=m.NewBoolVar('') + m.Add(s<=CAP[o]);m.Add(s==CAP[o]).OnlyEnforceIf(f);m.Add(s==0).OnlyEnforceIf(e) + if CAP[o]<48: m.Add(e==0) + p=m.NewBoolVar('');m.AddBoolOr([f,e,p]);pen.append(p) + for k,v in byfam.items(): + if len(v)>1:m.AddAtMostOne(v) + m.Minimize(sum(pen)) + s=cp_model.CpSolver();s.parameters.max_time_in_seconds=TL;s.parameters.num_workers=10 + st=s.Solve(m) + if st in (cp_model.OPTIMAL,cp_model.FEASIBLE): + new=dict(asg) + for (i,j),v in x.items(): + if s.Value(v):new[freer[i]]=O[j] + old=(un,len(part)) + asg_bak=asg;asg=new + un2,part2,_=score(state()) + if (un2,len(part2))<=old: pickle.dump(asg,open(ST,'wb')) + else: asg=asg_bak + print(it,'owners',len(O),'roles',len(freer),s.StatusName(st),'obj',s.ObjectiveValue(),'->',un2,len(part2),flush=True) + else: print(it,s.StatusName(st),flush=True) +occ=state();un,part,fill=score(occ) +print('final',un,len(part),sorted(Counter(CAP[o]-fill.get(o,0) for o in part).items())) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/load.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/load.py new file mode 100644 index 0000000000..393c1397ae --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/load.py @@ -0,0 +1,7 @@ +import json,gzip +from collections import Counter,defaultdict +D='/Users/joelpulikkan/imb-r2/research/pr638-improvement-20261013/witness638/data' +P=json.load(open(D+'/PLAN.json')) +rows=json.loads(gzip.decompress(open(D+'/OWNERS.json.gz','rb').read())) +reg=P['provider_registry'] +def fam(owner,child,port): return owner*896+(2*P['pairs'][str(child)]+P['sides'][str(child)])*112+port diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mip.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mip.py new file mode 100644 index 0000000000..423b06ee72 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mip.py @@ -0,0 +1,90 @@ +"""free-dispatch exact-window pattern MIP over {jp8(u4), st2, ck}; >=MINALT non-jp8 calls; Hall masks; V639 values.""" +import json,sys,time,pickle +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import milp,LinearConstraint,Bounds +from scipy.sparse import coo_matrix +from load import P,reg +V={int(k):v for k,v in json.load(open('V639.json')).items()};V[0]=0.0 +st2=json.load(open('st2-PLAN.json'))['provider_registry']['jp8'] +REG={'jp8':reg['jp8'],'ck':reg['ck'],'st2':st2} +provs=sys.argv[1].split(',');TIGHT=int(sys.argv[2]);SECS=float(sys.argv[3]);OUT=sys.argv[4];MINALT=int(sys.argv[5]);LPONLY=len(sys.argv)>6 and sys.argv[6]=='lp' +# check sibling families have disjoint times +fams=defaultdict(list) +for k,(c,ts) in P['windows'].items(): + o,ch=map(int,k.split(','));fams[o,P['pairs'][str(ch)],P['sides'][str(ch)]].append(set(ts)) +for f,L in fams.items(): + for i in range(len(L)): + for j in range(i):assert not L[i]&L[j],f +inv={p:Counter(REG[p]['widths']) for p in provs} +val={p:sum(V[int(r)]*c for r,c in REG[p]['actual_paid_profile'].items()) for p in provs} +print('vals',{p:val[p]-val['jp8'] for p in provs}) +WID=[4,5,7,8,9] +def pats(cap,L): + out=[] + def rec(pre,i,tot): + r=cap-tot + if cap<48 or r<=TIGHT or r==48: out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)>b&1} + req=tuple(i for (tt,w),i in fl.items() if tt==t and w in cols) + pres=tuple(j for j,c in zip(pv,cnts) if any(c[w] for w in cols)) + if req and (req,pres) not in seen: + seen.add((req,pres));row={i:1 for i in req} + for j in pres:row[j]=row.get(j,0)-1 + con(row,-np.inf,0) + for w in WID: + row={i:1 for (t,ww),i in fl.items() if ww==w} + for j,c in zip(pv,cnts): + if c[w]:row[j]=row.get(j,0)-c[w] + if row:con(row,0,0) + groups[key]=dict(cap=cap,times=times,ps=ps,pv=pv,fl=fl) +for t in range(72): + for w in WID: + row=dict(invrow[t,w]) + for p in provs: + if inv[p][w]:row[sel[t,p]]=-inv[p][w] + con(row,0,0) +rr,cc,vv=[],[],[] +for i,row in enumerate(R): + for j,x in row.items():rr.append(i);cc.append(j);vv.append(x) +A=coo_matrix((vv,(rr,cc)),shape=(len(R),len(ub))).tocsr() +print('vars',len(ub),'cons',len(R),flush=True) +base=71*val['jp8']+val['ck']+V[36]+2229*V[48] if 'ck' in provs else None +t0=time.time() +res=milp(np.array(cost),integrality=np.zeros(len(ub)) if LPONLY else np.ones(len(ub)),bounds=Bounds(np.zeros(len(ub)),np.array(ub)),constraints=LinearConstraint(A,lo,hi),options={'time_limit':SECS,'mip_rel_gap':1e-12,'disp':False}) +print(res.status,res.message,'obj',-res.fun if res.x is not None else None,'base639',base,'gain(1e-9b)',(-res.fun-base) if res.x is not None and base else None,'secs',time.time()-t0,flush=True) +if res.x is None:sys.exit() +if LPONLY: + d=Counter() + for (t,p),i in sel.items():d[p]+=res.x[i] + print('lp dispatch',dict(d));sys.exit() +x=np.round(res.x).astype(int) +disp={t:p for (t,p),i in sel.items() if x[i]} +rem=Counter() +for key,g in groups.items(): + for q,i in zip(g['ps'],g['pv']): + if x[i] and g['cap']-sum(q):rem[g['cap']-sum(q)]+=x[i] +print('dispatch',Counter(disp.values()),{t:p for t,p in disp.items() if p!='jp8'},'remainders',dict(rem)) +pickle.dump(dict(groups=groups,x=x,disp=disp,REG=REG),open(OUT+'.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mkV.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mkV.py new file mode 100644 index 0000000000..1e604f98ce --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/mkV.py @@ -0,0 +1,11 @@ +from price import price +from load import * +import json +base=Counter({int(k):v for k,v in P['profile'].items()}) +b0=price(base) +V={} +for r in range(1,49): + p=Counter(base);p[r]+=1;p[1]-=r + V[r]=float((price(p)-b0)*10**9) +print({r:round(v,4) for r,v in V.items()}) +json.dump(V,open('V639.json','w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/price.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/price.py new file mode 100644 index 0000000000..cf550736a3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/price.py @@ -0,0 +1,7 @@ +import sys,json,importlib.util,time +from fractions import Fraction as Q +S='/Users/joelpulikkan/imb-src/pr568/research/complex-multicold' +s=importlib.util.spec_from_file_location('moment',S+'/engines/moment.py');M=importlib.util.module_from_spec(s);s.loader.exec_module(M) +def price(H,live=7168):return Q(M.certify({int(k):v for k,v in H.items()},73,live,False)['lower']) +if __name__=='__main__': + H=json.loads(sys.argv[1]);t=time.time();b=price(H);print(float(b),b,time.time()-t) diff --git a/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/submip.py b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/submip.py new file mode 100644 index 0000000000..31bcacd1bb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r2-dispatch/submip.py @@ -0,0 +1,104 @@ +"""Window-subset exact repack: free every role hosted in windows S (plus unplaced roles), re-solve S with the +aggregate pattern MIP (Hall masks; cap<48 exact full; cap48 full/empty or remainder<=TIGHT), expand literally.""" +import sys,json,pickle,random,time +import numpy as np +from collections import Counter,defaultdict +from scipy.optimize import milp,LinearConstraint,Bounds +from scipy.sparse import coo_matrix +from load import P,reg +sys.path.insert(0,'.') +V={int(k):v for k,v in json.load(open('V639.json')).items()};V[0]=0.0 +WID=[4,5,7,8,9] +def build_state(T,neww): + import gzip + rows=json.loads(gzip.decompress(open('/Users/joelpulikkan/imb-r2/research/pr638-improvement-20261013/witness638/data/OWNERS.json.gz','rb').read())) + own={};pool=defaultdict(list) + for h in range(492):pool[neww[h]].append(h) + for r in sorted(rows,key=lambda r:(r['time'],r['helper'])): + o=(r['owner_replica'],r['child'],r['port']) + if r['time']!=T:own[r['time'],r['helper']]=o + elif pool[r['width']]:own[T,pool[r['width']].pop()]=o + for w,v in pool.items(): + for h in v:own[T,h]=None + return own +def solve(own,widthof,S,TIGHT,SECS,seed=0): + W=P['windows'] + free=[r for r,o in own.items() if o is None or f'{o[0]},{o[1]}' in S] + dem=Counter((r[0],widthof(r)) for r in free) + ub=[];cost=[];R=[];lo=[];hi=[] + def var(c,u=np.inf):ub.append(u);cost.append(-c);return len(ub)-1 + def con(row,l,h):R.append(row);lo.append(l);hi.append(h) + groups={};invrow=defaultdict(dict) + for key in S: + cap,times=W[key] + def pats(cap,L): + out=[] + def rec(pre,i,tot): + r=cap-tot + if r==0 or (cap==48 and (r<=TIGHT or r==48)): out.append(tuple(pre)) + for j in range(i,len(WID)): + w=WID[j] + if tot+w<=cap and len(pre)>b&1} + req=tuple(i for (tt,w),i in fl.items() if tt==t and w in cols) + pres=tuple(j for j,c in zip(pv,cnts) if any(c[w] for w in cols)) + if req and (req,pres) not in seen: + seen.add((req,pres));row={i:1 for i in req} + for j in pres:row[j]=row.get(j,0)-1 + con(row,-np.inf,0) + for w in WID: + row={i:1 for (t,ww),i in fl.items() if ww==w} + for j,c in zip(pv,cnts): + if c[w]:row[j]=row.get(j,0)-c[w] + if row:con(row,0,0) + groups[key]=dict(cap=cap,times=times,ps=ps,pv=pv,fl=fl) + for (t,w),n in dem.items(): + if not invrow[t,w]:print('no window for',t,w);return None + con(dict(invrow[t,w]),n,n) + rr,cc,vv=[],[],[] + for i,row in enumerate(R): + for j,x in row.items():rr.append(i);cc.append(j);vv.append(x) + A=coo_matrix((vv,(rr,cc)),shape=(len(R),len(ub))).tocsr() + t0=time.time() + res=milp(np.array(cost),integrality=np.ones(len(ub)),bounds=Bounds(np.zeros(len(ub)),np.array(ub)),constraints=LinearConstraint(A,lo,hi),options={'time_limit':SECS,'mip_rel_gap':1e-9}) + print('submip',len(S),'windows vars',len(ub),'status',res.status,res.message[:60],'obj',None if res.x is None else -res.fun,'secs',round(time.time()-t0,1),flush=True) + if res.x is None:return None + x=np.round(res.x).astype(int) + rem=Counter() + for key,g in groups.items(): + for q,i in zip(g['ps'],g['pv']): + if x[i] and g['cap']-sum(q) and not (g['cap']==48 and not q):rem[g['cap']-sum(q)]+=x[i];print(' win',key,q,x[i]) + print(' remainders',dict(rem)) + # expand + exec(open('expand_fn.py').read(),globals()) + pools=defaultdict(list) + for r in free:pools[r[0],widthof(r)].append(r) + new=dict(own) + for gi,(key,g) in enumerate(sorted(groups.items())): + plist=[q for q,i in zip(g['ps'],g['pv']) for _ in range(x[i])] + flows={k:x[i] for k,i in g['fl'].items() if x[i]} + slots=expand(plist,flows,gi) + a,b=map(int,key.split(',')) + for port,s in enumerate(slots): + for t,wd in s:new[pools[t,wd].pop()]=(a,b,port) + assert not any(pools.values()) + return new,rem +if __name__=='__main__': + T=int(sys.argv[1]);neww=json.load(open(sys.argv[2])) if sys.argv[2]!='-' else reg['jp8']['widths'] + S=sys.argv[3].split(';');TIGHT=int(sys.argv[4]);SECS=float(sys.argv[5]);OUT=sys.argv[6] + own=build_state(T,neww) + calls=[c['provider'] for c in P['provider_calls']] + widthof=lambda r:neww[r[1]] if r[0]==T else reg[calls[r[0]]]['widths'][r[1]] + out=solve(own,widthof,S,TIGHT,SECS) + if out:pickle.dump(out[0],open(OUT,'wb'));print('saved',OUT) From 8f0150e0d0a8a02370907be35cccd3ca69cdd941 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 10:42:35 -0400 Subject: [PATCH 05/17] Add Lorentzian puncture/helper notes and the no-new-kappa triage Co-Authored-By: Claude Opus 5.5 --- .../README.md | 19 ++ .../s4-lorentzian-helpers/README.md | 277 ++++++++++++++++++ .../run1-logs/clique_orbits11.json | 92 ++++++ .../run1-logs/model3.json | 48 +++ .../s4-lorentzian-helpers/scripts/anneal.py | 80 +++++ .../scripts/cellcover.py | 50 ++++ .../s4-lorentzian-helpers/scripts/census.py | 25 ++ .../s4-lorentzian-helpers/scripts/check.py | 27 ++ .../scripts/clique_orbits.py | 76 +++++ .../s4-lorentzian-helpers/scripts/cover.py | 147 ++++++++++ .../scripts/est_model.py | 135 +++++++++ .../scripts/example_pairlabel.py | 13 + .../s4-lorentzian-helpers/scripts/frames.py | 89 ++++++ .../s4-lorentzian-helpers/scripts/model3.py | 41 +++ .../scripts/packbases.py | 67 +++++ .../scripts/partition.py | 86 ++++++ .../s4-lorentzian-helpers/scripts/punct.py | 107 +++++++ .../scripts/punct_sat.py | 75 +++++ .../s4-lorentzian-helpers/scripts/rank.py | 115 ++++++++ .../s4-lorentzian-helpers/scripts/twocover.py | 39 +++ .../s4-lorentzian-helpers/scripts/units.py | 210 +++++++++++++ .../s4-lorentzian-helpers/verify.sh | 31 ++ 22 files changed, 1849 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/clique_orbits11.json create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.json create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/anneal.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cellcover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/census.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/check.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/clique_orbits.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/est_model.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/example_pairlabel.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/frames.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/model3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/packbases.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/partition.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct_sat.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/rank.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/twocover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/units.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index fe1c5e4966..a447aba0e7 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -52,3 +52,22 @@ On the bit side ranks are counted over Q, so a K-line unit is a rank-k unit with - Exact prices at concentrated idle (PR568 `moment.certify`): #639 b = 0.0016391439889600812; 72×jp8 b = 0.0016392558 (not admissible, since the global controls need two dispatched providers); 71 jp8 + 1 st2 (#635's jp8 word as the second provider) b = 0.0016392519, i.e. **+1.08e-7 b** from removing the forced ck call. #643 (jeremykothe2) later realised this twin dispatch independently. - Exact sub-MIP obstruction: with jp8 at t = 5 and every window not touching t ≤ 7 held fixed, the optimum leaves 64 idle-3 owners, so no perfect packing exists in that neighbourhood. Once ck's 9+7 pairs are gone, the early windows are the tight ones. - Mass-neutral V pricing per end per call (V639.json): u4-type premix-only dim-8 ends ({1:+2, 2:−1, 4:+1, 5:−1}, width −1) ≈ −0.025, essentially neutral, consistent with #637's 23 LP-negative candidates. **Dim-7 factored end pairs (#627 type) ≈ +2.25 per pair: the positive direction.** This should carry over to the gauge44 lineage (#645 onward) and was not searched here. + +## 7. I_{11,1} / I_{13,1}: punctures and helper structure (`s4-lorentzian-helpers/`) +Status: README and scripts are complete, and run1 logs are included. `expected/` is **not yet frozen**, and one SAT case (clique orbit 0 at k = 19) is still running. +- **Punctures never help the centre decoder** (exact matroid-partition certificate). The 220 lines of I_{11,1} split into 22 independent sets, 2 inside each centre reader set, so the cost is ≥ |L|/2 for every live set L. For I_{13,1}, three copies of the lines split into 91 sets, giving ≥ (3/7)|L|. Every label decoder satisfies x ≥ 1/2 for |P| ≤ 8 (x ≥ 3/7 for |P| ≤ 10 on I_{13,1}). +- **Minrank stays 11** after ≤ 19 punctures for 4 of the 5 maximum-clique S_11-orbits (SAT, no proof logs; orbit 0 pending). The 38,786 maximum cliques fall into 5 orbits, and a union bound shows any ≤ 21 punctures leave one alive. +- **Helpers.** E8's R = 439 is 240 carriers (3 branches per source, forced by E7 being a 4-design) + 180 cell registers + 9 totals + 10 puncture carriers. In I_{11,1}: + - triple sources also need ≥ 3 branches in any frame design (exact quadratic-form nullspace); + - the cell cover number is 3; + - 20,790 good 4-frames exist, but **no partition into good 4-frames exists**: every good frame holds 0 or 2 pairs, and there are 55 pairs. + - Needers cover 56–70 lines, against 30 in E8. + - The transferred cell skeleton has 787 helpers (3.58 per port), or 732 (3.33 per port) with 2-branch pairs. The calibrated model gives a_est 1.58–1.81e-3, against E8's 1.806e-3: no robust gain without a new middle network. + +## 8. Why "no new kappa" construction savings cannot move κ (R5 triage, 2026-10-13) +κ depends only on (i) the complex paid rank profile H = calls × local_paid + idle and the live stock W (b is the moment root of Σ_r r·n_r·(73/r)^b = 73W), and (ii) the bit profile (c). Primitive, ADD and XOR counts enter only finite admission checks, all with large slack: local_primitive_bound < 2^48, prefix < payload, row < 20161, and so on. The outer row gap also uses the fixed 20161. +- #883, #916, #901, #917, #919, #922, #882, #932, #937 and #945 each leave the paid children, helper widths and phase events unchanged, so the gain is exactly 0. Several act on historical CK/JP8/X4 words rather than the selected U8 word. +- Bit savings (#914, #915, #908, #904, #891, #713, #884, #936, #941) leave c unchanged. c = 0.0017334 > b ≈ 0.0016647, so the bit side doesn't bind anyway. +- #892 (rank-8 coded helper) does change the local profile, but it is excluded by its own packing obstruction on the old word. +- The only path from these savings to κ is a lower local prefix bound, which allows more replicas. Raising the charged payload does that directly: row slack 3,692 allows payload up to ~2,350, which is what T37 (#942) uses. +- **Next real lever:** dim-7 factored end pairs (#627 type, local {1:+2, 2:+2, 5:−2}, width −4) on the U8 word. Priced at ideal packing, each is worth about **+1.7e-7 b per pair per call**, about 60× the T37 gain. diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/README.md b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/README.md new file mode 100644 index 0000000000..39d4367cbe --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/README.md @@ -0,0 +1,277 @@ +# Lorentzian unit I_{11,1}: punctures, E8 skeleton features, and the helper budget (no κ claim) + +This note follows up S3 (`e8-decoder-floors-and-cold14-notes/s3-unit-families`). That package found I_{11,1} to be the best non-E8 unit lead: + +- v 220, window h 11, minrank 11; +- label decoder cost 110, so x = 1/2; +- certified ceiling X* 7.1715·10⁻³; +- but the helper-adjusted model puts it below E8 unless it reaches ≤ 2.66 (abs) / 3.34 (rel) helpers per port. E8 ships 3.95. + +This package asks three questions: + +1. Can punctures (the analogue of E8's 120 → 111) lower x or raise X*? +2. Which E8 geometry sets E8's helper count, and does I_{11,1} have it? +3. What does the calibrated model say for the best punctured unit? + +**Answers, in short:** + +1. **No improving puncture.** + - For the centre (label) decoder, no puncture of any size improves x. This is an exact certificate for I_{11,1} and I_{13,1}. + - For every label decoder, the same holds up to 8 (I_{11,1}) or 10 (I_{13,1}) punctures. + - No puncture of ≤ 19 lines lowers the minrank to 10 (SAT). + - The E8 mechanism exists here: puncturing a line makes its own label functional one unit cheaper. But that functional starts at h, not h − 1, so it only ties the centres. +2. **The E8 skeleton features carry over, but the count lands at or above break-even.** + - Carried over: cell cover number 3, orthogonal 4-frames with F₂ parity, and a "design" obstruction for triple sources. + - Not carried over: a partition into such frames is impossible (55 pairs is odd), and each needer must sum 56–70 lines against E8's 30. + - The transferred skeleton counts 3.33–3.58 helpers per port. + - Within the cell architecture, carriers alone are exactly 2 per port. Reaching ≤ 3 would need a middle network that costs less than ⅔ of E8's per cell while carrying about twice E8's demand per needer (argued; not a theorem over all designs). +3. **The model gives no gain over E8.** The best unit is the unpunctured one. At the transferred-skeleton intensity the model gives 1.58·10⁻³ (abs) / 1.73·10⁻³ (rel), against E8's 1.806·10⁻³. Only the optimistic variant (pair sources on two non-cell branches, rel scaling) touches E8: 1.811·10⁻³, which is +0.3% and inside model error. + +Status labels: + +- **checked**: `verify.sh` recomputes it. Exact rational arithmetic, or a CP-SAT/SAT optimum or UNSAT as stated. +- **argued**: a proof written here. +- **model**: an estimate, not a bound. + +SAT answers come from CaDiCaL 1.5.3 (python-sat) without proof logs. CP-SAT optima are reported only when the status is OPTIMAL. + +```sh +PY=/path/to/python3.11 sh verify.sh /path/to/integer-mult-bounds-main /fresh/out # ~30-40 min, 10 cores +``` + +Requirements: numpy, sympy, networkx, python-sat, ortools. The scripts import #595's `skel.py`/`cert.py` (main, `research/format-limits/scripts`) and S3's `units.py`, `est_model.py` and `rank.py`, which are copied unchanged into `scripts/`. `check.py` compares every log with `expected/`; it ignores run times and allows a relative difference of 1e-9 on model floats. + +Conventions follow #595 §A and S3: + +- **Pattern.** The decoder pattern is B_TT = 1, with B_TS = 0 where G_TS ≠ 0. +- **Labels.** The labels are S3's I_{n,1} labels (a 3-set, or the complement of a pair). +- **Reader set.** R(p) = {t : ⟨u_t, p⟩ = 1}. +- **Star cost.** A label total p costs dim_Q span(R(p) ∩ live). +- **Optimal label decoder.** It is the minimum-weight basis of the live label space (matroid greedy, exact). + +## 1. Reader census (`census.py`, checked) + +| n | functional weight k | readers | span | Gram rank | +|---|---|---|---|---| +| 11 | 1 (the 11 centres e_i) | 90 (45 triples ∋ i, 45 pairs ∌ i) | **10** | 10 | +| 11 | 2 … 11 | 90 … 220 | **11** | 11 | +| 13 | 1 (centres) | 132 | **12** | 12 | +| 13 | 2 … 13 | 132 … 364 | **13** | 13 | + +Only the centres span h − 1, so the unpunctured label decoder is forced to be the centre decoder: 11 × 10 = 110 and 13 × 12 = 156. The Theorem C pairing floor (#446) of every centre equals its span, so on the unpunctured unit Theorem C = star. + +## 2. Punctures (Q1) + +### 2.1 The centre decoder never improves (`partition.py`, checked certificate + argued) + +**Theorem P1.** For every live set L of I_{11,1}: Σ_i rank(R(e_i) ∩ L) ≥ |L|/2. For I_{13,1}: Σ_i rank(R(e_i) ∩ L) ≥ (3/7)|L|. + +**Proof (I_{11,1}).** + +- `partition.py` partitions the 220 lines into 22 sets I_{i,1}, I_{i,2}, each linearly independent and contained in R(e_i). Exact ranks are verified; every set has size 10. +- Then 2·rank(R(e_i) ∩ L) ≥ |L ∩ I_{i,1}| + |L ∩ I_{i,2}|. Summing over i gives the claim. + +**Proof (I_{13,1}).** Take three copies of each of the 364 lines. They split into 91 independent sets, 7 inside each R(e_i), each of size 12. ∎ + +**Consequence.** For the centre decoder, x(P) ≥ 1/2 (resp. ≥ 3/7) for every puncture P, so the unpunctured ratio is never beaten. + +**Scope.** + +- Costs are span ranks. This is the right measure for live sets with a nondegenerate span. +- A degenerate live set (a radical appears, e.g. the E8 sub-configuration) is dominated by its quotient unit (#595 Lemma Q). It is a different unit with a smaller window; see §2.4. + +### 2.2 All label decoders, small punctures (`packbases.py`, checked + argued) + +`packbases.py` treats each functional orbit (Hamming weight k, under S_n). It strips coloops (lines outside the span of the rest of R(p)) and packs q disjoint bases into the rest. The packings are verified exactly. + +**The E8 mechanism, here.** For k = 3 (triple labels) and k = n − 2 (pair labels), the coloop is the line itself: + +- R(u_t) = {t} ∪ (readers of t); +- the readers of t span only h − 1. + +So puncturing t makes its own label functional drop from h to h − 1. That is exactly E8's 8 → 7. Here, however, h − 1 is what the centres already cost. + +**Theorem P2.** Every label decoder has x ≥ 1/2 on I_{11,1} for |P| ≤ 8, and x ≥ 3/7 on I_{13,1} for |P| ≤ 10. + +**Proof.** + +1. **Disjoint bases protect rank.** If R(p) minus its coloops holds q disjoint bases, deleting P costs that rest at most ⌊|P|/q⌋ rank. +2. **Every non-centre functional keeps cost ≥ h − 1.** The binding orbits are: + - n = 11: k = 9, rest rank 10, q = 9, so |P| ≤ 8; + - n = 13: k = 11, rest rank 12, q = 11, so |P| ≤ 10. + + The full table is in `packbases*.log`; for n = 11 the other orbits allow 10–39 deletions. +3. **The live space is unchanged.** The weight-n functional holds 20 (resp. 28) disjoint bases, so the live span and window stay h. +4. **Combine with P1.** Let S be the set of centres in an optimal basis. Then W ≥ Σ_{i∈S} r_i + (h − 1)(n − |S|) ≥ Σ_i r_i ≥ |L|/2 (resp. (3/7)|L|), because r_i ≤ h − 1. ∎ + +**First cheap non-centre total (`example_pairlabel.py`, checked).** + +- **Puncture.** Delete the pair {0,1} and the 9 triples {0,1,x}, 10 lines in all. The pair-label functional then drops to 9: star 109/210 = 0.519, X* 6.3495·10⁻³ (s = 3). +- **Ratio.** One cost unit costs 10 punctures, while improving x needs more than 1 unit per 2 punctures. +- **Theorem C.** The floor here is 108 (X* at 108/210 = 6.5549·10⁻³), still below the unpunctured unit. + +### 2.3 Fewer totals after puncturing? (`clique_orbits.py`, `punct_sat.py`, checked by SAT) + +The maximum pairwise non-orthogonal cliques of I_{11,1} (the #595 pattern's identity blocks) are 38,786 cliques of size 10, in 5 S_11-orbits. The orbit sizes are: + +| orbit size | composition | +|---|---| +| 27,720 | 8 triples + 2 pairs | +| 9,240 | 7 triples + 3 pairs | +| 495 | 8 triples + 2 pairs | +| 1,320 | 3 triples + 7 pairs | +| 11 | 10 pairs | + +(#634's 10,395 sets Cl(a, M) are pairwise *orthogonal*. They belong to #634's knowing graph, not to this pattern.) + +**Gauge argument.** + +- A line lies in at most 1,784 of these cliques. So any ≤ 21 punctures leave a maximum clique alive (`survival_bound`, checked). +- The clique gauge U_K = W_K = [I | 0] on one representative per orbit, forced live, is therefore without loss of generality for k ≤ 21. + +**Result: the minrank stays 11 for every puncture of ≤ 19 lines.** With c = 10 and k = 19, the SAT instance is UNSAT for all 5 orbits (`sat19_o*.log`). Runs at k = 0, 4 and 8 (orbit 0) were UNSAT in under 6 s. + +**Consequence.** A 10-total decoder needs ≥ 20 punctures. If its totals cost 10 each, that gives x ≥ 100/200 = 1/2. + +**Scope.** General (non-label) 11-total decoders with totals cheaper than 10 are not excluded. The same open item, the non-star decoder, holds for E8 (#595 F.2, #631). + +### 2.4 Window-lowering sub-units (`punct.py`, checked) + +These keep the lines avoiding a set of coordinates. Labels are inherited from I_{11,1}; Theorem C is the greedy Gram-rank floor. + +| live lines | v | h | star (x) | X* | Theorem C (X*) | +|---|---|---|---|---|---| +| all | 220 | 11 | 110 (1/2) | **7.1715·10⁻³** (s 3) | 110 | +| avoid coordinate 10 (I_{10,1}-type) | 165 | 10 | 99 (3/5) | 4.0462·10⁻³ (s 5) | 88 (6.3329·10⁻³) | +| avoid 9, 10 (the E8 lines; radical 1) | 120 | 8 | 80 (2/3) | 3.4352·10⁻³ | 80 | +| avoid 8, 9, 10 | 84 | 8 | 63 (3/4) | 1.9850·10⁻³ | 63 | + +The inherited labels are not minimal on sub-units. E8's own labels give 72, not 80 (#595 C.6). So these rows are upper bounds on those units' cost. The E8 rows are the known E8 unit, and the I_{10,1}-type unit loses even at its Theorem C floor. + +### 2.5 Heuristic search (`anneal.py`, exploratory; logs in `data/`) + +The search used two objectives: + +- x, 3 seeds × 4000 moves; +- an X* proxy (2 − 3x)/h that also rewards lowering h, 1 seed. + +Each run used float ranks and exact greedy bases. Every run ended at the unpunctured unit. This is evidence only. + +## 3. Helpers (Q2) + +### 3.1 What sets E8's 3.95 helpers per port + +The shipped word is #555 (= `research/e8-bit-p111`, R = 439 at v = 111). It sits on #543's L3 skeleton (`research/e8-bit-r420`, itself on #493's `e8-bit-carrier-skeleton` partition): + +| component | count | per port (v = 111) | geometric reason | +|---|---|---|---| +| carriers | 240 | 2.16 | 3 branches per source, at the three cells {s, e} of its 4-frame. 3 is the minimum: s⊥ = E7 is a spherical 4-design, so no 2 hyperplane sections cover it (#463). Checked here as "no nonzero quadratic form vanishes on the 63 readers" (`twocover.py 8`). | +| empty cell registers | 180 | 1.62 | 30 orthogonal 4-frames × 6 cells; **3 registers per cell** (#493/#543 family bound): 2 line branches + 1 register that delivers the cell sum to the two quads (D4 frames of 6 cells from 6 blocks) and keeps a line | +| totals | 9 | 0.08 | F₂ minrank 9 | +| extra carriers (puncture) | 10 | 0.09 | #545 pseudo-target mini-nodes | + +**E8 features this rests on:** + +- **(a) Good 4-frames.** The 120 lines partition into 30 orthogonal 4-frames whose half-sum is a lattice vector (F₂ parity). +- **(b) Free inclusion–exclusion on the target side.** By (a), every reader s of t meets 1 or 3 of t's frame-mates. + - So t's three needers at the D6 frames {t, e}⊥ count every reader an odd number of times. + - The exceptions are t's 3 frame-mates, which are fixed by single "d-line" reads. +- **(c) Small, well-shared needer sums.** {t, e}⊥ holds 30 lines. Each is assembled from two quad outputs plus one Q-delivery, and each quad output serves 6 needers. +- **(d) Block nodes.** D4 blocks in complementary pairs supply the Q-deliveries. + +### 3.2 What I_{11,1} (and I_{13,1}) has (`cellcover.py`, `twocover.py`, `frames.py`, checked; parity argued) + +| feature | E8 | I_{11,1} | I_{13,1} | +|---|---|---|---| +| readers per source | 63 | 115 (triple), 129 (pair) | 183, 231 | +| cell cover number (branches into 2-dim cells {s, e}) | 3 | **3** (both types; CP-SAT OPTIMAL; attained by pairwise-orthogonal e's) | **3** (both types) | +| 2 branches of *any* frame dimension possible? | no (quadratic forms vanishing on readers: dim 0) | **triple: no (dim 0)**; pair: not excluded (dim 1; S3 has a 2-cover certificate) | triple: no; pair: not excluded | +| good 4-frames (orthogonal, F₂ parity) | 9,450 | **20,790**: 6,930 of type (pair, pair, triple, triple), 13,860 of type (triple)⁴ | not computed | +| partition into good 4-frames | yes (30) | **impossible**: every good frame holds 0 or 2 pairs, and 55 is odd | – | +| lines in a cell-perp (needer demand) | 30 | **56–70** | – | +| needers per port | 1.5 | 1.5 (3 per target, each shared by 2 targets) | 1.5 | + +**Parity (argued).** For example, {e₀−e₁, e₂−e₃, t_{401}, t_{423}} has half-sum e₀ + e₂ + e₄ + e₁₂ ∈ Z^{11,1} ∩ w⊥. For every integral line s, the sum of s's products with the frame is then even, so (b) holds verbatim, including the d-line correction. `frames.py` checks the parity condition directly on all 1,926,540 orthogonal 4-sets. + +### 3.3 A concrete skeleton sketch (argued count) and the ≤ 3 obstruction + +**Sketch: the E8 cell architecture transferred.** + +- Cover the lines by good 4-frames: 54 frames plus one frame reused for the odd pair, about 6 extra registers. +- Sources branch into their 3 cells: 2 carriers per source, 440. +- Each of the 330 cells holds 3 registers, adding 1 empty register per cell (330). +- Targets read 3 needers at {t, e}⊥ plus the d-lines. +- 11 totals. + +| variant | helpers | **per port** | +|---|---|---| +| all sources on cells | 440 + 330 + 11 + 6 = **787** | **3.58** | +| pair sources on their 2-cover (2 non-cell branches) | 385 + 330 + 11 + 6 = **732** | **3.33** | + +Not constructed: + +- the middle network (quad and block-node analogues); +- the d-line reads, cleanup, hosting. + +**The count assumes a middle as cheap per cell as E8's, while each needer must collect 56–70 lines instead of 30.** + +**Obstruction to ≤ 3 helpers per port (argued, conditional on the architecture).** + +- **Cell-branch designs.** Sources branch only into 2-dim cells and carriers are pure, as in #493/#543/#555. Then carriers are **exactly ≥ 2 per port**, because the cell cover number is 3 for both line types (checked). +- **Remaining budget.** ≤ 3 per port leaves ≤ 1 − 11/220 = 0.95 per port for the whole middle, i.e. ≤ 0.63 empty registers per cell. +- **Comparison with E8.** E8 needs 1 per cell (#493's family bound of 3 per cell) for half the demand per needer. +- **Any design with pure carriers.** Triple sources need ≥ 3 branches whatever the frame dimension (exact: no quadratic form vanishes on their 115 readers, the analogue of E8's design-strength carrier bound). So carriers are ≥ (2·165 + 55)/220 = **1.75 per port**, leaving ≤ 1.2 per port for the middle and totals. +- **Design-free floors.** S3 §4's floors (cover numbers 2–3, about 1 per port) remain the only bounds that hold for every design, so this is **not** a theorem excluding ≤ 3. +- **What it says.** A ≤ 3 skeleton must abandon E8's "3 registers per cell" middle, or share registers across sources (mixing carriers) far more than #555 does. + +## 4. Calibrated model on the best unit (Q3; `model3.py`, model) + +The best punctured unit is the unpunctured 220/110 (§2). S3's calibrated helper model (`est_model.py`, unchanged; bit calibration 1.0032 against #555) gives E8's target a = 1.8058·10⁻³ (#555 at ρ = 439/111): + +| unit | ρ* abs / rel | a_est at ρ = 3.577 (cells) | at 3.327 (pairs on 2 branches) | at 3.955 (E8's) | at 3.0 | at ρ_dim 5.207 | +|---|---|---|---|---|---|---| +| I_{11,1} 220/110 | 2.66 / 3.34 | 1.580 / 1.730 ·10⁻³ | 1.654 / **1.811**·10⁻³ | 1.480 / 1.620 | 1.716 / 1.875 | 1.225 / 1.339 | +| I_{11,1} 210/109 | 2.38 / 2.84 | 1.495 / 1.638 | 1.566 / 1.715 | 1.401 / 1.534 | 1.625 / 1.775 | 1.165 / 1.267 | + +(Pairs are abs / rel; the unit is 10⁻³.) + +Reading: + +- The transferred-skeleton intensity is 3.33–3.58 against break-even 2.66 / 3.34. +- Only the optimistic corner (rel scaling, pair 2-branch variant, a middle as cheap as E8's) reaches E8, at +0.3%. That is inside model error and rests on the unbuilt middle. +- Clearing E8 robustly (rel) needs ρ ≈ 3.0, i.e. the ≤ 0.63-per-cell middle of §3.3. + +## 5. Not done / open + +- **Exact rank-loss thresholds.** δ_d for the non-centre functionals: the MIP in a first draft was too slow and was dropped. These would extend P2 beyond 8 / 10 punctures (the 10-line example shows the first sub-10 total appears at exactly 10 for pair labels, and it is not an improvement). +- **Non-label decoders.** Non-label 11-total decoders on punctured I_{11,1}; Theorem C floors are listed for comparison. +- **The middle network.** Constructing a middle network for I_{11,1} with ≤ 0.63 empty registers per cell, or proving a family bound for it. D4 blocks and complementary pairs (E8 feature (d)) were not searched. +- **I_{13,1} frames.** Good-frame and frame-partition data for I_{13,1}. +- **Overlap check.** #920 (complex shared-parity frame dimensions), #912 (Hurwitz adaptive routing) and #907 (abelian Cayley metric scope) do not touch Lorentzian units, punctures or bit carrier skeletons. + +## Files + +- `scripts/census.py`: §1. +- `scripts/partition.py`: P1. +- `scripts/packbases.py`: P2. +- `scripts/punct.py`: exact star and Theorem C evaluation, §2.4. +- `scripts/example_pairlabel.py`: the 10-line puncture example of §2.2. +- `scripts/clique_orbits.py`, `scripts/punct_sat.py`: §2.3. +- `scripts/anneal.py`: §2.5, not in `verify.sh`. +- `scripts/cellcover.py`, `scripts/twocover.py`, `scripts/frames.py`: §3. +- `scripts/model3.py`: §4. +- Copied unchanged from S3: `units.py`, `est_model.py`, `rank.py`, `cover.py`. +- `scripts/check.py`, `expected/`, `verify.sh`; `data/` holds the anneal logs. + +## Credits + +This note builds on: + +- DreamingOfClouds: #595 (skel/cert, §A/§D/§F, Lemma Q), #446 Theorem C, #463's carrier bound, #545's punctured star; +- ShikeChen01: #634, the Lorentzian family; +- Chi Zhao: #631, the puncture-threshold method; +- eumemic: #493, #543 and the E8 bit skeletons; +- #555; +- S3 (GamingPuzzled). + +Prepared by Joel Pulikkan (GamingPuzzled) with Claude Opus 5.5 (Anthropic) via Claude Code. Apache-2.0. diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/clique_orbits11.json b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/clique_orbits11.json new file mode 100644 index 0000000000..1c2cd3f198 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/clique_orbits11.json @@ -0,0 +1,92 @@ +{ + "n": 11, + "omega": 10, + "count": 38786, + "orbits": [ + { + "size": 27720, + "rep": [ + 0, + 10, + 11, + 12, + 13, + 128, + 129, + 164, + 174, + 191 + ], + "triples": 8, + "pairs": 2 + }, + { + "size": 9240, + "rep": [ + 0, + 114, + 119, + 123, + 126, + 128, + 129, + 167, + 176, + 184 + ], + "triples": 7, + "pairs": 3 + }, + { + "size": 495, + "rep": [ + 0, + 1, + 2, + 3, + 4, + 5, + 7, + 8, + 172, + 181 + ], + "triples": 8, + "pairs": 2 + }, + { + "size": 1320, + "rep": [ + 0, + 1, + 9, + 168, + 169, + 170, + 171, + 172, + 173, + 174 + ], + "triples": 3, + "pairs": 7 + }, + { + "size": 11, + "rep": [ + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174 + ], + "triples": 0, + "pairs": 10 + } + ] +} \ No newline at end of file diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.json b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.json new file mode 100644 index 0000000000..fccb6524e2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.json @@ -0,0 +1,48 @@ +{ + "target": 0.0018058295009581778, + "rhos": { + "skel_cells": 3.577272727272727, + "skel_pairs2": 3.327272727272727, + "E8_shipped": 3.954954954954955, + "rho_dim": 5.207357357357357, + "three": 3.0 + }, + "rows": [ + { + "name": "I_{11,1} 220/110 (best; no improving puncture)", + "x": "1/2", + "Xstar": 0.007171522653821519, + "Xs": 3, + "rho_star_abs": 2.661677615469671, + "a_skel_cells_abs": 0.001579623287924217, + "a_skel_pairs2_abs": 0.0016536874570675746, + "a_E8_shipped_abs": 0.0014795179684754338, + "a_rho_dim_abs": 0.0012246258053555746, + "a_three_abs": 0.0017161978484465659, + "rho_star_rel": 3.343132941881777, + "a_skel_cells_rel": 0.0017300228085654601, + "a_skel_pairs2_rel": 0.0018112054999365138, + "a_E8_shipped_rel": 0.001620305554277553, + "a_rho_dim_rel": 0.0013387673129528718, + "a_three_rel": 0.0018751364738724314 + }, + { + "name": "I_{11,1} punctured 210/109 (pair-label total at 9)", + "x": "109/210", + "Xstar": 0.00634946069331934, + "Xs": 3, + "rho_star_abs": 2.375657844095258, + "a_skel_cells_abs": 0.0014953581884038232, + "a_skel_pairs2_abs": 0.0015654701238274383, + "a_E8_shipped_abs": 0.0014005944896649703, + "a_rho_dim_abs": 0.0011646251714212523, + "a_three_abs": 0.0016246403189826247, + "rho_star_rel": 2.8384077548253117, + "a_skel_cells_rel": 0.0016377099752249154, + "a_skel_pairs2_rel": 0.001714558212979752, + "a_E8_shipped_rel": 0.0015338502528991858, + "a_rho_dim_rel": 0.0012673405048824879, + "a_three_rel": 0.0017750707046173396 + } + ] +} \ No newline at end of file diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/anneal.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/anneal.py new file mode 100644 index 0000000000..2e3731a979 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/anneal.py @@ -0,0 +1,80 @@ +"""Heuristic (model-free, exploratory) search for punctures of I_{n,1} that lower the star label-decoder ratio +x = C_star(P)/v (window kept) or raise X*(h, x). Float ranks; every reported point is re-evaluated exactly by punct.py. +Usage: python anneal.py n seed steps [objective: x|X]""" +import sys, math, random, itertools +import numpy as np +sys.dont_write_bytecode = True +import units as U + +n, seed, steps = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]) +obj = sys.argv[4] if len(sys.argv) > 4 else 'x' +rng = random.Random(seed) +L, G, lab = U.lorentz_family(n) +v = len(L) +Q = np.diag([1.0] * n + [-1.0]) +Lf = L.astype(float) +F = [np.array([(p >> j) & 1 for j in range(n)]) for p in range(1, 1 << n)] +RM = np.array([(lab @ f) % 2 for f in F], dtype=bool) # functional x line + + +def cost(live): + lv = np.nonzero(live)[0] + Ll = Lf[lv] + h = np.linalg.matrix_rank(Ll @ Q @ Ll.T) + rad = np.linalg.matrix_rank(Ll) - h + items = [] + for i in range(len(F)): + R = lv[RM[i, lv]] + if len(R) == 0: + continue + sp = np.linalg.matrix_rank(Lf[R] @ Q @ Ll.T) if rad else np.linalg.matrix_rank(Lf[R]) + w = 0 + for t in R: + w |= 1 << int(t) + items.append((sp, w)) + items.sort(key=lambda t: t[0]) + basis, tot, seen = {}, 0, set() + for sp, w in items: + if w in seen: + continue + seen.add(w) + x = w + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x; break + if x: + tot += sp + return tot, h + + +def score(live): + c, h = cost(live) + x = c / live.sum() + if obj == 'x': + return x - 0.0 * h, (c, int(live.sum()), h) + # proxy for X*: X ~ (2 - 3x)/... use D/Cost-like proxy at s = 3: (2 - 3x)/(h) as monotone stand-in + return -(2 - 3 * x) / h, (c, int(live.sum()), h) + + +live = np.ones(v, dtype=bool) +cur, info = score(live) +best = (cur, info, live.copy()) +T0 = 0.01 +for it in range(steps): + T = T0 * (1 - it / steps) + 1e-5 + nl = live.copy() + for _ in range(rng.choice([1, 1, 1, 2, 3])): + t = rng.randrange(v); nl[t] = not nl[t] + if nl.sum() < v // 3: + continue + s, inf = score(nl) + if s <= cur or rng.random() < math.exp(-(s - cur) / T): + live, cur, info = nl, s, inf + if s < best[0]: + best = (s, inf, live.copy()) + print('it %d best %.5f (C=%d v=%d h=%d) dead=%s' % (it, s, inf[0], inf[1], inf[2], + list(np.nonzero(~live)[0]) if (~live).sum() < 40 else '%d lines' % (~live).sum()), flush=True) +print('final best', best[0], best[1], 'dead', list(np.nonzero(~best[2])[0])) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cellcover.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cellcover.py new file mode 100644 index 0000000000..4bd4fbb1ee --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cellcover.py @@ -0,0 +1,50 @@ +"""Cell cover numbers (E8 skeleton analogue of the source-branch count). +A source s branches into cells {s, e} (e a line orthogonal to s); a branch at frame span{s, e} can be read by exactly +the targets t orthogonal to both s and e. The cell cover number b(s) is the least number of such e covering every +reader t of s (t orthogonal to s, t != s), where t is covered by e when t is orthogonal to e and t != e. +E8 control: b = 3 (the 4-frame, #463's carrier bound). CP-SAT, exact optimum (OPTIMAL status reported). +Also reports whether an optimal cover can be chosen pairwise orthogonal (a frame), and the target-side analogue is +the same number because the reader relation is symmetric. Usage: python cellcover.py n""" +import sys, itertools +import numpy as np +from ortools.sat.python import cp_model +sys.dont_write_bytecode = True +import units as U + + +def lines(n): + return list(itertools.combinations(range(n), 3)) + list(itertools.combinations(range(n), 2)) + + +def cover(G, s, frame=False, tl=600): + v = len(G) + R = [t for t in range(v) if t != s and G[s, t] == 0] + m = cp_model.CpModel() + x = {e: m.NewBoolVar('x%d' % e) for e in R} + for t in R: + m.AddBoolOr([x[e] for e in R if e != t and G[t, e] == 0]) + if frame: + for e, f in itertools.combinations(R, 2): + if G[e, f] != 0: + m.AddBoolOr([x[e].Not(), x[f].Not()]) + m.Minimize(sum(x.values())) + so = cp_model.CpSolver(); so.parameters.max_time_in_seconds = tl; so.parameters.num_workers = 4 + st = so.Solve(m) + return so.StatusName(st), int(so.ObjectiveValue()), int(so.BestObjectiveBound()), [e for e in R if so.Value(x[e])], len(R) + + +if __name__ == '__main__': + n = int(sys.argv[1]) + if n == 8: + sys.path.insert(0, '.') + L = U.roots_E8(); G = U.gram_doubled(L); reps = [('E8 root', 0)] + else: + L, G, lab = U.lorentz_family(n) + LN = lines(n) + reps = [('triple', 0), ('pair', LN.index((0, 1)))] + for name, s in reps: + for fr in (False, True): + st, val, bd, sol, z = cover(G, s, fr) + assert st == 'OPTIMAL' and val == bd + print('n=%s %s source (readers %d) pairwise-orthogonal=%s: cell cover number %d (OPTIMAL)' % ( + n, name, z, fr, val), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/census.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/census.py new file mode 100644 index 0000000000..fd83dabc27 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/census.py @@ -0,0 +1,25 @@ +"""Reader-set census of the label functionals of I_{n,1} (S_n orbit = Hamming weight of the functional). +For each weight k: |R(p)|, its type split (triples / pairs), span rank over Q, Gram (pairing) rank of G[R,R].""" +import sys, json +import numpy as np +sys.dont_write_bytecode = True +import units as U + +def census(n): + L, G, lab = U.lorentz_family(n) + out = [] + for k in range(1, n + 1): + p = np.array([1] * k + [0] * (n - k)) + R = np.nonzero((lab @ p) % 2)[0] + ntri = int((R < len(L) - n * (n - 1) // 2).sum()) + sp = U.rank_q(L[R].tolist()) if len(R) else 0 + gr = U.gram_rank(G, R) + out.append(dict(k=k, readers=len(R), triples=ntri, pairs=len(R) - ntri, span=sp, gram=gr)) + return out + +if __name__ == '__main__': + res = {} + for n in map(int, sys.argv[1:] or ['9', '11', '13']): + res[n] = census(n) + for r in res[n]: + print(n, r, flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/check.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/check.py new file mode 100644 index 0000000000..1bdc4bb6e6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/check.py @@ -0,0 +1,27 @@ +"""Compare every recomputed log with the frozen expected log, ignoring run times ('sec ...') and float noise in the +model lines (relative 1e-9). Usage: python check.py EXPECTED_DIR OUT_DIR""" +import os, re, sys +exp, out = sys.argv[1], sys.argv[2] +num = re.compile(r'-?\d+\.\d+(?:e[-+]?\d+)?') +norm = lambda s: re.sub(r'sec [\d.]+', 'sec *', re.sub(r'\d+\.\d+s user.*', '', s)).strip() +bad = 0 +for f in sorted(os.listdir(exp)): + if not f.endswith('.log'): + continue + a = [norm(l) for l in open(os.path.join(exp, f)) if l.strip()] + try: + b = [norm(l) for l in open(os.path.join(out, f)) if l.strip()] + except FileNotFoundError: + print('FAIL', f, 'missing'); bad += 1; continue + ok = len(a) == len(b) + if ok: + for x, y in zip(a, b): + if num.sub('#', x) != num.sub('#', y): + ok = False; break + for u, w in zip(num.findall(x), num.findall(y)): + if abs(float(u) - float(w)) > 1e-9 * max(1.0, abs(float(u))): + ok = False + print('PASS' if ok else 'FAIL', f) + bad += not ok +print('%d FAIL' % bad) +sys.exit(1 if bad else 0) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/clique_orbits.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/clique_orbits.py new file mode 100644 index 0000000000..3d345eb342 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/clique_orbits.py @@ -0,0 +1,76 @@ +"""Maximum pairwise non-orthogonal cliques of I_{n,1} (the #595 pattern graph, G_st != 0) and their S_n orbits. +Orbits are found by isomorphism of the coloured incidence graph (clique lines x points of [n]); each class is checked +with networkx's exact VF2 isomorphism against its representative.""" +import sys, itertools, json +from collections import Counter, defaultdict +import networkx as nx +import numpy as np +sys.dont_write_bytecode = True +import units as U + + +def lines(n): + return list(itertools.combinations(range(n), 3)) + list(itertools.combinations(range(n), 2)) + + +def inc_graph(n, cl, LN): + g = nx.Graph() + for p in range(n): + g.add_node(('p', p), col='p') + for t in cl: + g.add_node(('l', t), col='T' if len(LN[t]) == 3 else 'P') + for p in LN[t]: + g.add_edge(('l', t), ('p', p)) + return g + + +def orbits(n): + L, G, lab = U.lorentz_family(n) + LN = lines(n) + v = len(L) + H = nx.Graph(); H.add_nodes_from(range(v)) + H.add_edges_from((i, j) for i in range(v) for j in range(i + 1, v) if G[i, j] != 0) + cls = [sorted(c) for c in nx.find_cliques(H)] + om = max(map(len, cls)) + cls = [c for c in cls if len(c) == om] + by = defaultdict(list) + for c in cls: + g = inc_graph(n, c, LN) + by[nx.weisfeiler_lehman_graph_hash(g, node_attr='col', iterations=4)].append(c) + reps = [] + nm = lambda a, b: a['col'] == b['col'] + for hsh, cs in by.items(): + local = [] + for c in cs: + g = inc_graph(n, c, LN) + for r in local: + if nx.is_isomorphic(g, r[1], node_match=nm): + r[2].append(c); break + else: + local.append([c, g, [c]]) + reps += [(r[0], r[2]) for r in local] + return om, cls, reps, LN + + +if __name__ == '__main__': + n = int(sys.argv[1]) + om, cls, reps, LN = orbits(n) + print('n=%d omega=%d maximum cliques=%d orbits=%d' % (n, om, len(cls), len(reps))) + out = [] + for rep, mem in reps: + typ = Counter(len(LN[t]) for t in rep) + print(' orbit size %d: %d triples + %d pairs, rep %s' % (len(mem), typ[3], typ[2], [LN[t] for t in rep])) + out.append(dict(size=len(mem), rep=rep, triples=typ[3], pairs=typ[2])) + if len(sys.argv) > 2: + json.dump(dict(n=n, omega=om, count=len(cls), orbits=out), open(sys.argv[2], 'w'), indent=1) + + +def survival_bound(n): + """Union bound: k punctures kill at most k * max_t #{maximum cliques through t} cliques, so some maximum clique + survives every puncture of k < count / max_per_line lines (then the clique gauge of punct_sat.py is WLOG up to S_n, + run once per orbit).""" + om, cls, reps, LN = orbits(n) + per = Counter(t for c in cls for t in c) + mx = max(per.values()) + kmax = (len(cls) - 1) // mx + return dict(count=len(cls), max_per_line=mx, survive_upto=kmax, orbits=len(reps)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cover.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cover.py new file mode 100644 index 0000000000..041baf1e42 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/cover.py @@ -0,0 +1,147 @@ +"""#595 section D cover numbers for candidate units (source end; the terminal end is the same geometry for these +self-dual line systems). + +For a source s, the readers T(s) are the lines orthogonal to s. A register crossing from into serves the +readers in w^perp, a 'hyperplane section' of T(s). The cover number is the least number of proper sections covering T(s). + * lower bound 2 <=> rank_Q T(s) = h - 1 (then no single proper section covers T(s)); exact + * upper bound 2 <=> two subsets of T(s), each of rank <= h - 2, with union T(s) (certificate); exact check + (the witnesses are found by sampling hyperplanes through h - 2 random readers plus structured functionals; + a found pair is re-checked exactly) + * the best single section found (a LOWER bound on the largest section; sampled, not exhaustive) +#595's single-cut floors (d_geometry.single_cut_bounds): sink-only R >= (c - 1) v - floor(v / leftover), strict +R >= max(c v - 2 v, 0). With cover number 2 the strict floor is 0 and the sink-only floor is at most v. +Control: E8 bit (cover number 3, best pair union 52 of 63: #595 D1). +Usage: python cover.py REPO_ROOT unit[,unit...] [samples] [--json out] unit in {E8, T, L} +""" +import itertools +import sys + +import numpy as np + +sys.dont_write_bytecode = True +import units as U # noqa: E402 + + +def load(name): + if name == 'E8': + L = U.roots_E8(); G = U.gram_doubled(L); return L, G, [0] + if name.startswith('T'): + L, G, _ = U.triple_family(int(name[1:])); return L, G, [0] + if name.startswith('L'): + n = int(name[1:]) + L, G, _ = U.lorentz_family(n) + from math import comb + return L, G, [0, comb(n, 3)] # one triple-type and one pair-type source + raise ValueError(name) + + +def sections_for(L, G, s, h, samples, rng): + T = np.nonzero(G[s] == 0)[0] + T = T[T != s] + X = L[T].astype(float) + rT = U.rank_q(L[T].tolist()) + secs = set() + # structured functionals: coordinate patterns c with c . t = 0 + n = L.shape[1] + cands = [] + for i in range(n): + e = np.zeros(n); e[i] = 1; cands.append(e) + for i, j in itertools.combinations(range(n), 2): + for sg in (1, -1): + e = np.zeros(n); e[i] = 1; e[j] = sg; cands.append(e) + for i, j, k in itertools.combinations(range(n), 3): + for sg in itertools.product((1, -1), repeat=2): + e = np.zeros(n); e[i] = 1; e[j] = sg[0]; e[k] = sg[1]; cands.append(e) + for i, j in itertools.combinations(range(n), 2): # triple family: 1 off {i, j}, -2 on {i, j} (two-class sections) + e = np.ones(n); e[i] = e[j] = -2; cands.append(e) + for i in range(n - 1): + for wgt in (1, 2, 3): + e = np.ones(n); e[-1] = wgt; e[i] -= 1; cands.append(e) + e = np.ones(n); e[-1] = wgt; e[i] += 1; cands.append(e) + for c in cands: + m = np.abs(X @ c) < 1e-9 + if 0 < m.sum() < len(T): + secs.add(tuple(np.nonzero(m)[0])) + # random hyperplanes of span(T) through h - 2 readers + for _ in range(samples): + idx = rng.choice(len(T), rT - 1, replace=False) + M = X[idx] + if np.linalg.matrix_rank(M) < rT - 1: + continue + # normal of the hyperplane inside span(T): project onto span(T) via SVD of X + _, _, Vt = np.linalg.svd(np.vstack([M, np.zeros((0, n))])) + null = Vt[rT - 1:] # functionals vanishing on M + # choose a functional that does not vanish on all of T + best = None + for c in null: + m = np.abs(X @ c) < 1e-7 + if m.sum() < len(T): + best = m; break + if best is not None: + secs.add(tuple(np.nonzero(best)[0])) + return T, rT, secs + + +def run(name, samples=20000, seed=1): + L, G, srcs = load(name) + h = U.rank_q(G.tolist()) + rng = np.random.default_rng(seed) + out = [] + for s in srcs: + T, rT, secs = sections_for(L, G, s, h, samples, rng) + # exact check of every kept section: rank <= h - 2 (a proper section) + secs = [S for S in secs if U.rank_q(L[T[list(S)]].tolist()) <= h - 2] + masks = [sum(1 << int(k) for k in S) for S in secs] + full = (1 << len(T)) - 1 + best1 = max(len(S) for S in secs) + order = sorted(range(len(masks)), key=lambda i: -len(secs[i])) + bestpair, cover2 = 0, None + for a in range(len(order)): + ma = masks[order[a]] + for b in range(a, len(order)): + u = bin(ma | masks[order[b]]).count('1') + if u > bestpair: + bestpair = u + if u == len(T): + cover2 = (secs[order[a]], secs[order[b]]) + break + if cover2: + break + lb = 2 if rT == h - 1 else 1 + res = dict(unit=name, source=int(s), readers=len(T), rank_readers=rT, h=h, cover_lb=lb, + cover_ub=2 if cover2 else None, best_section_found=best1, best_pair_union=bestpair, + n_sections=len(secs)) + if not cover2: # look for a 3-cover among the largest sections + top = order[:400] + found = None + for a in range(len(top)): + for b in range(a, len(top)): + rest = full & ~(masks[top[a]] | masks[top[b]]) + for c in range(len(masks)): + if rest & ~masks[c] == 0: + found = (secs[top[a]], secs[top[b]], secs[c]); break + if found: + break + if found: + break + if found: + assert set().union(*found) == set(range(len(T))) + res['cover_ub'] = 3 + res['cover3_sizes'] = tuple(len(x) for x in found) + if cover2: + A, B = cover2 + assert U.rank_q(L[T[list(A)]].tolist()) <= h - 2 and U.rank_q(L[T[list(B)]].tolist()) <= h - 2 + assert set(A) | set(B) == set(range(len(T))) + res['cover2_sizes'] = (len(A), len(B)) + print(res, flush=True) + out.append(res) + return out + + +if __name__ == '__main__': + import json + allres = [] + for u in sys.argv[2].split(','): + allres += run(u, int(sys.argv[3]) if len(sys.argv) > 3 else 20000) + if '--json' in sys.argv: + json.dump(allres, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/est_model.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/est_model.py new file mode 100644 index 0000000000..2ba54d9815 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/est_model.py @@ -0,0 +1,135 @@ +"""Helper-adjusted estimate of the achievable saving a for a unit family (MODEL, not a bound). + +The #595 ceiling X(s, h, x) is helper-free. On the realised E8 words the gap to the ceiling is 87-93% helper cost +(#595 A.5). This model puts the helpers back with the piece statistics of the shipped words and solves the same +pressure root, so that unit families can be compared at the same design maturity: + + per port and replica (v = 1), stage count s = 2K + 1, a = h - 1, m = 1 + s a, D = (s - 1) - s x (#595 A.1, k = 1) + data 2 s a mass, cut into pieces with the per-port run histogram of the shipped word + (bit: #555 x/y runs; complex: #569 four-cold data runs, as replayed by #595 a4_gap.py) + copies s x mass in children of rank h - 1 (#555: 9 x 7, #568: 8 x 8) + helpers rho = R / v helpers per port, each climbing h per stage, cut with the shipped helper run histogram + (bit: #555, complex: #568's implied helper histogram, both from #595 a4_gap.py) + idle the B_K idle slots (skel.idle_slots, 2 per port), hosting: the helper mass is hosted into the largest idle + slots first; a hosted slot of rank r leaves a remainder child of rank r / a (the #555 pattern: 42, 28, 14 -> + 6, 4, 2); helper mass beyond the hostable mass stays as extra paid children (#595 step D semantics) +Piece-size scaling to another window h: + 'abs' pieces keep the shipped absolute ranks (their number grows like h) -- pessimistic for large h + 'rel' pieces scale with the window (rank x a/a_ref; same number of stops per register) -- optimistic +Calibration: the model is run on the E8 words themselves (bit h 8, x 63/111, rho 439/111; complex h 9, x 64/112, +rho 496/112) and the ratio to the published value (1.7331e-3 / 1.5987e-3, #595 A.5) is reported; the same factor is +applied to every candidate of that side (a_cal). +Also printed: #634's loss-chain factors 1/m, D/W (W = 2K v, whole hosting), L_ent = ln m - E ln r on the model profile. +""" +import math +import sys +from fractions import Fraction as Fr + +REPO = sys.argv[1] if len(sys.argv) > 1 else None +if REPO: + sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import idle_slots # noqa: E402 + +# shipped piece statistics (checked by #595 a4_gap.py: A4.runs.#555, A4.data.#568, A4.inputs.#568) +BIT = dict(h=8, v=111, R=439, + data={1: 10 + 317, 2: 209 + 92, 3: 10, 4: 3, 5: 92, 6: 10, 7: 9}, # x + y runs per stage + helpers={1: 994, 2: 1041, 3: 70, 4: 44, 5: 10}, published=Fr(17331, 10 ** 7), x=Fr(63, 111), s=9) +CX = dict(h=9, v=112, R=496, + data={1: 718, 2: 74, 3: 60, 4: 92, 5: 42, 6: 28}, # per call, v = 112 + helpers={1: 115952, 2: 26320, 3: 28064, 4: 7072, 5: 1008, 6: 2016}, published=Fr(15987, 10 ** 7), + x=Fr(64, 112), s=9) + + +def _norm(hist, mass): + tot = sum(r * z for r, z in hist.items()) + return {Fr(r): Fr(z) * mass / tot for r, z in hist.items()} + + +def profile(side, h, x, rho, s, mode): + ref = BIT if side == 'bit' else CX + a, K = h - 1, (s - 1) // 2 + m = 1 + s * a + D = (s - 1) - s * Fr(x) + scale = Fr(a, ref['h'] - 1) if mode == 'rel' else Fr(1) + prof = {} + + def add(r, n): + r = Fr(r) + if n <= 0 or r >= m: + return + prof[r] = prof.get(r, 0) + n + # data: 2 s a mass with the reference run shape + for r, n in _norm(ref['data'], 2 * s * a).items(): + rr = min(r * scale, a) + add(rr, n * r / rr) # keep the mass of each class + # copies + add(a, Fr(s) * Fr(x) / a) + # helpers + hmass = Fr(rho) * s * h + hsc = Fr(h, ref['h']) if mode == 'rel' else Fr(1) + for r, n in _norm(ref['helpers'], hmass).items(): + rr = min(r * hsc, h) + add(rr, n * r / rr) + # idle with hosting + slots = sorted([r for r in idle_slots(K, a) for _ in range(2)], reverse=True) + left = hmass + for r in slots: + rem = Fr(r, a) # remainder piece of a hosted slot + cap = r - rem + if left >= cap: + left -= cap + add(rem, 1) + elif left > 0: + add(r - left, 1) # partially hosted slot: one paid child of the rest + left = 0 + else: + add(r, 1) + return prof, m, D + + +def root(prof, m, D, iters=200): + m = float(m); D = float(D) + F = lambda q: sum(float(z) * float(r) * ((m / float(r)) ** q - 1) for r, z in prof.items()) + lo, hi = 0.0, 0.5 + for _ in range(iters): + mid = (lo + hi) / 2 + if F(mid) >= D: + hi = mid + else: + lo = mid + return hi + + +def chain(prof, m, D, s): + """#634 loss chain on the model profile: a ~ (1/m)(D/W)/L_ent with W = (mass + D)/m data-equivalent wires per port + (= 2K when every helper is hosted) and L_ent = ln m - E ln r (rank-weighted).""" + mass = sum(float(r * z) for r, z in prof.items()) + W = (mass + float(D)) / float(m) + Eln = sum(float(r * z) * math.log(float(r)) for r, z in prof.items()) / mass + Lent = math.log(float(m)) - Eln + return dict(inv_m=1 / float(m), W=W, DW=float(D) / W, Lent=Lent, first=float(D) / W / float(m) / Lent) + + +def estimate(side, h, x, rho, mode, S=range(3, 26, 2)): + best = None + for s in S: + prof, m, D = profile(side, h, x, rho, s, mode) + if D <= 0: + continue + a = root(prof, m, D) + if best is None or a > best[1]: + best = (s, a, chain(prof, m, D, s)) + return best + + +def calibration(side, mode): + ref = BIT if side == 'bit' else CX + prof, m, D = profile(side, ref['h'], ref['x'], Fr(ref['R'], ref['v']), ref['s'], mode) + return float(ref['published']) / root(prof, m, D) + + +if __name__ == '__main__': + for side in ('bit', 'complex'): + for mode in ('abs', 'rel'): + print(side, mode, 'calibration factor (published / model at s = 9): %.4f' % calibration(side, mode)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/example_pairlabel.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/example_pairlabel.py new file mode 100644 index 0000000000..d012374848 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/example_pairlabel.py @@ -0,0 +1,13 @@ +"""First cheap non-centre functional: puncture the pair {0,1} and the 9 triples {0,1,x}. Exact star evaluation.""" +import sys, itertools +sys.dont_write_bytecode = True +import punct +punct._REPO = sys.argv[1] +LN = list(itertools.combinations(range(11), 3)) + list(itertools.combinations(range(11), 2)) +dead = {LN.index((0, 1))} | {LN.index(tuple(sorted((0, 1, x)))) for x in range(2, 11)} +live = [i for i in range(220) if i not in dead] +r = punct.evaluate(11, live) +xs = punct.Xstar(r['h'], r['x']) +print('punctured %d: v=%d h=%d star=%d per total %s thmC=%d x=%s=%.4f X*=%.5e@s%d' % ( + len(dead), r['v'], r['h'], r['star'], sorted(c for c, _ in r['star_totals']), r['thmC'], r['x'], float(r['x']), + float(xs[1]), xs[0])) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/frames.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/frames.py new file mode 100644 index 0000000000..d15654b518 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/frames.py @@ -0,0 +1,89 @@ +"""E8 skeleton features transferred to I_{n,1}: good 4-frames, F2 parity, partitions, cell-perp sizes. +A 4-frame is a set of 4 pairwise orthogonal lines. It is GOOD when for every member t and every line s orthogonal to t +(s not in the frame) the number of other members orthogonal to s is odd (E8: the half-sum of a 4-frame is a lattice +vector, so the count is 1 or 3). Then the three cells {t, e} of t cover t-perp, and over F2 the three cell-perp sums +count every non-frame reader of t an odd number of times (the inclusion-exclusion is free; only the 3 frame lines +are counted twice and need the single 'd-line' reads of #493/#543). +Checks: number of 4-frames, number of good 4-frames, orbit types, and whether the lines can be partitioned into good +4-frames (CP-SAT exact cover). Usage: python frames.py n (n = 8 is the E8 control)""" +import sys, itertools +from collections import Counter +import numpy as np +from ortools.sat.python import cp_model +sys.dont_write_bytecode = True +import units as U + + +def setup(n): + if n == 8: + L = U.roots_E8(); G = U.gram_doubled(L); LN = [tuple(r) for r in L] + else: + L, G, lab = U.lorentz_family(n) + LN = list(itertools.combinations(range(n), 3)) + list(itertools.combinations(range(n), 2)) + return L, G, LN + + +def frames4(G): + v = len(G) + O = [set(np.nonzero(G[i] == 0)[0]) - {i} for i in range(v)] + out = [] + for a in range(v): + for b in O[a]: + if b <= a: continue + for c in O[a] & O[b]: + if c <= b: continue + for d in O[a] & O[b] & O[c]: + if d <= c: continue + out.append((a, b, c, d)) + return out, O + + +def good(F, O): + for t in F: + others = [e for e in F if e != t] + for s in O[t]: + if s in F: continue + if sum(1 for e in others if s in O[e]) % 2 == 0: + return False + return True + + +if __name__ == '__main__': + n = int(sys.argv[1]) + L, G, LN = setup(n) + v = len(G) + Fs, O = frames4(G) + gd = [F for F in Fs if good(F, O)] + typ = Counter(tuple(sorted(len(LN[t]) if n != 8 else 0 for t in F)) for F in gd) + print('n=%d v=%d 4-frames=%d good=%d types(line sizes)=%s' % (n, v, len(Fs), len(gd), dict(typ)), flush=True) + if n != 8: + for ty in typ: + ex = next(F for F in gd if tuple(sorted(len(LN[t]) for t in F)) == ty) + print(' example', [LN[t] for t in ex]) + # cell-perp sizes + if gd: + F = gd[0]; t, e = F[0], F[1] + print('cell-perp {t,e} contains %d lines; t-perp %d' % (len(O[t] & O[e]), len(O[t]))) + if n != 8: + npairs = sum(1 for t in range(v) if len(LN[t]) == 2) + ppf = sorted(set(sum(1 for t in F if len(LN[t]) == 2) for F in gd)) + print('pairs per good frame: %s; pairs in total: %d' % (ppf, npairs)) + if all(c % 2 == 0 for c in ppf) and npairs % 2 == 1: + print('NO partition into good 4-frames: every good frame holds an even number of pairs, %d is odd' % npairs) + sys.exit(0) + if v % 4 == 0 and gd: + m = cp_model.CpModel() + x = [m.NewBoolVar('f%d' % i) for i in range(len(gd))] + inc = [[] for _ in range(v)] + for i, F in enumerate(gd): + for t in F: inc[t].append(x[i]) + for t in range(v): + m.AddExactlyOne(inc[t]) + so = cp_model.CpSolver(); so.parameters.max_time_in_seconds = 600; so.parameters.num_workers = 4 + st = so.Solve(m) + print('partition into good 4-frames:', so.StatusName(st)) + if st in (cp_model.OPTIMAL, cp_model.FEASIBLE): + P = [gd[i] for i in range(len(gd)) if so.Value(x[i])] + assert sorted(t for F in P for t in F) == list(range(v)) and all(good(F, O) for F in P) + print(' verified partition of %d lines into %d good 4-frames; frame types %s' % ( + v, len(P), dict(Counter(tuple(sorted(len(LN[t]) if n != 8 else 0 for t in F)) for F in P)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/model3.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/model3.py new file mode 100644 index 0000000000..0c7767062c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/model3.py @@ -0,0 +1,41 @@ +"""Q3 (MODEL, not a bound): S3's calibrated helper model (est_model.py, unchanged) on the best punctured I_{11,1} unit. +Q1 shows no puncture lowers x below 1/2 for centre decoders (certificate) and none was found for label decoders, +so the best unit is the unpunctured 220/110 (h = 11); the first sub-10 total (210/109) is shown for comparison. +Helper intensities rho (helpers per port): + skel_cells : E8 cell architecture transferred (2 carriers/source + 1 aux per cell, 3 cells/line/2 = 1.5/port, + + 11 totals + one extra 4-frame for the odd pair count: (440 + 330 + 11 + 6)/220) + skel_pairs2 : same, pair sources with 2 branches (pair 2-cover; frames not cells): (385 + 330 + 11 + 6)/220 + E8 shipped : 439/111 + rho_dim : S3's readers-per-window-dimension transfer +Usage: python model3.py REPO_ROOT [--json out]""" +import sys, json +from fractions import Fraction as Fr +sys.dont_write_bytecode = True +import rank as RK # noqa (imports skel via REPO in argv[1]) +E = RK.E + +units = [dict(name='I_{11,1} 220/110 (best; no improving puncture)', h=11, v=220, cst=110), + dict(name='I_{11,1} punctured 210/109 (pair-label total at 9)', h=11, v=210, cst=109)] +rhos = {'skel_cells': Fr(440 + 330 + 11 + 6, 220), 'skel_pairs2': Fr(385 + 330 + 11 + 6, 220), + 'E8_shipped': Fr(439, 111), 'rho_dim': Fr(439, 111) * Fr(165 * 115 + 55 * 129, 220) / 10 / Fr(63, 7), + 'three': Fr(3)} +cals = {md: E.calibration('bit', md) for md in ('abs', 'rel')} +target = RK.cal_est('bit', 8, Fr(63, 111), Fr(439, 111), 'abs', E.calibration('bit', 'abs'))[1] +out = [] +print('E8 #555 calibrated model target a = %.4e' % target) +for u in units: + x = Fr(u['cst'], u['v']) + xs = RK.xstar(u['h'], x) + row = dict(name=u['name'], x=str(x), Xstar=float(xs[1]), Xs=xs[0]) + for md in ('abs', 'rel'): + row['rho_star_' + md] = RK.breakeven('bit', u['h'], x, md, cals[md], target) + for k, r in rhos.items(): + s_, a_, _ = RK.cal_est('bit', u['h'], x, r, md, cals[md]) + row['a_%s_%s' % (k, md)] = a_ + out.append(row) + print('%s: x=%s X*=%.4e | rho* abs %.2f rel %.2f' % (u['name'], x, row['Xstar'], row['rho_star_abs'], row['rho_star_rel'])) + for k, r in rhos.items(): + print(' rho %-11s = %.3f: a_est abs %.4e rel %.4e (E8 %.4e)' % (k, float(r), row['a_%s_abs' % k], row['a_%s_rel' % k], target)) +if '--json' in sys.argv: + json.dump(dict(target=target, rhos={k: float(v) for k, v in rhos.items()}, rows=out), + open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/packbases.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/packbases.py new file mode 100644 index 0000000000..a3ef1601b8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/packbases.py @@ -0,0 +1,67 @@ +"""Disjoint spanning sets inside reader sets: a puncture-robustness certificate. +For a functional p (Hamming weight k, S_n orbit representative) we pack q pairwise disjoint bases of span R(p) inside +R(p) (matroid union by augmenting paths; every packed basis is re-verified with exact rational rank). +If R(p) holds q disjoint bases, deleting any P leaves rank(R(p) - P) >= rank R(p) - floor(|P| / q): some basis loses +at most floor(|P|/q) lines. Usage: python packbases.py n""" +import sys +from collections import deque +import numpy as np +sys.dont_write_bytecode = True +import units as U + + +def pack(Lsub, q, seed=0): + Lf = Lsub.astype(float) + m = len(Lsub) + S = [set() for _ in range(q)] + where = {} + ind = lambda ss: len(ss) == 0 or np.linalg.matrix_rank(Lf[sorted(ss)]) == len(ss) + for x in range(m): + prev = {x: None}; dq = deque([x]); found = None + while dq and found is None: + y = dq.popleft() + for si in range(q): + if where.get(y) == si: + continue + if ind(S[si] | {y}): + found = (y, si); break + for z in S[si]: + if z not in prev and ind((S[si] - {z}) | {y}): + prev[z] = (y, si); dq.append(z) + if found is None: + continue + y, si = found + while True: + old = where.get(y) + if old is not None: + S[old].discard(y) + S[si].add(y); where[y] = si + if prev[y] is None: + break + y, si = prev[y] + return S + + +if __name__ == '__main__': + n = int(sys.argv[1]) + L, G, lab = U.lorentz_family(n) + for k in range(1, n + 1): + p = np.array([1] * k + [0] * (n - k)) + R = np.nonzero((lab @ p) % 2)[0] + r0 = U.rank_q(L[R].tolist()) + # strip coloops (lines outside the span of the rest of R): deleting one drops the rank by one + col = [t for t in R if U.rank_q(L[[u for u in R if u != t]].tolist()) < r0] + R = np.array([t for t in R if t not in col]) + r = U.rank_q(L[R].tolist()) + assert r == r0 - len(col) + q = len(R) // r + while q > 0: + S = pack(L[R], q) + if all(len(s) == r for s in S): + break + q -= 1 + for s in S: + assert U.rank_q(L[R[sorted(s)]].tolist()) == r + assert len(set().union(*S)) == q * r + print('n=%d k=%d: |R|=%d rank=%d coloops=%d %s; rest rank %d holds %d disjoint bases (verified exactly)' + ' -> rank(R - P) >= %d - floor(|P|/%d)' % (n, k, len(R) + len(col), r0, len(col), [int(c) for c in col], r, q, r, q), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/partition.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/partition.py new file mode 100644 index 0000000000..f8c7370cf9 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/partition.py @@ -0,0 +1,86 @@ +"""Matroid-partition certificate for the centre decoder of I_{n,1} under every puncture. + +Claim: for every live set L of lines, sum_i rank(R(e_i) & L) >= |L| / 2. +Certificate: a partition of all v lines into 2n sets I_{i,1}, I_{i,2}, each linearly independent and contained in +R(e_i) (the readers of centre i). Then 2 rank(R(e_i) & L) >= |L & I_{i,1}| + |L & I_{i,2}| and summing gives the +claim. For n = 11, 13: v = 2 n (n - 1) / 2 ... (220 = 22 x 10, 364 = 26 x 14 ... see output). +Found by Edmonds/Knuth matroid partitioning (shortest augmenting paths, float rank oracle); the final partition is +verified with exact rational ranks. Usage: python partition.py n copies mult [--json out] +(copies of each line = mult, slots per centre = copies: proves sum_i rank >= (mult/copies)|L|)""" +import sys, json, random +from collections import deque +import numpy as np +sys.dont_write_bytecode = True +import units as U + + +def run(n, copies=2, seed=0, mult=1): + L0, G, lab0 = U.lorentz_family(n) + L = np.repeat(L0, mult, axis=0); lab = np.repeat(lab0, mult, axis=0) # each line mult times + v = len(L) + Lf = L.astype(float) + allowed = [[i for i in range(n) if lab[t, i]] for t in range(v)] # t in R(e_i) <=> label_t[i] = 1 + slots = [(i, c) for i in range(n) for c in range(copies)] + S = [set() for _ in slots] + where = {} + indep = lambda ss: len(ss) == 0 or np.linalg.matrix_rank(Lf[sorted(ss)]) == len(ss) + rng = random.Random(seed) + order = list(range(v)); rng.shuffle(order) + for x in order: + # BFS: nodes are elements; edge y -> slot s: y can enter s (allowed) and S_s + y indep => done + # else y -> z for z in S_s with S_s - z + y independent (z then must move) + prev = {x: None} + q = deque([x]); found = None + while q and found is None: + y = q.popleft() + for si, (i, c) in enumerate(slots): + if i not in allowed[y] or where.get(y) == si: + continue + if indep(S[si] | {y}): + found = (y, si); break + for z in S[si]: + if z not in prev and indep((S[si] - {z}) | {y}): + prev[z] = (y, si); q.append(z) + if found is None: + return None, x + y, si = found + # augment along the path: y goes into si; then walk back + while True: + old = where.get(y) + if old is not None: + S[old].discard(y) + S[si].add(y); where[y] = si + if prev[y] is None: + break + y, si = prev[y][0], prev[y][1] + # prev[z] = (y', si'): y' entered si' displacing z -- here z is the element we just moved; walk on + # sanity + return (S, slots, L, lab), None + + +def verify(S, slots, L, lab): + for (i, c), ss in zip(slots, S): + ss = sorted(ss) + assert all(lab[t, i] for t in ss) + assert U.rank_q(L[ss].tolist()) == len(ss), 'dependent slot' + allv = sorted(t for ss in S for t in ss) + assert allv == list(range(len(L))), 'not a partition' + return True + + +if __name__ == '__main__': + n = int(sys.argv[1]); copies = int(sys.argv[2]); mult = int(sys.argv[3]) + for seed in range(5): + res, fail = run(n, copies, seed, mult) + if res: + S, slots, L, lab = res + verify(S, slots, L, lab) + print('n=%d: %d copies of each of the %d lines partitioned into %d independent slots (%d per centre) VERIFIED exactly' + ' => sum_i rank(R(e_i) & L) >= (%d/%d)|L| for every live set L; slot sizes %s' % ( + n, mult, len(L) // mult, len(slots), copies, mult, copies, sorted(set(len(s) for s in S)))) + if '--json' in sys.argv: + json.dump(dict(n=n, copies=copies, slots=[dict(centre=i, copy=c, lines=sorted(int(t) for t in s)) + for (i, c), s in zip(slots, S)]), + open(sys.argv[sys.argv.index('--json') + 1], 'w')) + break + print('seed %d: no augmenting path for element %d (a set violating |S| <= copies*rank exists)' % (seed, fail)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct.py new file mode 100644 index 0000000000..13c2a25093 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct.py @@ -0,0 +1,107 @@ +"""Punctures of the Lorentzian units I_{n,1} (#634 note 3), star label decoders and Theorem C floors. + +A puncture keeps a set of live lines. For a live set: + h = Gram rank of the live lines (stage window, after the Lemma Q quotient by the radical), + c0 = F2 rank of the live labels, + star = min over bases of the live label space of sum_j dim_Q span(R(p_j) & live) (matroid greedy, exact) + thmC = the same with the Gram rank of the block G[R,R] (#446 Theorem C pairing floor of a label total) + x = star / v_live. +Spans are measured modulo the radical of the live Gram matrix (Lemma Q, #595 C.4), so that n = 9 gives E8 (8 per total).""" +import itertools, sys +from fractions import Fraction as Fr +import numpy as np +sys.dont_write_bytecode = True +import units as U + + +def family(n): + L, G, lab = U.lorentz_family(n) + return L, G, lab + + +def _rank_f(M): + if len(M) == 0: + return 0 + return int(np.linalg.matrix_rank(np.asarray(M, dtype=float))) + + +def evaluate(n, live, exact=True, fam=None): + L, G, lab = fam or family(n) + live = np.asarray(sorted(live)) + Ll, Gl, labl = L[live], G[np.ix_(live, live)], lab[live] + rk = U.rank_q if exact else _rank_f + span_all = rk(Ll.tolist()) + h = rk(Gl.tolist()) + rad = span_all - h # dimension of the radical of the live span + # radical basis: vectors in span(Ll) orthogonal (Lorentz) to every live line + Q = np.diag([1] * n + [-1]) + costs = [] + for pint in range(1, 1 << n): + pv = np.array([(pint >> j) & 1 for j in range(n)]) + R = np.nonzero((labl @ pv) % 2)[0] + if len(R) == 0: + continue + sp = rk(Ll[R].tolist()) + if rad: + # dim of image in span/radical: rank of Gram pairing of R against all live lines + sp = rk((Ll[R] @ Q @ Ll.T).tolist()) + gr = rk(Gl[np.ix_(R, R)].tolist()) + word = sum(1 << int(i) for i in R) # reader word over the live lines + costs.append((sp, gr, word, pint)) + star, used = _greedy(costs, 0, n) + thc, usedc = _greedy(costs, 1, n) + c0 = U.f2_rank(labl) + return dict(n=n, v=len(live), h=h, rad=rad, c0=c0, star=star, star_totals=used, thmC=thc, thmC_totals=usedc, + x=Fr(star, len(live))) + + +def _greedy(costs, key, n): + cs = sorted(costs, key=lambda t: (t[key], t[3])) + basis, tot, used = {}, 0, [] + seen = set() + for t in cs: + if t[2] in seen: + continue + seen.add(t[2]) + x = t[2] + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + if x: + tot += t[key]; used.append((t[key], t[3])) + return tot, used + + +def coord_puncture(n, S): + """live = lines not involving any coordinate in S (triples A with A & S empty, pairs disjoint from S).""" + T = list(itertools.combinations(range(n), 3)); P = list(itertools.combinations(range(n), 2)) + lines = T + P + return [i for i, l in enumerate(lines) if not set(l) & set(S)] + + +def Xstar(h, x, smax=15): + sys.path.insert(0, _REPO + '/research/format-limits/scripts') + from skel import X + best = None + for s in range(3, smax + 1, 2): + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi']) + return best + + +_REPO = None +if __name__ == '__main__': + _REPO = sys.argv[1] + for n in map(int, sys.argv[2:]): + for k in range(0, n - 7): + live = coord_puncture(n, range(n - k, n)) + r = evaluate(n, live) + xs = Xstar(r['h'], r['x']) + print('I_{%d,1} minus coords %s: v=%d h=%d rad=%d c0=%d star=%d %s thmC=%d x=%s=%.4f X*=%s' % ( + n, list(range(n - k, n)), r['v'], r['h'], r['rad'], r['c0'], r['star'], sorted(c for c, _ in r['star_totals']), + r['thmC'], r['x'], float(r['x']), ('%.5e@s%d' % (float(xs[1]), xs[0])) if xs else 'D<=0'), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct_sat.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct_sat.py new file mode 100644 index 0000000000..e0d7ade518 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/punct_sat.py @@ -0,0 +1,75 @@ +"""Can a puncture of at most k lines bring the F2 minrank of the I_{n,1} pattern down to n - 1? +Variables: live selectors y_t, sink/source labels W, U (v x c over F2), B = W U^T. Constraints: live t => B_tt = 1; +live s, t with G_st != 0 => B_ts = 0; at most k lines dead. Gauge U_K = W_K = [I|0] on one fixed maximum clique K that +is forced live (WLOG while k is below the clique hitting number, see clique_hit.py, and S_n is transitive on maximum +cliques). CaDiCaL 1.5.3 via python-sat, no proof log. +Usage: python punct_sat.py n c k [timeout_s [orbits.json orbit_index]]""" +import sys, time, itertools +import numpy as np +from pysat.formula import CNF, IDPool +from pysat.card import CardEnc, EncType +from pysat.solvers import Cadical153 +sys.dont_write_bytecode = True +import units as U + + +def build(n, c, k, K=None): + L, G, lab = U.lorentz_family(n) + v = len(L) + if K is None: + K = U.nonorth_clique(G) + pool = IDPool(); cl = [] + nv = lambda: pool.id() + y = [nv() for _ in range(v)] + Uv = [[nv() for _ in range(c)] for _ in range(v)] + Wv = [[nv() for _ in range(c)] for _ in range(v)] + + def AND(a, b): + z = nv(); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + + def XOR(a, b): + z = nv(); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + + def B(T, S): + lits = [AND(Wv[T][j], Uv[S][j]) for j in range(c)] + z = lits[0] + for l in lits[1:]: + z = XOR(z, l) + return z + for T in range(v): + cl.append([-y[T], B(T, T)]) + for T in range(v): + for S in range(v): + if S != T and G[T, S] != 0: + cl.append([-y[T], -y[S], -B(T, S)]) + for i, T in enumerate(K): + cl.append([y[T]]) + for j in range(c): + cl.append([Uv[T][j]] if j == i else [-Uv[T][j]]) + cl.append([Wv[T][j]] if j == i else [-Wv[T][j]]) + card = CardEnc.atmost(lits=[-x for x in y], bound=k, vpool=pool, encoding=EncType.seqcounter) + cl.extend(card.clauses) + return cl, y, Uv, Wv, L, G, lab, K + + +if __name__ == '__main__': + n, c, k = map(int, sys.argv[1:4]) + T = float(sys.argv[4]) if len(sys.argv) > 4 else 3600 + K = None + if len(sys.argv) > 6: # clique orbit json, orbit index + import json + K = json.load(open(sys.argv[5]))['orbits'][int(sys.argv[6])]['rep'] + cl, y, Uv, Wv, L, G, lab, K = build(n, c, k, K) + print('n=%d c=%d k=%d clauses=%d clique=%s' % (n, c, k, len(cl), K), flush=True) + s = Cadical153(bootstrap_with=cl) + import threading + tm = threading.Timer(T, lambda: s.interrupt()); tm.start() + t0 = time.time() + r = s.solve_limited(expect_interrupt=True) + tm.cancel() + res = {True: 'SAT', False: 'UNSAT', None: 'TIMEOUT'}[r] + print('result', res, 'sec %.1f' % (time.time() - t0), flush=True) + if r: + m = set(x for x in s.get_model() if x > 0) + dead = [t for t in range(len(L)) if y[t] not in m] + print('dead', dead, flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/rank.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/rank.py new file mode 100644 index 0000000000..500b2b215f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/rank.py @@ -0,0 +1,115 @@ +"""Rank candidate unit families by a helper-adjusted estimate of the achievable saving (MODEL; see est_model.py). + +Per candidate (side, h, v, cst, z): + X* certified #595 ceiling (skel.X, best odd s in 3..15) at x = cst / v [exact interval upper end] + rho* break-even helpers per port: the rho at which the calibrated model estimate (best s) equals the calibrated + model estimate of the E8 word of the same side at its shipped intensity and best s (bit #555 at + rho = 439/111, complex #568 at rho = 496/112): same model, same s freedom on both sides + a_est calibrated model estimate at three helper-intensity hypotheses (E8's shipped intensity transferred): + rho_same = rho_E8 (helpers per port unchanged) + rho_dim = rho_E8 * (z/(h-1)) / (z_E8/(h_E8-1)) (readers per window dimension) <- central + rho_read = rho_E8 * z / z_E8 (readers per port) + in both piece-scaling modes ('abs' pessimistic, 'rel' optimistic). +Candidates: E8 references; triple family T_h (bit cst = h(h-1), complex cst = h^2: centre decoders, checked by +triple.py); Lorentzian I_{11,1}, I_{13,1} (label decoders, cst 110 / 156, checked by lorentz.py). +Usage: python rank.py REPO_ROOT [--json out] +""" +import json +import sys +from fractions import Fraction as Fr +from math import comb + +REPO = sys.argv[1] +sys.path.insert(0, REPO + '/research/format-limits/scripts') +sys.dont_write_bytecode = True +from skel import X # noqa: E402 + +import est_model as E # noqa: E402 + +REF = {'bit': dict(h=8, rho=Fr(439, 111), z=63, a=1.7331e-3), + 'complex': dict(h=9, rho=Fr(496, 112), z=52, a=1.5987e-3)} + + +def xstar(h, x): + best = None + for s in range(3, 17, 2): + r = X(s, h, 1, Fr(x)) + if r and (best is None or r['hi'] > best[1]): + best = (s, r['hi']) + return best + + +def candidates(): + C = [dict(name='E8 bit 111/63 (#555)', side='bit', h=8, v=111, cst=63, z=63), + dict(name='E8 bit 120/72', side='bit', h=8, v=120, cst=72, z=63), + dict(name='E8 complex 112/64 (#568)', side='complex', h=9, v=112, cst=64, z=52), + dict(name='I_{11,1} bit', side='bit', h=11, v=220, cst=110, z=Fr(165 * 115 + 55 * 129, 220)), + dict(name='I_{13,1} bit', side='bit', h=13, v=364, cst=156, z=Fr(286 * 183 + 78 * 231, 364))] + for h in (10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28, 30): + C.append(dict(name='T_%d bit' % h, side='bit', h=h, v=comb(h, 3), cst=h * (h - 1), z=3 * comb(h - 3, 2))) + for h in (12, 14, 16, 18, 20, 22, 24, 26, 28, 30): + C.append(dict(name='T_%d complex' % h, side='complex', h=h, v=comb(h, 3), cst=h * h, + z=comb(h - 3, 3) + 3 * (h - 3))) + return C + + +def cal_est(side, h, x, rho, mode, cal): + b = E.estimate(side, h, x, rho, mode) + return (None, None, None) if b is None else (b[0], b[1] * cal, b[2]) + + +def breakeven(side, h, x, mode, cal, target): + lo, hi = Fr(0), Fr(400) + f = lambda r: (cal_est(side, h, x, r, mode, cal)[1] or 0.0) + if f(lo) < target: + return 0.0 + if f(hi) >= target: + return float(hi) + for _ in range(40): + mid = (lo + hi) / 2 + if f(mid) >= target: + lo = mid + else: + hi = mid + return float(lo) + + +def main(): + rows = [] + cals = {(sd, md): E.calibration(sd, md) for sd in ('bit', 'complex') for md in ('abs', 'rel')} + print('calibration factors', {k: round(v, 4) for k, v in cals.items()}) + target = {sd: cal_est(sd, REF[sd]['h'], Fr(63, 111) if sd == 'bit' else Fr(64, 112), REF[sd]['rho'], 'abs', + cals[(sd, 'abs')])[1] for sd in ('bit', 'complex')} + print('E8 targets (calibrated model, best s):', {k: '%.4e' % v for k, v in target.items()}) + for c in candidates(): + sd, h = c['side'], c['h'] + x = Fr(c['cst'], c['v']) + ref = REF[sd] + xs = xstar(h, x) + rh = {'same': ref['rho'], + 'dim': ref['rho'] * Fr(c['z']) / (h - 1) / Fr(ref['z'], ref['h'] - 1), + 'read': ref['rho'] * Fr(c['z']) / ref['z']} + row = dict(name=c['name'], side=sd, h=h, v=c['v'], cst=c['cst'], x=float(x), z=float(c['z']), + Xstar=float(xs[1]) if xs else None, Xs=xs[0] if xs else None, + rho={k: float(v) for k, v in rh.items()}) + for md in ('abs', 'rel'): + row['rho_star_' + md] = breakeven(sd, h, x, md, cals[(sd, md)], target[sd]) + for k, r in rh.items(): + s_, a_, ch = cal_est(sd, h, x, r, md, cals[(sd, md)]) + row['a_%s_%s' % (k, md)] = a_ + row['s_%s_%s' % (k, md)] = s_ + if k == 'dim' and md == 'abs' and ch: + row['chain_dim_abs'] = {kk: round(vv, 5) for kk, vv in ch.items()} + rows.append(row) + f = lambda v: '%.3e' % v if v else ' - ' + print('%-26s h=%2d v=%5d x=%.4f z=%7.1f X*=%s | rho*: abs %6.2f rel %6.2f | rho same/dim/read %5.2f/%6.2f/%7.2f | ' + 'a_dim abs %s rel %s | a_same abs %s | a_read abs %s' + % (row['name'], h, c['v'], row['x'], row['z'], f(row['Xstar']), row['rho_star_abs'], row['rho_star_rel'], + row['rho']['same'], row['rho']['dim'], row['rho']['read'], f(row['a_dim_abs']), f(row['a_dim_rel']), + f(row['a_same_abs']), f(row['a_read_abs'])), flush=True) + if '--json' in sys.argv: + json.dump(rows, open(sys.argv[sys.argv.index('--json') + 1], 'w'), indent=1, default=str) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/twocover.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/twocover.py new file mode 100644 index 0000000000..f0d98c4c65 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/twocover.py @@ -0,0 +1,39 @@ +"""Exact test: can the readers of a source be covered by TWO hyperplane sections (i.e. two branch frames of any +dimension)? Readers T(s) = lines orthogonal to s. Two sections {t : n1.t = 0} and {t : n2.t = 0} (n1, n2 nonzero on +span T(s)) cover T(s) iff the quadratic form q(y) = (n1.y)(n2.y) vanishes on every reader, and q is then a nonzero +quadratic form on span T(s). So: if the only quadratic form on span T(s) vanishing on all readers is 0, the branch +cover number is >= 3 (no two branches suffice in any design). Computed exactly over Q (sympy nullspace). +If the space is nonzero we report its dimension (a 2-cover may or may not exist; S3 gives certificates for pairs). +Usage: python twocover.py n (n = 8: E8 control)""" +import sys, itertools +import numpy as np +import sympy as sp +sys.dont_write_bytecode = True +import units as U + + +def test(L, G, s, Q): + R = [t for t in range(len(L)) if t != s and G[s, t] == 0] + M = sp.Matrix(L[R].tolist()) + B = M.T.columnspace() # basis of span T(s) in ambient coordinates + Bm = sp.Matrix.hstack(*B) # ambient x r + Y = [Bm.solve_least_squares(sp.Matrix(L[t].tolist())) for t in R] # exact coordinates (consistent system) + r = Bm.shape[1] + idx = [(i, j) for i in range(r) for j in range(i, r)] + A = sp.Matrix([[y[i] * y[j] * (1 if i == j else 2) for (i, j) in idx] for y in Y]) + ns = A.nullspace() + return len(R), r, len(ns) + + +if __name__ == '__main__': + n = int(sys.argv[1]) + if n == 8: + L = U.roots_E8(); G = U.gram_doubled(L); reps = [('E8 root', 0)]; Q = None + else: + L, G, lab = U.lorentz_family(n) + LN = list(itertools.combinations(range(n), 3)) + list(itertools.combinations(range(n), 2)) + reps = [('triple', 0), ('pair', LN.index((0, 1)))] + for name, s in reps: + z, r, d = test(L, G, s, None) + print('n=%d %s source: %d readers spanning %d dims; quadratic forms vanishing on all readers: dim %d -> %s' % ( + n, name, z, r, d, 'NO 2-cover (branch cover number >= 3)' if d == 0 else '2-cover not excluded'), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/units.py b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/units.py new file mode 100644 index 0000000000..358c72b4b5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/scripts/units.py @@ -0,0 +1,210 @@ +"""Exact line systems used by the S3 screens (integer coordinates, exact Gram matrices). + +Every unit is returned as (L, G, name) with L an integer v x n matrix of line representatives and G the exact integer +Gram matrix of the unit's bilinear form (diagonal 2). The F2 decoder pattern of #595 is: B_TT = 1 and B_TS = 0 whenever +G_TS != 0 (non-orthogonal lines); the entries at orthogonal pairs are free. + +Families + * ADE root systems A_n, D_n, E6, E7, E8 (doubled coordinates, G = L L^T / 4). + E6 is the root lines orthogonal to an A2 = , a = (1/2)^8, b = e1 + e2 (a.b = 1), as in #630; + the archived #620 screen used b = e1 - e2 (a.b = 0), which gives D6. + * Triple family T_h (upstream 03-motifs.tex, bit construction): the C(h,3) indicator vectors t_T of 3-subsets of [h] + with the form I - J/9, so G_ST = |S n T| - 1 (diagonal 2). + * Lorentzian I_{n,1} (n odd, #634 note 3): e_A + e_{n+1} (|A| = 3) and e_i - e_j, Lorentz form diag(1^n, -1). +""" +import itertools + +import numpy as np + + +def roots_A(n): + R = [] + for i in range(n + 1): + for j in range(i + 1, n + 1): + r = [0] * (n + 1); r[i] = 2; r[j] = -2; R.append(r) + return np.array(R, dtype=np.int64) + + +def roots_D(n): + R = [] + for i in range(n): + for j in range(i + 1, n): + for s in (1, -1): + r = [0] * n; r[i] = 2; r[j] = 2 * s; R.append(r) + return np.array(R, dtype=np.int64) + + +def roots_E8(): + """120 lines, doubled coordinates: +-2e_i +-2e_j (one sign class per line) and (+-1)^8 with an even number of + minus signs and first coordinate +1.""" + R = [list(r) for r in roots_D(8)] + for sg in itertools.product((1, -1), repeat=8): + if sg[0] == 1 and sg.count(-1) % 2 == 0: + R.append(list(sg)) + return np.array(R, dtype=np.int64) + + +def perp(R, w): + return R[(R @ np.asarray(w, dtype=np.int64)) == 0] + + +A_VEC = np.ones(8, dtype=np.int64) # (1/2)^8 doubled +B_E6 = np.array([2, 2, 0, 0, 0, 0, 0, 0]) # e1 + e2 doubled; a.b = 4/4 = 1 -> A2 with a +B_D6 = np.array([2, -2, 0, 0, 0, 0, 0, 0]) # e1 - e2 doubled; a.b = 0 -> A1 + A1 (archived "E6") + + +def roots_E7(): + return perp(roots_E8(), A_VEC) + + +def roots_E6(): + return perp(perp(roots_E8(), A_VEC), B_E6) + + +def roots_D6_archived(): + return perp(perp(roots_E8(), A_VEC), B_D6) + + +def gram_doubled(L): + G4 = L @ L.T + assert (G4 % 4 == 0).all() + G = G4 // 4 + assert (np.diag(G) == 2).all() + return G + + +def triple_family(h): + """lines t_T, T in C([h],3); G_ST = |S n T| - 1 (form I - J/9).""" + T = list(itertools.combinations(range(h), 3)) + M = np.zeros((len(T), h), dtype=np.int64) + for i, t in enumerate(T): + M[i, list(t)] = 1 + G = M @ M.T - 1 + return M, G, T + + +def lorentz_family(n): + """I_{n,1}: e_A + e_{n+1} (|A| = 3) and e_i - e_j; Lorentz Gram; binary labels: A, resp. [n] minus {i, j}.""" + vecs, labels = [], [] + for A in itertools.combinations(range(n), 3): + v = [0] * (n + 1) + for a in A: + v[a] = 1 + v[n] = 1 + vecs.append(v); labels.append([1 if i in A else 0 for i in range(n)]) + for i, j in itertools.combinations(range(n), 2): + v = [0] * (n + 1); v[i] = 1; v[j] = -1 + vecs.append(v); labels.append([0 if k in (i, j) else 1 for k in range(n)]) + L = np.array(vecs, dtype=np.int64) + Q = np.diag([1] * n + [-1]).astype(np.int64) + G = L @ Q @ L.T + return L, G, np.array(labels, dtype=np.int64) + + +# ------------------------------------------------------------------ exact linear algebra +def rank_q(rows): + """exact rank over Q (fraction-free Bareiss-style elimination on Python ints).""" + import math + red = [] + for r in rows: + r = [int(x) for x in r] + for pc, rr in red: + if r[pc]: + f, g = r[pc], rr[pc] + r = [g * x - f * y for x, y in zip(r, rr)] + c = math.gcd(*r) + if c > 1: + r = [x // c for x in r] + pc = next((j for j, x in enumerate(r) if x), None) + if pc is not None: + red.append((pc, r)) + return len(red) + + +def rank_mod_p(M, p): + """rank of an integer matrix over F_p (p prime).""" + A = (np.array(M, dtype=object) % p).tolist() + rk, rows, cols = 0, len(A), len(A[0]) if A else 0 + for c in range(cols): + piv = next((i for i in range(rk, rows) if A[i][c] % p), None) + if piv is None: + continue + A[rk], A[piv] = A[piv], A[rk] + inv = pow(A[rk][c], p - 2, p) + A[rk] = [(x * inv) % p for x in A[rk]] + for i in range(rows): + if i != rk and A[i][c] % p: + f = A[i][c] + A[i] = [(x - f * y) % p for x, y in zip(A[i], A[rk])] + rk += 1 + return rk + + +def f2_rank(M): + rows = [int(''.join('1' if x & 1 else '0' for x in row[::-1]) or '0', 2) for row in np.asarray(M)] + basis = {} + for x in rows: + while x: + hb = x.bit_length() - 1 + if hb in basis: + x ^= basis[hb] + else: + basis[hb] = x + break + return len(basis) + + +def gram_rank(G, idx): + """rank over Q of the principal block G[idx, idx] (= pairing rank of span(idx) with itself).""" + if len(idx) == 0: + return 0 + return rank_q(G[np.ix_(idx, idx)].tolist()) + + +def nonorth_clique(G): + """exact maximum set of pairwise non-orthogonal lines (identity block of the pattern => minrank lower bound).""" + import networkx as nx + v = len(G) + H = nx.Graph() + H.add_nodes_from(range(v)) + H.add_edges_from((i, j) for i in range(v) for j in range(i + 1, v) if G[i, j] != 0) + best, _ = nx.max_weight_clique(H, weight=None) + return sorted(best) + + +def _query(args): + solver, v, pattern, c, clique = args + out = solver(v, pattern, None, c, 'none', clique) + if out['result'] == 'SAT': + B = out['B'] + assert all(B[i, i] == 1 for i in range(v)) and all(B[i, j] == 0 for i, j in pattern) + return out['result'], out['sec'] + + +def minrank_f2(G, clique, cmax, solver=None, timeout=300): + """exact F_2 minrank of the pattern of G (B_TT = 1, B_TS = 0 where G_TS != 0), asymmetric decoders allowed. + Asks SAT for c = |clique|, |clique| + 1, ... < cmax; each query fixes the GL(c, F_2) gauge U_K = W_K = [I | 0] on + the clique K (solve_asym of #595's minrank_sat.py; a SAT answer is re-checked on the returned B). A query that does + not finish within `timeout` seconds is recorded as 'TIMEOUT' and the search stops there. + Returns (lo, hi, log): the minrank lies in [lo, hi]; lo == hi when it is determined (hi = cmax is attained by a + known decoder).""" + import multiprocessing as mp + v = len(G) + pattern = [(i, j) for i in range(v) for j in range(v) if i != j and G[i, j] != 0] + log = {} + lo = len(clique) + ctx = mp.get_context('fork') + for c in range(len(clique), cmax): + with ctx.Pool(1) as pool: + res = pool.apply_async(_query, ((solver, v, pattern, c, list(clique)),)) + try: + r, sec = res.get(timeout) + except mp.TimeoutError: + log[c] = ('TIMEOUT', timeout) + pool.terminate() + return lo, cmax, log + log[c] = (r, sec) + if r == 'SAT': + return c, c, log + lo = c + 1 + return cmax, cmax, log diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/verify.sh new file mode 100644 index 0000000000..9fa2b4b7ea --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/verify.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# S4 package: punctures of I_{11,1}/I_{13,1}, E8-skeleton features, calibrated model. No kappa claim. +# Usage: sh verify.sh /path/to/integer-mult-bounds-main /fresh/out [PY=python3.11] (~30-40 min on 10 cores) +set -eu +REPO=$1; OUT=$2; PY=${PY:-python3} +HERE=$(cd "$(dirname "$0")" && pwd) +mkdir -p "$OUT"; cd "$HERE/scripts" +run() { name=$1; shift; "$PY" -B "$@" > "$OUT/$name.log" 2>&1; } +# exact certificates and exact computations (fast) +run census census.py 11 13 +run partition11 partition.py 11 2 1 +run partition13 partition.py 13 7 3 +run twocover8 twocover.py 8 +run twocover11 twocover.py 11 +run twocover13 twocover.py 13 +run cellcover8 cellcover.py 8 +run cellcover11 cellcover.py 11 +run cellcover13 cellcover.py 13 +run orbits11 clique_orbits.py 11 "$OUT/clique_orbits11.json" +"$PY" -B -c "import clique_orbits as C; print(C.survival_bound(11))" > "$OUT/survival11.log" +# slower steps in parallel +run packbases11 packbases.py 11 & +run packbases13 packbases.py 13 & +run punct11 punct.py "$REPO" 11 & +run example example_pairlabel.py "$REPO" & +run frames8 frames.py 8 & +run frames11 frames.py 11 & +run model3 model3.py "$REPO" --json "$OUT/model3.json" & +for o in 0 1 2 3 4; do run sat19_o$o punct_sat.py 11 10 19 7200 "$OUT/clique_orbits11.json" $o & done +wait +"$PY" -B check.py "$HERE/expected" "$OUT" From b20f7bf01e607cb71b1a4aa6103bcce6c72f1a86 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 15:25:53 -0400 Subject: [PATCH 06/17] Freeze S4 expected logs; all five clique orbits UNSAT at k=19 Co-Authored-By: Claude Opus 5.5 --- research/e8-decoder-floors-and-cold14-notes/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index a447aba0e7..27bf28d9ff 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -54,9 +54,9 @@ On the bit side ranks are counted over Q, so a K-line unit is a rank-k unit with - Mass-neutral V pricing per end per call (V639.json): u4-type premix-only dim-8 ends ({1:+2, 2:−1, 4:+1, 5:−1}, width −1) ≈ −0.025, essentially neutral, consistent with #637's 23 LP-negative candidates. **Dim-7 factored end pairs (#627 type) ≈ +2.25 per pair: the positive direction.** This should carry over to the gauge44 lineage (#645 onward) and was not searched here. ## 7. I_{11,1} / I_{13,1}: punctures and helper structure (`s4-lorentzian-helpers/`) -Status: README and scripts are complete, and run1 logs are included. `expected/` is **not yet frozen**, and one SAT case (clique orbit 0 at k = 19) is still running. +Status: `expected/` is frozen from the complete run1 (all steps finished; the k = 19 SAT runs take ~2 h). No second fresh run has been done yet. - **Punctures never help the centre decoder** (exact matroid-partition certificate). The 220 lines of I_{11,1} split into 22 independent sets, 2 inside each centre reader set, so the cost is ≥ |L|/2 for every live set L. For I_{13,1}, three copies of the lines split into 91 sets, giving ≥ (3/7)|L|. Every label decoder satisfies x ≥ 1/2 for |P| ≤ 8 (x ≥ 3/7 for |P| ≤ 10 on I_{13,1}). -- **Minrank stays 11** after ≤ 19 punctures for 4 of the 5 maximum-clique S_11-orbits (SAT, no proof logs; orbit 0 pending). The 38,786 maximum cliques fall into 5 orbits, and a union bound shows any ≤ 21 punctures leave one alive. +- **Minrank stays 11** after ≤ 19 punctures for **all 5** maximum-clique S_11-orbits (SAT, no proof logs; orbit 0 UNSAT in 7,484 s). So any 10-total decoder needs ≥ 20 punctures. The 38,786 maximum cliques fall into 5 orbits, and a union bound shows any ≤ 21 punctures leave one alive. - **Helpers.** E8's R = 439 is 240 carriers (3 branches per source, forced by E7 being a 4-design) + 180 cell registers + 9 totals + 10 puncture carriers. In I_{11,1}: - triple sources also need ≥ 3 branches in any frame design (exact quadratic-form nullspace); - the cell cover number is 3; From 7e0ced087aad38f5a6af1f6ebb488b7c7741f607 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 19:08:38 -0400 Subject: [PATCH 07/17] Add exhaustive fusion/slot search results on the fused X54 words Co-Authored-By: Claude Opus 5.5 --- .../README.md | 16 +++ .../r7-fusion-search/REPORT.md | 8 ++ .../r7-fusion-search/fusion2.py | 74 +++++++++++++ .../r7-fusion-search/joint2.py | 100 ++++++++++++++++++ .../r7-fusion-search/x54site.py | 13 +++ 5 files changed, 211 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/REPORT.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/fusion2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/joint2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/x54site.py diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 27bf28d9ff..dbf781c747 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -71,3 +71,19 @@ Status: `expected/` is frozen from the complete run1 (all steps finished; the k - #892 (rank-8 coded helper) does change the local profile, but it is excluded by its own packing obstruction on the old word. - The only path from these savings to κ is a lower local prefix bound, which allows more replicas. Raising the charged payload does that directly: row slack 3,692 allows payload up to ~2,350, which is what T37 (#942) uses. - **Next real lever:** dim-7 factored end pairs (#627 type, local {1:+2, 2:+2, 5:−2}, width −4) on the U8 word. Priced at ideal packing, each is worth about **+1.7e-7 b per pair per call**, about 60× the T37 gain. + +## 9. Exhaustive local-rewrite search on the fused X54 words (`r7-fusion-search/`, 2026-10-13) +**Search target:** the composed words of #1239/#1218: #1193's mixed T37 construction with #1195's source-holder fusions and #1130's X54 slot. JFX is the joint-entrance provider and D4FX its group-627 variant. Prices are exact, from #1186's frame toolkit (b = #1094 cost units): X54 = −1,788 and the two fusions = −4,338 on the joint word. + +**Frames:** the exact frame optimum of both words equals their published profiles, so frame changes have no slack left. + +| family | candidates per word | feasible | gains | +|---|---|---|---| +| every read fusion × {B-after, B-before-next, tail, prepend} restores × {none, single/pair swaps, full exchange} (#1186 `repair.py`) | 2,238 fusions / 2,850 pricings | 969 | **0** | +| standalone SW4 read-set exchanges (all non-empty subsets per copy pair) | 50 | 0 (cone cap "L not inside U" at end-of-B target vertices) | 0 | +| #1186 `joint.py` triangle-hybrid family | 161 (JFX/D4FX); 259 on the fusions-only word | 24 | 0 | +| `joint2.py`: generalised slots over all donation/restore gap classes, with keep/swap/fuse per movable read (reproduces X54 exactly in 120 equivalent variants), non-target holders | 3,924 | 0 | 0 | +| a fusion analogue at the X54 site (in place of the slot) | 45 touching 54/589 | 4 | 0 (best +956, worse than the slot) | +| `joint2` with target holders (+2,940 per word) and `fusion2.py` (10,603 generalised fusions on JFX) | — | **partial**: stopped before completion | none found | + +**Conclusion (scoped):** on these words, no further width-neutral source-holder fusion, read exchange or triangle slot of the searched families gains. The remaining κ gains in the lineage come from other mechanisms, such as #1274's helper-donor cold kernels and #1252/#1268's idle-pair exchanges. Scripts `joint2.py` and `fusion2.py` extend #1186's exact-frame toolkit (credit mdumitrean #1186/#1195), which is not re-vendored here. diff --git a/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/REPORT.md b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/REPORT.md new file mode 100644 index 0000000000..3b607b4710 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/REPORT.md @@ -0,0 +1,8 @@ +# R7 progress +- [start] reading R6, PR list +- [setup] no newer kappa head than #1218/#1239 (0.001675809683500501). Using #1186 exact-frame-toolkit (~/imb-harness/r7/tk, IMB_ROOT=~/imb-r6c). Exact frame optimum of JFX/D4FX = published profiles (frames already optimal). Running repair.py fusion+repair sweep on both (~/imb-harness/r7/sweep). +- [understand] source-holder fusion (#1195): at a two-donation triangle slot (E->H2 for t2, X->H1 for t1), keep only X->H1 donation (+FULL restore) and replace E delivery by full SW4 read-set exchange of copy pair (E,X) (t2 reads X, pre-read of E deleted). X54 slot (#1130) = donations 367+=589 (t223), 335+=54 (t159) + swaps t219/t155. Toolkit joint.py family = {1..3 fused reads per copy pair} x {rest kept/swapped/partial}; repair.py = every read fusion + single/pair swap repairs + full exchange. Plan: repair+joint sweeps on JFX, D4FX and on F (pre-X54) to test 'fusion at X54 site' (X54 analog of #1195). +- [result] cost units (b=#1094 default): J 11683162362, X(J+X54) -1788, F(J+fusions) -4338, JFX = F-1788. X54-site source-holder fusion analog (t159 A54 H335 + exchange 589) on F: best +956 (worse than slot's -1788); 45 fusions touching 54/589 tried, only 4 feasible, none gain. So no 'X54 -> fusion' upgrade. +- [result] repair.py sweep (every read fusion x {B-after,B-before-next,tail,prepend restores} x {none, single/pair swaps, full exchange}) on JFX and D4FX: 2238 fusions each, 2850 exact pricings each, 969 feasible, 0 gains (1911s, 5 workers). Standalone SW4 exchange subsets (50 per word, all non-empty subsets per copy pair): all 50 infeasible (cone caps: 'L not inside U' at end-of-B target vertices). Next: joint.py triangle-hybrid family on JFX/D4FX/F. +- [result] joint.py family (#1186) on JFX/D4FX: 161 cands, 24 ok, 0 gains; on F 259, 0 gains -- but it does NOT contain the X54 slot (donation position fixed at lwH+1). Wrote joint2.py (generalized: all donation/restore gap classes, keep/swap/fuse per movable read). Validated: on F it reproduces X54 slot = -1788 (120 equivalent variants ok). Counts: JFX 3924, D4FX 3924, F 42132 (38264 at pair 589). Running JFX/D4FX. +- [result] joint2 (non-target holders) JFX/D4FX: 3924 each, ALL infeasible (exact frame solver). Extended to target holders (as joint.py allows): +2940 candidates each, running (joint2b). fusion2.py (generalized fusion, all gap classes, alone + with full exchange): 10603 cands/2305 (t,A,H) on JFX, queued. diff --git a/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/fusion2.py b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/fusion2.py new file mode 100644 index 0000000000..35f990d3ed --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/fusion2.py @@ -0,0 +1,74 @@ +"""fusion2: generalized read fusion sweep (superset of fusion.py + repair.py's base fusions). +A target t reads A (coef a, at iA) and H (coef h, at iH), H any non-target register (entrance pre-read reads included). +Donate q*A into H (q = a/h dyadic) at EVERY gap class p in the valid window, delete t's read of A, restore H -= q*A at +every B gap class up to H's next read/scale or A's next write, every early-tail class, or as FULL prepend. +Each fusion is priced alone and with the full read-set exchange of the copy pair containing A or H (as in #1195). +usage: python -B fusion2.py WORD.json.gz OUT.json [--workers N] [--count]""" +import argparse, collections, json, multiprocessing as mp, time +from fractions import Fraction as Q +import harness as C +from fusion import index, dyadic, any_in +from exchange import copy_pairs, movable_reads, is_target +from joint import swap_edit, merge +from joint2 import gap_classes +def candidates(w): + I = index(w); B = I['B']; nB = len(B); out = [] + by_t = collections.defaultdict(list) + for i, e in enumerate(B): + if e[0] == 'add' and is_target(e[1]): by_t[e[1]].append((i, e[2], Q(e[3], e[4]))) + for t, rs in by_t.items(): + for (iA, A, a) in rs: + for (iH, H, ch) in rs: + if H == A or iH == iA or t in (A, H): continue + if any_in(I['RD'][t] + I['SC'][t], min(iA, iH), max(iA, iH) + 1): continue + q = a / ch + if not dyadic(q): continue + othersH = [x for x in I['RD'][H] + I['SC'][H] if x != iH] + lo = max([x + 1 for x in othersH if x < iH], default=0) + ev = set(I['W'][H]) | set(I['W'][A]) | set(I['RD'][A]) | set(othersH) + for p in gap_classes(ev, lo, iH): + if any_in(I['W'][A], min(p, iA + 1), max(p, iA + 1)): continue # A holds the value t read + g = ['add', H, A, q.numerator, q.denominator, []]; gr = ['add', H, A, -q.numerator, q.denominator, []] + nxt = min([x for x in othersH if x > iH] + [x for x in I['W'][A] if x >= p] + [nB]) + rests = [] + for rp in gap_classes(ev, iH + 1, nxt): rests.append(("B%d" % rp, dict(insert=[(p, g), (rp, gr)]))) + if not any_in(I['W'][A], p, nB) and not any_in(othersH, iH + 1, nB): + jmax = min(I['TW'][A] + I['TR'][H] + [len(I['T'])]) + tev = {k for k, e in enumerate(I['T']) if H in C.regs(e) or A in C.regs(e)} + for tp in sorted({0, jmax} | {k + 1 for k in tev if k + 1 <= jmax}): rests.append(("T%d" % tp, dict(insert=[(p, g)], tail=[(tp, gr)]))) + if A not in I['low'] and H not in I['low'] and not I['TW'][A] and not I['TR'][H]: rests.append(("P", dict(insert=[(p, g)], prepend=[gr]))) + for tag, e in rests: + ed = dict(delete=[iA], insert=e.get('insert', []), tail=e.get('tail', []), prepend=e.get('prepend', [])) + out.append(dict(t=t, A=A, H=H, iA=iA, iH=iH, p=p, rest=tag, ed=ed)) + return out +P = None; PAIRS = None +def init(path): + global P, PAIRS + P = C.Pricer(path); PAIRS = copy_pairs(P.base) +def run(c): + best = None; tried = [] + eds = [((), c['ed'])] + for h, (r, ic) in PAIRS.items(): + if c['A'] in (h, r) or c['H'] in (h, r): + rest = [m for m in movable_reads(P.base, h, r, ic) if m[0] not in c['ed']['delete']] + ss = [swap_edit(P.base, h, r, ic, m) for m in rest] + if rest and all(ss): + m2 = merge([c['ed']] + ss) + if m2: eds.append(((("exchange", h),), m2)) + for key, ed in eds: + x = P.price(ed, timeout=120); tried.append((key, x.get('status'))) + if x.get('delta') is not None and (best is None or x['delta'] < best['delta']): best = dict(delta=x['delta'], steps=key, profile=x['profile']) + return dict({k: v for k, v in c.items() if k != 'ed'}, tried=tried, best=best) +if __name__ == "__main__": + ap = argparse.ArgumentParser(); ap.add_argument("word"); ap.add_argument("out"); ap.add_argument("--workers", type=int, default=4); ap.add_argument("--count", action="store_true") + a = ap.parse_args(); cs = candidates(C.load(a.word)); print("fusion2 candidates:", len(cs), "distinct (t,A,H):", len({(c['t'], c['A'], c['H']) for c in cs}), flush=True) + if a.count: raise SystemExit + t0 = time.time(); res = [] + with mp.get_context("fork").Pool(a.workers, initializer=init, initargs=(a.word,), maxtasksperchild=40) as pool: + for n, r in enumerate(pool.imap_unordered(run, cs, chunksize=1), 1): + res.append(r) + if n % 500 == 0: print(f" {n}/{len(cs)} {time.time()-t0:.0f}s feasible {sum(1 for x in res if x['best'])} gains {sum(1 for x in res if x['best'] and x['best']['delta'] < 0)}", flush=True) + gains = sorted([x for x in res if x['best'] and x['best']['delta'] < 0], key=lambda x: x['best']['delta']) + print(f"done {len(res)} in {time.time()-t0:.0f}s feasible {sum(1 for x in res if x['best'])} gains {len(gains)}") + for x in gains[:30]: print(" gain", -x['best']['delta'], x['t'], x['A'], x['H'], x['p'], x['rest'], x['best']['steps'], x['best']['profile']) + json.dump(res, open(a.out, "w")) diff --git a/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/joint2.py b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/joint2.py new file mode 100644 index 0000000000..320139dc44 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/joint2.py @@ -0,0 +1,100 @@ +"""joint2: generalized triangle-hybrid / source-holder family per copy pair (superset of joint.py). + +Differences from joint.py: (1) the holder donation position p ranges over EVERY gap class (positions between consecutive +B events touching H or src) in the window where src keeps the value t read and H is neither read by another register nor +scaled before t's read of H; (2) restores range over every B gap class after t's read of H up to the next read/scale of H or +write of src, every early-tail position class, and the FULL prepend; (3) each movable read independently chooses +keep / swap / fuse(option) (cartesian product). Exactness is re-checked by harness.Pricer (mod p) and frames solved exactly. +usage: python -B joint2.py WORD.json.gz OUT.json [--workers N] [--count]""" +import argparse, itertools, json, multiprocessing as mp, time, collections +from fractions import Fraction as Q +import harness as C +from fusion import index, dyadic, any_in +from exchange import copy_pairs, movable_reads, is_target +from joint import swap_edit, merge +def reads_of(B, t): + return [(i, e[2], Q(e[3], e[4])) for i, e in enumerate(B) if e[0] == 'add' and e[1] == t] +def gap_classes(events, lo, hi): + """representative insertion positions in [lo, hi]: one per gap between consecutive events (position = event index + 1).""" + pts = sorted({lo} | {x + 1 for x in events if lo <= x + 1 <= hi}) + return pts +def holder_options(w, I, m, h, r, ic): + j, t, src, (a0, b0) = m; a = Q(a0, b0); B = I['B']; nB = len(B); out = [] + for (iH, H, cH) in reads_of(B, t): + if iH == j or H in (h, r, t): continue + q = a / cH + if not dyadic(q): continue + lo_t, hi_t = min(j, iH), max(j, iH) + if any_in(I['RD'][t] + I['SC'][t], lo_t, hi_t + 1): continue + othersH = [x for x in I['RD'][H] + I['SC'][H] if x != iH] + # donation window: p <= iH, no other read/scale of H in [p, iH), src unchanged between p and j + lo = max([x + 1 for x in othersH if x < iH] + [ic + 1 if src == h else 0]) + lo = max(lo, max([x + 1 for x in I['W'][src] if x < min(j, iH)], default=0)) if j < iH else lo + if j < iH: lo = max(lo, max([x + 1 for x in I['W'][src] if x < j], default=0)) + else: lo = max(lo, j + 1) if any_in(I['W'][src], j + 1, iH) else lo + if lo > iH: continue + ev = set(I['W'][H]) | set(I['W'][src]) | set(I['RD'][src]) | set(othersH) + for p in gap_classes(ev, lo, iH): + if j < iH and p <= j and any_in(I['W'][src], p, j): continue + if (j < iH and p > j and any_in(I['W'][src], j + 1, p)) or (j > iH and any_in(I['W'][src], p, j)): continue + g = ['add', H, src, q.numerator, q.denominator, []]; gr = ['add', H, src, -q.numerator, q.denominator, []] + nxt = min([x for x in othersH if x > iH] + [x for x in I['W'][src] if x > iH] + [nB]) + rs = [] + for rp in gap_classes(ev, iH + 1, nxt): + if any_in(I['W'][src], p, rp): continue + rs.append(("B%d" % rp, dict(insert=[(p, g), (rp, gr)]))) + if not any_in(I['W'][src], p, nB) and not any_in(othersH, iH + 1, nB): + jmax = min(I['TW'][src] + I['TR'][H] + [len(I['T'])]) + tev = {k for k, e in enumerate(I['T']) if H in C.regs(e) or src in C.regs(e)} + for tp in sorted({0, jmax} | {k + 1 for k in tev if k + 1 <= jmax}): rs.append(("T%d" % tp, dict(insert=[(p, g)], tail=[(tp, gr)]))) + if src not in I['low'] and H not in I['low'] and not I['TW'][src] and not I['TR'][H]: rs.append(("P", dict(insert=[(p, g)], prepend=[gr]))) + for tag, e in rs: + ed = dict(delete=[j], insert=e.get('insert', []), tail=e.get('tail', []), prepend=e.get('prepend', [])) + out.append((dict(H=H, iH=iH, p=p, rest=tag), ed)) + return out +def candidates(w): + I = index(w); out = [] + for h, (r, ic) in copy_pairs(w).items(): + M = movable_reads(w, h, r, ic) + if not M: continue + choices = [] + for m in M: + ch = [("keep", None, dict(delete=[], insert=[], tail=[], prepend=[]))] + s = swap_edit(w, h, r, ic, m) + if s: ch.append(("swap", None, s)) + ch += [("fuse", o, e) for o, e in holder_options(w, I, m, h, r, ic)] + choices.append(ch) + for combo in itertools.product(*choices): + if all(c[0] == "keep" for c in combo): continue + if not any(c[0] == "fuse" for c in combo): continue # pure exchanges: xsweep.py + ed = merge([c[2] for c in combo]) + if ed is None: continue + out.append(dict(h=h, r=r, choice=[(m[0], m[1], m[2], c[0], c[1]) for m, c in zip(M, combo)], ed=ed)) + return out +P = None +def init(path): + global P + P = C.Pricer(path) +def run(c): + x = P.price(c['ed'], timeout=120) + return dict(c, status=x.get('status'), delta=x.get('delta'), profile=x.get('profile')) +if __name__ == "__main__": + ap = argparse.ArgumentParser(); ap.add_argument("word"); ap.add_argument("out"); ap.add_argument("--workers", type=int, default=4) + ap.add_argument("--count", action="store_true"); ap.add_argument("--pairs", default=""); ap.add_argument("--skip", default="") + a = ap.parse_args(); w = C.load(a.word); cs = candidates(w) + if a.pairs: cs = [c for c in cs if str(c['h']) in a.pairs.split(",")] + if a.skip: + done = {json.dumps(r["ed"], sort_keys=True) for r in json.load(open(a.skip))}; n0 = len(cs) + cs = [c for c in cs if json.dumps(json.loads(json.dumps(c["ed"])), sort_keys=True) not in done]; print("skipped", n0 - len(cs)) + per = collections.Counter(c['h'] for c in cs); print("joint2 candidates:", len(cs), dict(per), flush=True) + if a.count: raise SystemExit + t0 = time.time(); res = [] + with mp.get_context("fork").Pool(a.workers, initializer=init, initargs=(a.word,), maxtasksperchild=40) as pool: + for n, r in enumerate(pool.imap_unordered(run, cs, chunksize=1), 1): + res.append(r) + if n % 200 == 0: print(f" {n}/{len(cs)} {time.time()-t0:.0f}s gains {sum(1 for x in res if x['delta'] is not None and x['delta'] < 0)}", flush=True) + st = collections.Counter(r['status'] for r in res) + gains = sorted([r for r in res if r['delta'] is not None and r['delta'] < 0], key=lambda r: r['delta']) + print(f"done {len(res)} in {time.time()-t0:.0f}s status {dict(st)} gains {len(gains)}") + for g in gains[:40]: print(" gain", -g['delta'], "pair", g['h'], g['r'], g['choice'], g['profile']) + json.dump(res, open(a.out, "w")) diff --git a/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/x54site.py b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/x54site.py new file mode 100644 index 0000000000..e6cf6fe50d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r7-fusion-search/x54site.py @@ -0,0 +1,13 @@ +import sys; sys.path.insert(0,'.') +import harness as C, repair as R, fusion as G +from exchange import copy_pairs, movable_reads +F=sys.argv[1] +R.init(F); w=R.P.base +h=589; r,ic=copy_pairs(w)[h]; print('pair',h,r,ic, 'movable', movable_reads(w,h,r,ic)) +cs=[c for c in G.candidates(w) if 54 in (c['A'],c['H']) or 589 in (c['A'],c['H'])] +print('fusions touching 54/589:',len(cs)) +best=[] +for c in cs: + x=R.repair(c) + if x['best']: best.append((x['best']['delta'],c['t'],c['A'],c['H'],c['p'],c['rest'],x['best']['steps'])) +for b in sorted(best)[:15]: print(b) From 1f579161888e9c17399df6612dc643471d652c57 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 21:18:18 -0400 Subject: [PATCH 08/17] Add extension-ring copy-cost floors: Eisenstein and Hurwitz excluded, Gaussian >= 40 Co-Authored-By: Claude Opus 5.5 --- .../README.md | 13 ++ .../s5-extension-ring-floors/README.md | 113 ++++++++++++ .../s5-extension-ring-floors/expected.txt | 19 ++ .../scripts/eisen_floor.py | 107 +++++++++++ .../s5-extension-ring-floors/scripts/flats.py | 26 +++ .../scripts/gauss_bound40.py | 77 ++++++++ .../scripts/gauss_c15.py | 56 ++++++ .../scripts/gauss_lp3.py | 23 +++ .../scripts/gauss_pi.py | 28 +++ .../scripts/hurwitz_direct.py | 40 +++++ .../scripts/hurwitz_floor.py | 75 ++++++++ .../s5-extension-ring-floors/scripts/lines.py | 166 ++++++++++++++++++ .../s5-extension-ring-floors/verify.sh | 14 ++ 13 files changed, 757 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/expected.txt create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/eisen_floor.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_bound40.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_c15.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_lp3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_pi.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_direct.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_floor.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index dbf781c747..15311a37e4 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -87,3 +87,16 @@ Status: `expected/` is frozen from the complete run1 (all steps finished; the k | `joint2` with target holders (+2,940 per word) and `fusion2.py` (10,603 generalised fusions on JFX) | — | **partial**: stopped before completion | none found | **Conclusion (scoped):** on these words, no further width-neutral source-holder fusion, read exchange or triangle slot of the searched families gains. The remaining κ gains in the lineage come from other mechanisms, such as #1274's helper-donor cold kernels and #1252/#1268's idle-pair exchanges. Scripts `joint2.py` and `fusion2.py` extend #1186's exact-frame toolkit (credit mdumitrean #1186/#1195), which is not re-vendored here. + +## 10. Extension-ring E8: copy-cost floors over every decoder (`s5-extension-ring-floors/`, `verify.sh` ALL MATCH) +This follows up §4, where S1 obtained x_K = 1 only under the full-rank-totals premise. Here every F2 decoder on the orthogonality pattern is covered: any number of totals, asymmetric pairings, no star premise. Costs are #446 Theorem C costs Σ_j β_j. +- **Eisenstein / Witting (40 lines, w = 4): Σβ ≥ 40 = v for every decoder, so it is excluded** (checked plus proof). + - Sum-rank step: Σβ ≥ rank(G∘N), where N counts the totals covering each pair. + - If every line lattice span(T) ∩ E8 has odd determinant, G∘N reduces mod 2 to an invertible block-diagonal matrix. The Witting line lattices are A2 (det 3); the Hermitian diagonal 2+ω is a unit at the inert prime 2. + - Real E8 escapes this because its line lattices are √2·Z (det 2). That is consistent with 72 < 120. +- **Hurwitz (10 lines, w = 2): minimum over all decoders = 10 = v, so it is excluded** (checked plus a König/rank proof). The reduced SAT is UNSAT at cost ≤ 9, and the frame decoder attains 10. +- **Gaussian (60 lines, w = 4): Σβ ≥ 40 for every decoder (x_K ≥ 2/3, checked).** Not yet excluded: the float screen says x = 42/60 would still beat bit E8's ceiling (X* 6.33e-3), while 43/60 would not (5.66e-3). + - How the 40 is proved: 640 twelve-line cliques each force B[C,C] = I₁₂, and hits ≤ 192·rank holds on all 2271² flat pairs. + - The 2-adic method caps at 12, and an LP over clique families stays at 40. + - The best known decoder costs 60. A decoder at ≤ 42 would need ≥ 16 totals with at most 10 of full rank. + - **Open:** close the window 40–42. A pending SAT (`gauss_c15.py`) would show that every 15-total decoder costs exactly 60. diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/README.md b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/README.md new file mode 100644 index 0000000000..6e6f70e552 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/README.md @@ -0,0 +1,113 @@ +# E8 over extension rings: copy-cost floors under the #595 format (no κ claim) + +This package follows up #620 §4 (S1). It asks whether E8, viewed as a unit over a larger ring, can beat real E8 under the #595 format: + +- over Z[i]: 60 Gaussian lines, w = 4; +- over Z[ω]: the 40 Witting lines, w = 4; +- over the Hurwitz quaternions: 10 lines, w = 2. + +The quantity that decides it is x_K = Σ_j β_j / v. Here β_j is the #446 Theorem C pairing-rank cost of total j, counted in K-ranks, and the minimum runs over **every** F2 decoder on the K-orthogonality pattern: any number of totals, asymmetric W ≠ U allowed, and no star premise. + +To beat the bit E8 ceiling X* = 6.0005e-3, a w = 4 view needs x_K < ~0.708, so Σβ ≤ 42 for the Gaussian view. A w = 2 view needs x_K < ~0.86. These thresholds come from S1's float screen `fx.py`, which uses skel.X's profile with float roots. They are not certified here. + +Labels used below: +- **checked**: `verify.sh` recomputes the claim with exact arithmetic, or with SAT where stated (CaDiCaL via python-sat, no proof logs). +- **argued**: the proof is in this README. +- **recorded**: a run whose log is shipped; `verify.sh` does not repeat it. + +## Results + +| view | v | w | floor proven here | best decoder known | excluded? | +|---|---|---|---|---|---| +| Eisenstein (Witting) | 40 | 4 | **Σβ ≥ 40 for every decoder** (x_K ≥ 1) | 40 (frames) | **yes** | +| Hurwitz | 10 | 2 | **Σβ ≥ 10 for every decoder** (x_K ≥ 1) | 10 (frames) | **yes** | +| Gaussian | 60 | 4 | **Σβ ≥ 40 for every decoder** (x_K ≥ 2/3); Σβ = 60 for 15-total decoders (pending; see G2) | 60 (frames) | **no**: the window 40–42 is open | + +**Theorem (Eisenstein and Hurwitz).** Viewed over Z[ω] or over the Hurwitz order, E8 cannot beat real E8 in the #595 format. Under #446 Theorem C, every decoder of the K-orthogonality pattern pays Σ_j β_j ≥ v. This covers any number of totals, asymmetric pairings and non-star copy trees. So x_K ≥ 1, D_s/k = (s − 1)·v − s·x_K·v < 0 at every s, and there is no saving at all. **Gaussian E8 is not settled.** The floor proven here is 40 and exclusion needs 43. + +## Model (as in #595 C.6 and #446 Theorem C) + +- **Decoder.** B = Σ_j w_j u_j^T over F2, with readers R_j = supp w_j and sources A_j = supp u_j. +- **Pattern.** B_TT = 1, and B_TS = 0 whenever the lines T and S are K-non-orthogonal. B_TS is free on orthogonal pairs. +- **Cost.** By Theorem C, β_j/k ≥ rk(span A_j × span R_j), the rank of the real pairing divided by k = dim_Q K. For K-subspaces this is the K-rank of the Gram block G[R_j, A_j]. Since the form is positive definite, rk(X × Y) = dim Y − dim(Y ∩ X^⊥). +- **Cost-0 totals.** A total of cost 0 has R_j × A_j made only of orthogonal pairs. It touches only free entries and can be dropped. Every total that is kept costs ≥ 1. +- **Construction (checked).** `lines.py` builds the three views exactly from the 240 roots in doubled coordinates. The Gaussian view uses J = left multiplication by i on H². The Eisenstein view uses a fixed-point-free order-3 element ρ of W(E8), the product of the Coxeter elements of 4 orthogonal A2's. The Hurwitz view uses left multiplication by i, j, k. + +## E. Eisenstein: Σβ ≥ 40 for every decoder (argued; every ingredient checked by `eisen_floor.py`) + +**Lemma (sum-rank).** Let M_j = P_{R_j} G P_{A_j}, padded to v × v. Then Σ_j β_j ≥ Σ_j rank M_j ≥ rank(Σ_j M_j) = rank(G ∘ N), where N_TS = #{j : T ∈ R_j, S ∈ A_j} is an integer matrix. Because N ≡ B (mod 2), N_TT is odd and N_TS is even on every non-orthogonal pair. + +**Lemma (odd line lattices).** For every line T, pick a Z-basis of span(T) ∩ E8. Suppose every such line lattice has **odd** determinant. Then the real Gram G̃ on these bases is integral, its diagonal blocks are invertible mod 2, and its off-diagonal blocks vanish on orthogonal pairs. So G̃ ∘ (N ⊗ J) ≡ diag(G̃_TT) (mod 2), which has full rank. Hence rank_Q ≥ Σ_T dim T, that is, Σ_j β_j ≥ v in K-units. + +- **Eisenstein satisfies it.** Each line lattice is ⟨r, ρr⟩ ≅ A2, with Gram [[2, −1], [−1, 2]] and det 3 (checked for all 40 lines). +- **Complex form of the same proof.** Let h_E(x, y) = (⟨x,y⟩ − ω⟨x, ρy⟩)/4. It is Z[ω]-valued and sesquilinear (checked), and its K-ranks equal the Theorem C ranks (checked on 200 random blocks). Its diagonal is 2 + ω, of norm 3, so a unit at the inert prime 2. Hence (h_E ∘ N) mod 2 is (2+ω)·I over F4, of rank 40 (checked on random admissible N as a sanity test). +- **Why real E8 itself escapes.** Its line lattices are √2·Z (det 2), so the lemma does not apply. This is consistent with the shipped 72 < 120. +- **The other two views.** The Gaussian line lattices are √2·Z[i] (det 4) and the Hurwitz ones are D4 (det 4), so neither is covered. + +## H. Hurwitz: min Σβ = 10 exactly (argued; checked by `hurwitz_floor.py`, plus a direct SAT) + +**Structure (checked).** There are 10 lines and w = 2. Each line is orthogonal to exactly one other, so the orthogonal pairs form a perfect matching. Any two distinct lines span H². + +**Cost formula.** Because of that structure, a total costs one of three amounts: +- 0 if R × A has no equal-or-non-orthogonal pair; +- 2 if |R| ≥ 2 and |A| ≥ 2; +- 1 otherwise. + +This follows from rk = dim Y − dim(Y ∩ X^⊥) and is checked exactly on all 3,025 pairs with |R|, |A| ≤ 2. Larger sets add nothing, because their span is already H². + +**Proof that every decoder costs ≥ 10.** +1. Drop the cost-0 totals. +2. Let X be the sum of the cost-2 totals, so they cost 2g ≥ 2·rank X. Let ρ be the rows of the single-row totals and γ the columns of the single-column totals; these cost ≥ |ρ| + |γ|. +3. B + X vanishes outside the rows ρ and the columns γ. So on ρ' × γ' (the complements), X agrees with the pattern. +4. Let C = ρ' ∩ γ'. The C × C part of X is block-diagonal, with blocks [1] or [[1, *], [*, 1]], because non-orthogonal entries are 0. So rank X ≥ |C|/2. +5. Hence cost ≥ |C| + (10 − |ρ'|) + (10 − |γ'|) ≥ 10, using |ρ'| + |γ'| ≤ 10 + |C|. + +The 5 orthogonal pairs, used as frames, attain 10. + +**Checks.** +- The reduced form (2g + |ρ| + |γ| ≤ 9, for g = 0..4) is UNSAT. +- This closes S1's open item 2. A direct SAT run with S1-style encoding (`hurwitz_direct.py C`: arbitrary totals, exact cost formula, cost cap C) gives: + - C = 10: SAT (calibration). + - C = 9: see `logs/hurwitz_direct.log` (**recorded** if present). + +## G. Gaussian (not settled) + +**G1. Floor of 40 for every decoder (checked, `gauss_bound40.py`).** +- **Counting step.** Take each of the 640 twelve-line sets C of pairwise K-non-orthogonal lines. The pattern forces B[C, C] = I₁₂, and every total contributes a rank-1 F2 matrix there. So ≥ 12 totals hit C on both sides. +- **Reduction to flats.** For fixed spans, hit(R, A) is largest at R = lines(X) and A = lines(Y). +- **The inequality.** The script checks hit(X, Y) ≤ 192·rk(X, Y) on all 2271² flat pairs. Hits are exact integers. Ranks are certified lower bounds: integer pairing matrices, rank mod 2³¹ − 1. +- **Conclusion.** Σβ ≥ 12·640/192 = **40**. +- **Binding pair.** X is a 6-line 2-flat through ℓ and Y is the 15-line hyperplane ℓ^⊥: pairing rank 1, 192 hits. +- **Trying to improve it.** An LP over uniform orbit weights added every maximal clique size (7, 8, 10, 11, 12), the singletons, the non-orthogonal pairs and the whole set (minrank 15). It stays at 40 (`gauss_lp3.py` in the work dir; float LP, **recorded**). + +**G2. Decoders with exactly minrank = 15 totals.** +- **Why every total then costs 4.** With n = rank B = 15, W and U have full column rank. If every nonzero vector of col W and of col U has a support that spans K⁴, then each total pairs K⁴ with K⁴ at rank 4, and Σβ = 60. +- **The SAT question.** `gauss_c15.py` asks whether a valid 15-total decoder exists together with x ≠ 0 such that supp(Wx) lies in one of the 1500 three-dimensional flats. The calibration without the flat constraint is SAT in 1 s. The main run is **pending** (see `logs/gauss_c15.log`). If it comes back UNSAT, every 15-total decoder costs exactly 60. That would be the Gaussian analogue of #595's "9 totals × 8" premise, but without the star assumption. + +**G3. What the 2-adic method can give (checked, `gauss_pi.py`).** +- The Gaussian diagonal is 2 = −i·π². The off-diagonal entries are units on 32 pairs per line and π·unit on 12 pairs per line. +- The mod-π reduction of G ∘ N/2 is I plus free entries on the unit pairs. +- The unit graph splits into 12 five-cliques, so this method can certify at most 12. +- No per-line 2-adic rescaling helps either. Write p_T, q_T for the 2-adic valuations of the determinants of the row and column rescalings of line T. One needs p_T + q_T = −2 on every line and p_T + q_S ≥ 0 on every unit pair (argued). Adding the two pair inequalities for a unit pair (T, S) gives 0 ≤ (p_T + q_T) + (p_S + q_S) = −4, a contradiction. + +**Open (Gaussian).** +- A decoder with Σβ ≤ 42 would need ≥ 16 totals, most of them with a support confined to a hyperplane flat. By counting, n_full ≤ 10 totals of cost 4. +- The obvious next step is an LP with new kinds of Z-sets: rectangles C₁ × C₂ with a known minrank, or sets that the cheap (6-line 2-flat, ℓ^⊥) totals rarely hit on both sides. +- Alternatively, an exact SAT with sub-spanning totals. + +## Files (`scripts/`) + +| file | what | +|---|---| +| `lines.py` | exact E8 roots, the three views, K-ranks, pairing ranks, Hermitian Gram matrices | +| `flats.py` | all flats of a view (Gaussian: 2271) | +| `eisen_floor.py` | §E checks | +| `hurwitz_floor.py`, `hurwitz_direct.py` | §H checks; the direct SAT with a cost cap | +| `gauss_bound40.py`, `gauss_pi.py`, `gauss_c15.py` | §G1, G3, G2 | +| `verify.sh`, `expected.txt` | runs the checked items and compares the PASS/RESULT lines with the frozen output | + +```sh +PY=/path/to/python3.11 sh verify.sh # numpy, scipy, networkx, python-sat; ~3-6 min +``` + +Credits: builds on S1 (#620 §4), #595 (DreamingOfClouds, format limits) and #446 (Theorems A–C). Prepared with Claude Opus 5.5 via Claude Code. diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/expected.txt b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/expected.txt new file mode 100644 index 0000000000..607dc2c94a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/expected.txt @@ -0,0 +1,19 @@ +Gauss v 60 w 4 nonorth degree [44] +Eisen v 40 w 4 nonorth degree [27] +Hurwitz v 10 w 2 nonorth degree [8] +PASS eisen.gram: hE sesquilinear, diag 2+w (norm 3), 27 non-orthogonal pairs per line all of norm 1 +PASS eisen.linelattice: every line lattice has Gram [[2,-1],[-1,2]] (det 3, odd) +PASS eisen.krank: rank_K hE[R,A] == pairing rank on 200 random (R,A) +PASS eisen.reduction: (hE o N) mod 2 has F4-rank 40 for 50 random admissible N +PASS eisen.sumrank: sum_j rank >= rank(hE o N) on 20 random term families +RESULT eisen: every decoder (any number of totals, asymmetric) has sum_j beta_j >= 40 = v => x_K >= 1 +PASS hurwitz.structure: v 10, w 2, 5 orthogonal pairs (perfect matching), any two lines span H^2 +PASS hurwitz.costformula: exact pairing rank == formula on all 3025 (R,A) with |R|,|A| <= 2 +PASS hurwitz.sat: 2g + |rho| + |gamma| <= 9 is UNSAT for every g = 0..4 (CaDiCaL, no proof log) +PASS hurwitz.attained: frame decoder (5 orthogonal pairs) is a valid decoder of cost 10 +RESULT hurwitz: min over all decoders of sum_j beta_j = 10 = v => x_K = 1 +PASS gauss.pi: unit graph has 6144 five-cliques and a partition into 12 of them -> mod-pi bound <= 12 +PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all 2271x2271 flat pairs, histogram [5450, 369780, 2631390, 2150820, 1] +PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks) +RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43) +calibration (no flat constraint) SAT diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/eisen_floor.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/eisen_floor.py new file mode 100644 index 0000000000..ce0cceac1b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/eisen_floor.py @@ -0,0 +1,107 @@ +"""Eisenstein E8 (Witting, 40 lines, w = 4): every decoder on the pattern costs >= 40 (Theorem C pairing rank). + +Checks (exact): + 1. hE(x,y) = ( - w)/4 is Z[w]-valued, sesquilinear (hE(x,rho y) = w hE, hE(rho x,y) = w^2 hE), + diag = 2+w on all 40 representatives (norm 3: a 2-adic unit), |hE|^2 = 1 on the 27 non-orthogonal pairs per line. + 2. For random line sets R, A: K-rank of hE[R,A] (realified, exact) == Theorem-C pairing rank (real rank / 2). + 3. Line lattices: Gram of {r, rho r} is [[2,-1],[-1,2]] (det 3, odd) for every line. + 4. Reduction: for random integer N with N_TT odd and N_TS even on non-orthogonal pairs (arbitrary on orthogonal + pairs), (hE o N) mod 2 over F4 = Z[w]/2 has rank 40, hence rank_K(hE o N) = 40. + 5. Sum-rank: on random term families, sum_j rank hE[R_j,A_j] >= rank (hE o N). +""" +import sys, random; sys.path.insert(0, sys.path[0]) +from lines import * + +def f4_rank(Ma, Mb): + """rank over F4 = F2[w]/(w^2+w+1) of the matrix (Ma + Mb w) mod 2. Elements as 2-bit ints a + 2b.""" + mul = [[0]*4 for _ in range(4)] + def m(x, y): # (a+bw)(c+dw) = ac + (ad+bc)w + bd w^2, w^2 = w+1 + a, b = x & 1, x >> 1; c, d = y & 1, y >> 1 + r0 = (a*c + b*d) & 1; r1 = (a*d + b*c + b*d) & 1 + return r0 | (r1 << 1) + for x in range(4): + for y in range(4): mul[x][y] = m(x, y) + inv = {1: 1, 2: 3, 3: 2} + M = [[int(a % 2) | (int(b % 2) << 1) for a, b in zip(ra, rb)] for ra, rb in zip(Ma, Mb)] + n, mm = len(M), len(M[0]); r = 0 + for c in range(mm): + p = next((i for i in range(r, n) if M[i][c]), None) + if p is None: continue + M[r], M[p] = M[p], M[r] + iv = inv[M[r][c]]; M[r] = [mul[iv][x] for x in M[r]] + for i in range(n): + if i != r and M[i][c]: + f = M[i][c]; M[i] = [x ^ mul[f][y] for x, y in zip(M[i], M[r])] + r += 1 + return r + +def realify(Ma, Mb): + """a + b w acting on Q^2 = Q(w) (basis 1, w): mult by w maps 1->w, w->w^2=-1-w.""" + n, m = Ma.shape + R = np.zeros((2*n, 2*m), dtype=object) + for i in range(n): + for j in range(m): + a, b = int(Ma[i, j]), int(Mb[i, j]) + R[2*i:2*i+2, 2*j:2*j+2] = [[a, -b], [b, a - b]] + return R + +def main(seed=1, trials=200): + W = views(); S = W['Eisen']; V = S.reps; rho4 = W['_rho']; v = S.v + assert v == 40 and S.w == 4 + a, b = eisen_gram(V, rho4) + # 1. sesquilinearity on all representative pairs and their rho-images + rV = (V @ rho4.T) // 4 + a2, b2 = eisen_gram_pair(V, rV, rho4) + # hE(x, rho y) = w hE(x,y): w(a + b w) = a w + b(-1 - w) = -b + (a - b) w + assert (a2 == -b).all() and (b2 == a - b).all() + a3, b3 = eisen_gram_pair(rV, V, rho4) + # hE(rho x, y) = w^2 hE = (-1-w)(a+bw) = -a - (a) w - b w - b w^2 = -a + b + (-a) w ... compute: w^2(a+bw)=a w^2 + b w^3 = a(-1-w) + b + assert (a3 == -a + b).all() and (b3 == -a).all() + assert set(zip(np.diag(a).tolist(), np.diag(b).tolist())) == {(2, 1)} + nrm = a*a - a*b + b*b + off = ~np.eye(v, dtype=bool) + assert ((nrm > 0) & off == S.NO).all() and set(nrm[S.NO].tolist()) == {1} and (S.NO.sum(1) == 27).all() + print('PASS eisen.gram: hE sesquilinear, diag 2+w (norm 3), 27 non-orthogonal pairs per line all of norm 1') + # 3. line lattices + for T in range(v): + r = V[T]; rr = rho4 @ r // 4 + Gm = [[r @ r // 4, r @ rr // 4], [rr @ r // 4, rr @ rr // 4]] + assert Gm == [[2, -1], [-1, 2]] + print('PASS eisen.linelattice: every line lattice has Gram [[2,-1],[-1,2]] (det 3, odd)') + rnd = random.Random(seed) + # 2. K-rank of hE block == Theorem C pairing rank + for _ in range(trials): + R = rnd.sample(range(v), rnd.randint(1, 8)); A = rnd.sample(range(v), rnd.randint(1, 8)) + Rk = rank_q(realify(a[np.ix_(R, A)], b[np.ix_(R, A)]).tolist()) + assert Rk % 2 == 0 and Rk // 2 == S.pair_rank(R, A) + print(f'PASS eisen.krank: rank_K hE[R,A] == pairing rank on {trials} random (R,A)') + # 4. reduction + for _ in range(trials // 4): + N = np.zeros((v, v), dtype=np.int64) + for T in range(v): + for U_ in range(v): + if T == U_: N[T, U_] = 2 * rnd.randint(0, 3) + 1 + elif S.NO[T, U_]: N[T, U_] = 2 * rnd.randint(0, 3) + else: N[T, U_] = rnd.randint(0, 7) + assert f4_rank(a * N, b * N) == 40 + print(f'PASS eisen.reduction: (hE o N) mod 2 has F4-rank 40 for {trials//4} random admissible N') + # 5. sum-rank on random term families (not necessarily decoders) + for _ in range(trials // 10): + terms = [(rnd.sample(range(v), rnd.randint(1, 10)), rnd.sample(range(v), rnd.randint(1, 10))) for _ in range(rnd.randint(1, 6))] + N = np.zeros((v, v), dtype=np.int64) + for R, A in terms: + for T in R: + for U_ in A: N[T, U_] += 1 + lhs = sum(S.pair_rank(R, A) for R, A in terms) + rhs = rank_q(realify(a * N, b * N).tolist()) // 2 + assert lhs >= rhs + print(f'PASS eisen.sumrank: sum_j rank >= rank(hE o N) on {trials//10} random term families') + print('RESULT eisen: every decoder (any number of totals, asymmetric) has sum_j beta_j >= 40 = v => x_K >= 1') + +def eisen_gram_pair(X, Y, rho4): + A = X @ Y.T; Bm = X @ rho4 @ Y.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/flats.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/flats.py new file mode 100644 index 0000000000..b064c81896 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/flats.py @@ -0,0 +1,26 @@ +"""All flats (line sets of K-subspaces spanned by configuration lines) of a view, with dims. Exact.""" +import sys, pickle; sys.path.insert(0,sys.path[0]) +from lines import * +def flats(S): + v=S.v; out={} + def close(idx): + d=S.arank(idx); return frozenset(T for T in range(v) if S.arank(list(idx)+[T])==d), d + level=[ (frozenset([T]),1) for T in range(v)] + for f,d in level: out[f]=d + frontier=list(out.items()) + while frontier: + new={} + for f,d in frontier: + if d>=S.w: continue + for T in range(v): + if T in f or any(T in g and f= 40 (x_K >= 2/3). + +Argument. C ranges over the 640 twelve-line sets of pairwise K-non-orthogonal lines. On C x C the pattern forces +B[C,C] = I_12 (diagonal 1, all off-diagonal pairs non-orthogonal), and each total contributes a rank-1 F2 matrix there, +so at least 12 totals hit C on both sides (R_j & C != {} and A_j & C != {}). Summing: sum_j hit(R_j, A_j) >= 12*640. +For fixed spans X = span R_j, Y = span A_j the hit count is largest for R = lines(X), A = lines(Y) (flats), and this +script shows hit(X, Y) <= 192 * rank(X, Y) for all 2271^2 flat pairs, rank = K-rank of the pairing (Theorem C cost). +Hence sum_j beta_j >= 12*640/192 = 40. Binding pair: X = 6-line 2-flat containing l, Y = the 15-line hyperplane l^perp +(pairing rank 1, 192 hits). Mixing all clique sizes, singletons, non-orthogonal pairs and the whole set in an LP over +uniform orbit weights does not improve 40 (gauss_lp3.py in the work dir). +Ranks: exact integer pairing matrices, rank mod p = 2^31-1 (a certified lower bound on the rank over Q).""" +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from flats import flats +import networkx as nx + +def main(): + W = views(); S = W['Gauss']; J = W['_J']; v = S.v + F = flats(S); fl = sorted(F.items(), key=lambda t: (t[1], len(t[0]), sorted(t[0]))) + n = len(fl); assert n == 2271 + g = nx.Graph([(i, j) for i in range(v) for j in range(i+1, v) if S.NO[i, j]]) + C = [c for c in nx.find_cliques(g) if len(c) == 12] + assert len(C) == 640 and all(S.NO[a, b] for c in C for a, b in itertools.combinations(c, 2)) + Lo = np.zeros((n, v), np.int64) + for i, (f, d) in enumerate(fl): Lo[i, sorted(f)] = 1 + inc = np.zeros((v, len(C)), np.int64) + for k, c in enumerate(C): inc[c, k] = 1 + meet = (Lo @ inc > 0).astype(np.int64); hits = meet @ meet.T # exact integers + # exact integer bases (vectors r_T, J r_T), pairing ranks by batched elimination mod p = 2^31-1 + # (rank mod p <= rank over Q, so these are certified LOWER bounds; the claim only needs lower bounds) + def ibasis(f): + rows = [] + for T in sorted(f): + for x in (S.reps[T], J @ S.reps[T]): + if rank_q(rows + [x]) > len(rows): rows.append(x) + return np.array(rows, np.int64) + Bs = [ibasis(f) for f, d in fl] + assert all(len(b) == 2 * d for b, (f, d) in zip(Bs, fl)) + P = 2147483647 + def modrank(A): + A = A % P; N, r, c = A.shape; used = np.zeros((N, r), bool); idx = np.arange(N) + for col in range(c): + cand = (A[:, :, col] != 0) & ~used; has = cand.any(1) + if not has.any(): continue + pr = cand.argmax(1); it = idx[has]; prow = A[it, pr[has]] + inv = np.array([pow(int(x), P - 2, P) for x in prow[:, col]], np.int64) if len(it) < 64 else modinv(prow[:, col]) + prow = (prow * inv[:, None]) % P + fac = A[it, :, col].copy(); fac[np.arange(len(it)), pr[has]] = 0 + A[it] = (A[it] - (fac[:, :, None] * prow[:, None, :]) % P) % P + used[it, pr[has]] = True + return used.sum(1) + def modinv(x): + res = np.ones_like(x); base = x % P; e = P - 2 + while e: + if e & 1: res = (res * base) % P + base = (base * base) % P; e >>= 1 + return res + R = np.zeros((n, n), np.int64) + groups = {} + for i, b in enumerate(Bs): groups.setdefault(len(b), []).append(i) + for da, ia in groups.items(): + A = np.stack([Bs[i] for i in ia]) + for db, ib in groups.items(): + Bm = np.stack([Bs[i] for i in ib]) + M = np.einsum('aik,bjk->abij', A, Bm).reshape(-1, da, db) + r = modrank(M).reshape(len(ia), len(ib)); assert (r % 2 == 0).all() + R[np.ix_(ia, ib)] = r // 2 + print(f'PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all {n}x{n} flat pairs, histogram {np.bincount(R.flatten()).tolist()}') + assert (hits <= 192 * R).all() + worst = np.argwhere((hits == 192 * R) & (R > 0)) + print('PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks)') + a, b = worst[0] + print(f' binding example: |X| = {len(fl[a][0])} (dim {fl[a][1]}), |Y| = {len(fl[b][0])} (dim {fl[b][1]}), rank {R[a,b]}, hits {hits[a,b]}; {len(worst)} binding pairs') + print('RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43)') + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_c15.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_c15.py new file mode 100644 index 0000000000..93289c3fd4 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_c15.py @@ -0,0 +1,56 @@ +"""Gaussian E8 (60 lines, w = 4): every decoder with exactly c = minrank = 15 totals costs exactly 60 (Theorem C). + +B = W U^T with W, U in F2^{60x15}; rank B >= 15 (minrank, S1: c = 14 UNSAT) so W, U have full column rank and every +total j has w_j = W e_j != 0, u_j != 0. If every nonzero vector of col(W) and of col(U) has a support spanning K^4, +then every total pairs span(R_j) = span(A_j) = K^4 with rank 4 (positive definite), so sum_j beta_j = 15 * 4 = 60. +SAT question (asymmetric decoders, clique gauge): exists a valid 15-total decoder and x != 0 with supp(W x) inside a +3-dim flat? (Every proper flat lies in one of the 1500 3-flats.) The U side follows by transposition (B^T is valid +for the transposed decoder, the pattern being symmetric). argv: [optional 'calib'] -> drop the flat constraint.""" +import sys, time, pickle, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from flats import flats +from pysat.formula import IDPool +from pysat.card import CardEnc, EncType +from pysat.solvers import Cadical153 +import networkx as nx + +def main(calib=False, c=15): + S = views()['Gauss']; v = S.v + F = flats(S) + hyp = [sorted(f) for f, d in F.items() if d == 3] + assert len(hyp) == 1500 + g = nx.Graph([(i, j) for i in range(v) for j in range(i + 1, v) if S.NO[i, j]]) + K = sorted(max(nx.find_cliques(g), key=len)); assert len(K) == 12 + pool = IDPool(); cl = [] + U = [[pool.id(('u', T, j)) for j in range(c)] for T in range(v)] + W = [[pool.id(('w', T, j)) for j in range(c)] for T in range(v)] + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + def xorlist(ls): + acc = ls[0] + for l in ls[1:]: acc = XOR(acc, l) + return acc + for T in range(v): + for S_ in range(v): + if T == S_ or S.NO[T, S_]: + b = xorlist([AND(W[T][j], U[S_][j]) for j in range(c)]) + cl.append([b] if T == S_ else [-b]) + for i, T in enumerate(K): + for j in range(c): + cl.append([U[T][j]] if j == i else [-U[T][j]]); cl.append([W[T][j]] if j == i else [-W[T][j]]) + if not calib: + x = [pool.id(('x', j)) for j in range(c)]; cl.append(x) + y = [xorlist([AND(W[T][j], x[j]) for j in range(c)]) for T in range(v)] + sel = [pool.id(('s', h)) for h in range(len(hyp))]; cl.append(sel) + for h, H in enumerate(hyp): + Hs = set(H) + for T in range(v): + if T not in Hs: cl.append([-sel[h], -y[T]]) + s = Cadical153(bootstrap_with=cl); t = time.time(); r = s.solve() + print(('calibration (no flat constraint) ' if calib else 'support-in-a-3-flat ') + ('SAT' if r else 'UNSAT'), round(time.time() - t, 1), 's', flush=True) + return r + +if __name__ == '__main__': + main(calib=len(sys.argv) > 1 and sys.argv[1] == 'calib') diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_lp3.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_lp3.py new file mode 100644 index 0000000000..8c589e9a51 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_lp3.py @@ -0,0 +1,23 @@ +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +import networkx as nx +from scipy.optimize import linprog +d = np.load('rankmat.npz'); R = d['R'].astype(float); L = d['lines_of'].astype(np.float32) +S = views()['Gauss']; v = S.v +g = nx.Graph([(i, j) for i in range(v) for j in range(i+1, v) if S.NO[i, j]]) +fams = {} +for c in nx.find_cliques(g): fams.setdefault('max%d' % len(c), []).append(c) +fams['single'] = [[T] for T in range(v)] +fams['nopair'] = [[a, b] for a in range(v) for b in range(a+1, v) if S.NO[a, b]] +fams['all'] = [list(range(v))] +mval = {k: (15 if k == 'all' else len(fams[k][0])) for k in fams} +names = sorted(fams); H = [] +for k in names: + inc = np.zeros((v, len(fams[k])), np.float32) + for j, c in enumerate(fams[k]): inc[c, j] = 1 + meet = (L @ inc > 0).astype(np.float32); H.append(meet @ meet.T) +H = np.stack(H, -1).reshape(-1, len(names)); Rf = R.reshape(-1) +rows = np.unique(np.concatenate([H, Rf[:, None]], 1), axis=0); rows = rows[rows[:, :-1].sum(1) > 0] +c = -np.array([mval[k] * len(fams[k]) for k in names], float) +res = linprog(c, A_ub=rows[:, :-1], b_ub=rows[:, -1], bounds=[(0, None)]*len(names), method='highs') +print('LP bound', -res.fun, {k: round(x*len(fams[k]), 4) for k, x in zip(names, res.x) if x > 1e-12}) diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_pi.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_pi.py new file mode 100644 index 0000000000..57eb8cd6fb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/gauss_pi.py @@ -0,0 +1,28 @@ +"""Gaussian view: the 2-adic (mod pi) sum-rank bound is at most 12, so it cannot reach 43. +The unit-pair graph (|hG|^2 = 1, 32 per line) has a partition into 12 five-cliques; with N/2 odd exactly inside the +cliques, (hG o N)/2 mod pi = block-diag(J_5) of F2-rank 12. Also: max set with no unit pair = 12 (lower bound 12).""" +import sys; sys.path.insert(0, sys.path[0]) +from lines import * +import networkx as nx +from pysat.solvers import Cadical153 +from pysat.card import CardEnc +W = views(); S = W['Gauss']; a, b = gauss_gram(S.reps, W['_J']); N = a*a + b*b; v = S.v +U = nx.Graph([(i, j) for i in range(v) for j in range(i+1, v) if N[i, j] == 1]) +cl5 = [c for c in nx.find_cliques(U) if len(c) == 5] +# exact cover by SAT +x = list(range(1, len(cl5)+1)); cls = [] +for T in range(v): + lits = [x[k] for k, c in enumerate(cl5) if T in c] + cls.append(lits) + for i in range(len(lits)): + for j in range(i+1, len(lits)): cls.append([-lits[i], -lits[j]]) +s = Cadical153(bootstrap_with=cls); assert s.solve() +part = [cl5[k] for k in range(len(cl5)) if s.get_model()[k] > 0] +assert len(part) == 12 and sorted(sum(part, [])) == list(range(v)) +M = np.zeros((v, v), int) +for c in part: + for i in c: + for j in c: M[i, j] = 1 +from lines import rank_q +# F2 rank of block-diag J_5 is 12 (one per block) +print('PASS gauss.pi: unit graph has', len(cl5), 'five-cliques and a partition into 12 of them -> mod-pi bound <= 12') diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_direct.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_direct.py new file mode 100644 index 0000000000..b96a7ed5e2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_direct.py @@ -0,0 +1,40 @@ +"""Direct SAT (S1's open item): is there any Hurwitz decoder with n <= C terms and sum of exact costs <= C? +Terms (w_j, u_j) arbitrary; cost_j from the exact formula (checked in hurwitz_floor.py); cost-0 terms are useless +(they touch only free entries) so n <= C. Symmetry: terms sorted by (cost desc) not imposed; lex order on w_j words.""" +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from pysat.formula import IDPool +from pysat.card import CardEnc, EncType +from pysat.solvers import Cadical153 +C = int(sys.argv[1]) +S = views()['Hurwitz']; v = S.v +rel = [(T, U_) for T in range(v) for U_ in range(v) if T == U_ or S.NO[T, U_]] +pool = IDPool(); cl = [] +W = [[pool.id(('w', j, T)) for T in range(v)] for j in range(C)] +U = [[pool.id(('u', j, T)) for T in range(v)] for j in range(C)] +P = {} +for j in range(C): + for T, S_ in rel: + p = pool.id(('p', j, T, S_)); P[j, T, S_] = p + cl += [[-p, W[j][T]], [-p, U[j][S_]], [p, -W[j][T], -U[j][S_]]] +def xor_eq(lits, target): + # chain xor with aux vars + acc = lits[0] + for l in lits[1:]: + z = pool.id(('x', len(cl), acc, l)) + cl.extend([[-z, acc, l], [-z, -acc, -l], [z, -acc, l], [z, acc, -l]]); acc = z + cl.append([acc] if target else [-acc]) +for T, S_ in rel: + xor_eq([P[j, T, S_] for j in range(C)], 1 if T == S_ else 0) +costlits = [] +for j in range(C): + r1 = pool.id(('r1', j)); r2 = pool.id(('r2', j)) + for T, S_ in rel: cl.append([-P[j, T, S_], r1]) + # r2 >= (|R|>=2 & |A|>=2 & relevant): any 2 rows, 2 cols -> relevant automatically (each line orth to one other) + for T1, T2 in itertools.combinations(range(v), 2): + for S1, S2 in itertools.combinations(range(v), 2): + cl.append([-W[j][T1], -W[j][T2], -U[j][S1], -U[j][S2], r2]) + costlits += [r1, r2] +enc = CardEnc.atmost(lits=costlits, bound=C, vpool=pool, encoding=EncType.seqcounter) +s = Cadical153(bootstrap_with=cl + enc.clauses) +print('C', C, 'SAT' if s.solve() else 'UNSAT', flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_floor.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_floor.py new file mode 100644 index 0000000000..8f634fb7f1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/hurwitz_floor.py @@ -0,0 +1,75 @@ +"""Hurwitz E8 (10 quaternionic lines in H^2, w = 2): every decoder on the pattern costs >= 10 = v (exact). + +Model: decoder B = sum_j w_j u_j^T over F2 (R_j = supp w_j readers, A_j = supp u_j sources), B_TT = 1, B_TS = 0 on +H-non-orthogonal pairs, free on orthogonal pairs; cost_j = Theorem-C pairing rank of span(R_j) x span(A_j) in H-units. +Checks: + 1. structure: v = 10, w = 2, every line is H-orthogonal to exactly one other (5 orthogonal pairs), any two distinct + lines span H^2. + 2. cost formula, exact for all |R|,|A| <= 2 and implied for larger sets (span saturates): + cost = 0 if R x A has no equal-or-non-orthogonal pair; else 2 if |R| >= 2 and |A| >= 2; else 1. + 3. Proof (argued, README): split terms into cost-2 'general' terms X (2 per term >= 2 rank X), cost-1 single-row / + single-column terms (rho, gamma), cost-0 terms (absorbed in free entries). B + X vanishes off rows rho and columns + gamma, so on rho' x gamma' (complements) X equals the pattern; its C x C principal part (C = rho' & gamma') is block + diagonal with blocks [1] / [[1,*],[*,1]] (non-orthogonal pairs are 0), so rank X >= |C|/2 and + cost >= |C| + 20 - |rho'| - |gamma'| >= 10. + 4. SAT cross-check of the reduced form: no (X of rank <= g, rho, gamma) with 2g + |rho| + |gamma| <= 9 (CaDiCaL). + 5. Attained: the 5 orthogonal pairs as frames, cost 5 x 2 = 10. +""" +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from pysat.formula import IDPool +from pysat.card import CardEnc, EncType +from pysat.solvers import Cadical153 + +def formula(S, R, A): + if not any(T == U_ or S.NO[T, U_] for T in R for U_ in A): return 0 + return 2 if len(R) >= 2 and len(A) >= 2 else 1 + +def main(): + S = views()['Hurwitz']; v = S.v + assert v == 10 and S.w == 2 + orth = ~S.NO & ~np.eye(v, dtype=bool) + assert (orth.sum(1) == 1).all() + assert all(S.arank([a, b]) == 2 for a, b in itertools.combinations(range(v), 2)) + print('PASS hurwitz.structure: v 10, w 2, 5 orthogonal pairs (perfect matching), any two lines span H^2') + small = [c for k in (1, 2) for c in itertools.combinations(range(v), k)] + for R in small: + for A in small: + assert S.pair_rank(R, A) == formula(S, R, A) + print(f'PASS hurwitz.costformula: exact pairing rank == formula on all {len(small)**2} (R,A) with |R|,|A| <= 2') + # 4. reduced SAT: B + X supported on rows rho / cols gamma; X = sum_{k x_K = 1') + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/lines.py b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/lines.py new file mode 100644 index 0000000000..fc4eac011d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/scripts/lines.py @@ -0,0 +1,166 @@ +"""Self-contained exact construction of the E8 root lines over Q(i), Q(omega) and the Hurwitz quaternions. + +Real model: the 240 E8 roots in doubled standard coordinates (norm 8; in {0,+-4,+-8}). + * Gaussian view : J = left multiplication by i on R^8 = H^2 (coords 1,i,j,k per block); J preserves E8. + Hermitian Gram hG(x,y) = ( + i )/4 in Z[i] (diag 2). + * Eisenstein view: rho = fixed-point-free order-3 isometry of E8 (product of Coxeter elements of 4 orthogonal A2's). + Sesquilinear Gram hE(x,y) = ( - w )/4 in Z[w] (w^2+w+1=0; diag 2+w, a unit at 2). + * Hurwitz view : left multiplication by i, j, k; quaternionic lines of real dim 4. +Elements of Z[i] / Z[w] are stored as integer pairs (a, b) = a + b*i / a + b*w. +Everything is exact integer arithmetic; asserts check every structural claim used downstream. +""" +import itertools +from fractions import Fraction +import numpy as np + + +def e8_roots(): + R = [] + for i, j in itertools.combinations(range(8), 2): + for si in (2, -2): + for sj in (2, -2): + r = [0] * 8; r[i], r[j] = si, sj; R.append(r) + for s in itertools.product((1, -1), repeat=8): + if s.count(-1) % 2 == 0: + R.append(list(s)) + R = np.array(R, dtype=np.int64) + assert len(R) == 240 and set((R * R).sum(1)) == {8} + return R + + +def rank_q(rows): + """exact rank over Q of an integer matrix (fraction-free Gaussian elimination).""" + M = [list(map(int, r)) for r in rows] + if not M: + return 0 + n, m = len(M), len(M[0]); r = 0 + for c in range(m): + p = next((i for i in range(r, n) if M[i][c] != 0), None) + if p is None: + continue + M[r], M[p] = M[p], M[r] + for i in range(r + 1, n): + if M[i][c]: + f, g = M[i][c], M[r][c] + M[i] = [g * a - f * b for a, b in zip(M[i], M[r])] + r += 1 + if r == n: + break + return r + + +def left_quat(): + Li = np.array([[0, -1, 0, 0], [1, 0, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) + Lj = np.array([[0, 0, -1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, -1, 0, 0]]) + Lk = Li @ Lj + blk = lambda M: np.kron(np.eye(2, dtype=np.int64), M).astype(np.int64) + return blk(Li), blk(Lj), blk(Lk) + + +def a2_rotation(roots): + """rho, an order-3 fixed-point-free isometry preserving E8 (returned as an exact integer matrix 4*rho).""" + chosen = [] + for a in roots: + if len(chosen) == 4: + break + if any(np.dot(a, x) != 0 for p in chosen for x in p): + continue + for b in roots: + if np.dot(a, b) == -4 and all(np.dot(a, x) == 0 and np.dot(b, x) == 0 for p in chosen for x in p): + chosen.append((a, b)); break + assert len(chosen) == 4 + g = np.eye(8, dtype=object) + for a, b in chosen: + sa = np.eye(8, dtype=object) * 8 - 2 * np.outer(a, a).astype(object) + sb = np.eye(8, dtype=object) * 8 - 2 * np.outer(b, b).astype(object) + g = g.dot(sa).dot(sb) + D = 8 ** 8 // 4 + assert all(x % D == 0 for x in g.flatten()) + g = (g // D).astype(np.int64) + I = np.eye(8, dtype=np.int64) + assert (g @ g @ g == 64 * I).all() and (g @ g + 4 * g + 16 * I == 0).all() + return g + + +def lines_over(roots, ops): + """group roots into A-lines, A spanned by the integer matrices ops (identity first; ops may be scaled by 4). + Returns representatives (first root met of each line, in root order).""" + S = {tuple(r) for r in roots} + reps, seen = [], set() + for r in roots: + if tuple(r) in seen: + continue + span = [B @ r for B in ops] + assert rank_q(span) == len(ops) + reps.append(r) + members = [s for s in roots if rank_q(span + [s]) == len(ops)] + for s in members: + seen.add(tuple(s)) + return np.array(reps) + + +class View: + """name, reps (v x 8), ops (real matrices spanning A, scaled so that ops[i] @ root is a root times scale[i]).""" + def __init__(self, name, reps, ops, d): + self.name, self.reps, self.ops, self.d = name, reps, ops, d + self.v = len(reps) + NO = np.zeros((self.v, self.v), bool) + for B in ops: + NO |= (reps @ B @ reps.T) != 0 + np.fill_diagonal(NO, False) + self.NO = NO # A-non-orthogonal pairs (the decoder pattern forces B_TS = 0 there) + self.w = self.arank(range(self.v)) + + def arank(self, idx): + rows = [B @ self.reps[i] for i in idx for B in self.ops] + r = rank_q(rows) if rows else 0 + assert r % self.d == 0 + return r // self.d + + def pair_rank(self, R, A): + """A-rank of the pairing span(R) x span(A) = real rank of the real Gram block / dim A (Theorem C cost).""" + BR = [B @ self.reps[i] for i in R for B in self.ops] + BA = [B @ self.reps[i] for i in A for B in self.ops] + if not BR or not BA: + return 0 + M = [[int(x @ y) for y in BA] for x in BR] + r = rank_q(M); assert r % self.d == 0 + return r // self.d + + +def views(): + roots = e8_roots(); I8 = np.eye(8, dtype=np.int64) + Li, Lj, Lk = left_quat() + S = {tuple(r) for r in roots} + for M in (Li, Lj, Lk): + assert all(tuple(M @ r) in S for r in roots) + g = a2_rotation(roots) # 4*rho + assert all(tuple(g @ r // 4) in S and ((g @ r) % 4 == 0).all() for r in roots) + rho = g # 4*rho (exact integer matrix); ranks are scale-free + out = {} + out['Gauss'] = View('Gauss', lines_over(roots, [I8, Li]), [I8, Li], 2) + out['Eisen'] = View('Eisen', lines_over(roots, [I8, rho]), [I8, rho], 2) + out['Hurwitz'] = View('Hurwitz', lines_over(roots, [I8, Li, Lj, Lk]), [I8, Li, Lj, Lk], 4) + out['_J'], out['_rho'] = Li, rho + return out + + +def gauss_gram(V, J): + """hG(r_T, r_S) = ( + i)/4 as integer pairs (a, b).""" + A = V @ V.T; Bm = V @ J @ V.T + assert (A % 4 == 0).all() and (Bm % 4 == 0).all() + return A // 4, Bm // 4 + + +def eisen_gram(V, rho4): + """hE(r_T, r_S) = ( - w)/4 as integer pairs (a, b) = a + b w (rho4 = 4*rho).""" + A = V @ V.T; Bm = V @ rho4 @ V.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + + +if __name__ == '__main__': + W = views() + for nm in ('Gauss', 'Eisen', 'Hurwitz'): + S = W[nm] + print(nm, 'v', S.v, 'w', S.w, 'nonorth degree', sorted(set(S.NO.sum(1)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/verify.sh new file mode 100644 index 0000000000..f2bf8316cf --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/verify.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Re-run the checked claims and compare PASS/RESULT lines with expected.txt. Usage: PY=python3.11 sh verify.sh +set -e +PY=${PY:-python3} +D=$(cd "$(dirname "$0")" && pwd) +OUT=$(mktemp -d) +cd "$D/scripts" +for s in lines eisen_floor hurwitz_floor gauss_pi gauss_bound40; do + echo "== $s" >&2 + "$PY" -B "$s.py" | grep -E '^(PASS|FAIL|RESULT|Gauss|Eisen|Hurwitz) ' | sed 's/np\.int64(\([0-9]*\))/\1/g' >> "$OUT/got.txt" +done +"$PY" -B gauss_c15.py calib >> "$OUT/got.txt" +sed -E 's/ [0-9.]+ s$//' "$OUT/got.txt" > "$OUT/got2.txt" +if diff -u "$D/expected.txt" "$OUT/got2.txt"; then echo "ALL MATCH ($(grep -c '^PASS' "$OUT/got2.txt") PASS lines)"; else echo "MISMATCH"; exit 1; fi From 6416a6d9fcda429b50a3a8fa19c97d9506c2f37f Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 21:26:05 -0400 Subject: [PATCH 09/17] Correct section 9: idle-overlay exchanges (#1268/#1280/#1305/#1324) were withdrawn as invalid Co-Authored-By: Claude Opus 5.5 --- research/e8-decoder-floors-and-cold14-notes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 15311a37e4..1f377a1cf4 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -86,7 +86,7 @@ Status: `expected/` is frozen from the complete run1 (all steps finished; the k | a fusion analogue at the X54 site (in place of the slot) | 45 touching 54/589 | 4 | 0 (best +956, worse than the slot) | | `joint2` with target holders (+2,940 per word) and `fusion2.py` (10,603 generalised fusions on JFX) | — | **partial**: stopped before completion | none found | -**Conclusion (scoped):** on these words, no further width-neutral source-holder fusion, read exchange or triangle slot of the searched families gains. The remaining κ gains in the lineage come from other mechanisms, such as #1274's helper-donor cold kernels and #1252/#1268's idle-pair exchanges. Scripts `joint2.py` and `fusion2.py` extend #1186's exact-frame toolkit (credit mdumitrean #1186/#1195), which is not re-vendored here. +**Conclusion (scoped):** on these words, no further width-neutral source-holder fusion, read exchange or triangle slot of the searched families gains. The remaining κ gains in the lineage come from other mechanisms, notably #1274's helper-donor cold kernels. (**Correction, 2026-10-13:** an earlier version also cited the idle-overlay exchanges of #1268/#1280. The author has since closed #1268, #1280, #1305 and #1324 as invalid, because those moves shift axes between idle children of different (pair, side) families whose DATA-PHASES fix the Z4 multiplier; see #1286. The valid frontier is #1319/#1424.) Scripts `joint2.py` and `fusion2.py` extend #1186's exact-frame toolkit (credit mdumitrean #1186/#1195), which is not re-vendored here. ## 10. Extension-ring E8: copy-cost floors over every decoder (`s5-extension-ring-floors/`, `verify.sh` ALL MATCH) This follows up §4, where S1 obtained x_K = 1 only under the full-rank-totals premise. Here every F2 decoder on the orthogonality pattern is covered: any number of totals, asymmetric pairings, no star premise. Costs are #446 Theorem C costs Σ_j β_j. From 4fdf9c415595a62411d78d1b89f0cc98c2d74f72 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Tue, 13 Oct 2026 23:20:25 -0400 Subject: [PATCH 10/17] Add helper-donor cold-kernel census on PR1319's word (no gains) Co-Authored-By: Claude Opus 5.5 --- .../README.md | 22 +++ .../r8-cold-kernel-census/PROGRESS.md | 18 +++ .../r8-cold-kernel-census/README.md | 69 ++++++++++ .../r8-cold-kernel-census/scripts/climbs.py | 27 ++++ .../scripts/price_all.py | 25 ++++ .../scripts/relations.py | 109 +++++++++++++++ .../scripts/relations2.py | 110 +++++++++++++++ .../scripts/relations3.py | 128 ++++++++++++++++++ .../r8-cold-kernel-census/scripts/retime.py | 20 +++ .../scripts/run_census.sh | 5 + 10 files changed, 533 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/PROGRESS.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/climbs.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/price_all.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/retime.py create mode 100755 research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/run_census.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 1f377a1cf4..d516f48e78 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -100,3 +100,25 @@ This follows up §4, where S1 obtained x_K = 1 only under the full-rank-totals p - The 2-adic method caps at 12, and an LP over clique families stays at 40. - The best known decoder costs 60. A decoder at ≤ 42 would need ≥ 16 totals with at most 10 of full rank. - **Open:** close the window 40–42. A pending SAT (`gauss_c15.py`) would show that every 15-total decoder costs exactly 60. + +## 11. Helper-donor cold-kernel census on #1319's word (`r8-cold-kernel-census/`, 2026-10-13) +**Base:** #1297/#1319's four-kernel base word (byte-identical to the one #1319 uses), κ 0.001676855229071321. #1305 and #1324 were withdrawn as invalid (#1286), so the idle-axis overlay is not used. + +**What a kernel is:** a cold pivot q (still at its rank-one start frame) has early B reads `t += k·q`. A kernel deletes them and repairs exactness with one or two shocks: a cut (`h += a·q`, undone in early cleanup) or a hook (`s ± c·q` around a donor read). The coefficients come from an exact rational equation. + +**Admissibility:** the scalar map is unchanged mod p, the cone caps hold, and #1186's exact frame solver finds feasible frames. + +**Why kernel 555 gains:** deleting reads merges the consecutive climbs of targets 187 and 483 (H1 −3, H3 +1, cost −11,676). + +**Control:** restricted to pivots 531/543/555/567 on the #1286 word, the census recovers exactly #1297's 5 + 5 relations, each −11,676. + +| family | candidates | admissible | gains | +|---|---|---|---| +| #1297's family, all 376 pivots with early uses | 254 | 99 (all Δ0) | **0** | +| G: cuts after any read or at B start, hooks around any early add gate, 1–2 shocks, every subset of early uses | 9,916 at 248 pivots | 1,435 (Δ0 or +4,913) | **0** | +| K2: two shocks of the same type, 15,507 new (**13,402 priced**) | 13,402 | 4,346 (Δ0 or +4,913) | **0** | +| 3-shock spot check (pivots 554, 448) | 12 | 0 | 0 | + +The dominant failure is "L not inside U", at the early-cleanup restore or at the end-of-B cone caps. The (output, output, helper) three-read pattern that the four kernels use occurs only at 531/543/555/567, which are already spent. + +**Not done:** 2,105 K2 candidates, pricing on the extra word, a full 3-shock census, and 3,066 gate-retiming candidates (generated by `retime.py`, unpriced; they target the same climb-merging mechanism directly). diff --git a/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/PROGRESS.md b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/PROGRESS.md new file mode 100644 index 0000000000..2399290c3f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/PROGRESS.md @@ -0,0 +1,18 @@ +# R8 progress +- Tue Oct 13 20:42:47 EDT 2026 started +- FINDING: #1324, #1305 (and #1280) CLOSED by author 23:22Z as INVALID (idle-overlay moves axes between idle children of different (pair,side) families; DATA-PHASES fix each family's Z4 multiplier). Flagged by rohanarun #1286. Overlay-free: #1261, #1274. +- Valid frontier therefore: #1319 rohanarun 0.001676855229071321 (four kernels exact fill, 74 replicas), #1377 jk2 regrid ...886144, #1424 rohanarun draft regrid ...886206. Retarget: beat #1319 construction (κ > 0.0016768552290713219), lever = more helper-donor cold kernels; NO idle overlay. +- [setup] worktree ~/imb-r8 = pr1319 head 603defe5. Final #1297 words rebuilt to $SP/r8/words/{base,extra}.json.gz (recount matches published profiles). Pricer (r7/tk harness, IMB_ROOT=~/imb-r6c) reproduces -11676 for 555/567 kernels on PR1286 input word (positive control). +- [running] tools/relations.py: generalized census (all 376 registers with early uses; two-shock + one-shock helper/hook + 'free' deletions), then tools/price_all.py exact pricing. +- [result] full-deletion census on final #1297 base word: 376 pivots, 254 relation candidates at 48 pivots (131 hook, 87 two-shock, 36 helper); exact pricing: 99 ok all delta 0, 80 infeasible (frame/cone), 75 inexact mod p. ZERO gains. +- [control] same census on PR1286 input word restricted to 531/543/555/567 recovers exactly PR1297's 5+5 relations, all -11676 (matches SEARCH-RESULT.json). +- [running] subsets-of-early-uses census on base (~1950 jobs). +- [tool] tools/relations2.py = family G (cuts after ANY read of ANY register h, or at B start; hooks around ANY early add gate, any reader/donor; one- and two-shock; all subsets of early uses), solved by hashing images in V/. Contains PR1297's family (control: all 10 original relations recovered, +2 new on 1286 word). +- [running] G on final base word: 1952 (pivot,subset) jobs -> 9916 candidates at 248 pivots (7632 two, 2116 hook, 168 helper); exact pricing in progress (cands/G-base-sub-price.log). +- [result] G census on final base word fully priced: 9916 cands -> 1435 ok (1311 delta 0, 124 delta +4913), 7287 infeasible (exact frame solver/cone caps), 1194 inexact mod p. ZERO negative-delta (gaining) kernels. Extra word pricing running (same scalar program, different frames/entrances). +- [tool] tools/relations3.py: any-type 2-shock (cut+cut, hook+hook, cut+hook) and 3-shock census via Gaussian quotient hashing. K=2 subsets on base: 23139 cands; 15507 new beyond G (11220 hook+hook, 4229 cut+cut, 58 hook+cut). Pricing on base running (cands/K2-base-new-price.log). Extra-word full pricing paused (CPU). +- [result] K2 (any-type 2-shock) on base: first 9601/15507 priced (no timeouts): 1407 ok (1230 Δ0, 177 Δ+4913), 8125 infeasible, 69 inexact, 0 gains. Remainder resumed with 30 s timeout. +- [result] 3-shock spot (554,448): 12 cands, all infeasible. Full 3-shock ~4 CPU-min/pivot -> not run. +- [mechanism] kernel 555 gain = climb merging at read-into targets 187/483 ((1,1)->2, (1,2)->3). +- [notes] draft at $SP/r8/notes/README.md, scripts in notes/scripts. +- [final] K2 on base: 13402/15507 priced: 4346 ok (4169 Δ0, 177 Δ+4913), 8808 infeasible, 248 inexact, 0 gains; 2105 unpriced (budget). Extra-word pricing not completed. No admissible negative-cost kernel found -> NEGATIVE RESULT, notes at $SP/r8/notes/README.md. No branch/commit (nothing to realise). diff --git a/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/README.md b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/README.md new file mode 100644 index 0000000000..4350d661d3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/README.md @@ -0,0 +1,69 @@ +# Helper-donor cold kernels: exhaustive census on the four-kernel words (negative result, no new κ) + +Scope: the two local provider words of PR1297 (pivots 531/543/555/567 already applied), which are byte-identical +to the words used by PR1319's T74 exact-fill construction (current valid κ head 0.001676855229071321; #1377/#1424 +only refine the outer grid). PR1305/PR1324 were withdrawn by their author (idle-overlay invalid), so their idle +reassignment step is not used here. + +## What a helper-donor cold kernel is + +A *cold pivot* q is a register whose value is still its start value (start frame `start_extra[q]`, here usually a +rank-one frame such as `[24]` or `[30]`) when it is read by some early B gates `t += k*q` ("early uses", before +q's first B write). A kernel deletes a set D of those early uses and restores the scalar program with *shocks*: + +* **cut** `h += a*q` inserted in B (PR1274/PR1297: right after an early output read of helper h), undone by + `h -= a*q` at the end of early cleanup; +* **hook** `s += c*q` / `s -= c*q` around an early gate `t += k*s` (the donor s is already at q's frame). + +Exactness is the rational equation `d = Σ coeff_i · response_i`, where `d` is the change of q's final column +caused by deleting D and each response is the backward column of the shocked register. Admissibility is then: +scalar map unchanged (mod p check of B + early tails + prepends), the cone caps (`audit_local` inverse-row and +dirt-column cones as end-of-B caps), and a feasible frame annotation; the profile is the exact optimum of the +#1186 frame solver (integer duality certificate). Widths, ports, centres and entrances are unchanged, so any +admitted kernel would reuse the owners/dispatch unchanged. + +Why the four kernels pay: per-register climb diff of pivot 555 on the PR1286 word +(`tools/climbs.py`): `(187,1)-2, (187,2)+1, (483,1)-1, (483,2)-1, (483,3)+1`. Deleting the rank-one early reads +of q into 187/483 merges two consecutive climbs of those targets into one larger climb; the new gates land on +registers (donor 620, helper 383) that are already at the needed frame. Paid profile H1 −3, H3 +1, cost −11676. + +## Census (all exact) + +| Family | Generator | (pivot, D) jobs | candidates | priced: ok / infeasible / inexact | gains | +|---|---|---|---|---|---| +| PR1297 family, D = all early uses | `relations.py full` | 376 | 254 (48 pivots) | 99 (all Δ=0) / 80 / 75 | 0 | +| G: cut after any read of any h (or B start), hook around any early add, 1- or 2-shock cut+hook, all subsets D | `relations2.py subsets` | 1952 | 9916 (248 pivots) | 1435 (1311 Δ=0, 124 Δ=+4913) / 7287 / 1194 | 0 | +| K2: any two shocks (cut+cut, hook+hook), all subsets D, beyond G | `relations3.py 2 subsets` | 1952 | 15507 | 13402 priced: 4346 (4169 Δ=0, 177 Δ=+4913) / 8808 / 248; 2105 unpriced (time budget; 38 pivots, 1547 hook+hook, 557 cut+cut, 1 hook+cut) | 0 | +| 3-shock spot check (pivots 554, 448) | `relations3.py 3 full` | 2 | 12 | 0 / 12 / 0 | 0 | + +Controls: on the PR1286 input word, `relations.py` restricted to 531/543/555/567 recovers exactly PR1297's +5 + 5 relations, all priced −11676 (= its SEARCH-RESULT.json); `relations2.py` contains all ten and finds two more +(same pivots). So the generators and the pricer reproduce the published kernels. + +Per pivot (family G): 376 registers have early uses; 128 admit no exact dyadic relation at all; of the 248 with +relations, the best outcome is Δ=0 for 171, Δ=+4913 for 6, infeasible for 67, inexact for 4. +Main infeasibility reason: `L not inside U at mutable vertex` (3566 at the early-cleanup restore of a cut, the +rest at end-of-B cone caps) — the restore `h -= a*q` or the hook pair forces a register below its cone. + +Cold-pivot inventory on the final base word: 192 registers have start frames; 152 have early uses +(rank-one start: 24 with 1 use, 12 with 2, 5 with 3, 53 with 4, 2 with 5; rank 4: 44 with 3; rank 5: 12 with 3). +The (out, out, helper) three-use pattern of the four kernels occurs only at 531/543/555/567, which are spent. + +## Conclusion + +On the current four-kernel words there is **no further admissible helper-donor cold kernel with negative cost** +in families PR1297 ⊂ G (fully priced) and in the 13402 priced members of K2 (all subsets of early uses, any single or paired cut/hook shocks). PR1297's +four kernels are the only positive ones in this neighbourhood. Not covered: three or more shocks in general +(cost ~4 CPU-min per pivot; only spot-checked), cuts at gaps not adjacent to a read, and gate reordering. +Only the base provider word was priced exhaustively. The extra word has the identical scalar program (same 1844 B gates; it differs only in frames, report and one start_extra entrance group), so the census produces the identical 9916 / 23139 candidate sets; its pricing was started and paused for CPU and is not claimed. 2105 K2 candidates on base remain unpriced (resume: `TMO=30 price_all.py words/base.json.gz cands/K2-base-new.json cands/K2-base-new-price.json`, it skips priced keys). + +## Reproduce + +```sh +export IMB_ROOT= # #1186 toolkit roots +python build_word.py --base vendor/pr1297/inputs/base.json.gz --recipe vendor/pr1297/RECIPE-base.json.gz --out words/base.json.gz +python -B scripts/relations2.py words/base.json.gz cands/G-base-sub.json subsets +python -B scripts/price_all.py words/base.json.gz cands/G-base-sub.json cands/G-base-sub-price.json 10 +python -B scripts/relations3.py words/base.json.gz cands/K2-base-sub.json 2 subsets +``` +`price_all.py` imports the #1186 exact frame toolkit (`harness.Pricer`). diff --git a/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/climbs.py b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/climbs.py new file mode 100644 index 0000000000..85c990f523 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/climbs.py @@ -0,0 +1,27 @@ +import sys,gzip,json,collections +sys.path.insert(0,'/Users/joelpulikkan/imb-harness/r7/tk') +import word as Wd +def climbs(w): + ev=collections.Counter() + orig=Wd.dimension + cur=None + # re-implement recount with per-register tally + v,helpers=w['report']['v'],w['report']['R'];n=2*v+helpers + full=frozenset(range(512));span=Wd.span;dim=Wd.dimension + cur=[frozenset((0,p)) for p in w['ports']]+[frozenset((0,))]*(n-v) + for r,vs in w['start_extra'].items(): cur[int(r)]=span(tuple(vs)) + def climb(r,t,tag): + k=dim(t)-dim(cur[r]) + if k: ev[(r,k)]+=1 + cur[r]=t + for sec in ('A','B'): + for e in w[sec]: + t=span(tuple(e[-1]));rs=e[1:3] if e[0]=='add' else e[1:2] + for r in rs: climb(r,t,sec) + for i,vs in w['early_cleanup']: + e=w['cleanup'][i];rs=e[1:3] if e[0]=='add' else e[1:2] + for r in rs: climb(r,span(tuple(vs)),'E') + return ev +a=climbs(json.load(gzip.open(sys.argv[1])));b=climbs(json.load(gzip.open(sys.argv[2]))) +diff={k:b[k]-a[k] for k in set(a)|set(b) if a[k]!=b[k]} +print(sorted(diff.items())) diff --git a/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/price_all.py b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/price_all.py new file mode 100644 index 0000000000..b7d6d51346 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/price_all.py @@ -0,0 +1,25 @@ +"""Price every census candidate exactly with the #1186 exact frame toolkit (scalar exactness mod p, cone caps, exact frames). +usage: price_all.py WORD CANDS.json OUT.json [workers]""" +import sys,json,time +sys.path.insert(0,'/Users/joelpulikkan/imb-harness/r7/tk') +from multiprocessing import Pool +P=None +def init(w): + global P + import harness as H; P=H.Pricer(w) +def job(a): + k,c=a + r=P.price(c['ed'],timeout=int(__import__('os').environ.get('TMO','120'))); r.pop('word',None) + return k,r +if __name__=='__main__': + w,cp,op=sys.argv[1:4]; nw=int(sys.argv[4]) if len(sys.argv)>4 else 10 + C=json.load(open(cp)); t=time.time(); import os; out=json.load(open(op)) if os.path.exists(op) else {}; out={int(k):v for k,v in out.items()} + with Pool(nw,initializer=init,initargs=(w,),maxtasksperchild=200) as pool: + for n,(k,r) in enumerate(pool.imap_unordered(job,[(k,c) for k,c in enumerate(C) if k not in out],chunksize=1)): + out[k]=r + if r.get('status')=='ok' and r['delta']<0: print('GAIN',k,C[k]['pivot'],C[k]['kind'],r['delta'],r['profile'],flush=True) + if n%200==0: print('progress',n,len(C),round(time.time()-t),flush=True); json.dump(out,open(op,'w')) + json.dump(out,open(op,'w')) + import collections + print('status',collections.Counter(r['status'] for r in out.values())) + print('ok deltas',collections.Counter(r['delta'] for r in out.values() if r['status']=='ok').most_common(20)) diff --git a/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations.py b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations.py new file mode 100644 index 0000000000..830bfaf3ca --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r8-cold-kernel-census/scripts/relations.py @@ -0,0 +1,109 @@ +"""Exhaustive helper-donor cold-kernel relation census (generalizes PR1297 search_relations.py). +For every register q with early uses (reads before q's first B write) and every non-empty subset D of those early uses, +solve d = a*u + b*v exactly over Q, where d is the change of q's final column caused by deleting D, u is a helper cut +shock (h += a*q right after an early output read of helper h; restored at end of early cleanup), v is a donor hook +(donor += c*q / donor -= c*q around an output read of donor). Also one-shock relations (a only / b only). +Dyadic coefficients only. Output: list of candidate edit dicts in harness format (indices into the given word).""" +import gzip,json,sys,itertools,time +from fractions import Fraction as Q +from multiprocessing import Pool +def coef(g): return Q(g[3],g[4]) if g[0]=='add' else Q(g[2],g[3]) +def add(a,b,c): + d=dict(a) + for k,v in b.items(): + x=d.get(k,Q(0))+c*v + if x: d[k]=x + else: d.pop(k,None) + return d +def col(seq,q): + r={q:Q(1)} + for g in seq: + t=g[1] + if g[0]=='add': + x=r.get(t,Q(0))+coef(g)*r.get(g[2],Q(0)) + else: x=r.get(t,Q(0))*coef(g) + if x: r[t]=x + else: r.pop(t,None) + return r +dy=lambda c: not (c.denominator&(c.denominator-1)) +def solve2(u,v,d): + keys=set(u)|set(v)|set(d) + # least squares-free exact: pick two independent rows + ks=sorted(keys) + for i,j in itertools.combinations(ks,2): + det=u.get(i,0)*v.get(j,0)-u.get(j,0)*v.get(i,0) + if det: + a=Q(d.get(i,0)*v.get(j,0)-d.get(j,0)*v.get(i,0))/det;b=Q(u.get(i,0)*d.get(j,0)-u.get(j,0)*d.get(i,0))/det + if all(a*u.get(k,0)+b*v.get(k,0)==d.get(k,0) for k in keys): return a,b + return None + return None # u,v dependent: covered by one-shock +def solve1(u,d): + k=next(iter(d)); + if not u.get(k): return None + a=Q(d[k])/u[k] + if set(u)==set(d) and all(a*u[x]==d[x] for x in d): return a + return None +W=None +def init(path): + global W; W=json.load(gzip.open(path)) +def work(args): + q,dele=args; w=W; B=w['B']; dele=set(dele) + first=next((i for i,g in enumerate(B) if g[1]==q),len(B)) + tail=[w['cleanup'][i] for i,U in w['early_cleanup']]; seq=B+tail + new=[g for i,g in enumerate(B) if i not in dele]+tail + d=add(col(seq,q),col(new,q),-1) + if not d: return [dict(pivot=q,dele=sorted(dele),kind='free',ed=dict(delete=sorted(dele),insert=[],tail=[],prepend=[]))] + cols={};hs=[];ts=[] + get=lambda j: cols.get(j) or {j:Q(1)} + for i in range(len(seq)-1,-1,-1): + g=seq[i] + if i=224 and g[2]!=q and i not in dele: + h=g[2];eff=add(get(h),{h:Q(1)},-1) + if eff: hs.append((i+1,h,eff)) + ts.append((i,g,dict(get(g[1])))) + if i in dele: continue + if g[0]=='add': + t,s=g[1:3];cols[s]=add(get(s),get(t),coef(g)) + else: cols[g[1]]={k:v*coef(g) for k,v in get(g[1]).items()} + out=[];NE=len(w['early_cleanup']) + def hookins(i,g,c): return [[i,['add',g[2],q,c.numerator,c.denominator,[]]],[i+1,['add',g[2],q,-c.numerator,c.denominator,[]]]] + def cutins(cut,h,a): return [[cut,['add',h,q,a.numerator,a.denominator,[]]]],[[NE,['add',h,q,-a.numerator,a.denominator,[]]]] + for cut,h,u in hs: + a=solve1(u,d) + if a is not None and dy(a): + ins,tl=cutins(cut,h,a);out.append(dict(pivot=q,dele=sorted(dele),kind='helper',helper=h,cut=cut,amount=str(a),ed=dict(delete=sorted(dele),insert=ins,tail=tl,prepend=[]))) + for i,g,v in ts: + b=solve1(v,d) + if b is not None: + c=b/coef(g) + if dy(c): out.append(dict(pivot=q,dele=sorted(dele),kind='hook',donor=g[2],hook=i,output=g[1],correction=str(b),ed=dict(delete=sorted(dele),insert=hookins(i,g,c),tail=[],prepend=[]))) + for cut,h,u in hs: + for i,g,v in ts: + if h==g[2]: continue + s=solve2(u,v,d) + if s: + a,b=s;c=b/coef(g) + if not a or not b or not dy(a) or not dy(c): continue + ins,tl=cutins(cut,h,a) + out.append(dict(pivot=q,dele=sorted(dele),kind='two',helper=h,cut=cut,amount=str(a),donor=g[2],hook=i,output=g[1],correction=str(b),ed=dict(delete=sorted(dele),insert=hookins(i,g,c)+ins,tail=tl,prepend=[]))) + return out +if __name__=='__main__': + path,outp=sys.argv[1],sys.argv[2]; mode=sys.argv[3] if len(sys.argv)>3 else 'full' + init(path);B=W['B'];first={} + for i,g in enumerate(B): first.setdefault(g[1],i) + early={} + for i,g in enumerate(B): + if g[0]=='add' and i (projectively normalized); dyadic a, c only. +All candidates are then screened exactly (mod p map, cones, exact frames) by price_all.py.""" +import gzip,json,sys,itertools,time,os +from fractions import Fraction as Q +from multiprocessing import Pool +from relations import coef,add,col,dy +W=None +def init(path): + global W; W=json.load(gzip.open(path)) +def canon(x,d,k0): + if not x: return None + r=Q(x.get(k0,0))/d[k0] + y={k:v-r*d.get(k,0) for k,v in x.items()} + for k,v in d.items(): + if k not in x and r: y[k]=-r*v + y={k:v for k,v in y.items() if v} + if not y: return ('D',) # x proportional to d + ks=sorted(y); n=y[ks[0]] + return tuple((k,y[k]/n) for k in ks) +def work(args): + q,dele=args; w=W; B=w['B']; dele=set(dele) + first=next((i for i,g in enumerate(B) if g[1]==q),len(B)) + tail=[w['cleanup'][i] for i,U in w['early_cleanup']]; seq=B+tail + new=[g for i,g in enumerate(B) if i not in dele]+tail + d=add(col(seq,q),col(new,q),-1) + if not d: return [dict(pivot=q,dele=sorted(dele),kind='free',ed=dict(delete=sorted(dele),insert=[],tail=[],prepend=[]))] + k0=min(d) + cols={}; get=lambda j: cols.get(j) or {j:Q(1)} + U=[];Vv=[] + for i in range(len(seq)-1,-1,-1): + g=seq[i] + if i20000: return out + return out +if __name__=='__main__': + path,outp,k,mode=sys.argv[1],sys.argv[2],int(sys.argv[3]),sys.argv[4]; minu=int(sys.argv[5]) if len(sys.argv)>5 else 1 + init(path,k);B=W['B'];first={} + for i,g in enumerate(B): first.setdefault(g[1],i) + early={} + for i,g in enumerate(B): + if g[0]=='add' and i=0 and comm(g,B[k-1]): k-=1 + gate=list(g[:5])+[[]] + if j>i: out.append(dict(pivot=g[2],kind='late',gate=i,to=j,ed=dict(delete=[i],insert=[[j+1,gate]],tail=[],prepend=[]))) + if k Date: Wed, 14 Oct 2026 13:11:55 -0400 Subject: [PATCH 11/17] Add supplier census on the 566+554 T74 word; explain section 11's missed kernels Co-Authored-By: Claude Opus 5.5 --- .../README.md | 27 ++- .../r9-supplier-census/PROGRESS.md | 21 ++ .../r9-supplier-census/README.md | 67 +++++++ .../r9-supplier-census/scripts/base.py | 9 + .../r9-supplier-census/scripts/ext.py | 39 ++++ .../r9-supplier-census/scripts/ext3.py | 39 ++++ .../r9-supplier-census/scripts/gencomp3.py | 181 ++++++++++++++++++ .../r9-supplier-census/scripts/groups.py | 27 +++ .../r9-supplier-census/scripts/price_all.py | 25 +++ .../r9-supplier-census/scripts/retime.py | 20 ++ 10 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/PROGRESS.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/base.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/gencomp3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/groups.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/price_all.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/retime.py diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index d516f48e78..a045e29424 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -101,7 +101,7 @@ This follows up §4, where S1 obtained x_K = 1 only under the full-rank-totals p - The best known decoder costs 60. A decoder at ≤ 42 would need ≥ 16 totals with at most 10 of full rank. - **Open:** close the window 40–42. A pending SAT (`gauss_c15.py`) would show that every 15-total decoder costs exactly 60. -## 11. Helper-donor cold-kernel census on #1319's word (`r8-cold-kernel-census/`, 2026-10-13) +## 11. Helper-donor cold-kernel census on #1319's word (**scope limited; see §12**) (`r8-cold-kernel-census/`, 2026-10-13) **Base:** #1297/#1319's four-kernel base word (byte-identical to the one #1319 uses), κ 0.001676855229071321. #1305 and #1324 were withdrawn as invalid (#1286), so the idle-axis overlay is not used. **What a kernel is:** a cold pivot q (still at its rank-one start frame) has early B reads `t += k·q`. A kernel deletes them and repairs exactness with one or two shocks: a cut (`h += a·q`, undone in early cleanup) or a hook (`s ± c·q` around a donor read). The coefficients come from an exact rational equation. @@ -122,3 +122,28 @@ This follows up §4, where S1 obtained x_K = 1 only under the full-rank-totals p The dominant failure is "L not inside U", at the early-cleanup restore or at the end-of-B cone caps. The (output, output, helper) three-read pattern that the four kernels use occurs only at 531/543/555/567, which are already spent. **Not done:** 2,105 K2 candidates, pricing on the extra word, a full 3-shock census, and 3,066 gate-retiming candidates (generated by `retime.py`, unpriced; they target the same climb-merging mechanism directly). + +## 12. Supplier census on the current pivot-566 + 554 T74 word, and why §11 missed those kernels (`r9-supplier-census/`, 2026-10-14) +**Why this matters:** at #1800's ε → 1 outer point, κ ≈ the complex supplier endpoint b, so only supplier gains move κ. + +**Word and controls.** The words are #1800's `new/{gauge44,extra}-word-566.json.gz`, identical to #1672/#1957. Our pricer reproduces their profiles, gauge44 {1:2348, 2:500, 3:416, 4:130, 5:82} and extra {1:2340, 2:508, 3:420, 4:126, 5:82}, and prices #1672's k554 edit at Δ −1,850. + +**Correction to §11.** Its families G and K2 allowed only paired shocks, at most two per relation: a cut restored at the end of early cleanup, or a hook around one gate. The 566 and signed-554 kernels are 5–7-term relations built from three things those families lacked: +- unpaired direct reads `x += c·q` that are never undone; +- windows closed in early cleanup right after q's own restoration (the "signed restore" reads q while it holds −q0); +- donor windows spanning many gates. + +The right family is the span of reads `x += λ·y` wherever y holds a multiple of the deleted value; this is #1957's gencomp2. Positive controls: it recovers 566 (delete B755 on the #1319 word) and signed 554 (delete B342 on step 0), each at −1,850. + +#1957 (rohanarun) already ran that family on the current word, with 0 gains. The extensions here also find nothing: + +| run | coverage | gains | +|---|---|---| +| E1: several reads of the same value deleted together | 128 / 330 | 0 (all infeasible) | +| E2: relaxed frame-cost budget (+4,913) on 24 small-gain reads, incl. 858/876 not in #1957 | 24 / 24 | 0 (only trivial Δ0 slot moves) | +| E3: retime every B add to both ends of its commuting window | 3,074 / 3,074 | 0 (2,068 ok at Δ0, 1,006 infeasible) | +| E4: new two-value holder family (`gencomp3.py`; reads of holders of v+u with u cancelled, exactness on matrix entries, no insert feeding a later insert) | 144 / 1,002 (small-gain, mid and largest-gain bands) | 0 (126 shape-infeasible, 18 CP-infeasible) | + +E4's control recovers 566 plus new exact two-value variants at −1,850. A reversed-constraint bug in gencomp3 was found and fixed mid-run, and the affected results were rerun. + +**Not covered:** the rest of E1 and E4, three-value holders, multi-deletions across different values, A-section edits, and the extra word. The negative results suggest further supplier gains need changes outside B-local kernels, such as the A-section or the parent/centre interface. diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/PROGRESS.md b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/PROGRESS.md new file mode 100644 index 0000000000..75a12ef5e2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/PROGRESS.md @@ -0,0 +1,21 @@ +# R9 progress +- Wed Oct 14 11:57:10 EDT 2026 started +- [setup] current supplier words (pr1800 new/*-word-566 = pr1672 = pr1957 inputs, blobs 76c01583/e86142b3) at $SP/r9/words. Pricer reproduces local profiles gauge44 {1:2348,2:500,3:416,4:130,5:82}, extra {1:2340,2:508,3:420,4:126,5:82}; EDITS-1 (k554) on step0 word prices delta -1850 ok (positive control). +- [control] PR1957's value-holder compensation search (gencomp2.py: delete read(s), compensate with inserted reads x += lam*y at ANY position where some y holds a multiple of the deleted source value, B and early-cleanup tails, CP-SAT with frame-cost oracle) recovers on the PR1297/#1319 gauge44 word: dels [755] (494 -= 566) -> 5-term kernel delta -1850 ok; on step0 word dels [342] (482 -= 554) -> 5-term kernel -1850 ok. => positive control passes. +- [why R8 missed] R8 families (G/K2) only had: cut = h += a*q after a read, restored at END of early cleanup (+q0), hook = +/- pair around ONE gate, <=2 shocks. The 566/554 kernels are 5-7 term relations using UNPAIRED direct reads (x += q at B0..q's first write, never undone: response col_p[x], not col-e or hook form), helper windows closed in early cleanup right after q's OWN restoration (holder windows, possibly with alpha=-1 = signed restore), and donor windows spanning many gates. Every term sits in a holder window of q's initial value; so the right family is the full linear span of holder-read responses (PR1957 gencomp2), not a pairwise cut/hook family. +- [overlap] PR1957 (rohanarun, open) already ran that family on the CURRENT word for 101 single deletions + '0,1,2', '3,4' (budget -ddel-1, oracle-pruned candidates), plus single-gate commuting moves at 4461 positions: 0 gains. R9 extension therefore targets: (E1) multi-deletion same-value subsets, (E2) relaxed oracle budget (oracle is not additive; price exactly), (E3) retiming of ALL B adds (not only gain-carrying), running. +- [running] E1: 330 same-value multi-deletion subsets (pairs..quads from 172 value groups of gain reads), gencomp2 maxterms 8, budget -ddel-1. First ~15: all shape-infeasible/INFEASIBLE. +- [running] E2: relaxed oracle budget (+4913) on 24 small-gain reads (16 one-split, 783/802 -6763, 822/840/858/876 -6100 (858/876 not run by PR1957), 857/875 -3050): first 8 -> only the trivial "move the read one slot" (delta 0), else infeasible/shape-infeasible. +- [running] E3: retime census (3074 commuting extreme moves, all B adds) on current gauge44 word, exact pricing. +- [next] E4 two-value holder family: compensation reads of registers holding v+u combos cancelled by holders of u (matrix-valued exactness); not in PR1957 or R8. +- [tool] tools/gencomp3.py: two-value holder family (holders of v, or of v+u where >=2 windows share residual u, incl. pure-u holders), matrix-valued exactness, chain-exclusion (no insert target feeds a later insert), holder values from the DELETED program. Control on #1319 word dels [755]: recovers the pure 566 kernel (-1850) AND new exact two-value variants (e.g. 210 += 1/2*676 at 293 / -1/2 at 237 replacing 210 -= q), all -1850 (same cost). +- [result] E2 complete (24 jobs, slack +4913): 0 gains (only delta-0 trivial slot moves). +- [running] E4 = gencomp3 on all 1002 gain-carrying single reads of current gauge44 word, ordered by smallest gain; E1 (330 multi-deletion) and retime (3074) still running. +- [result] E3 retime census (3074 commuting-window extreme moves of every B add, current gauge44 word): 2068 ok all delta 0, 1006 infeasible, 0 gains. +- [running] E4b = E4 in reverse order (largest-gain delivery reads first), 3 more workers. +- [load] advisor cap: stopped E1 (resumable; SUMMARY skips done) to keep <=6 workers (E4 3 + E4b 3). +- [fix] gencomp3 chain-exclusion direction was reversed (inexact solutions at 783/802: 506 += 66 then 530 += 506). Fixed (exclude reads of x at positions >= insert); E4/E4b relaunched, keeping only shape-infeasible results (independent of the chain constraint). +- [notes] draft at $SP/r9/notes/README.md (scripts in notes/scripts) +- [result] E4 stopped at 144/1002 (time/CPU cap): 126 shape-infeasible, 18 CP-infeasible, 0 gains. Covered: all small-gain reads (-3050..-6763), the -8857/-9567/-9826 reads done so far, and the largest-gain reads (-22039..-26389). Control rerun with fixed tool: 566 kernel + two-value variant -1850 ok. +- [result] E1 stopped at 128/330: 0 solutions. +- [final] NO GAIN. Notes: $SP/r9/notes/README.md. No branch/commit/PR. diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/README.md b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/README.md new file mode 100644 index 0000000000..b4e4ae9e3e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/README.md @@ -0,0 +1,67 @@ +# Why the R8 census missed the pivot-566 / signed-554 kernels, and an extended census on the current T74 supplier (no new κ) + +Words: the current supplier words carried by PR1800 / PR1839 (`new/{gauge44,extra}-word-566.json.gz`, git blobs +76c01583… / e86142b3…, byte-identical to PR1672 and to PR1957's inputs). Exact frame pricing (PR1186 toolkit) +reproduces their local paid profiles: gauge44 {1:2348,2:500,3:416,4:130,5:82}, extra {1:2340,2:508,3:420,4:126,5:82}. +PR1672's EDITS-1 (pivot-554 kernel) on the step-0 word prices at Δ = −1850 (ok), same as published. + +## 1. The missed mechanism + +R8's families (PR1297 ⊂ G ⊂ K2) were built from two shock types, both paired: +* cut `h += a·q` right after a read of h, undone by `h −= a·q` at the **end** of early cleanup; +* hook `s ± c·q` around **one** existing gate; +with at most two shocks per relation. + +The pivot-566 (PR1625) and pivot-554 (PR1672/PR1718) kernels are 5–7-term relations that lie outside this: +* **unpaired direct reads** `x += c·q` anywhere q still holds its start value; never undone (response `col_p[x]`, + neither the cut form `col − e` nor the hook form); +* **windows closed in early cleanup right after q's own restoration** (q holds ±q0 again there: the "signed restore" + of PR1718 is a read at a position where q's holder coefficient is −1), not at the end of cleanup; +* donor windows spanning many gates. + +The common description: every term is a read `x += λ·y` at a position where some register y holds a multiple of the +deleted read's source value (a "holder window", in B or in early-cleanup tails), and the relation is the full linear +span of these responses. This is PR1957's `gencomp2` family. Positive controls (this run): +* PR1297/#1319 gauge44 word, delete B755 (`494 −= 566`): gencomp2 and our `gencomp3` find the 5-term kernel + `98+=q@1963(tail), 98−=q@866, 210−=q@514, 150+=q@484, 146+=q@480`, Δ = −1850 ok; +* step-0 word, delete B342 (`482 −= 554`): 5-term kernel with signed tail `74 −= q`, Δ = −1850 ok. +So R8's families were too narrow (paired shocks, ≤2 terms, restore only at the end, only `+q0` value). + +## 2. Extended census on the current supplier word (gauge44) + +PR1957 (open) already ran gencomp2 on these words for 101 single-read deletions (budget −Δdel−1), and single-gate +commuting moves at 4461 positions: 0 gains. The extensions here: + +| Ext | Family | Jobs | Result | +|---|---|---|---| +| E1 | same-value **multi-deletion** subsets (pairs–quads within 172 value groups of gain-carrying reads), gencomp2, ≤8 terms | 128 / 330 run (stopped for CPU cap) | 0 solutions (74 shape-infeasible, rest CP-infeasible) | +| E2 | relaxed oracle budget (+4913; the frame-cost oracle is not additive) on 24 small-gain reads: 16 one-split (−4913), 783/802 (−6763), 822/840/858/876 (−6100; 858/876 not in PR1957), 857/875 (−3050) | 24 / 24 | 0 gains: only the trivial "read moved one slot" (Δ 0), else infeasible / shape-infeasible | +| E3 | commuting-window retiming of **every** B add to its earliest/latest slot | 3074 / 3074 priced exactly | 2068 ok (all Δ 0), 1006 infeasible, 0 gains | +| E4 | NEW **two-value holder** family (`gencomp3.py`): reads of registers holding v + u, with u cancelled by other holders of u (incl. pure-u registers); matrix-valued exactness on (input, output) entries + B-only target rows; chain exclusion; holder values taken in the deleted program | 144 / 1002 single deletions: all 24 small-gain reads (−3050…−9826 band complete: 54 of the −9567/−9826 reads) + 90 largest-gain reads (−22039…−26389) | 126 shape-infeasible, 18 CP-infeasible, 0 gains | + +E4 control: on the #1319 word it recovers the 566 kernel and new exact two-value variants (e.g. `210 += ½·676 @293`, +`210 −= ½·676 @237` replacing `210 −= q`), all Δ = −1850. So the family is strictly larger and still prices +correctly. On the current word: 144 single deletions done (smallest-gain first, and largest-gain first in parallel): +all 24 small-gain reads (−3050…−6763) are shape-infeasible or CP-infeasible within budget −Δdel−1 even with +two-value holders (e.g. 783/802: 1857 candidates, 4 two-value classes, INFEASIBLE; one-split reads 1/3: ~2400 +candidates, 8–11 classes, INFEASIBLE). All large-gain delivery reads finished so far are shape-infeasible: +some effect row has no candidate under the budget. + +## 3. Conclusion + +The R8 negative was an artifact of a too-narrow relation family, not of the words. With the right family +(holder-window spans, PR1957) and two further generalizations (multi-deletion, two-value holders), plus relaxed +oracle budgets and full retiming, the current pivot-566+554 T74 supplier word has **no admissible negative-cost +kernel in the covered sets**. Not covered: the remaining E1/E4 jobs (CPU budget), three-or-more-value holders, +cross-value multi-deletions, A-section edits (see PR1957 §5), and the extra word (it has the same scalar program, +so it yields the same candidate sets, but frames differ). + +## Reproduce +```sh +export IMB_ROOT= OPENBLAS_NUM_THREADS=1 +python scripts/ext3.py words/gauge44.json.gz E4-jobs.json out/E4 10 4 40 0 3 # two-value census +python scripts/ext.py words/gauge44.json.gz E1-jobs.json out/E1 8 4 30 0 3 # multi-deletion (gencomp2) +python scripts/ext.py words/gauge44.json.gz E2-jobs.json out/E2 10 6 40 4913 2 # relaxed budget +python scripts/retime.py words/gauge44.json.gz retime.json && python scripts/price_all.py words/gauge44.json.gz retime.json out.json 6 +``` +`gencomp3.py` imports PR1957's `fc3.solve`, `costorc.CostOracle` and the vendored PR1186 toolkit. diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/base.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/base.py new file mode 100644 index 0000000000..5add0ef610 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/base.py @@ -0,0 +1,9 @@ +import sys,json,time +sys.path.insert(0,'/Users/joelpulikkan/imb-harness/r7/tk') +import harness as H +SP='/private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/r9/' +for w in ['gauge44','extra']: + t=time.time();P=H.Pricer(SP+f'words/{w}.json.gz');print(w,P.base_cost,P.base_profile,round(time.time()-t),flush=True) +t=time.time();P=H.Pricer(SP+'words/step0-gauge44.json.gz');print('step0',P.base_cost,P.base_profile) +ed=json.load(open(SP+'src/EDITS-1.json'))['edit'];print(ed) +r=P.price(ed);print('k554 on step0',r) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext.py new file mode 100644 index 0000000000..cbb7e058b2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext.py @@ -0,0 +1,39 @@ +"""Extended value-holder kernel census driver (wraps PR1957 gencomp2.run unchanged). +usage: ext.py WORD JOBS.json OUTDIR maxterms K tl slack nworkers +JOBS = [[dels...], ...]; budget = -ddel - 1 + slack (slack>0 relaxes the non-additive frame-cost oracle; every +solution is priced exactly by the PR1186 frame solver).""" +import os, sys, json, time +sys.path.insert(0, '/private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/r9/src/pr1957/research/t74-k554-local-exhaustion/tools') +import _paths, contextlib, io +from multiprocessing import Pool +P = None; ARGS = None +def init(word, args): + global P, ARGS + import harness as H; P = H.Pricer(word); ARGS = args +def job(dels): + import gencomp2 as G + from fc3 import solve + mt, K, tl, slack, od = ARGS + t = time.time() + try: + _, r = solve(P, dict(delete=dels)); ddel = r['cost'] - P.base_cost + if ddel >= 0: return dels, dict(ddel=ddel, skip=True) + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + ddel, res = G.run(P, dels, mt, K, tl, os.path.join(od, 'x-%s.json' % '-'.join(map(str, dels))), budget=-ddel - 1 + slack) + best = min([x['delta'] for x in res if x.get('delta') is not None], default=None) + return dels, dict(ddel=ddel, nsol=len(res), best=best, st=[x['status'] for x in res], log=buf.getvalue()[-600:], sec=round(time.time() - t, 1)) + except Exception as e: + return dels, dict(err=repr(e)[:200]) +if __name__ == '__main__': + word, jp, od = sys.argv[1:4]; mt, K, tl, slack, nw = int(sys.argv[4]), int(sys.argv[5]), float(sys.argv[6]), int(sys.argv[7]), int(sys.argv[8]) + os.makedirs(od, exist_ok=True); jobs = json.load(open(jp)); summ = os.path.join(od, 'SUMMARY.json') + done = json.load(open(summ)) if os.path.exists(summ) else {} + jobs = [j for j in jobs if '-'.join(map(str, j)) not in done] + t = time.time() + with Pool(nw, initializer=init, initargs=(word, (mt, K, tl, slack, od)), maxtasksperchild=40) as pool: + for n, (dels, r) in enumerate(pool.imap_unordered(job, jobs)): + done['-'.join(map(str, dels))] = r + if r.get('best') is not None and r['best'] < 0: print('GAIN', dels, r, flush=True) + print(n, len(jobs), dels, r.get('ddel'), r.get('nsol'), r.get('best'), r.get('err', ''), r.get('sec'), round(time.time() - t), flush=True) + json.dump(done, open(summ, 'w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext3.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext3.py new file mode 100644 index 0000000000..22ead22719 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/ext3.py @@ -0,0 +1,39 @@ +"""Extended value-holder kernel census driver (wraps PR1957 gencomp2.run unchanged). +usage: ext.py WORD JOBS.json OUTDIR maxterms K tl slack nworkers +JOBS = [[dels...], ...]; budget = -ddel - 1 + slack (slack>0 relaxes the non-additive frame-cost oracle; every +solution is priced exactly by the PR1186 frame solver).""" +import os, sys, json, time +sys.path.insert(0, '/private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/r9/tools') +import contextlib, io, gencomp3 +from multiprocessing import Pool +P = None; ARGS = None +def init(word, args): + global P, ARGS + import harness as H; P = H.Pricer(word); ARGS = args +def job(dels): + import gencomp3 as G + from fc3 import solve + mt, K, tl, slack, od = ARGS + t = time.time() + try: + _, r = solve(P, dict(delete=dels)); ddel = r['cost'] - P.base_cost + if ddel >= 0: return dels, dict(ddel=ddel, skip=True) + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + info = G.run(P, dels, mt, K, tl, os.path.join(od, 'x-%s.json' % '-'.join(map(str, dels))), slack=slack, verbose=False); res = info['res']; info.pop('res') + best = min([x['delta'] for x in res if x.get('delta') is not None], default=None) + return dels, dict(info, nsol=len(res), best=best, st=[(x['status'], x['delta'], x['two_value']) for x in res], sec=round(time.time() - t, 1)) + except Exception as e: + return dels, dict(err=repr(e)[:200]) +if __name__ == '__main__': + word, jp, od = sys.argv[1:4]; mt, K, tl, slack, nw = int(sys.argv[4]), int(sys.argv[5]), float(sys.argv[6]), int(sys.argv[7]), int(sys.argv[8]) + os.makedirs(od, exist_ok=True); jobs = json.load(open(jp)); summ = os.path.join(od, 'SUMMARY.json') + done = json.load(open(summ)) if os.path.exists(summ) else {} + jobs = [j for j in jobs if '-'.join(map(str, j)) not in done] + t = time.time() + with Pool(nw, initializer=init, initargs=(word, (mt, K, tl, slack, od)), maxtasksperchild=40) as pool: + for n, (dels, r) in enumerate(pool.imap_unordered(job, jobs)): + done['-'.join(map(str, dels))] = r + if r.get('best') is not None and r['best'] < 0: print('GAIN', dels, r, flush=True) + print(n, len(jobs), dels, r.get('ddel'), r.get('nsol'), r.get('best'), r.get('err', ''), r.get('sec'), round(time.time() - t), flush=True) + json.dump(done, open(summ, 'w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/gencomp3.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/gencomp3.py new file mode 100644 index 0000000000..07bcf63b34 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/gencomp3.py @@ -0,0 +1,181 @@ +"""Two-value holder kernel search (extends PR1957 gencomp2): delete B read(s) of a value v, compensate with inserted +reads x += lam*y where y holds v, OR holds v + u for a residual direction u shared by >=2 holder windows (so the u parts +can cancel). Exactness is matrix-valued: sum_j lam_j val_j (x) col_j == Delta on every (input, output) entry, over B+tails, +plus the B-only target-row (dirt) rows. Frame-cost oracle = PR1957 CostOracle; every solution is priced exactly.""" +import os, sys, json, time, math +from fractions import Fraction as Q +from collections import defaultdict +sys.path.insert(0, '/private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/r9/src/pr1957/research/t74-k554-local-exhaustion/tools') +import _paths +from ortools.sat.python import cp_model +from fc3 import solve +from costorc import CostOracle +V = 112 +coef = lambda g: Q(g[3], g[4]) if g[0] == 'add' else Q(g[2], g[3]) + +def windows(seq, nreg, skip=frozenset()): + rows = {}; get = lambda x: rows.get(x, {x: Q(1)}) + start = [0] * nreg; cur = [None] * nreg; out = [] + for i, g in enumerate(seq): + if i in skip: continue + t = g[1] + if g[0] == 'add': + a = dict(get(t)); c = coef(g) + for k, v in get(g[2]).items(): + a[k] = a.get(k, 0) + c * v + if not a[k]: del a[k] + else: a = {k: v * coef(g) for k, v in get(t).items()} + old = get(t); rows[t] = a + if a != old: + out.append((t, start[t], i + 1, old)); start[t] = i + 1 + for x in range(nreg): out.append((x, start[x], len(seq) + 1, get(x))) + return out + +def resid(val, v, k0): + r = Q(val.get(k0, 0)) / v[k0] + y = {k: x - r * v.get(k, 0) for k, x in val.items()} + for k, x in v.items(): + if k not in val and r: y[k] = -r * x + y = {k: x for k, x in y.items() if x} + if not y: return (), r + ks = sorted(y); n = y[ks[0]] + return tuple((k, y[k] / n) for k in ks), r + +_W = None +def run(P, dels, maxterms, K, tl, out=None, slack=0, lamr=8, maxcand=6000, verbose=True): + global _W + w = P.base; B = w['B']; NB = len(B); tails = [w['cleanup'][i] for i, U in w['early_cleanup']]; seq = B + tails; L = len(seq) + nreg = 2 * V + w['report']['R'] + W = windows(seq, nreg, frozenset(dels)) # holder values of the DELETED program + # values at deletion + rows = {}; get = lambda x: rows.get(x, {x: Q(1)}); srcval = {} + for i, g in enumerate(seq): + if i > max(dels): break + if i in dels: srcval[i] = dict(get(g[2])) + t = g[1] + if g[0] == 'add': + a = dict(get(t)); c = coef(g) + for k, v in get(g[2]).items(): + a[k] = a.get(k, 0) + c * v + if not a[k]: del a[k] + else: a = {k: v * coef(g) for k, v in get(t).items()} + rows[t] = a + v = srcval[dels[0]]; k0 = min(v) + for i in dels: assert resid(srcval[i], v, k0)[0] == (), 'deleted reads must share a value' + w2, r = solve(P, dict(delete=dels)); ddel = r['cost'] - P.base_cost + bud = -ddel - 1 + slack + orc = CostOracle(w2, P.costs.c); ds = sorted(dels) + def to2(p): return p - sum(1 for t in ds if t < p) + vin = set(v) + cls = defaultdict(list) + ov = set() + for wid, (y, a, b, val) in enumerate(W): + key, _ = resid(val, v, k0) + cls[key].append(wid) + if set(val) & vin: ov.add(wid) + keep = [wid for key, L_ in cls.items() if key == () or (len(L_) >= 2 and any(x in ov for x in L_)) for wid in L_] + holders = defaultdict(list) + for wid in keep: + y, a, b, val = W[wid] + for p in range(a, min(b, L + 1)): holders[p].append(wid) + colsF, colsB = {}, {} + SF = lambda x: colsF.get(x, {x: Q(1)}); SB = lambda x: colsB.get(x, {x: Q(1)}) + D, DB = defaultdict(Q), defaultdict(Q); dset = set(dels); seen = {}; cand = [] + def step(cols, Sf, g): + if g[0] == 'add': + t, s = g[1], g[2]; c = coef(g); a = dict(Sf(s)) + for k, x in Sf(t).items(): + a[k] = a.get(k, 0) + c * x + if not a[k]: del a[k] + cols[s] = a + else: x = g[1]; cols[x] = {k: y * coef(g) for k, y in Sf(x).items()} + for i in range(L, -1, -1): + g = seq[i] if i < L else None + if g is None: pass + elif i in dset: + t = g[1]; c = coef(g) + for kin, vv in srcval[i].items(): + for k, x in SF(t).items(): D[(kin, k)] += c * vv * x + if i < NB: + for k, x in SB(t).items(): + if V <= k < 2 * V: DB[(kin, k)] += c * vv * x + else: + step(colsF, SF, g) + if i < NB: step(colsB, SB, g) + if i in holders and i not in dset: + p2 = to2(i) if i < NB else i - len(ds) + for wid in holders[i]: + y, a, b, val = W[wid] + for x in range(nreg): + if x == y: continue + cst = orc.cost(x, y, p2) + if cst > bud: continue + vf = SF(x); vb = {k: z for k, z in SB(x).items() if V <= k < 2 * V} if i < NB else {} + kk = (x, wid, tuple(sorted(vf.items())), tuple(sorted(vb.items())), i < NB) + if kk in seen and seen[kk] <= cst: continue + seen[kk] = cst + rf = {(ki, k): vi * z for ki, vi in val.items() for k, z in vf.items()} + rb = {(ki, k): vi * z for ki, vi in val.items() for k, z in vb.items()} + cand.append((x, y, i, rf, rb, cst, wid)) + D = {k: x for k, x in D.items() if x}; DB = {k: x for k, x in DB.items() if x} + nwin0 = len(cls.get((), [])); ncls = sum(1 for k, L_ in cls.items() if k and len(L_) >= 2 and any(x in ov for x in L_)) + info = dict(dels=dels, ddel=ddel, bud=bud, pure_windows=nwin0, two_value_classes=ncls, cand=len(cand), D=len(D), DB=len(DB)) + if verbose: print(info, flush=True) + if len(cand) > maxcand or ddel >= 0: return dict(info, skipped='too many candidates' if ddel < 0 else 'no deletion gain', res=[]) + den = 1 + for c_ in cand: + for z in list(c_[3].values()) + list(c_[4].values()): den = den * z.denominator // math.gcd(den, z.denominator) + for z in list(D.values()) + list(DB.values()): den = den * z.denominator // math.gcd(den, z.denominator) + SC = 2 * den + m = cp_model.CpModel(); lam = []; zz = [] + for j in range(len(cand)): + l = m.NewIntVar(-lamr, lamr, ''); b = m.NewBoolVar(''); m.Add(l == 0).OnlyEnforceIf(b.Not()); m.Add(l != 0).OnlyEnforceIf(b); lam.append(l); zz.append(b) + byF = defaultdict(list); byB = defaultdict(list) + for j, c_ in enumerate(cand): + for e, z in c_[3].items(): byF[e].append((j, z)) + for e, z in c_[4].items(): byB[e].append((j, z)) + ok = True + for E, T, by in ((D, set(D) | set(byF), byF), (DB, set(DB) | set(byB), byB)): + for e in T: + rhs = E.get(e, Q(0)) * 2 * SC; terms = [lam[j] * int(z * SC) for j, z in by.get(e, [])] + if not terms: + if rhs: ok = False + continue + m.Add(sum(terms) == int(rhs)) + byY = defaultdict(list) + for j, c_ in enumerate(cand): byY[c_[1]].append(j) + npair = 0 + for j, c_ in enumerate(cand): # an insert into x must not feed a later (or same-slot) insert reading x + for k in byY.get(c_[0], []): + if cand[k][2] >= c_[2]: m.Add(zz[j] + zz[k] <= 1); npair += 1 + info['chain_pairs'] = npair + m.Add(sum(zz) <= maxterms) + m.Add(sum(zz[j] * cand[j][5] for j in range(len(cand))) <= bud) + m.Minimize(sum(zz[j] * (cand[j][5] + 50) for j in range(len(cand)))) + info['shape'] = ok; res = [] + if ok: + for it in range(K): + s = cp_model.CpSolver(); s.parameters.max_time_in_seconds = tl; s.parameters.num_workers = 2 + st = s.Solve(m) + if st not in (cp_model.OPTIMAL, cp_model.FEASIBLE): info['stop'] = s.StatusName(st); break + supp = [j for j in range(len(cand)) if s.Value(zz[j])] + terms = [(cand[j][0], cand[j][1], cand[j][2], Q(s.Value(lam[j]), 2)) for j in supp] + m.Add(sum(zz[j] for j in supp) <= len(supp) - 1) + ed = dict(delete=list(dels), insert=[], tail=[], prepend=[]) + for x, y, p, l in terms: + gte = ['add', x, y, l.numerator, l.denominator, []] + if p < NB: ed['insert'].append((p, gte)) + else: ed['tail'].append((p - NB, gte)) + ed['insert'].sort(key=lambda t: t[0]); ed['tail'].sort(key=lambda t: t[0]) + rr = P.price(ed, timeout=240) + two = any(resid(W[cand[j][6]][3], v, k0)[0] != () for j in supp) + res.append(dict(terms=[(x, y, p, str(l)) for x, y, p, l in terms], two_value=two, est=sum(cand[j][5] for j in supp), status=rr.get('status'), delta=rr.get('delta'), profile=rr.get('profile'), ed=ed)) + if verbose: print(it, res[-1]['terms'], two, rr.get('status'), rr.get('delta'), flush=True) + info['res'] = res + if out: json.dump(info, open(out, 'w'), indent=1, default=str) + return info + +if __name__ == '__main__': + import harness as H + P = H.Pricer(sys.argv[1]); jobs = json.loads(sys.argv[2]) + for dels in jobs: run(P, dels, int(sys.argv[3]), int(sys.argv[4]), float(sys.argv[5]), slack=int(sys.argv[6]) if len(sys.argv) > 6 else 0) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/groups.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/groups.py new file mode 100644 index 0000000000..05178543a0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/groups.py @@ -0,0 +1,27 @@ +"""Group B reads by normalized source value at read time; report groups of gain-carrying reads (PR1957 relaxation).""" +import gzip,json,sys,collections +from fractions import Fraction as Q +w=json.load(gzip.open(sys.argv[1])); rel=json.load(open(sys.argv[2])) +coef=lambda g: Q(g[3],g[4]) if g[0]=='add' else Q(g[2],g[3]) +B=w['B']; tails=[w['cleanup'][i] for i,U in w['early_cleanup']]; seq=B+tails +rows={}; get=lambda x: rows.get(x,{x:Q(1)}) +def norm(r): + k0=min(r);a=r[k0];return tuple(sorted((k,v/a) for k,v in r.items())) +G=collections.defaultdict(list) +for i,g in enumerate(seq): + if g[0]=='add': + if i=2: out.append(dict(key=str(key)[:60],reads=L,gain=gl,deltas=[rel[str(i)]['delta'] for i in gl],src=sorted({B[i][2] for i in gl}))) +out.sort(key=lambda r:-len(r['gain'])) +print(len(out),'groups with >=2 gain reads; sizes',collections.Counter(len(r['gain']) for r in out)) +for r in out[:60]: print(r['src'],r['gain'],r['deltas']) +json.dump(out,open(sys.argv[3],'w')) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/price_all.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/price_all.py new file mode 100644 index 0000000000..b7d6d51346 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/price_all.py @@ -0,0 +1,25 @@ +"""Price every census candidate exactly with the #1186 exact frame toolkit (scalar exactness mod p, cone caps, exact frames). +usage: price_all.py WORD CANDS.json OUT.json [workers]""" +import sys,json,time +sys.path.insert(0,'/Users/joelpulikkan/imb-harness/r7/tk') +from multiprocessing import Pool +P=None +def init(w): + global P + import harness as H; P=H.Pricer(w) +def job(a): + k,c=a + r=P.price(c['ed'],timeout=int(__import__('os').environ.get('TMO','120'))); r.pop('word',None) + return k,r +if __name__=='__main__': + w,cp,op=sys.argv[1:4]; nw=int(sys.argv[4]) if len(sys.argv)>4 else 10 + C=json.load(open(cp)); t=time.time(); import os; out=json.load(open(op)) if os.path.exists(op) else {}; out={int(k):v for k,v in out.items()} + with Pool(nw,initializer=init,initargs=(w,),maxtasksperchild=200) as pool: + for n,(k,r) in enumerate(pool.imap_unordered(job,[(k,c) for k,c in enumerate(C) if k not in out],chunksize=1)): + out[k]=r + if r.get('status')=='ok' and r['delta']<0: print('GAIN',k,C[k]['pivot'],C[k]['kind'],r['delta'],r['profile'],flush=True) + if n%200==0: print('progress',n,len(C),round(time.time()-t),flush=True); json.dump(out,open(op,'w')) + json.dump(out,open(op,'w')) + import collections + print('status',collections.Counter(r['status'] for r in out.values())) + print('ok deltas',collections.Counter(r['delta'] for r in out.values() if r['status']=='ok').most_common(20)) diff --git a/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/retime.py b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/retime.py new file mode 100644 index 0000000000..f9018d10b4 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/r9-supplier-census/scripts/retime.py @@ -0,0 +1,20 @@ +"""Commuting-window retiming census: move one B add gate g=(t += c*s) to the far end (earliest / latest) of its maximal +window of commuting neighbours (h commutes with g iff h neither reads t nor writes s; scales on t or s block). +Exact by construction (checked again mod p by the pricer). usage: retime.py WORD OUT.json""" +import gzip,json,sys +w=json.load(gzip.open(sys.argv[1]));B=w['B'];out=[] +def comm(g,h): + if g[0]!='add': return False + t,s=g[1],g[2] + if h[0]=='scale': return h[1] not in (t,s) + return h[2]!=t and h[1]!=s +for i,g in enumerate(B): + if g[0]!='add': continue + j=i + while j+1=0 and comm(g,B[k-1]): k-=1 + gate=list(g[:5])+[[]] + if j>i: out.append(dict(pivot=g[2],kind='late',gate=i,to=j,ed=dict(delete=[i],insert=[[j+1,gate]],tail=[],prepend=[]))) + if k Date: Wed, 14 Oct 2026 16:12:41 -0400 Subject: [PATCH 12/17] Add Gaussian E8 15-total theorem: every 15-total decoder costs exactly 60 Co-Authored-By: Claude Opus 5.5 --- .../README.md | 15 ++ .../s6-gaussian-c15/README.md | 42 +++++ .../s6-gaussian-c15/expected.txt | 12 ++ .../s6-gaussian-c15/scripts/flats.py | 26 +++ .../s6-gaussian-c15/scripts/lines.py | 166 ++++++++++++++++++ .../s6-gaussian-c15/scripts/orbits.py | 53 ++++++ .../s6-gaussian-c15/scripts/price_c15.py | 19 ++ .../s6-gaussian-c15/scripts/prove_c15.py | 44 +++++ .../s6-gaussian-c15/scripts/sub_minrank.py | 50 ++++++ .../s6-gaussian-c15/verify.sh | 11 ++ 10 files changed, 438 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/expected.txt create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/orbits.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/price_c15.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/prove_c15.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/sub_minrank.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index a045e29424..71ac75dba3 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -147,3 +147,18 @@ The right family is the span of reads `x += λ·y` wherever y holds a multiple o E4's control recovers 566 plus new exact two-value variants at −1,850. A reversed-constraint bug in gencomp3 was found and fixed mid-run, and the affected results were rerun. **Not covered:** the rest of E1 and E4, three-value holders, multi-deletions across different values, A-section edits, and the extra word. The negative results suggest further supplier gains need changes outside B-local kernels, such as the A-section or the parent/centre interface. + +## 13. Gaussian E8: every 15-total decoder costs exactly 60 (`s6-gaussian-c15/`, 2026-10-14) +This answers §10's open question G2. + +**Reduction (argued).** Take a valid 15-total decoder B = W·Uᵀ over F2. If supp(Wx) lies in a 3-flat H for some x ≠ 0, then on O = the lines outside H we get W[O]x = 0. So B[O,O] has rank ≤ 14 and still obeys the decoder pattern. It therefore suffices to show that the induced pattern on O has F2 minrank ≥ 15 for every 3-flat H; the U side follows by transposition. Then every total spans K⁴, so β_j = 4 and the cost is 60. + +**Symmetry (checked).** The 60 complex reflections preserve the lines and their non-orthogonality, and generate a group of order **11,520**. The 1,500 3-flats fall into **3 orbits**: 60 of size 15, 480 of size 6 and 960 of size 4. + +**SAT (checked, no proof logs).** On the complement of each orbit representative (45, 54 and 56 lines), c = 14 is **UNSAT** with all three of CaDiCaL 1.5.3, Glucose 4 and MapleChrono, in 5–123 s. Calibrations: c = 15 is SAT on every O, and on all 60 lines c = 14 is UNSAT and c = 15 is SAT. The previous monolithic encoding ran for 18 h without an answer. + +**Group-free cross-check** (every 3-flat individually): **in progress**, 7/1,500 UNSAT so far, none SAT (`logs/prove_all.partial.log`). + +**Side lemma (argued).** For a c-total decoder and any family of flats, at most c − minrank(pattern on the complement of their union) totals have R_j inside the union (and likewise for A_j). + +**Consequence:** a Gaussian decoder costing ≤ 42, which would beat real E8, needs **≥ 16 totals**. The lower bound of 40 from §10 is unchanged. The 16-total case is being checked with this multi-flat argument. diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/README.md b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/README.md new file mode 100644 index 0000000000..97fd51dd3e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/README.md @@ -0,0 +1,42 @@ +# S6 — Gaussian E8: every decoder with exactly 15 totals costs exactly 60 (question G2 of §10 closed) + +Context: `s5-extension-ring-floors/` (#620 §10). The Gaussian view of E8 has 60 Q(i)-lines in K^4. Its F2 decoder pattern has +minrank 15 (S1: c = 14 UNSAT). S5 proved that every decoder costs >= 40. Excluding the Gaussian view needs 43; beating real E8 needs <= 42. +S5 left question G2 open (`gauss_c15.py`, a monolithic CaDiCaL run stopped after 18 h without an answer). The question was: +can a 15-total decoder have a column-space vector `W x` (or `U x`) supported inside a 3-flat? A "no" means every 15-total +decoder costs exactly 15 * 4 = 60. + +## Result +**G2 is UNSAT. Every Gaussian-E8 decoder with exactly 15 totals has Theorem-C cost exactly 60** (x_K = 1). The cheap +decoders that remain possible (cost 40..42) must have >= 16 totals. + +## Proof +1. **[argued] Reduction to small minrank instances.** Let B = W U^T be valid (B_TT = 1, B_TS = 0 on K-non-orthogonal pairs), with W, U in F2^{60x15}. + Suppose x != 0 and supp(W x) lies in a 3-flat H. Put O = the lines outside H. + - Then W[O] x = 0, so rank W[O] <= 14. + - So B[O,O] = W[O] U[O]^T has rank <= 14, and it obeys the decoder pattern induced on O. + - So if minrank(pattern on O) >= 15 for every 3-flat H, no such x exists. The same holds for U by transposition. + - Coverage: every proper flat lies in a 3-flat spanned by lines (extend by lines one at a time). W has full column rank, so W x != 0. + - Once no such x exists, every total has span(R_j) = span(A_j) = K^4. The Hermitian form is definite, so beta_j = 4 and the sum is 60 (S5 Theorem-C argument). +2. **[checked] Symmetry.** + - `orbits.py` builds the 60 complex reflections s_r s_{Jr}. Each is exact, preserves E8 and commutes with J, and each is checked to preserve the line set and the non-orthogonality relation. + - Closing them gives a permutation group of order 11520 on the lines (G31 modulo scalars). + - The 1500 3-flats form 3 orbits: 60 of size 15, 480 of size 6 and 960 of size 4. + - minrank(pattern on O) is invariant under the group, so one H per orbit is enough. +3. **[checked] SAT.** `prove_c15.py orbits` runs on each orbit representative's complement O (45, 54 and 56 lines). + - The encoding is S1's clique gauge: a 12-clique of O gets W = U = [I_12 0]. This is valid for any clique size <= c. + - c = 14 is **UNSAT** with each of three independent solvers: CaDiCaL 1.5.3, Glucose 4 and MapleChrono (5-120 s each). + - Calibrations: c = 15 is SAT on every O, and on all 60 lines c = 14 is UNSAT and c = 15 is SAT. So the encoding is not trivially infeasible. +4. **[in progress, group-free] All 1500 3-flats.** `prove_c15.py all` repeats the c = 14 test on every 3-flat complement, without using the group. Status: 700/1500 UNSAT so far and no SAT. The log is at s6/prove_all.log, and the advisor will append the final count. +5. **[script provided, not yet run] Sanity.** `price_c15.py` takes a SAT-found 15-total decoder and prices it, expecting every beta_j = 4 and a sum of 60. It was not run, to stay under the 3-process cap while the sweep runs. + +What made it tractable: S5's encoding asked for a decoder together with a witness x and a selector over 1500 flats in a single instance, and it ran 18 h or more without an answer. The rank-drop reformulation turns this into 3 orbit instances, each a 45-56-line minrank test that is UNSAT in about 5 s. + +## Side lemma (argued, from the same runs; possible input for the >= 43 question) +Take any decoder with c totals and any 3-flat H. At most c - 15 totals have R_j inside H, and the same holds for A_j. +- Proof: those columns vanish on O, so B[O,O] has rank <= c - #. +- More generally, for any family of 3-flats H_1..H_m: #{j : R_j inside some H_i} <= c - minrank(pattern on the complement of the union of the H_i). + +## Reproduce +`PATH=$SP/venv/bin:$PATH sh verify.sh` takes about 3-5 min and uses 3 processes. `FULL=1` adds the 1500-flat sweep, which takes about 45 min. Compare the output with `expected.txt`. +Logs from the original runs are in `logs/` (`prove_orbits.log`; `prove_all.log` is still running in s6/). diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/expected.txt b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/expected.txt new file mode 100644 index 0000000000..1fef093acf --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/expected.txt @@ -0,0 +1,12 @@ +Gauss Counter(...) (flats.py; includes 1500 dimension-3 flats: 60 of size 15, 480 of size 6, 960 of size 4) +group order on lines 11520 +orbits on 3-flats: [(15, 60), (4, 960), (6, 480)] total 1500 +ALL AS EXPECTED +all 60 lines c=14 UNSAT +all 60 lines c=15 SAT +|H|= 4 |O|=56 c=14 {cadical153,glucose4,maplechrono} UNSAT (clique-gauge 12) +|H|= 4 |O|=56 c=15 {cadical153,glucose4,maplechrono} SAT +|H|= 6 |O|=54 c=14 ... UNSAT ; c=15 ... SAT +|H|=15 |O|=45 c=14 ... UNSAT ; c=15 ... SAT +rank_F2 check: W,U columns 15 betas [4, 4, ..., 4] sum 60 +FULL=1: ALL AS EXPECTED (1500 of 1500 3-flats, c=14 UNSAT on every complement) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/flats.py b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/flats.py new file mode 100644 index 0000000000..b064c81896 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/flats.py @@ -0,0 +1,26 @@ +"""All flats (line sets of K-subspaces spanned by configuration lines) of a view, with dims. Exact.""" +import sys, pickle; sys.path.insert(0,sys.path[0]) +from lines import * +def flats(S): + v=S.v; out={} + def close(idx): + d=S.arank(idx); return frozenset(T for T in range(v) if S.arank(list(idx)+[T])==d), d + level=[ (frozenset([T]),1) for T in range(v)] + for f,d in level: out[f]=d + frontier=list(out.items()) + while frontier: + new={} + for f,d in frontier: + if d>=S.w: continue + for T in range(v): + if T in f or any(T in g and f in {0,+-4,+-8}). + * Gaussian view : J = left multiplication by i on R^8 = H^2 (coords 1,i,j,k per block); J preserves E8. + Hermitian Gram hG(x,y) = ( + i )/4 in Z[i] (diag 2). + * Eisenstein view: rho = fixed-point-free order-3 isometry of E8 (product of Coxeter elements of 4 orthogonal A2's). + Sesquilinear Gram hE(x,y) = ( - w )/4 in Z[w] (w^2+w+1=0; diag 2+w, a unit at 2). + * Hurwitz view : left multiplication by i, j, k; quaternionic lines of real dim 4. +Elements of Z[i] / Z[w] are stored as integer pairs (a, b) = a + b*i / a + b*w. +Everything is exact integer arithmetic; asserts check every structural claim used downstream. +""" +import itertools +from fractions import Fraction +import numpy as np + + +def e8_roots(): + R = [] + for i, j in itertools.combinations(range(8), 2): + for si in (2, -2): + for sj in (2, -2): + r = [0] * 8; r[i], r[j] = si, sj; R.append(r) + for s in itertools.product((1, -1), repeat=8): + if s.count(-1) % 2 == 0: + R.append(list(s)) + R = np.array(R, dtype=np.int64) + assert len(R) == 240 and set((R * R).sum(1)) == {8} + return R + + +def rank_q(rows): + """exact rank over Q of an integer matrix (fraction-free Gaussian elimination).""" + M = [list(map(int, r)) for r in rows] + if not M: + return 0 + n, m = len(M), len(M[0]); r = 0 + for c in range(m): + p = next((i for i in range(r, n) if M[i][c] != 0), None) + if p is None: + continue + M[r], M[p] = M[p], M[r] + for i in range(r + 1, n): + if M[i][c]: + f, g = M[i][c], M[r][c] + M[i] = [g * a - f * b for a, b in zip(M[i], M[r])] + r += 1 + if r == n: + break + return r + + +def left_quat(): + Li = np.array([[0, -1, 0, 0], [1, 0, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) + Lj = np.array([[0, 0, -1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, -1, 0, 0]]) + Lk = Li @ Lj + blk = lambda M: np.kron(np.eye(2, dtype=np.int64), M).astype(np.int64) + return blk(Li), blk(Lj), blk(Lk) + + +def a2_rotation(roots): + """rho, an order-3 fixed-point-free isometry preserving E8 (returned as an exact integer matrix 4*rho).""" + chosen = [] + for a in roots: + if len(chosen) == 4: + break + if any(np.dot(a, x) != 0 for p in chosen for x in p): + continue + for b in roots: + if np.dot(a, b) == -4 and all(np.dot(a, x) == 0 and np.dot(b, x) == 0 for p in chosen for x in p): + chosen.append((a, b)); break + assert len(chosen) == 4 + g = np.eye(8, dtype=object) + for a, b in chosen: + sa = np.eye(8, dtype=object) * 8 - 2 * np.outer(a, a).astype(object) + sb = np.eye(8, dtype=object) * 8 - 2 * np.outer(b, b).astype(object) + g = g.dot(sa).dot(sb) + D = 8 ** 8 // 4 + assert all(x % D == 0 for x in g.flatten()) + g = (g // D).astype(np.int64) + I = np.eye(8, dtype=np.int64) + assert (g @ g @ g == 64 * I).all() and (g @ g + 4 * g + 16 * I == 0).all() + return g + + +def lines_over(roots, ops): + """group roots into A-lines, A spanned by the integer matrices ops (identity first; ops may be scaled by 4). + Returns representatives (first root met of each line, in root order).""" + S = {tuple(r) for r in roots} + reps, seen = [], set() + for r in roots: + if tuple(r) in seen: + continue + span = [B @ r for B in ops] + assert rank_q(span) == len(ops) + reps.append(r) + members = [s for s in roots if rank_q(span + [s]) == len(ops)] + for s in members: + seen.add(tuple(s)) + return np.array(reps) + + +class View: + """name, reps (v x 8), ops (real matrices spanning A, scaled so that ops[i] @ root is a root times scale[i]).""" + def __init__(self, name, reps, ops, d): + self.name, self.reps, self.ops, self.d = name, reps, ops, d + self.v = len(reps) + NO = np.zeros((self.v, self.v), bool) + for B in ops: + NO |= (reps @ B @ reps.T) != 0 + np.fill_diagonal(NO, False) + self.NO = NO # A-non-orthogonal pairs (the decoder pattern forces B_TS = 0 there) + self.w = self.arank(range(self.v)) + + def arank(self, idx): + rows = [B @ self.reps[i] for i in idx for B in self.ops] + r = rank_q(rows) if rows else 0 + assert r % self.d == 0 + return r // self.d + + def pair_rank(self, R, A): + """A-rank of the pairing span(R) x span(A) = real rank of the real Gram block / dim A (Theorem C cost).""" + BR = [B @ self.reps[i] for i in R for B in self.ops] + BA = [B @ self.reps[i] for i in A for B in self.ops] + if not BR or not BA: + return 0 + M = [[int(x @ y) for y in BA] for x in BR] + r = rank_q(M); assert r % self.d == 0 + return r // self.d + + +def views(): + roots = e8_roots(); I8 = np.eye(8, dtype=np.int64) + Li, Lj, Lk = left_quat() + S = {tuple(r) for r in roots} + for M in (Li, Lj, Lk): + assert all(tuple(M @ r) in S for r in roots) + g = a2_rotation(roots) # 4*rho + assert all(tuple(g @ r // 4) in S and ((g @ r) % 4 == 0).all() for r in roots) + rho = g # 4*rho (exact integer matrix); ranks are scale-free + out = {} + out['Gauss'] = View('Gauss', lines_over(roots, [I8, Li]), [I8, Li], 2) + out['Eisen'] = View('Eisen', lines_over(roots, [I8, rho]), [I8, rho], 2) + out['Hurwitz'] = View('Hurwitz', lines_over(roots, [I8, Li, Lj, Lk]), [I8, Li, Lj, Lk], 4) + out['_J'], out['_rho'] = Li, rho + return out + + +def gauss_gram(V, J): + """hG(r_T, r_S) = ( + i)/4 as integer pairs (a, b).""" + A = V @ V.T; Bm = V @ J @ V.T + assert (A % 4 == 0).all() and (Bm % 4 == 0).all() + return A // 4, Bm // 4 + + +def eisen_gram(V, rho4): + """hE(r_T, r_S) = ( - w)/4 as integer pairs (a, b) = a + b w (rho4 = 4*rho).""" + A = V @ V.T; Bm = V @ rho4 @ V.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + + +if __name__ == '__main__': + W = views() + for nm in ('Gauss', 'Eisen', 'Hurwitz'): + S = W[nm] + print(nm, 'v', S.v, 'w', S.w, 'nonorth degree', sorted(set(S.NO.sum(1)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/orbits.py b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/orbits.py new file mode 100644 index 0000000000..11e4c31837 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/orbits.py @@ -0,0 +1,53 @@ +"""Permutation group of the 60 Gaussian-E8 lines generated by the 60 complex reflections s_r s_{Jr}; orbits on the 1500 3-flats.""" +import sys, pickle +sys.path.insert(0, sys.path[0]) +import numpy as np +from lines import views, e8_roots + +def line_index(S): + J = S.ops[1]; idx = {} + for T, r in enumerate(S.reps): + for v in (r, -r, J @ r, -(J @ r)): idx[tuple(v)] = T + return idx + +def generators(S): + J = S.ops[1]; idx = line_index(S); gens = [] + for r in S.reps: + out = [] + for T in range(S.v): + x = S.reps[T] + for a in (r, J @ r): # real reflections in r and Jr (norm 8): x -> x - 2/8 a + x = x - (2 * int(x @ a) // 8) * a + out.append(idx[tuple(x)]) + assert sorted(out) == list(range(S.v)) + gens.append(tuple(out)) + return gens + +def closure(gens, n): + G = {tuple(range(n))}; frontier = list(G) + while frontier: + new = [] + for p in frontier: + for g in gens: + q = tuple(g[p[i]] for i in range(n)) + if q not in G: G.add(q); new.append(q) + frontier = new + return G + +if __name__ == '__main__': + S = views()['Gauss'] + gens = generators(S) + # check: generators preserve non-orthogonality + for g in gens: + P = np.array(g); assert (S.NO[np.ix_(P, P)] == S.NO).all() + G = closure(gens, S.v); print('group order on lines', len(G)) + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = sorted(sorted(f) for f, d in F.items() if d == 3) + hs = {frozenset(h) for h in hyp}; seen = set(); reps = [] + for h in hyp: + if frozenset(h) in seen: continue + orb = {frozenset(g[i] for i in h) for g in G} + assert orb <= hs + seen |= orb; reps.append((h, len(orb))) + print('orbits on 3-flats:', [(len(h), n) for h, n in reps], 'total', sum(n for _, n in reps)) + pickle.dump({'G': sorted(G), 'hyp_reps': reps}, open('orbits.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/price_c15.py b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/price_c15.py new file mode 100644 index 0000000000..89b103fdd7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/price_c15.py @@ -0,0 +1,19 @@ +"""Sanity: a 15-total decoder found by SAT (calibration) has Theorem-C cost sum_j beta_j = 60, as the G2 theorem forces.""" +import sys +sys.path.insert(0, sys.path[0]) +import numpy as np +from lines import views +from sub_minrank import encode +from pysat.solvers import Solver + +S = views()['Gauss']; c = 15 +cl, pool, _ = encode(S, range(60), c) +with Solver(name='cadical153', bootstrap_with=cl) as s: + assert s.solve(); m = set(l for l in s.get_model() if l > 0) +W = np.array([[pool.id(('w', T, j)) in m for j in range(c)] for T in range(60)], int) +U = np.array([[pool.id(('u', T, j)) in m for j in range(c)] for T in range(60)], int) +B = W @ U.T % 2 +assert all(B[T, T] == 1 for T in range(60)) and not (B[S.NO] != 0).any(), 'invalid decoder' +beta = [S.pair_rank([T for T in range(60) if W[T, j]], [T for T in range(60) if U[T, j]]) for j in range(c)] +print('rank_F2 check: W,U columns', c, ' betas', beta, ' sum', sum(beta)) +assert sum(beta) == 60 diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/prove_c15.py b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/prove_c15.py new file mode 100644 index 0000000000..625d34bc02 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/prove_c15.py @@ -0,0 +1,44 @@ +"""G2 settled: no 15-total Gaussian-E8 decoder has a column-space vector supported in a 3-flat. + +Reduction (argued, elementary): if B = W U^T (W, U in F2^{60x15}) is valid and x != 0 has supp(W x) inside a 3-flat H, +then with O = lines outside H, W[O] x = 0, so rank W[O] <= 14 and B[O,O] = W[O] U[O]^T is a rank-<=14 matrix obeying +the decoder pattern induced on O. Hence it suffices that minrank(pattern on O) >= 15 for every 3-flat H (same for U by +transposition). By symmetry (group of order 11520 on the lines, orbits.py) one H per orbit suffices; argv 'all' runs all +1500 3-flats instead (group-free check). Calibrations: c = 15 on O must be SAT; c = 14 / 15 on all 60 lines UNSAT / SAT.""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from multiprocessing import Pool +from lines import views +from sub_minrank import run + +S = views()['Gauss'] + +def job(args): + H, c, solver = args + O = [T for T in range(60) if T not in set(H)] + r, t, k = run(S, O, c, solver) + return H, c, solver, r, t, k + +if __name__ == '__main__': + mode = sys.argv[1] if len(sys.argv) > 1 else 'orbits' + solvers = sys.argv[2].split(',') if len(sys.argv) > 2 else ['cadical153'] + if mode == 'all': + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + Hs = sorted(sorted(f) for f, d in F.items() if d == 3); assert len(Hs) == 1500 + jobs = [(H, 14, solvers[0]) for H in Hs] + else: + reps = pickle.load(open('orbits.pkl', 'rb'))['hyp_reps'] + Hs = [h for h, _ in reps] + jobs = [(H, c, s) for H in Hs for c in (14, 15) for s in solvers] + for c in (14, 15): + r, t, k = run(S, range(60), c, solvers[0]) + print('all 60 lines c=%d %s %.1fs' % (c, 'SAT' if r else 'UNSAT', t), flush=True) + bad = 0; n = 0 + with Pool(3) as p: + for H, c, s, r, t, k in p.imap_unordered(job, jobs): + n += 1 + exp = (c == 15) + bad += (r != exp) + if mode != 'all' or r or n % 100 == 0: + print('|H|=%2d |O|=%2d c=%d %-11s %s %.1fs clique-gauge %d [%d/%d]' % (len(H), 60 - len(H), c, s, 'SAT' if r else 'UNSAT', t, k, n, len(jobs)), flush=True) + print('ALL AS EXPECTED' if bad == 0 else 'MISMATCHES: %d' % bad, flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/sub_minrank.py b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/sub_minrank.py new file mode 100644 index 0000000000..771a4690b0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/scripts/sub_minrank.py @@ -0,0 +1,50 @@ +"""F2 minrank test of the Gaussian-E8 decoder pattern induced on a line subset O (asymmetric B = W U^T, clique gauge).""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from lines import views +from pysat.formula import IDPool +from pysat.solvers import Solver +import networkx as nx + +def encode(S, O, c, gauge=True): + O = list(O); pool = IDPool(); cl = [] + U = {T: [pool.id(('u', T, j)) for j in range(c)] for T in O} + W = {T: [pool.id(('w', T, j)) for j in range(c)] for T in O} + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + def xorlist(ls): + acc = ls[0] + for l in ls[1:]: acc = XOR(acc, l) + return acc + for T in O: + for S_ in O: + if T == S_ or S.NO[T, S_]: + b = xorlist([AND(W[T][j], U[S_][j]) for j in range(c)]) + cl.append([b] if T == S_ else [-b]) + K = [] + if gauge: + g = nx.Graph([(i, j) for i in O for j in O if i < j and S.NO[i, j]]) + K = sorted(max(nx.find_cliques(g), key=len))[:c] + for i, T in enumerate(K): + for j in range(c): + cl.append([U[T][j]] if j == i else [-U[T][j]]); cl.append([W[T][j]] if j == i else [-W[T][j]]) + return cl, pool, len(K) + +def run(S, O, c, solver='cadical153'): + cl, pool, k = encode(S, O, c) + t = time.time() + with Solver(name=solver, bootstrap_with=cl) as s: r = s.solve() + return r, time.time() - t, k + +if __name__ == '__main__': + S = views()['Gauss'] + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = [sorted(f) for f, d in F.items() if d == 3] + c = int(sys.argv[1]) if len(sys.argv) > 1 else 14 + print('full 60, c', c, run(S, range(60), c), flush=True) + for size in (15, 6, 4): + H = next(h for h in hyp if len(h) == size) + O = [T for T in range(60) if T not in H] + print('|H|', size, '|O|', len(O), 'c', c, run(S, O, c), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/verify.sh new file mode 100644 index 0000000000..de30fb27d8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/verify.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Usage: PATH=/bin:$PATH sh verify.sh (python3.11 + numpy, networkx, python-sat). ~3 min (orbit mode), 3 processes. +# Optional: FULL=1 also runs the group-free sweep over all 1500 3-flats (~45 min with 3 processes). +set -e +export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 +cd "$(dirname "$0")/scripts" +python -B flats.py Gauss # writes flats_Gauss.pkl +python -B orbits.py # group of order 11520, orbits on 3-flats +python -B prove_c15.py orbits cadical153,glucose4,maplechrono | sed 's/ [0-9.]*s / Ts /; s/\[[0-9]*\/18\]//' | sort +python -B price_c15.py +if [ -n "$FULL" ]; then python -B prove_c15.py all | tail -1; fi From 792e193fb8176a4b6cdc635331bf885cafb2b891 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Wed, 14 Oct 2026 19:15:29 -0400 Subject: [PATCH 13/17] Section 13: group-free sweep complete, all 1,500 3-flats UNSAT Co-Authored-By: Claude Opus 5.5 --- research/e8-decoder-floors-and-cold14-notes/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 71ac75dba3..2ae2e0f412 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -157,7 +157,7 @@ This answers §10's open question G2. **SAT (checked, no proof logs).** On the complement of each orbit representative (45, 54 and 56 lines), c = 14 is **UNSAT** with all three of CaDiCaL 1.5.3, Glucose 4 and MapleChrono, in 5–123 s. Calibrations: c = 15 is SAT on every O, and on all 60 lines c = 14 is UNSAT and c = 15 is SAT. The previous monolithic encoding ran for 18 h without an answer. -**Group-free cross-check** (every 3-flat individually): **in progress**, 7/1,500 UNSAT so far, none SAT (`logs/prove_all.partial.log`). +**Group-free cross-check** (every 3-flat individually, no symmetry used): **complete: all 1,500 UNSAT** ("ALL AS EXPECTED", `logs/prove_all.log`). **Side lemma (argued).** For a c-total decoder and any family of flats, at most c − minrank(pattern on the complement of their union) totals have R_j inside the union (and likewise for A_j). From 93273df2d01eaa058960b06dbad966eb6c8d1702 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Wed, 14 Oct 2026 19:15:49 -0400 Subject: [PATCH 14/17] Force-add evidence and expected logs that the repository's *.log ignore rule had excluded The notes packages' verify scripts (s4 expected/*.log) and the SAT/proof evidence (s1, s2, s5, s6 logs) were silently dropped by .gitignore's *.log rule. Co-Authored-By: Claude Opus 5.5 --- .../s1-extension-rings/ce_e16.log | 3 ++ .../s1-extension-rings/ce_g20.log | 3 ++ .../s1-extension-rings/gmin_13.log | 1 + .../s1-extension-rings/gmin_14.log | 1 + .../s1-extension-rings/gmin_15.log | 1 + .../s2-decoder/als_75_0_1.log | 1 + .../s2-decoder/als_75_0_2.log | 1 + .../s2-decoder/an75.log | 0 .../s2-decoder/flats.log | 2 ++ .../s2-decoder/run62_111.log | 30 +++++++++++++++++++ .../s2-decoder/s62_1.log | 0 .../s2-decoder/s62_2.log | 1 + .../s2-decoder/s62_3.log | 0 .../s2-decoder/sat_10_0.log | 1 + .../s2-decoder/sat_10_1.log | 1 + .../s2-decoder/uniq111.log | 1 + .../data/anneal11_X_4.log | 1 + .../data/anneal11_x_1.log | 1 + .../data/anneal11_x_2.log | 1 + .../data/anneal11_x_3.log | 1 + .../expected/cellcover11.log | 4 +++ .../expected/cellcover13.log | 4 +++ .../expected/cellcover8.log | 2 ++ .../s4-lorentzian-helpers/expected/census.log | 24 +++++++++++++++ .../expected/example.log | 1 + .../expected/frames11.log | 6 ++++ .../expected/frames8.log | 4 +++ .../s4-lorentzian-helpers/expected/model3.log | 13 ++++++++ .../expected/orbits11.log | 6 ++++ .../expected/packbases11.log | 11 +++++++ .../expected/packbases13.log | 13 ++++++++ .../expected/partition11.log | 1 + .../expected/partition13.log | 1 + .../expected/punct11.log | 4 +++ .../expected/sat19_o0.log | 2 ++ .../expected/sat19_o1.log | 2 ++ .../expected/sat19_o2.log | 2 ++ .../expected/sat19_o3.log | 2 ++ .../expected/sat19_o4.log | 2 ++ .../expected/survival11.log | 1 + .../expected/twocover11.log | 2 ++ .../expected/twocover13.log | 2 ++ .../expected/twocover8.log | 1 + .../run1-logs/cellcover11.log | 4 +++ .../run1-logs/cellcover13.log | 4 +++ .../run1-logs/cellcover8.log | 2 ++ .../run1-logs/census.log | 24 +++++++++++++++ .../run1-logs/example.log | 1 + .../run1-logs/frames11.log | 6 ++++ .../run1-logs/frames8.log | 4 +++ .../run1-logs/model3.log | 13 ++++++++ .../run1-logs/orbits11.log | 6 ++++ .../run1-logs/packbases11.log | 11 +++++++ .../run1-logs/packbases13.log | 13 ++++++++ .../run1-logs/partition11.log | 1 + .../run1-logs/partition13.log | 1 + .../run1-logs/punct11.log | 4 +++ .../run1-logs/sat19_o0.log | 2 ++ .../run1-logs/sat19_o1.log | 2 ++ .../run1-logs/sat19_o2.log | 2 ++ .../run1-logs/sat19_o3.log | 2 ++ .../run1-logs/sat19_o4.log | 2 ++ .../run1-logs/survival11.log | 1 + .../run1-logs/twocover11.log | 2 ++ .../run1-logs/twocover13.log | 2 ++ .../run1-logs/twocover8.log | 1 + .../logs/gauss_c15.log | 2 ++ .../logs/hurwitz_direct.log | 2 ++ .../s6-gaussian-c15/logs/prove_all.log | 16 ++++++++++ .../s6-gaussian-c15/logs/prove_orbits.log | 21 +++++++++++++ 70 files changed, 312 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_e16.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_g20.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_14.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_15.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/an75.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/flats.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62_111.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_0.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq111.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_X_4.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/census.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/example.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/model3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/orbits11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/punct11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o0.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o4.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/survival11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/census.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/example.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/orbits11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/punct11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o0.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o1.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o4.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/survival11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover11.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover13.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/gauss_c15.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/hurwitz_direct.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_all.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_orbits.log diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_e16.log b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_e16.log new file mode 100644 index 0000000000..636042ceec --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_e16.log @@ -0,0 +1,3 @@ +Eisen v 40 w 4 max flat sizes {1: 1, 2: 4, 3: 12} +Eisen cmax 16 sym 1 FEASIBLE obj 40.0 bound 9.0 x_K lb 0.225 900.7 +totals (rank of D_j, |A_j|): [(3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2)] diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_g20.log b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_g20.log new file mode 100644 index 0000000000..acf23a8502 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/ce_g20.log @@ -0,0 +1,3 @@ +Gauss v 60 w 4 max flat sizes {1: 1, 2: 6, 3: 15} +Gauss cmax 20 sym 1 FEASIBLE obj 60.0 bound 9.0 x_K lb 0.15 906.0 +totals (rank of D_j, |A_j|): [(3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3), (3, 3)] diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_13.log b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_13.log new file mode 100644 index 0000000000..9850f666db --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_13.log @@ -0,0 +1 @@ +13 12 UNSAT 0.1 diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_14.log b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_14.log new file mode 100644 index 0000000000..430a070f3b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_14.log @@ -0,0 +1 @@ +14 12 UNSAT 3.6 diff --git a/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_15.log b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_15.log new file mode 100644 index 0000000000..f26cab13c1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s1-extension-rings/gmin_15.log @@ -0,0 +1 @@ +15 12 SAT 1.8 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_1.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_1.log new file mode 100644 index 0000000000..5c40386a30 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_1.log @@ -0,0 +1 @@ +start 2 [75, 76] diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_2.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_2.log new file mode 100644 index 0000000000..5c40386a30 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/als_75_0_2.log @@ -0,0 +1 @@ +start 2 [75, 76] diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/an75.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/an75.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/flats.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/flats.log new file mode 100644 index 0000000000..89fa370828 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/flats.log @@ -0,0 +1,2 @@ +rank 2 4900 0.8 +rank 3 85680 36.5 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62_111.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62_111.log new file mode 100644 index 0000000000..ff3d37a193 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/run62_111.log @@ -0,0 +1,30 @@ +6 7c 7c 7c 7c 7c 7c 7c 7c False 1.0 +6 7r 7c 7c 7c 7c 7c 7c 7c False 0.8 +6 7r 7r 7c 7c 7c 7c 7c 7c False 0.8 +6 7r 7r 7r 7c 7c 7c 7c 7c False 0.6 +6 7r 7r 7r 7r 7c 7c 7c 7c False 0.8 +6 7r 7r 7r 7r 7r 7c 7c 7c False 0.8 +6 7r 7r 7r 7r 7r 7r 7c 7c False 0.5 +6 7r 7r 7r 7r 7r 7r 7r 7c False 0.4 +6 7r 7r 7r 7r 7r 7r 7r 7r False 1.1 +6 6 7c 7c 7c 7c 7c 7c 8 False 1.3 +6 6 7r 7c 7c 7c 7c 7c 8 False 1.0 +6 6 7r 7r 7c 7c 7c 7c 8 False 1.0 +6 6 7r 7r 7r 7c 7c 7c 8 False 0.7 +6 6 7r 7r 7r 7r 7c 7c 8 False 1.1 +6 6 7r 7r 7r 7r 7r 7c 8 False 1.1 +6 6 7r 7r 7r 7r 7r 7r 8 False 1.3 +6 6 6 7c 7c 7c 7c 8 8 False 1.2 +6 6 6 7r 7c 7c 7c 8 8 False 1.2 +6 6 6 7r 7r 7c 7c 8 8 False 1.2 +6 6 6 7r 7r 7r 7c 8 8 False 1.2 +6 6 6 7r 7r 7r 7r 8 8 False 1.2 +6 6 6 6 7c 7c 8 8 8 False 27.0 +6 6 6 6 7r 7c 8 8 8 False 25.0 +6 6 6 6 7r 7r 8 8 8 False 26.0 +6 6 6 6 6 8 8 8 8 False 41.7 +6 6 6 6 6 6 6 6 7c 7c False 0.0 +6 6 6 6 6 6 6 6 7r 7c False 0.0 +6 6 6 6 6 6 6 6 7r 7r False 0.1 +6 6 6 6 6 6 6 6 6 8 False 0.0 +result None diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_1.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_1.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_2.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_2.log new file mode 100644 index 0000000000..a459c94c8c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_2.log @@ -0,0 +1 @@ +0 [7, 9, 38, 73, 78, 88, 101, 110, 112] orth pairs 14 no diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_3.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/s62_3.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_0.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_0.log new file mode 100644 index 0000000000..a2f2f7e38c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_0.log @@ -0,0 +1 @@ +vars 134531 clauses 581478 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_1.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_1.log new file mode 100644 index 0000000000..e76400742c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/sat_10_1.log @@ -0,0 +1 @@ +vars 134540 clauses 581494 diff --git a/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq111.log b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq111.log new file mode 100644 index 0000000000..16c894740e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s2-decoder/uniq111.log @@ -0,0 +1 @@ +111 {(9, 'neB0'): 'UNSAT', (8, 'none'): 'UNSAT'} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_X_4.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_X_4.log new file mode 100644 index 0000000000..0e654a524a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_X_4.log @@ -0,0 +1 @@ +final best -0.045454545454545456 (np.int64(110), 220, np.int64(11)) dead [] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_1.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_1.log new file mode 100644 index 0000000000..576e385301 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_1.log @@ -0,0 +1 @@ +final best 0.5 (np.int64(110), 220, np.int64(11)) dead [] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_2.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_2.log new file mode 100644 index 0000000000..576e385301 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_2.log @@ -0,0 +1 @@ +final best 0.5 (np.int64(110), 220, np.int64(11)) dead [] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_3.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_3.log new file mode 100644 index 0000000000..576e385301 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/data/anneal11_x_3.log @@ -0,0 +1 @@ +final best 0.5 (np.int64(110), 220, np.int64(11)) dead [] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover11.log new file mode 100644 index 0000000000..4b7c8cce2a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover11.log @@ -0,0 +1,4 @@ +n=11 triple source (readers 115) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=11 triple source (readers 115) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) +n=11 pair source (readers 129) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=11 pair source (readers 129) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover13.log new file mode 100644 index 0000000000..ac261138d2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover13.log @@ -0,0 +1,4 @@ +n=13 triple source (readers 183) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=13 triple source (readers 183) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) +n=13 pair source (readers 231) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=13 pair source (readers 231) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover8.log new file mode 100644 index 0000000000..7e133838b5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/cellcover8.log @@ -0,0 +1,2 @@ +n=8 E8 root source (readers 63) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=8 E8 root source (readers 63) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/census.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/census.log new file mode 100644 index 0000000000..f2667108c6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/census.log @@ -0,0 +1,24 @@ +11 {'k': 1, 'readers': 90, 'triples': 45, 'pairs': 45, 'span': 10, 'gram': 10} +11 {'k': 2, 'readers': 90, 'triples': 72, 'pairs': 18, 'span': 11, 'gram': 11} +11 {'k': 3, 'readers': 116, 'triples': 85, 'pairs': 31, 'span': 11, 'gram': 11} +11 {'k': 4, 'readers': 116, 'triples': 88, 'pairs': 28, 'span': 11, 'gram': 11} +11 {'k': 5, 'readers': 110, 'triples': 85, 'pairs': 25, 'span': 11, 'gram': 11} +11 {'k': 6, 'readers': 110, 'triples': 80, 'pairs': 30, 'span': 11, 'gram': 11} +11 {'k': 7, 'readers': 104, 'triples': 77, 'pairs': 27, 'span': 11, 'gram': 11} +11 {'k': 8, 'readers': 104, 'triples': 80, 'pairs': 24, 'span': 11, 'gram': 11} +11 {'k': 9, 'readers': 130, 'triples': 93, 'pairs': 37, 'span': 11, 'gram': 11} +11 {'k': 10, 'readers': 130, 'triples': 120, 'pairs': 10, 'span': 11, 'gram': 11} +11 {'k': 11, 'readers': 220, 'triples': 165, 'pairs': 55, 'span': 11, 'gram': 11} +13 {'k': 1, 'readers': 132, 'triples': 66, 'pairs': 66, 'span': 12, 'gram': 12} +13 {'k': 2, 'readers': 132, 'triples': 110, 'pairs': 22, 'span': 13, 'gram': 13} +13 {'k': 3, 'readers': 184, 'triples': 136, 'pairs': 48, 'span': 13, 'gram': 13} +13 {'k': 4, 'readers': 184, 'triples': 148, 'pairs': 36, 'span': 13, 'gram': 13} +13 {'k': 5, 'readers': 188, 'triples': 150, 'pairs': 38, 'span': 13, 'gram': 13} +13 {'k': 6, 'readers': 188, 'triples': 146, 'pairs': 42, 'span': 13, 'gram': 13} +13 {'k': 7, 'readers': 176, 'triples': 140, 'pairs': 36, 'span': 13, 'gram': 13} +13 {'k': 8, 'readers': 176, 'triples': 136, 'pairs': 40, 'span': 13, 'gram': 13} +13 {'k': 9, 'readers': 180, 'triples': 138, 'pairs': 42, 'span': 13, 'gram': 13} +13 {'k': 10, 'readers': 180, 'triples': 150, 'pairs': 30, 'span': 13, 'gram': 13} +13 {'k': 11, 'readers': 232, 'triples': 176, 'pairs': 56, 'span': 13, 'gram': 13} +13 {'k': 12, 'readers': 232, 'triples': 220, 'pairs': 12, 'span': 13, 'gram': 13} +13 {'k': 13, 'readers': 364, 'triples': 286, 'pairs': 78, 'span': 13, 'gram': 13} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/example.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/example.log new file mode 100644 index 0000000000..6478d610c2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/example.log @@ -0,0 +1 @@ +punctured 10: v=210 h=11 star=109 per total [9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] thmC=108 x=109/210=0.5190 X*=6.34946e-03@s3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames11.log new file mode 100644 index 0000000000..6e0f546a89 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames11.log @@ -0,0 +1,6 @@ +n=11 v=220 4-frames=1926540 good=20790 types(line sizes)={(2, 2, 3, 3): 6930, (3, 3, 3, 3): 13860} + example [(0, 1, 2), (0, 3, 4), (1, 2), (3, 4)] + example [(0, 1, 2), (0, 3, 4), (1, 3, 5), (2, 4, 5)] +cell-perp {t,e} contains 56 lines; t-perp 115 +pairs per good frame: [0, 2]; pairs in total: 55 +NO partition into good 4-frames: every good frame holds an even number of pairs, 55 is odd diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames8.log new file mode 100644 index 0000000000..b9d67679f0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/frames8.log @@ -0,0 +1,4 @@ +n=8 v=120 4-frames=122850 good=9450 types(line sizes)={(0, 0, 0, 0): 9450} +cell-perp {t,e} contains 30 lines; t-perp 63 +partition into good 4-frames: OPTIMAL + verified partition of 120 lines into 30 good 4-frames; frame types {(0, 0, 0, 0): 30} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/model3.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/model3.log new file mode 100644 index 0000000000..58a3cef187 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/model3.log @@ -0,0 +1,13 @@ +E8 #555 calibrated model target a = 1.8058e-03 +I_{11,1} 220/110 (best; no improving puncture): x=1/2 X*=7.1715e-03 | rho* abs 2.66 rel 3.34 + rho skel_cells = 3.577: a_est abs 1.5796e-03 rel 1.7300e-03 (E8 1.8058e-03) + rho skel_pairs2 = 3.327: a_est abs 1.6537e-03 rel 1.8112e-03 (E8 1.8058e-03) + rho E8_shipped = 3.955: a_est abs 1.4795e-03 rel 1.6203e-03 (E8 1.8058e-03) + rho rho_dim = 5.207: a_est abs 1.2246e-03 rel 1.3388e-03 (E8 1.8058e-03) + rho three = 3.000: a_est abs 1.7162e-03 rel 1.8751e-03 (E8 1.8058e-03) +I_{11,1} punctured 210/109 (pair-label total at 9): x=109/210 X*=6.3495e-03 | rho* abs 2.38 rel 2.84 + rho skel_cells = 3.577: a_est abs 1.4954e-03 rel 1.6377e-03 (E8 1.8058e-03) + rho skel_pairs2 = 3.327: a_est abs 1.5655e-03 rel 1.7146e-03 (E8 1.8058e-03) + rho E8_shipped = 3.955: a_est abs 1.4006e-03 rel 1.5339e-03 (E8 1.8058e-03) + rho rho_dim = 5.207: a_est abs 1.1646e-03 rel 1.2673e-03 (E8 1.8058e-03) + rho three = 3.000: a_est abs 1.6246e-03 rel 1.7751e-03 (E8 1.8058e-03) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/orbits11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/orbits11.log new file mode 100644 index 0000000000..e8bcc374a6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/orbits11.log @@ -0,0 +1,6 @@ +n=11 omega=10 maximum cliques=38786 orbits=5 + orbit size 27720: 8 triples + 2 pairs, rep [(0, 1, 2), (0, 2, 4), (0, 2, 5), (0, 2, 6), (0, 2, 7), (3, 8, 10), (3, 9, 10), (8, 9, 10), (0, 10), (2, 10)] + orbit size 9240: 7 triples + 3 pairs, rep [(0, 1, 2), (3, 4, 10), (3, 5, 10), (3, 6, 10), (3, 7, 10), (3, 8, 10), (3, 9, 10), (0, 3), (1, 3), (2, 3)] + orbit size 495: 8 triples + 2 pairs, rep [(0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 1, 5), (0, 1, 6), (0, 1, 7), (0, 1, 9), (0, 1, 10), (0, 8), (1, 8)] + orbit size 1320: 3 triples + 7 pairs, rep [(0, 1, 2), (0, 1, 3), (0, 2, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (0, 10)] + orbit size 11: 0 triples + 10 pairs, rep [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (0, 10)] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases11.log new file mode 100644 index 0000000000..889734de46 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases11.log @@ -0,0 +1,11 @@ +n=11 k=1: |R|=90 rank=10 coloops=0 []; rest rank 10 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/9) +n=11 k=2: |R|=90 rank=11 coloops=0 []; rest rank 11 holds 8 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/8) +n=11 k=3: |R|=116 rank=11 coloops=1 [0]; rest rank 10 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/11) +n=11 k=4: |R|=116 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=5: |R|=110 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=6: |R|=110 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=7: |R|=104 rank=11 coloops=0 []; rest rank 11 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/9) +n=11 k=8: |R|=104 rank=11 coloops=0 []; rest rank 11 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/9) +n=11 k=9: |R|=130 rank=11 coloops=1 [219]; rest rank 10 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/9) +n=11 k=10: |R|=130 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=11: |R|=220 rank=11 coloops=0 []; rest rank 11 holds 20 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/20) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases13.log new file mode 100644 index 0000000000..4f26d87979 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/packbases13.log @@ -0,0 +1,13 @@ +n=13 k=1: |R|=132 rank=12 coloops=0 []; rest rank 12 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/11) +n=13 k=2: |R|=132 rank=13 coloops=0 []; rest rank 13 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/10) +n=13 k=3: |R|=184 rank=13 coloops=1 [0]; rest rank 12 holds 15 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/15) +n=13 k=4: |R|=184 rank=13 coloops=0 []; rest rank 13 holds 14 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/14) +n=13 k=5: |R|=188 rank=13 coloops=0 []; rest rank 13 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/10) +n=13 k=6: |R|=188 rank=13 coloops=0 []; rest rank 13 holds 14 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/14) +n=13 k=7: |R|=176 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=8: |R|=176 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=9: |R|=180 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=10: |R|=180 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=11: |R|=232 rank=13 coloops=1 [363]; rest rank 12 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/11) +n=13 k=12: |R|=232 rank=13 coloops=0 []; rest rank 13 holds 12 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/12) +n=13 k=13: |R|=364 rank=13 coloops=0 []; rest rank 13 holds 28 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/28) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition11.log new file mode 100644 index 0000000000..58924687b1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition11.log @@ -0,0 +1 @@ +n=11: 1 copies of each of the 220 lines partitioned into 22 independent slots (2 per centre) VERIFIED exactly => sum_i rank(R(e_i) & L) >= (1/2)|L| for every live set L; slot sizes [10] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition13.log new file mode 100644 index 0000000000..d26df56c43 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/partition13.log @@ -0,0 +1 @@ +n=13: 3 copies of each of the 364 lines partitioned into 91 independent slots (7 per centre) VERIFIED exactly => sum_i rank(R(e_i) & L) >= (3/7)|L| for every live set L; slot sizes [12] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/punct11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/punct11.log new file mode 100644 index 0000000000..d1bca15dc7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/punct11.log @@ -0,0 +1,4 @@ +I_{11,1} minus coords []: v=220 h=11 rad=0 c0=11 star=110 [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] thmC=110 x=1/2=0.5000 X*=7.17152e-03@s3 +I_{11,1} minus coords [10]: v=165 h=10 rad=0 c0=11 star=99 [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] thmC=88 x=3/5=0.6000 X*=4.04623e-03@s5 +I_{11,1} minus coords [9, 10]: v=120 h=8 rad=1 c0=10 star=80 [8, 8, 8, 8, 8, 8, 8, 8, 8, 8] thmC=80 x=2/3=0.6667 X*=3.43519e-03@s5 +I_{11,1} minus coords [8, 9, 10]: v=84 h=8 rad=0 c0=9 star=63 [7, 7, 7, 7, 7, 7, 7, 7, 7] thmC=63 x=3/4=0.7500 X*=1.98499e-03@s9 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o0.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o0.log new file mode 100644 index 0000000000..f69211c82f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o0.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 10, 11, 12, 13, 128, 129, 164, 174, 191] +result UNSAT sec 7484.3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o1.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o1.log new file mode 100644 index 0000000000..ebd5aa496d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o1.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 114, 119, 123, 126, 128, 129, 167, 176, 184] +result UNSAT sec 272.5 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o2.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o2.log new file mode 100644 index 0000000000..9df2abbbb7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o2.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 1, 2, 3, 4, 5, 7, 8, 172, 181] +result UNSAT sec 102.0 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o3.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o3.log new file mode 100644 index 0000000000..11ff895be8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o3.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 1, 9, 168, 169, 170, 171, 172, 173, 174] +result UNSAT sec 2413.2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o4.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o4.log new file mode 100644 index 0000000000..6374b814d8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/sat19_o4.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[165, 166, 167, 168, 169, 170, 171, 172, 173, 174] +result UNSAT sec 72.2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/survival11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/survival11.log new file mode 100644 index 0000000000..80cab23a7b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/survival11.log @@ -0,0 +1 @@ +{'count': 38786, 'max_per_line': 1784, 'survive_upto': 21, 'orbits': 5} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover11.log new file mode 100644 index 0000000000..077769d929 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover11.log @@ -0,0 +1,2 @@ +n=11 triple source: 115 readers spanning 10 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) +n=11 pair source: 129 readers spanning 10 dims; quadratic forms vanishing on all readers: dim 1 -> 2-cover not excluded diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover13.log new file mode 100644 index 0000000000..8ff142c645 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover13.log @@ -0,0 +1,2 @@ +n=13 triple source: 183 readers spanning 12 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) +n=13 pair source: 231 readers spanning 12 dims; quadratic forms vanishing on all readers: dim 1 -> 2-cover not excluded diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover8.log new file mode 100644 index 0000000000..4fcaaa148f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/expected/twocover8.log @@ -0,0 +1 @@ +n=8 E8 root source: 63 readers spanning 7 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover11.log new file mode 100644 index 0000000000..4b7c8cce2a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover11.log @@ -0,0 +1,4 @@ +n=11 triple source (readers 115) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=11 triple source (readers 115) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) +n=11 pair source (readers 129) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=11 pair source (readers 129) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover13.log new file mode 100644 index 0000000000..ac261138d2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover13.log @@ -0,0 +1,4 @@ +n=13 triple source (readers 183) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=13 triple source (readers 183) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) +n=13 pair source (readers 231) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=13 pair source (readers 231) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover8.log new file mode 100644 index 0000000000..7e133838b5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/cellcover8.log @@ -0,0 +1,2 @@ +n=8 E8 root source (readers 63) pairwise-orthogonal=False: cell cover number 3 (OPTIMAL) +n=8 E8 root source (readers 63) pairwise-orthogonal=True: cell cover number 3 (OPTIMAL) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/census.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/census.log new file mode 100644 index 0000000000..f2667108c6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/census.log @@ -0,0 +1,24 @@ +11 {'k': 1, 'readers': 90, 'triples': 45, 'pairs': 45, 'span': 10, 'gram': 10} +11 {'k': 2, 'readers': 90, 'triples': 72, 'pairs': 18, 'span': 11, 'gram': 11} +11 {'k': 3, 'readers': 116, 'triples': 85, 'pairs': 31, 'span': 11, 'gram': 11} +11 {'k': 4, 'readers': 116, 'triples': 88, 'pairs': 28, 'span': 11, 'gram': 11} +11 {'k': 5, 'readers': 110, 'triples': 85, 'pairs': 25, 'span': 11, 'gram': 11} +11 {'k': 6, 'readers': 110, 'triples': 80, 'pairs': 30, 'span': 11, 'gram': 11} +11 {'k': 7, 'readers': 104, 'triples': 77, 'pairs': 27, 'span': 11, 'gram': 11} +11 {'k': 8, 'readers': 104, 'triples': 80, 'pairs': 24, 'span': 11, 'gram': 11} +11 {'k': 9, 'readers': 130, 'triples': 93, 'pairs': 37, 'span': 11, 'gram': 11} +11 {'k': 10, 'readers': 130, 'triples': 120, 'pairs': 10, 'span': 11, 'gram': 11} +11 {'k': 11, 'readers': 220, 'triples': 165, 'pairs': 55, 'span': 11, 'gram': 11} +13 {'k': 1, 'readers': 132, 'triples': 66, 'pairs': 66, 'span': 12, 'gram': 12} +13 {'k': 2, 'readers': 132, 'triples': 110, 'pairs': 22, 'span': 13, 'gram': 13} +13 {'k': 3, 'readers': 184, 'triples': 136, 'pairs': 48, 'span': 13, 'gram': 13} +13 {'k': 4, 'readers': 184, 'triples': 148, 'pairs': 36, 'span': 13, 'gram': 13} +13 {'k': 5, 'readers': 188, 'triples': 150, 'pairs': 38, 'span': 13, 'gram': 13} +13 {'k': 6, 'readers': 188, 'triples': 146, 'pairs': 42, 'span': 13, 'gram': 13} +13 {'k': 7, 'readers': 176, 'triples': 140, 'pairs': 36, 'span': 13, 'gram': 13} +13 {'k': 8, 'readers': 176, 'triples': 136, 'pairs': 40, 'span': 13, 'gram': 13} +13 {'k': 9, 'readers': 180, 'triples': 138, 'pairs': 42, 'span': 13, 'gram': 13} +13 {'k': 10, 'readers': 180, 'triples': 150, 'pairs': 30, 'span': 13, 'gram': 13} +13 {'k': 11, 'readers': 232, 'triples': 176, 'pairs': 56, 'span': 13, 'gram': 13} +13 {'k': 12, 'readers': 232, 'triples': 220, 'pairs': 12, 'span': 13, 'gram': 13} +13 {'k': 13, 'readers': 364, 'triples': 286, 'pairs': 78, 'span': 13, 'gram': 13} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/example.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/example.log new file mode 100644 index 0000000000..6478d610c2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/example.log @@ -0,0 +1 @@ +punctured 10: v=210 h=11 star=109 per total [9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] thmC=108 x=109/210=0.5190 X*=6.34946e-03@s3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames11.log new file mode 100644 index 0000000000..6e0f546a89 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames11.log @@ -0,0 +1,6 @@ +n=11 v=220 4-frames=1926540 good=20790 types(line sizes)={(2, 2, 3, 3): 6930, (3, 3, 3, 3): 13860} + example [(0, 1, 2), (0, 3, 4), (1, 2), (3, 4)] + example [(0, 1, 2), (0, 3, 4), (1, 3, 5), (2, 4, 5)] +cell-perp {t,e} contains 56 lines; t-perp 115 +pairs per good frame: [0, 2]; pairs in total: 55 +NO partition into good 4-frames: every good frame holds an even number of pairs, 55 is odd diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames8.log new file mode 100644 index 0000000000..b9d67679f0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/frames8.log @@ -0,0 +1,4 @@ +n=8 v=120 4-frames=122850 good=9450 types(line sizes)={(0, 0, 0, 0): 9450} +cell-perp {t,e} contains 30 lines; t-perp 63 +partition into good 4-frames: OPTIMAL + verified partition of 120 lines into 30 good 4-frames; frame types {(0, 0, 0, 0): 30} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.log new file mode 100644 index 0000000000..58a3cef187 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/model3.log @@ -0,0 +1,13 @@ +E8 #555 calibrated model target a = 1.8058e-03 +I_{11,1} 220/110 (best; no improving puncture): x=1/2 X*=7.1715e-03 | rho* abs 2.66 rel 3.34 + rho skel_cells = 3.577: a_est abs 1.5796e-03 rel 1.7300e-03 (E8 1.8058e-03) + rho skel_pairs2 = 3.327: a_est abs 1.6537e-03 rel 1.8112e-03 (E8 1.8058e-03) + rho E8_shipped = 3.955: a_est abs 1.4795e-03 rel 1.6203e-03 (E8 1.8058e-03) + rho rho_dim = 5.207: a_est abs 1.2246e-03 rel 1.3388e-03 (E8 1.8058e-03) + rho three = 3.000: a_est abs 1.7162e-03 rel 1.8751e-03 (E8 1.8058e-03) +I_{11,1} punctured 210/109 (pair-label total at 9): x=109/210 X*=6.3495e-03 | rho* abs 2.38 rel 2.84 + rho skel_cells = 3.577: a_est abs 1.4954e-03 rel 1.6377e-03 (E8 1.8058e-03) + rho skel_pairs2 = 3.327: a_est abs 1.5655e-03 rel 1.7146e-03 (E8 1.8058e-03) + rho E8_shipped = 3.955: a_est abs 1.4006e-03 rel 1.5339e-03 (E8 1.8058e-03) + rho rho_dim = 5.207: a_est abs 1.1646e-03 rel 1.2673e-03 (E8 1.8058e-03) + rho three = 3.000: a_est abs 1.6246e-03 rel 1.7751e-03 (E8 1.8058e-03) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/orbits11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/orbits11.log new file mode 100644 index 0000000000..e8bcc374a6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/orbits11.log @@ -0,0 +1,6 @@ +n=11 omega=10 maximum cliques=38786 orbits=5 + orbit size 27720: 8 triples + 2 pairs, rep [(0, 1, 2), (0, 2, 4), (0, 2, 5), (0, 2, 6), (0, 2, 7), (3, 8, 10), (3, 9, 10), (8, 9, 10), (0, 10), (2, 10)] + orbit size 9240: 7 triples + 3 pairs, rep [(0, 1, 2), (3, 4, 10), (3, 5, 10), (3, 6, 10), (3, 7, 10), (3, 8, 10), (3, 9, 10), (0, 3), (1, 3), (2, 3)] + orbit size 495: 8 triples + 2 pairs, rep [(0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 1, 5), (0, 1, 6), (0, 1, 7), (0, 1, 9), (0, 1, 10), (0, 8), (1, 8)] + orbit size 1320: 3 triples + 7 pairs, rep [(0, 1, 2), (0, 1, 3), (0, 2, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (0, 10)] + orbit size 11: 0 triples + 10 pairs, rep [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (0, 10)] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases11.log new file mode 100644 index 0000000000..889734de46 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases11.log @@ -0,0 +1,11 @@ +n=11 k=1: |R|=90 rank=10 coloops=0 []; rest rank 10 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/9) +n=11 k=2: |R|=90 rank=11 coloops=0 []; rest rank 11 holds 8 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/8) +n=11 k=3: |R|=116 rank=11 coloops=1 [0]; rest rank 10 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/11) +n=11 k=4: |R|=116 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=5: |R|=110 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=6: |R|=110 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=7: |R|=104 rank=11 coloops=0 []; rest rank 11 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/9) +n=11 k=8: |R|=104 rank=11 coloops=0 []; rest rank 11 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/9) +n=11 k=9: |R|=130 rank=11 coloops=1 [219]; rest rank 10 holds 9 disjoint bases (verified exactly) -> rank(R - P) >= 10 - floor(|P|/9) +n=11 k=10: |R|=130 rank=11 coloops=0 []; rest rank 11 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/10) +n=11 k=11: |R|=220 rank=11 coloops=0 []; rest rank 11 holds 20 disjoint bases (verified exactly) -> rank(R - P) >= 11 - floor(|P|/20) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases13.log new file mode 100644 index 0000000000..4f26d87979 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/packbases13.log @@ -0,0 +1,13 @@ +n=13 k=1: |R|=132 rank=12 coloops=0 []; rest rank 12 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/11) +n=13 k=2: |R|=132 rank=13 coloops=0 []; rest rank 13 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/10) +n=13 k=3: |R|=184 rank=13 coloops=1 [0]; rest rank 12 holds 15 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/15) +n=13 k=4: |R|=184 rank=13 coloops=0 []; rest rank 13 holds 14 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/14) +n=13 k=5: |R|=188 rank=13 coloops=0 []; rest rank 13 holds 10 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/10) +n=13 k=6: |R|=188 rank=13 coloops=0 []; rest rank 13 holds 14 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/14) +n=13 k=7: |R|=176 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=8: |R|=176 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=9: |R|=180 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=10: |R|=180 rank=13 coloops=0 []; rest rank 13 holds 13 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/13) +n=13 k=11: |R|=232 rank=13 coloops=1 [363]; rest rank 12 holds 11 disjoint bases (verified exactly) -> rank(R - P) >= 12 - floor(|P|/11) +n=13 k=12: |R|=232 rank=13 coloops=0 []; rest rank 13 holds 12 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/12) +n=13 k=13: |R|=364 rank=13 coloops=0 []; rest rank 13 holds 28 disjoint bases (verified exactly) -> rank(R - P) >= 13 - floor(|P|/28) diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition11.log new file mode 100644 index 0000000000..58924687b1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition11.log @@ -0,0 +1 @@ +n=11: 1 copies of each of the 220 lines partitioned into 22 independent slots (2 per centre) VERIFIED exactly => sum_i rank(R(e_i) & L) >= (1/2)|L| for every live set L; slot sizes [10] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition13.log new file mode 100644 index 0000000000..d26df56c43 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/partition13.log @@ -0,0 +1 @@ +n=13: 3 copies of each of the 364 lines partitioned into 91 independent slots (7 per centre) VERIFIED exactly => sum_i rank(R(e_i) & L) >= (3/7)|L| for every live set L; slot sizes [12] diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/punct11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/punct11.log new file mode 100644 index 0000000000..d1bca15dc7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/punct11.log @@ -0,0 +1,4 @@ +I_{11,1} minus coords []: v=220 h=11 rad=0 c0=11 star=110 [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] thmC=110 x=1/2=0.5000 X*=7.17152e-03@s3 +I_{11,1} minus coords [10]: v=165 h=10 rad=0 c0=11 star=99 [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] thmC=88 x=3/5=0.6000 X*=4.04623e-03@s5 +I_{11,1} minus coords [9, 10]: v=120 h=8 rad=1 c0=10 star=80 [8, 8, 8, 8, 8, 8, 8, 8, 8, 8] thmC=80 x=2/3=0.6667 X*=3.43519e-03@s5 +I_{11,1} minus coords [8, 9, 10]: v=84 h=8 rad=0 c0=9 star=63 [7, 7, 7, 7, 7, 7, 7, 7, 7] thmC=63 x=3/4=0.7500 X*=1.98499e-03@s9 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o0.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o0.log new file mode 100644 index 0000000000..f69211c82f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o0.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 10, 11, 12, 13, 128, 129, 164, 174, 191] +result UNSAT sec 7484.3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o1.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o1.log new file mode 100644 index 0000000000..ebd5aa496d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o1.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 114, 119, 123, 126, 128, 129, 167, 176, 184] +result UNSAT sec 272.5 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o2.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o2.log new file mode 100644 index 0000000000..9df2abbbb7 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o2.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 1, 2, 3, 4, 5, 7, 8, 172, 181] +result UNSAT sec 102.0 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o3.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o3.log new file mode 100644 index 0000000000..11ff895be8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o3.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[0, 1, 9, 168, 169, 170, 171, 172, 173, 174] +result UNSAT sec 2413.2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o4.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o4.log new file mode 100644 index 0000000000..6374b814d8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/sat19_o4.log @@ -0,0 +1,2 @@ +n=11 c=10 k=19 clauses=1504140 clique=[165, 166, 167, 168, 169, 170, 171, 172, 173, 174] +result UNSAT sec 72.2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/survival11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/survival11.log new file mode 100644 index 0000000000..80cab23a7b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/survival11.log @@ -0,0 +1 @@ +{'count': 38786, 'max_per_line': 1784, 'survive_upto': 21, 'orbits': 5} diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover11.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover11.log new file mode 100644 index 0000000000..077769d929 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover11.log @@ -0,0 +1,2 @@ +n=11 triple source: 115 readers spanning 10 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) +n=11 pair source: 129 readers spanning 10 dims; quadratic forms vanishing on all readers: dim 1 -> 2-cover not excluded diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover13.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover13.log new file mode 100644 index 0000000000..8ff142c645 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover13.log @@ -0,0 +1,2 @@ +n=13 triple source: 183 readers spanning 12 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) +n=13 pair source: 231 readers spanning 12 dims; quadratic forms vanishing on all readers: dim 1 -> 2-cover not excluded diff --git a/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover8.log b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover8.log new file mode 100644 index 0000000000..4fcaaa148f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s4-lorentzian-helpers/run1-logs/twocover8.log @@ -0,0 +1 @@ +n=8 E8 root source: 63 readers spanning 7 dims; quadratic forms vanishing on all readers: dim 0 -> NO 2-cover (branch cover number >= 3) diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/gauss_c15.log b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/gauss_c15.log new file mode 100644 index 0000000000..079c77110d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/gauss_c15.log @@ -0,0 +1,2 @@ +calibration (no flat constraint) SAT 1.1 s +main run (support in a 3-flat): still running at package time (>20 min CPU) diff --git a/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/hurwitz_direct.log b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/hurwitz_direct.log new file mode 100644 index 0000000000..720f0f4710 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s5-extension-ring-floors/logs/hurwitz_direct.log @@ -0,0 +1,2 @@ +C 10 SAT +C 9: still running at package time (>20 min CPU) diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_all.log b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_all.log new file mode 100644 index 0000000000..a6edbd5ea6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_all.log @@ -0,0 +1,16 @@ +|H|= 6 |O|=54 c=14 cadical153 UNSAT 5.1s clique-gauge 12 [100/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 9.1s clique-gauge 12 [200/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 5.0s clique-gauge 12 [300/1500] +|H|= 6 |O|=54 c=14 cadical153 UNSAT 3.1s clique-gauge 12 [400/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 7.2s clique-gauge 12 [500/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 5.7s clique-gauge 12 [600/1500] +|H|= 6 |O|=54 c=14 cadical153 UNSAT 3.0s clique-gauge 12 [700/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 3.2s clique-gauge 12 [800/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 6.3s clique-gauge 12 [900/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 3.2s clique-gauge 12 [1000/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 4.8s clique-gauge 12 [1100/1500] +|H|= 6 |O|=54 c=14 cadical153 UNSAT 4.4s clique-gauge 12 [1200/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 4.2s clique-gauge 12 [1300/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 5.7s clique-gauge 12 [1400/1500] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 3.3s clique-gauge 12 [1500/1500] +ALL AS EXPECTED diff --git a/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_orbits.log b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_orbits.log new file mode 100644 index 0000000000..d9774a8206 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s6-gaussian-c15/logs/prove_orbits.log @@ -0,0 +1,21 @@ +all 60 lines c=14 UNSAT 5.6s +all 60 lines c=15 SAT 1.3s +|H|=15 |O|=45 c=14 cadical153 UNSAT 6.9s clique-gauge 12 [1/18] +|H|=15 |O|=45 c=15 cadical153 SAT 2.5s clique-gauge 12 [2/18] +|H|=15 |O|=45 c=14 glucose4 UNSAT 11.6s clique-gauge 12 [3/18] +|H|=15 |O|=45 c=15 glucose4 SAT 4.1s clique-gauge 12 [4/18] +|H|= 4 |O|=56 c=14 cadical153 UNSAT 7.4s clique-gauge 12 [5/18] +|H|= 4 |O|=56 c=14 glucose4 UNSAT 21.4s clique-gauge 12 [6/18] +|H|=15 |O|=45 c=15 maplechrono SAT 37.8s clique-gauge 12 [7/18] +|H|= 4 |O|=56 c=15 cadical153 SAT 1.2s clique-gauge 12 [8/18] +|H|=15 |O|=45 c=14 maplechrono UNSAT 57.6s clique-gauge 12 [9/18] +|H|= 4 |O|=56 c=15 maplechrono SAT 41.5s clique-gauge 12 [10/18] +|H|= 6 |O|=54 c=14 cadical153 UNSAT 4.9s clique-gauge 12 [11/18] +|H|= 6 |O|=54 c=14 glucose4 UNSAT 13.1s clique-gauge 12 [12/18] +|H|= 4 |O|=56 c=14 maplechrono UNSAT 122.7s clique-gauge 12 [13/18] +|H|= 6 |O|=54 c=15 cadical153 SAT 4.5s clique-gauge 12 [14/18] +|H|= 6 |O|=54 c=15 glucose4 SAT 2.3s clique-gauge 12 [15/18] +|H|= 6 |O|=54 c=14 maplechrono UNSAT 77.9s clique-gauge 12 [16/18] +|H|= 6 |O|=54 c=15 maplechrono SAT 55.1s clique-gauge 12 [17/18] +|H|= 4 |O|=56 c=15 glucose4 SAT 227.2s clique-gauge 12 [18/18] +ALL AS EXPECTED From 0cacc03f542534dacf58f3614cb54b521ab4bf02 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Wed, 14 Oct 2026 20:17:24 -0400 Subject: [PATCH 15/17] Add Gaussian E8 16-total theorem: no 16-total decoder costs <= 42 Co-Authored-By: Claude Opus 5.5 --- .../README.md | 31 +++ .../s7-gaussian-c16/README.md | 96 ++++++++ .../s7-gaussian-c16/data/flats_Gauss.pkl | Bin 0 -> 30575 bytes .../s7-gaussian-c16/data/lev_c16_d2.pkl | Bin 0 -> 8098 bytes .../s7-gaussian-c16/data/lev_c16_d3_hi.pkl | Bin 0 -> 64989 bytes .../s7-gaussian-c16/data/orbits.pkl | Bin 0 -> 1417286 bytes .../s7-gaussian-c16/data/pair_orbits.pkl | Bin 0 -> 1686 bytes .../s7-gaussian-c16/expected.txt | 43 ++++ .../s7-gaussian-c16/logs/calib.log | 2 + .../s7-gaussian-c16/logs/calib15.log | 4 + .../s7-gaussian-c16/logs/cross_glucose.log | 208 ++++++++++++++++++ .../s7-gaussian-c16/logs/dumbbell.log | 9 + .../s7-gaussian-c16/logs/lev2.log | 10 + .../s7-gaussian-c16/logs/lev3.log | 31 +++ .../s7-gaussian-c16/logs/lev3_hi.log | 21 ++ .../s7-gaussian-c16/logs/levels_c16.log | 15 ++ .../s7-gaussian-c16/logs/pairs_c14.log | 179 +++++++++++++++ .../s7-gaussian-c16/logs/price16.log | 1 + .../s7-gaussian-c16/logs/prove_c16.log | 22 ++ .../s7-gaussian-c16/logs/refine.log | 83 +++++++ .../s7-gaussian-c16/logs/verify_run.log | 44 ++++ .../s7-gaussian-c16/scripts/acap_sanity.py | 12 + .../s7-gaussian-c16/scripts/crosscheck.py | 37 ++++ .../s7-gaussian-c16/scripts/direct.py | 60 +++++ .../s7-gaussian-c16/scripts/dumbbell.py | 112 ++++++++++ .../s7-gaussian-c16/scripts/flats.py | 26 +++ .../s7-gaussian-c16/scripts/levels.py | 85 +++++++ .../s7-gaussian-c16/scripts/levels2.py | 110 +++++++++ .../s7-gaussian-c16/scripts/levels3.py | 62 ++++++ .../s7-gaussian-c16/scripts/lines.py | 166 ++++++++++++++ .../s7-gaussian-c16/scripts/orbits.py | 53 +++++ .../s7-gaussian-c16/scripts/pairs.py | 61 +++++ .../s7-gaussian-c16/scripts/price16.py | 18 ++ .../s7-gaussian-c16/scripts/prove_c16.py | 88 ++++++++ .../s7-gaussian-c16/scripts/refine.py | 97 ++++++++ .../s7-gaussian-c16/scripts/sub_minrank.py | 50 +++++ .../s7-gaussian-c16/scripts/test_pairs.py | 19 ++ .../s7-gaussian-c16/verify.sh | 17 ++ 38 files changed, 1872 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/flats_Gauss.pkl create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/lev_c16_d2.pkl create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/lev_c16_d3_hi.pkl create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/orbits.pkl create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/pair_orbits.pkl create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/expected.txt create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib15.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/cross_glucose.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/dumbbell.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3_hi.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/levels_c16.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/pairs_c14.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/price16.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/prove_c16.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/refine.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/verify_run.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/acap_sanity.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/crosscheck.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/direct.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/dumbbell.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels3.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/orbits.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/pairs.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/price16.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/prove_c16.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/refine.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/sub_minrank.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/test_pairs.py create mode 100755 research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 2ae2e0f412..425895e9bf 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -162,3 +162,34 @@ This answers §10's open question G2. **Side lemma (argued).** For a c-total decoder and any family of flats, at most c − minrank(pattern on the complement of their union) totals have R_j inside the union (and likewise for A_j). **Consequence:** a Gaussian decoder costing ≤ 42, which would beat real E8, needs **≥ 16 totals**. The lower bound of 40 from §10 is unchanged. The 16-total case is being checked with this multi-flat argument. + +## 14. Gaussian E8: no 16-total decoder costs ≤ 42 (`s7-gaussian-c16/`, `verify.sh` reproduces `expected.txt`, 2026-10-14) +Together with §13, a Gaussian E8 decoder that beats real E8 (cost ≤ 42) needs **≥ 17 totals**. + +**Proof chain** (k = number of cheap totals, β_j < 4): +1. **Cost types (argued).** β_j = dim X − dim(X ∩ Y^⊥). The cheap types are: + - *a*: a side of dim ≤ 2 (β ≥ 0); + - *p*: a (3,3) "dumbbell" with β = 2; + - *q*: everything else (β ≥ 3). + + Cost ≤ 42 at c = 16 forces **3a + p ≥ 22 − k**. +2. **Multi-flat rank drop (argued; generalises §13).** Choose one legal 3-flat per cheap total. Then for every subset J, the minrank of the pattern on the complement of their union is ≤ 16 − |J| (a "level-k" set). +3. **Level tables (checked; orbit reps under the order-11,520 group).** + + | k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | + |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + | reps | 3 | 28 | 120 | 368 | 498 | 464 | 315 | 206 | 124 | 74 | 32 | 24 | 10 | 6 | 1 | **0** | + +4. **(A) checked:** *a* = 0 whenever k ≥ 5. +5. **(B) checked:** the most jointly valid dumbbells are p* = 5, 4, 3, 2, 1 for k = 11..15, against the 11, 10, 9, 8, 7 needed. +6. **Arithmetic.** For k ≤ 4, 3a + p ≤ 3k < 22 − k. For 5 ≤ k ≤ 10, p ≤ k < 22 − k. For 11 ≤ k ≤ 15, p ≤ p* < 22 − k. For k = 16, no level-16 set exists. + +**Checks.** +- All 200 UNSAT prunings were re-solved with Glucose 4: 0 mismatches. +- The fast canonicaliser matches the slow one on levels 2–4. +- A from-scratch `verify.sh` matches exactly; `CROSS=1` adds the Glucose re-solve. +- Calibrations: c = 16 without a cost cap is SAT, and the decoder found prices at 64; c = 15 reproduces §13. +- Side result: at most 2 totals can have a side of dim ≤ 2. +- Related scoped results: #767, #2066. + +**Open:** c ≥ 17. The same machinery applies, with 3a + p ≥ 26 − k and level tables at rank ≤ 17 − |J|. diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/README.md b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/README.md new file mode 100644 index 0000000000..166f186b6b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/README.md @@ -0,0 +1,96 @@ +# S7 — Gaussian E8: no decoder with exactly 16 totals costs ≤ 42 + +Context: #620 §10 (`s5-extension-ring-floors/`) and §13 (S6, every 15-total decoder costs exactly 60). +- The setting is the Gaussian view of E8: 60 Q(i)-lines in K⁴ with the F2 decoder pattern (B_TT = 1, and B_TS = 0 on K-non-orthogonal pairs). The minrank is 15. +- Theorem-C cost is Σβ_j, where β_j is the K-rank of the pairing span(R_j) × span(A_j). +- Proven floor: 40 for every decoder (S5). +- To beat real E8 a decoder needs Σβ ≤ 42. By S5 and S6, such a decoder needs at least 16 totals. + +## Result + +**Theorem (S7).** Every Gaussian-E8 decoder with exactly 16 totals has Σβ_j ≥ 43. + +So a Gaussian decoder that beats real E8 needs at least 17 totals. + +## Definitions + +- **Level-k set.** A set of k distinct 3-flats Z_1..Z_k such that every subset J of it satisfies + minrank(pattern on O_J) ≤ 16 − |J|, where O_J is the set of lines outside ∪_{i∈J} Z_i. +- **Cheap total.** Total j is cheap when β_j < 4, and k is the number of cheap totals. + +## Proof + +### 1. [argued] Cost by type +The form is Hermitian definite on K⁴. With X = span R_j and Y = span A_j, β_j = dim X − dim(X ∩ Y^⊥) ≥ dim X + dim Y − 4. +- If neither side is proper, β_j = 4. +- Otherwise β_j < 4, and a side lies in a proper flat, hence in a 3-flat spanned by lines. + +Cheap totals fall into three types: +- **a**: one side has dim ≤ 2, so it lies in a 2-flat Z. **Every** 3-flat through Z is a legal 3-flat for this total. β ≥ 0. +- **p**: dims (3,3) with β = 2. Then X ≠ Y, and both X and Y are legal 3-flats (a "dumbbell"). +- **q**: everything else. Here β ≥ 3: either (3,4), or (3,3) with β = 3. + +With f = 16 − k full totals: +- cost ≥ 4f + 2p + 3q. +- So cost ≤ 42 forces **3a + p ≥ 22 − k**. + +### 2. [argued] Rank drop over several flats (extends the S6 lemma) +Pick one legal 3-flat Z_j for each cheap total j. +- For any subset J, the columns j ∈ J of W (or U) vanish on O_J. So B[O_J, O_J] has rank ≤ 16 − |J| and obeys the induced pattern. +- Hence **every** legal choice gives a level-k set. +- The flats are distinct. If one 3-flat H were legal for two totals, then rank B[O,O] ≤ 14 on O = the lines outside H, but S6 proved minrank 15 there. + +### 3. [checked] Level tables up to the line group (order 11520, from S6 `orbits.py`) +- `levels2.py` / `levels3.py` compute all orbit representatives of level-k sets, k = 1..16. The run uses these steps: + - extend each level-(k−1) rep by one 3-flat; + - canonicalise (least image under the group); + - keep the set only if all of its (k−1)-subsets are level-(k−1) sets; + - SAT-test the whole union at rank ≤ 16 − k. The SAT encoding is S6's clique-gauge encoding. +- Rep counts by level: + + | k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | + |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| + | reps | 3 | 28 | 120 | 368 | 498 | 464 | 315 | 206 | 124 | 74 | 32 | 24 | 10 | 6 | 1 | 0 | + +- Level 2: 28 of the 178 pair orbits pass. +- From level 7 on, subset closure does all of the pruning. +- Consistency check: the slow canonicaliser (`levels.py`) and the fast one (`levels2.py`/`levels3.py`) agree on levels 2–4. +- **Cross-check.** Every UNSAT pruning (levels 2–6, 200 instances) was re-solved with Glucose 4: see `logs/cross_glucose.log`. Only UNSAT verdicts remove configurations; a wrong SAT verdict would only keep more. + +### 4. [checked] (A): a = 0 whenever k ≥ 5 +A member of a level-k rep is *a-capable* if some 2-flat Z inside it has the following property: substituting each 3-flat through Z still gives a level-k set (6, 12 or 14 substitutions, depending on the 2-flat's size). +- Reps with an a-capable member: 3, 19, 18 and 3 at k = 1..4, and **0 for every k ≥ 5**. +- So a decoder with k ≥ 5 cheap totals has no total with a side of dim ≤ 2. +- The case of a line side is covered too: the line lies in a 2-flat inside the chosen 3-flat, and every 3-flat through that 2-flat contains the line. + +### 5. [checked] (B): few dumbbells +- β between 3-flats is computed for all 1500² pairs: β = 2 on 102180 ordered pairs and 3 on the rest. The method is SVD of products of orthonormal bases; an assert checks that no singular value is ambiguous. +- p* is the largest number of members of a level-k rep that can be doubled at once. Doubling means each chosen member gets a partner Y with β = 2, and **all 2^p** transversals are level-k sets. +- Results for k = 11..15: p* = 5, 4, 3, 2, 1. The requirement 22 − k is 11, 10, 9, 8, 7. + +### 6. Arithmetic +| k | why 3a + p ≥ 22 − k fails | +|---|---| +| ≤ 4 | 3a + p ≤ 3k < 22 − k | +| 5..10 | a = 0, so p ≤ k < 22 − k | +| 11..15 | p ≤ p* < 22 − k | +| 16 | no level-16 set exists | + +The 2-flat search (`lev_c16_d2`) is not needed for the theorem. It shows that at most 2 totals can have a side of dim ≤ 2, for any k. + +## Calibrations +- c = 16 decoders exist (SAT in 0.1 s, `calib.log`). A SAT-found c = 16 decoder is priced in `logs/price16.log`: all β_j = 4, sum 64. +- Level 1 at c = 15 is empty (rank ≤ 14 on each 3-flat complement is UNSAT), which reproduces S6. +- The direct monolithic encoding (`direct.py`: 16 columns, 9 of them confined to flats, no gauge) gave no answer in 20 min per instance. The orbit and level route is what made the problem tractable. + +## Related hub work (cited; no overlap) +- #767 (cbethin, planar and incidence obstructions) and #2066 (adamgall, whole-hyperplane reciprocity ≥ 180, symmetric B) are scoped subclass bounds. +- #738 and #770/#868 are about the clique-LP floor of 40. +- None of these treats 16-total decoders. + +## Reproduce +`PATH=$SP/venv/bin:$PATH sh verify.sh` uses 8 processes. It rebuilds the level tables from scratch (about 15 min), runs `prove_c16.py` (about 13 min), `price16.py` and the c = 15 calibration. Compare the output with `expected.txt`. A from-scratch run reproduced `expected.txt`, and the rebuilt tables are identical to `data/lev_c16_d3_hi.pkl`. `CROSS=1` adds the Glucose cross-check (about 45 min). + +## Notes +- `scripts/sub_minrank.py` is S6's encoder with one change: isolated lines are added to the clique graph, so a line set with no non-orthogonal pair still gets a 1-clique gauge. +- `refine.py`, `acap_sanity.py` and `dumbbell.py` are the exploratory forms of (A) and (B). `prove_c16.py` is the consolidated check. diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/flats_Gauss.pkl b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/flats_Gauss.pkl new file mode 100644 index 0000000000000000000000000000000000000000..1dd3283f66ec0be1a373ebfe4d703e91c33db893 GIT binary patch literal 30575 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 G?*0cUA_|xQ literal 0 HcmV?d00001 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/lev_c16_d3_hi.pkl b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/lev_c16_d3_hi.pkl new file mode 100644 index 0000000000000000000000000000000000000000..912743cd0c6bff4f01799f8954f69e74a05d3ac3 GIT binary patch literal 64989 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 literal 0 HcmV?d00001 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/orbits.pkl b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/data/orbits.pkl new file mode 100644 index 0000000000000000000000000000000000000000..c48f4194f8ab9705d57fabd9d9a0d746aa0ef794 GIT binary patch literal 1417286 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 z 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%0DO((Uj_x5G<~n9*7cIkVBCXF9Xdsx#9O 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 z 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 literal 0 HcmV?d00001 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/expected.txt b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/expected.txt new file mode 100644 index 0000000000..4324b68797 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/expected.txt @@ -0,0 +1,43 @@ +Gauss v 60 w 4 nonorth degree [np.int64(44)] +LEVEL 1: 3 good orbit reps (3s) +LEVEL 2: 28 good orbit reps (131s) +LEVEL 3: 120 good orbit reps (157s) +LEVEL 4: 368 good orbit reps +LEVEL 5: 498 good orbit reps +LEVEL 6: 464 good orbit reps +LEVEL 7: 315 good orbit reps +LEVEL 8: 206 good orbit reps +LEVEL 9: 124 good orbit reps +LEVEL 10: 74 good orbit reps +LEVEL 11: 32 good orbit reps +LEVEL 12: 24 good orbit reps +LEVEL 13: 10 good orbit reps +LEVEL 14: 6 good orbit reps +LEVEL 15: 1 good orbit reps +LEVEL 16: 0 good orbit reps +EMPTY at level 16 => at most 15 such flats/totals for c=16, dims 3 +level sizes: {1: 3, 2: 28, 3: 120, 4: 368, 5: 498, 6: 464, 7: 315, 8: 206, 9: 124, 10: 74, 11: 32, 12: 24, 13: 10, 14: 6, 15: 1, 16: 0} +(A) k= 1: 3 of 3 reps have an a-capable member +(A) k= 2: 19 of 28 reps have an a-capable member +(A) k= 3: 18 of 120 reps have an a-capable member +(A) k= 4: 3 of 368 reps have an a-capable member +(A) k= 5: 0 of 498 reps have an a-capable member +(A) k= 6: 0 of 464 reps have an a-capable member +(A) k= 7: 0 of 315 reps have an a-capable member +(A) k= 8: 0 of 206 reps have an a-capable member +(A) k= 9: 0 of 124 reps have an a-capable member +(A) k=10: 0 of 74 reps have an a-capable member +(A) k=11: 0 of 32 reps have an a-capable member +(A) k=12: 0 of 24 reps have an a-capable member +(A) k=13: 0 of 10 reps have an a-capable member +(A) k=14: 0 of 6 reps have an a-capable member +(A) k=15: 0 of 1 reps have an a-capable member +(B) k=11: max p* = 5 (need >= 11 for cost <= 42) +(B) k=12: max p* = 4 (need >= 10 for cost <= 42) +(B) k=13: max p* = 3 (need >= 9 for cost <= 42) +(B) k=14: max p* = 2 (need >= 8 for cost <= 42) +(B) k=15: max p* = 1 (need >= 7 for cost <= 42) +THEOREM: no Gaussian-E8 decoder with exactly 16 totals has sum beta_j <= 42 +c=16 decoder valid; betas [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] sum 64 cheap totals 0 +LEVEL 1: 0 good orbit reps +EMPTY at level 1 => at most 0 such flats/totals for c=15, dims 3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib.log new file mode 100644 index 0000000000..ab9ce64757 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib.log @@ -0,0 +1,2 @@ +calib no-gauge c=16 True 0.1s +calib no-gauge c=15 True 0.1s diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib15.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib15.log new file mode 100644 index 0000000000..8424c26255 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/calib15.log @@ -0,0 +1,4 @@ +level 1: candidates 3, subset-closed 3; testing minrank <= 14 (0s) +LEVEL 1: 0 good orbit reps (12s) + (flat sizes, |O|) histogram: [] +EMPTY at level 1 => at most 0 such flats/totals for c=15, dims 3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/cross_glucose.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/cross_glucose.log new file mode 100644 index 0000000000..a0b73265bc --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/cross_glucose.log @@ -0,0 +1,208 @@ +level 2: 178 candidates, 28 good, 150 UNSAT prunings to recheck with glucose4 + (0, 2) |O|=36 minrank<=14: UNSAT 10.1s + (0, 23) |O|=43 minrank<=14: UNSAT 12.6s + (0, 19) |O|=41 minrank<=14: UNSAT 14.2s + (0, 132) |O|=42 minrank<=14: UNSAT 14.7s + (0, 64) |O|=40 minrank<=14: UNSAT 24.2s + (0, 134) |O|=42 minrank<=14: UNSAT 16.8s + (0, 129) |O|=42 minrank<=14: UNSAT 27.8s + (0, 208) |O|=42 minrank<=14: UNSAT 14.3s + (0, 136) |O|=44 minrank<=14: UNSAT 16.0s + (0, 212) |O|=42 minrank<=14: UNSAT 14.3s + (0, 63) |O|=42 minrank<=14: UNSAT 32.1s + (0, 232) |O|=42 minrank<=14: UNSAT 9.8s + (0, 233) |O|=42 minrank<=14: UNSAT 15.8s + (15, 21) |O|=52 minrank<=14: UNSAT 19.6s + (15, 20) |O|=52 minrank<=14: UNSAT 23.6s + (0, 15) |O|=43 minrank<=14: UNSAT 54.5s + (15, 19) |O|=52 minrank<=14: UNSAT 26.3s + (0, 952) |O|=41 minrank<=14: UNSAT 29.0s + (15, 17) |O|=54 minrank<=14: UNSAT 32.4s + (15, 22) |O|=52 minrank<=14: UNSAT 28.7s + (15, 29) |O|=53 minrank<=14: UNSAT 17.8s + (15, 25) |O|=54 minrank<=14: UNSAT 23.0s + (15, 26) |O|=54 minrank<=14: UNSAT 23.2s + (15, 23) |O|=54 minrank<=14: UNSAT 35.6s + (15, 33) |O|=51 minrank<=14: UNSAT 28.1s + (15, 24) |O|=54 minrank<=14: UNSAT 39.9s + (15, 35) |O|=51 minrank<=14: UNSAT 29.9s + (15, 44) |O|=51 minrank<=14: UNSAT 15.0s + (15, 37) |O|=53 minrank<=14: UNSAT 20.2s + (15, 43) |O|=51 minrank<=14: UNSAT 26.1s + (15, 31) |O|=53 minrank<=14: UNSAT 47.2s + (15, 47) |O|=51 minrank<=14: UNSAT 13.7s + (15, 49) |O|=53 minrank<=14: UNSAT 15.8s + (15, 45) |O|=53 minrank<=14: UNSAT 36.4s + (15, 46) |O|=53 minrank<=14: UNSAT 27.9s + (15, 50) |O|=53 minrank<=14: UNSAT 25.7s + (15, 129) |O|=50 minrank<=14: UNSAT 23.5s + (15, 132) |O|=50 minrank<=14: UNSAT 24.0s + (15, 140) |O|=51 minrank<=14: UNSAT 20.2s + (15, 141) |O|=51 minrank<=14: UNSAT 20.6s + (15, 143) |O|=51 minrank<=14: UNSAT 22.0s + (15, 136) |O|=52 minrank<=14: UNSAT 42.0s + (15, 144) |O|=51 minrank<=14: UNSAT 23.7s + (15, 146) |O|=51 minrank<=14: UNSAT 14.8s + (15, 48) |O|=51 minrank<=14: UNSAT 73.3s + (15, 145) |O|=51 minrank<=14: UNSAT 30.7s + (15, 154) |O|=50 minrank<=14: UNSAT 18.0s + (15, 155) |O|=50 minrank<=14: UNSAT 19.6s + (15, 142) |O|=53 minrank<=14: UNSAT 55.7s + (15, 153) |O|=52 minrank<=14: UNSAT 24.7s + (15, 157) |O|=50 minrank<=14: UNSAT 20.3s + (15, 148) |O|=53 minrank<=14: UNSAT 41.8s + (15, 159) |O|=52 minrank<=14: UNSAT 18.8s + (15, 158) |O|=52 minrank<=14: UNSAT 29.2s + (15, 165) |O|=50 minrank<=14: UNSAT 24.0s + (15, 134) |O|=50 minrank<=14: UNSAT 102.1s + (15, 167) |O|=50 minrank<=14: UNSAT 19.2s + (15, 164) |O|=52 minrank<=14: UNSAT 36.4s + (15, 166) |O|=50 minrank<=14: UNSAT 30.3s + (15, 156) |O|=50 minrank<=14: UNSAT 55.2s + (15, 170) |O|=52 minrank<=14: UNSAT 25.4s + (15, 169) |O|=52 minrank<=14: UNSAT 26.6s + (15, 168) |O|=50 minrank<=14: UNSAT 55.3s + (15, 205) |O|=52 minrank<=14: UNSAT 28.9s + (15, 202) |O|=50 minrank<=14: UNSAT 40.6s + (15, 200) |O|=50 minrank<=14: UNSAT 45.6s + (15, 208) |O|=52 minrank<=14: UNSAT 29.9s + (15, 201) |O|=50 minrank<=14: UNSAT 48.4s + (15, 203) |O|=50 minrank<=14: UNSAT 44.4s + (15, 206) |O|=52 minrank<=14: UNSAT 37.4s + (15, 211) |O|=52 minrank<=14: UNSAT 24.6s + (15, 209) |O|=52 minrank<=14: UNSAT 38.0s + (15, 232) |O|=52 minrank<=14: UNSAT 26.3s + (15, 214) |O|=52 minrank<=14: UNSAT 44.7s + (15, 215) |O|=52 minrank<=14: UNSAT 42.5s + (15, 213) |O|=52 minrank<=14: UNSAT 51.8s + (15, 210) |O|=52 minrank<=14: UNSAT 68.2s + (15, 239) |O|=52 minrank<=14: UNSAT 18.1s + (15, 212) |O|=52 minrank<=14: UNSAT 73.3s + (15, 235) |O|=52 minrank<=14: UNSAT 46.5s + (15, 234) |O|=52 minrank<=14: UNSAT 62.3s + (15, 238) |O|=52 minrank<=14: UNSAT 50.8s + (15, 242) |O|=52 minrank<=14: UNSAT 45.1s + (15, 406) |O|=53 minrank<=14: UNSAT 23.9s + (15, 405) |O|=53 minrank<=14: UNSAT 32.1s + (15, 409) |O|=51 minrank<=14: UNSAT 15.6s + (15, 407) |O|=51 minrank<=14: UNSAT 27.4s + (15, 396) |O|=50 minrank<=14: UNSAT 51.3s + (15, 398) |O|=50 minrank<=14: UNSAT 44.0s + (15, 408) |O|=51 minrank<=14: UNSAT 25.5s + (15, 442) |O|=50 minrank<=14: UNSAT 16.2s + (15, 411) |O|=53 minrank<=14: UNSAT 25.0s + (15, 410) |O|=51 minrank<=14: UNSAT 30.4s + (15, 412) |O|=53 minrank<=14: UNSAT 31.9s + (15, 443) |O|=50 minrank<=14: UNSAT 30.6s + (15, 444) |O|=50 minrank<=14: UNSAT 31.4s + (15, 445) |O|=50 minrank<=14: UNSAT 41.4s + (15, 450) |O|=52 minrank<=14: UNSAT 41.6s + (15, 451) |O|=52 minrank<=14: UNSAT 38.2s + (15, 462) |O|=53 minrank<=14: UNSAT 27.5s + (15, 454) |O|=52 minrank<=14: UNSAT 38.5s + (15, 455) |O|=52 minrank<=14: UNSAT 42.1s + (15, 463) |O|=53 minrank<=14: UNSAT 36.0s + (15, 476) |O|=52 minrank<=14: UNSAT 22.4s + (15, 477) |O|=52 minrank<=14: UNSAT 25.5s + (15, 474) |O|=52 minrank<=14: UNSAT 42.5s + (15, 1129) |O|=51 minrank<=14: UNSAT 14.9s + (15, 481) |O|=52 minrank<=14: UNSAT 31.4s + (15, 1133) |O|=50 minrank<=14: UNSAT 18.2s + (15, 478) |O|=52 minrank<=14: UNSAT 45.4s + (15, 1128) |O|=51 minrank<=14: UNSAT 34.3s + (15, 1149) |O|=53 minrank<=14: UNSAT 24.4s + (15, 484) |O|=52 minrank<=14: UNSAT 55.2s + (15, 1160) |O|=51 minrank<=14: UNSAT 13.5s + (15, 1132) |O|=50 minrank<=14: UNSAT 38.9s + (15, 488) |O|=52 minrank<=14: UNSAT 55.9s + (15, 1148) |O|=53 minrank<=14: UNSAT 42.7s + (15, 1166) |O|=51 minrank<=14: UNSAT 28.9s + (15, 1167) |O|=51 minrank<=14: UNSAT 28.8s + (15, 1150) |O|=55 minrank<=14: UNSAT 56.3s + (15, 1182) |O|=53 minrank<=14: UNSAT 25.6s + (15, 1163) |O|=53 minrank<=14: UNSAT 46.9s + (15, 1180) |O|=53 minrank<=14: UNSAT 38.1s + (15, 1169) |O|=53 minrank<=14: UNSAT 51.4s + (15, 1161) |O|=51 minrank<=14: UNSAT 62.5s + (15, 1284) |O|=52 minrank<=14: UNSAT 19.3s + (15, 1276) |O|=52 minrank<=14: UNSAT 32.4s + (19, 21) |O|=50 minrank<=14: UNSAT 22.1s + (15, 1186) |O|=53 minrank<=14: UNSAT 47.7s + (19, 43) |O|=49 minrank<=14: UNSAT 20.5s + (15, 1208) |O|=50 minrank<=14: UNSAT 57.7s + (15, 1281) |O|=52 minrank<=14: UNSAT 43.5s + (19, 35) |O|=49 minrank<=14: UNSAT 39.6s + (19, 72) |O|=51 minrank<=14: UNSAT 6.9s + (19, 66) |O|=49 minrank<=14: UNSAT 18.4s + (19, 75) |O|=49 minrank<=14: UNSAT 8.6s + (15, 1209) |O|=50 minrank<=14: UNSAT 64.4s + (19, 79) |O|=49 minrank<=14: UNSAT 10.3s + (19, 88) |O|=49 minrank<=14: UNSAT 8.4s + (19, 67) |O|=49 minrank<=14: UNSAT 17.0s + (19, 44) |O|=49 minrank<=14: UNSAT 32.7s + (19, 64) |O|=51 minrank<=14: UNSAT 30.6s + (19, 129) |O|=48 minrank<=14: UNSAT 13.4s + (19, 149) |O|=48 minrank<=14: UNSAT 9.6s + (19, 150) |O|=48 minrank<=14: UNSAT 12.8s + (19, 161) |O|=48 minrank<=14: UNSAT 11.4s + (19, 83) |O|=51 minrank<=14: UNSAT 24.6s + (19, 134) |O|=48 minrank<=14: UNSAT 17.6s + (19, 160) |O|=48 minrank<=14: UNSAT 15.8s + (19, 154) |O|=48 minrank<=14: UNSAT 27.9s +level 3: 159 candidates, 120 good, 39 UNSAT prunings to recheck with glucose4 + (0, 127, 138) |O|=23 minrank<=13: UNSAT 0.1s + (0, 172, 690) |O|=36 minrank<=13: UNSAT 0.1s + (0, 172, 615) |O|=36 minrank<=13: UNSAT 0.3s + (0, 172, 694) |O|=38 minrank<=13: UNSAT 0.5s + (0, 172, 617) |O|=38 minrank<=13: UNSAT 0.5s + (0, 176, 617) |O|=40 minrank<=13: UNSAT 0.4s + (0, 173, 617) |O|=38 minrank<=13: UNSAT 0.6s + (0, 173, 694) |O|=38 minrank<=13: UNSAT 0.6s + (0, 176, 694) |O|=41 minrank<=13: UNSAT 0.4s + (15, 16, 27) |O|=53 minrank<=13: UNSAT 0.4s + (15, 16, 228) |O|=50 minrank<=13: UNSAT 0.3s + (15, 16, 395) |O|=50 minrank<=13: UNSAT 0.3s + (15, 16, 1197) |O|=50 minrank<=13: UNSAT 0.4s + (15, 16, 1196) |O|=52 minrank<=13: UNSAT 0.5s + (15, 16, 393) |O|=50 minrank<=13: UNSAT 0.7s + (15, 16, 1198) |O|=50 minrank<=13: UNSAT 0.5s + (15, 16, 1221) |O|=48 minrank<=13: UNSAT 0.3s + (15, 16, 1220) |O|=50 minrank<=13: UNSAT 0.4s + (15, 16, 1222) |O|=48 minrank<=13: UNSAT 0.3s + (15, 16, 131) |O|=50 minrank<=13: UNSAT 1.1s + (15, 16, 1223) |O|=50 minrank<=13: UNSAT 0.4s + (15, 131, 299) |O|=48 minrank<=13: UNSAT 0.6s + (15, 147, 348) |O|=49 minrank<=13: UNSAT 0.4s + (15, 393, 1155) |O|=47 minrank<=13: UNSAT 0.5s + (15, 393, 1157) |O|=47 minrank<=13: UNSAT 0.4s + (15, 395, 555) |O|=48 minrank<=13: UNSAT 0.3s + (19, 33, 416) |O|=46 minrank<=13: UNSAT 0.1s + (19, 33, 414) |O|=46 minrank<=13: UNSAT 0.2s + (15, 131, 925) |O|=48 minrank<=13: UNSAT 1.1s + (15, 147, 1159) |O|=50 minrank<=13: UNSAT 1.3s + (19, 172, 327) |O|=42 minrank<=13: UNSAT 0.6s + (19, 414, 899) |O|=42 minrank<=13: UNSAT 0.3s + (19, 74, 442) |O|=43 minrank<=13: UNSAT 2.4s + (19, 74, 119) |O|=44 minrank<=13: UNSAT 2.6s + (19, 65, 114) |O|=44 minrank<=13: UNSAT 3.0s + (19, 65, 468) |O|=43 minrank<=13: UNSAT 3.0s + (0, 131, 142) |O|=44 minrank<=13: UNSAT 8.3s + (19, 74, 1149) |O|=44 minrank<=13: UNSAT 7.3s + (19, 65, 1198) |O|=44 minrank<=13: UNSAT 11.3s +level 4: 375 candidates, 368 good, 7 UNSAT prunings to recheck with glucose4 + (0, 127, 294, 1148) |O|=28 minrank<=12: UNSAT 0.0s + (0, 172, 693, 1151) |O|=32 minrank<=12: UNSAT 0.0s + (0, 173, 615, 1200) |O|=32 minrank<=12: UNSAT 0.0s + (0, 172, 692, 1157) |O|=37 minrank<=12: UNSAT 0.0s + (0, 172, 692, 1158) |O|=37 minrank<=12: UNSAT 0.0s + (15, 393, 394, 404) |O|=48 minrank<=12: UNSAT 0.1s + (19, 20, 33, 34) |O|=49 minrank<=12: UNSAT 0.1s +level 5: 500 candidates, 498 good, 2 UNSAT prunings to recheck with glucose4 + (0, 127, 152, 372, 524) |O|=12 minrank<=11: UNSAT 0.0s + (19, 20, 33, 1221, 1226) |O|=48 minrank<=11: UNSAT 0.0s +level 6: 466 candidates, 464 good, 2 UNSAT prunings to recheck with glucose4 + (0, 127, 272, 402, 552, 1293) |O|=16 minrank<=10: UNSAT 0.0s + (0, 127, 272, 382, 538, 1170) |O|=16 minrank<=10: UNSAT 0.0s +level 7: 315 candidates, 315 good, 0 UNSAT prunings to recheck with glucose4 +level 8: 206 candidates, 206 good, 0 UNSAT prunings to recheck with glucose4 +CROSSCHECK glucose4: 200 UNSAT prunings rechecked, 0 mismatches (731s) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/dumbbell.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/dumbbell.log new file mode 100644 index 0000000000..d77386278a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/dumbbell.log @@ -0,0 +1,9 @@ +beta histogram over ordered pairs [0, 0, 102180, 2147820] (8s) +k=9 reps 124 (p*, #doublable members) histogram [((0, 0), 3), ((1, 1), 8), ((1, 2), 6), ((1, 3), 4), ((2, 2), 12), ((2, 3), 4), ((2, 5), 1), ((3, 5), 35), ((3, 9), 2), ((4, 8), 8), ((4, 9), 4), ((5, 5), 8), ((5, 9), 14), ((7, 9), 15)] feasible 15 (84s) +k=10 reps 74 (p*, #doublable members) histogram [((0, 0), 8), ((1, 1), 1), ((1, 2), 2), ((1, 3), 1), ((2, 2), 3), ((2, 3), 10), ((2, 6), 1), ((3, 6), 14), ((4, 6), 6), ((4, 10), 6), ((6, 6), 9), ((6, 10), 13)] feasible 22 (177s) +k=11 reps 32 (p*, #doublable members) histogram [((0, 0), 2), ((1, 1), 2), ((1, 3), 1), ((2, 4), 8), ((3, 3), 2), ((3, 7), 3), ((5, 7), 8), ((5, 11), 6)] feasible 14 (209s) +k=12 reps 24 (p*, #doublable members) histogram [((0, 0), 1), ((1, 2), 4), ((2, 4), 2), ((4, 4), 5), ((4, 8), 7), ((4, 12), 5)] feasible 17 (235s) +k=13 reps 10 (p*, #doublable members) histogram [((0, 0), 2), ((1, 1), 1), ((3, 5), 2), ((3, 9), 5)] feasible 7 (238s) +k=14 reps 6 (p*, #doublable members) histogram [((2, 2), 2), ((2, 6), 4)] feasible 6 (238s) +k=15 reps 1 (p*, #doublable members) histogram [((1, 3), 1)] feasible 1 (238s) +FEASIBLE CONFIGURATIONS: 82 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev2.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev2.log new file mode 100644 index 0000000000..655afa8a63 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev2.log @@ -0,0 +1,10 @@ +level 1: candidates 3, subset-closed 3; testing minrank <= 15 (0s) +LEVEL 1: 3 good orbit reps (3s) + (flat sizes, |O|) histogram: [(((2,), 58), 1), (((3,), 57), 1), (((6,), 54), 1)] +level 2: candidates 62, subset-closed 62; testing minrank <= 14 (3s) +LEVEL 2: 2 good orbit reps (155s) + (flat sizes, |O|) histogram: [(((2, 2), 56), 1), (((6, 6), 48), 1)] +level 3: candidates 37, subset-closed 0; testing minrank <= 13 (156s) +LEVEL 3: 0 good orbit reps (156s) + (flat sizes, |O|) histogram: [] +EMPTY at level 3 => at most 2 such flats/totals for c=16, dims 2 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3.log new file mode 100644 index 0000000000..f044e77536 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3.log @@ -0,0 +1,31 @@ +level 1: candidates 3, subset-closed 3; testing minrank <= 15 (0s) +LEVEL 1: 3 good orbit reps (4s) + (flat sizes, |O|) histogram: [(((4,), 56), 1), (((6,), 54), 1), (((15,), 45), 1)] +level 2: candidates 178, subset-closed 178; testing minrank <= 14 (5s) + 100/178 good 16 (148s) +LEVEL 2: 28 good orbit reps (243s) + (flat sizes, |O|) histogram: [(((4, 4), 52), 5), (((4, 4), 53), 1), (((4, 4), 54), 1), (((4, 6), 50), 2), (((4, 6), 51), 3), (((4, 15), 42), 2), (((4, 15), 44), 1), (((6, 6), 48), 3), (((6, 6), 49), 2), (((6, 6), 50), 1), (((6, 6), 51), 1), (((6, 15), 40), 3), (((15, 15), 32), 1), (((15, 15), 33), 1), (((15, 15), 36), 1)] +level 3: candidates 13029, subset-closed 159; testing minrank <= 13 (259s) + 100/159 good 78 (268s) +LEVEL 3: 120 good orbit reps (275s) + (flat sizes, |O|) histogram: [(((4, 4, 4), 48), 1), (((4, 4, 4), 49), 2), (((4, 4, 4), 50), 1), (((4, 4, 6), 48), 1), (((4, 4, 15), 39), 3), (((4, 4, 15), 40), 2), (((4, 4, 15), 41), 1), (((4, 6, 6), 44), 2), (((4, 6, 6), 46), 4), (((4, 6, 6), 48), 3), (((4, 6, 6), 49), 1), (((4, 6, 15), 37), 6), (((4, 6, 15), 38), 3), (((4, 15, 15), 30), 3), (((4, 15, 15), 31), 1), (((4, 15, 15), 32), 2), (((4, 15, 15), 34), 1), (((6, 6, 6), 42), 4), (((6, 6, 6), 43), 4), (((6, 6, 6), 44), 7), (((6, 6, 6), 46), 2), (((6, 6, 6), 48), 1), (((6, 6, 6), 49), 1), (((6, 6, 15), 35), 18), (((6, 6, 15), 36), 11), (((6, 6, 15), 37), 3), (((6, 6, 15), 38), 2), (((6, 15, 15), 28), 10), (((6, 15, 15), 29), 10), (((6, 15, 15), 32), 2), (((15, 15, 15), 21), 1), (((15, 15, 15), 22), 1), (((15, 15, 15), 23), 2), (((15, 15, 15), 24), 2), (((15, 15, 15), 26), 1), (((15, 15, 15), 32), 1)] +level 4: candidates 114809, subset-closed 375; testing minrank <= 12 (379s) + 100/375 good 99 (380s) + 200/375 good 196 (381s) + 300/375 good 294 (382s) +LEVEL 4: 368 good orbit reps (383s) +level 5: candidates 379250, subset-closed 500; testing minrank <= 11 (813s) + 100/500 good 100 (813s) + 200/500 good 200 (814s) + 300/500 good 299 (814s) + 400/500 good 399 (814s) + 500/500 good 498 (815s) +LEVEL 5: 498 good orbit reps (815s) +level 6: candidates 573713, subset-closed 466; testing minrank <= 10 (2794s) + 100/466 good 99 (2795s) + 200/466 good 199 (2795s) + 300/466 good 299 (2795s) + 400/466 good 399 (2795s) +LEVEL 6: 464 good orbit reps (2795s) +/opt/homebrew/Cellar/python@3.11/3.11.15_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/multiprocessing/resource_tracker.py:254: UserWarning: resource_tracker: There appear to be 6 leaked semaphore objects to clean up at shutdown + warnings.warn('resource_tracker: There appear to be %d ' diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3_hi.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3_hi.log new file mode 100644 index 0000000000..01e0fcf0a1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/lev3_hi.log @@ -0,0 +1,21 @@ +level 7: subset-closed candidates 315; testing minrank <= 9 (16s) +LEVEL 7: 315 good orbit reps (17s); |O| hist [(0, 8), (2, 5), (3, 5), (4, 15), (5, 15), (6, 21), (7, 48), (8, 56), (9, 44), (10, 28), (11, 36), (12, 22), (13, 4), (14, 1), (15, 3), (16, 2), (18, 2)] +level 8: subset-closed candidates 206; testing minrank <= 8 (34s) +LEVEL 8: 206 good orbit reps (34s); |O| hist [(0, 2), (1, 2), (2, 4), (3, 4), (4, 16), (5, 33), (6, 49), (7, 42), (8, 23), (9, 12), (10, 11), (11, 6), (12, 1), (16, 1)] +level 9: subset-closed candidates 124; testing minrank <= 7 (50s) +LEVEL 9: 124 good orbit reps (50s); |O| hist [(0, 2), (1, 4), (2, 1), (4, 28), (5, 43), (6, 27), (7, 9), (8, 5), (9, 3), (10, 1), (11, 1)] +level 10: subset-closed candidates 74; testing minrank <= 6 (65s) +LEVEL 10: 74 good orbit reps (65s); |O| hist [(0, 3), (1, 2), (4, 34), (5, 25), (6, 4), (7, 1), (8, 4), (10, 1)] +level 11: subset-closed candidates 32; testing minrank <= 5 (79s) +LEVEL 11: 32 good orbit reps (79s); |O| hist [(4, 23), (5, 7), (6, 1), (8, 1)] +level 12: subset-closed candidates 24; testing minrank <= 4 (92s) +LEVEL 12: 24 good orbit reps (93s); |O| hist [(4, 20), (5, 3), (8, 1)] +level 13: subset-closed candidates 10; testing minrank <= 3 (106s) +LEVEL 13: 10 good orbit reps (106s); |O| hist [(4, 9), (5, 1)] +level 14: subset-closed candidates 6; testing minrank <= 2 (119s) +LEVEL 14: 6 good orbit reps (119s); |O| hist [(4, 6)] +level 15: subset-closed candidates 1; testing minrank <= 1 (132s) +LEVEL 15: 1 good orbit reps (132s); |O| hist [(4, 1)] +level 16: subset-closed candidates 1; testing minrank <= 0 (145s) +LEVEL 16: 0 good orbit reps (145s); |O| hist [] +EMPTY at level 16 => at most 15 such flats/totals for c=16, dims 3 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/levels_c16.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/levels_c16.log new file mode 100644 index 0000000000..56e7eba137 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/levels_c16.log @@ -0,0 +1,15 @@ +level 1 reps 3 +level 2: candidates 178, after subset closure 178; SAT tests at rank <= 14 + level 2 progress 50/178 good 9 (52s) + level 2 progress 100/178 good 16 (94s) + level 2 progress 150/178 good 24 (144s) + level 2 progress 178/178 good 28 (174s) +LEVEL 2: 28 good orbit reps; |O| histogram [(32, 1), (33, 1), (36, 1), (40, 3), (42, 2), (44, 1), (48, 3), (49, 2), (50, 3), (51, 4), (52, 5), (53, 1), (54, 1)] +level 3: candidates 13029, after subset closure 159; SAT tests at rank <= 13 + level 3 progress 50/159 good 41 (312s) + level 3 progress 100/159 good 79 (316s) + level 3 progress 150/159 good 114 (319s) + level 3 progress 159/159 good 120 (322s) +LEVEL 3: 120 good orbit reps; |O| histogram [(21, 1), (22, 1), (23, 2), (24, 2), (26, 1), (28, 10), (29, 10), (30, 3), (31, 1), (32, 5), (34, 1), (35, 18), (36, 11), (37, 9), (38, 5), (39, 3), (40, 2), (41, 1), (42, 4), (43, 4), (44, 9), (46, 6), (48, 6), (49, 4), (50, 1)] +/opt/homebrew/Cellar/python@3.11/3.11.15_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/multiprocessing/resource_tracker.py:254: UserWarning: resource_tracker: There appear to be 6 leaked semaphore objects to clean up at shutdown + warnings.warn('resource_tracker: There appear to be %d ' diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/pairs_c14.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/pairs_c14.log new file mode 100644 index 0000000000..dc09597a8b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/pairs_c14.log @@ -0,0 +1,179 @@ +pair ( 0, 1) |H1|=15 |H2|=15 |O|=36 orbit 90 c=14 cadical153 SAT 0.1s [1/178] +pair ( 0, 2) |H1|=15 |H2|=15 |O|=36 orbit 360 c=14 cadical153 UNSAT 2.8s [2/178] +pair ( 0, 11) |H1|=15 |H2|=15 |O|=32 orbit 360 c=14 cadical153 SAT 0.1s [3/178] +pair ( 0, 15) |H1|=15 |H2|= 4 |O|=43 orbit 2880 c=14 cadical153 UNSAT 18.4s [4/178] +pair ( 0, 19) |H1|=15 |H2|= 6 |O|=41 orbit 2880 c=14 cadical153 UNSAT 5.0s [5/178] +pair ( 0, 23) |H1|=15 |H2|= 4 |O|=43 orbit 2880 c=14 cadical153 UNSAT 3.7s [6/178] +pair ( 0, 63) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 UNSAT 17.5s [7/178] +pair ( 0, 64) |H1|=15 |H2|= 6 |O|=40 orbit 5760 c=14 cadical153 UNSAT 6.3s [8/178] +pair ( 0, 127) |H1|=15 |H2|=15 |O|=33 orbit 960 c=14 cadical153 SAT 0.5s [9/178] +pair ( 0, 129) |H1|=15 |H2|= 6 |O|=42 orbit 1920 c=14 cadical153 UNSAT 11.1s [10/178] +pair ( 0, 131) |H1|=15 |H2|= 4 |O|=44 orbit 960 c=14 cadical153 SAT 2.4s [11/178] +pair ( 0, 132) |H1|=15 |H2|= 6 |O|=42 orbit 1920 c=14 cadical153 UNSAT 7.3s [12/178] +pair ( 0, 134) |H1|=15 |H2|= 6 |O|=42 orbit 1920 c=14 cadical153 UNSAT 6.0s [13/178] +pair ( 0, 136) |H1|=15 |H2|= 4 |O|=44 orbit 1920 c=14 cadical153 UNSAT 6.4s [14/178] +pair ( 0, 172) |H1|=15 |H2|= 6 |O|=40 orbit 5760 c=14 cadical153 SAT 1.0s [15/178] +pair ( 0, 173) |H1|=15 |H2|= 6 |O|=40 orbit 5760 c=14 cadical153 SAT 1.4s [16/178] +pair ( 0, 176) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 SAT 1.8s [17/178] +pair ( 0, 208) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 UNSAT 9.2s [18/178] +pair ( 0, 212) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 UNSAT 7.5s [19/178] +pair ( 0, 224) |H1|=15 |H2|= 6 |O|=40 orbit 2880 c=14 cadical153 SAT 0.1s [20/178] +pair ( 0, 228) |H1|=15 |H2|= 4 |O|=42 orbit 2880 c=14 cadical153 SAT 4.6s [21/178] +pair ( 0, 232) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 UNSAT 4.5s [22/178] +pair ( 0, 233) |H1|=15 |H2|= 4 |O|=42 orbit 5760 c=14 cadical153 UNSAT 5.3s [23/178] +pair ( 0, 952) |H1|=15 |H2|= 4 |O|=41 orbit 11520 c=14 cadical153 UNSAT 10.6s [24/178] +pair ( 15, 16) |H1|= 4 |H2|= 4 |O|=54 orbit 1440 c=14 cadical153 SAT 2.0s [25/178] +pair ( 15, 17) |H1|= 4 |H2|= 4 |O|=54 orbit 2880 c=14 cadical153 UNSAT 12.4s [26/178] +pair ( 15, 19) |H1|= 4 |H2|= 6 |O|=52 orbit 2880 c=14 cadical153 UNSAT 9.2s [27/178] +pair ( 15, 20) |H1|= 4 |H2|= 6 |O|=52 orbit 2880 c=14 cadical153 UNSAT 6.2s [28/178] +pair ( 15, 21) |H1|= 4 |H2|= 6 |O|=52 orbit 2880 c=14 cadical153 UNSAT 8.6s [29/178] +pair ( 15, 22) |H1|= 4 |H2|= 6 |O|=52 orbit 2880 c=14 cadical153 UNSAT 6.4s [30/178] +pair ( 15, 23) |H1|= 4 |H2|= 4 |O|=54 orbit 1440 c=14 cadical153 UNSAT 10.7s [31/178] +pair ( 15, 24) |H1|= 4 |H2|= 4 |O|=54 orbit 1440 c=14 cadical153 UNSAT 38.9s [32/178] +pair ( 15, 25) |H1|= 4 |H2|= 4 |O|=54 orbit 1440 c=14 cadical153 UNSAT 9.0s [33/178] +pair ( 15, 26) |H1|= 4 |H2|= 4 |O|=54 orbit 1440 c=14 cadical153 UNSAT 9.5s [34/178] +pair ( 15, 29) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 15.7s [35/178] +pair ( 15, 31) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 21.9s [36/178] +pair ( 15, 33) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.9s [37/178] +pair ( 15, 35) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 8.9s [38/178] +pair ( 15, 37) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 17.1s [39/178] +pair ( 15, 43) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.1s [40/178] +pair ( 15, 44) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 13.5s [41/178] +pair ( 15, 45) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 15.9s [42/178] +pair ( 15, 46) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 11.1s [43/178] +pair ( 15, 47) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.1s [44/178] +pair ( 15, 48) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 13.1s [45/178] +pair ( 15, 49) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 5.7s [46/178] +pair ( 15, 50) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 10.9s [47/178] +pair ( 15, 129) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 6.4s [48/178] +pair ( 15, 131) |H1|= 4 |H2|= 4 |O|=52 orbit 2880 c=14 cadical153 SAT 0.9s [49/178] +pair ( 15, 132) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 9.6s [50/178] +pair ( 15, 134) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 26.8s [51/178] +pair ( 15, 136) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 14.1s [52/178] +pair ( 15, 140) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 7.1s [53/178] +pair ( 15, 141) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 4.6s [54/178] +pair ( 15, 142) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 15.7s [55/178] +pair ( 15, 143) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.7s [56/178] +pair ( 15, 144) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 8.9s [57/178] +pair ( 15, 145) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 4.5s [58/178] +pair ( 15, 146) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 10.2s [59/178] +pair ( 15, 147) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 SAT 2.6s [60/178] +pair ( 15, 148) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 14.0s [61/178] +pair ( 15, 153) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 8.1s [62/178] +pair ( 15, 154) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 8.2s [63/178] +pair ( 15, 155) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 10.3s [64/178] +pair ( 15, 156) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 18.3s [65/178] +pair ( 15, 157) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 7.1s [66/178] +pair ( 15, 158) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 10.7s [67/178] +pair ( 15, 159) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 7.8s [68/178] +pair ( 15, 164) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 7.1s [69/178] +pair ( 15, 165) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 6.9s [70/178] +pair ( 15, 166) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 5.9s [71/178] +pair ( 15, 167) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 3.7s [72/178] +pair ( 15, 168) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 17.8s [73/178] +pair ( 15, 169) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.3s [74/178] +pair ( 15, 170) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 6.1s [75/178] +pair ( 15, 200) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 10.7s [76/178] +pair ( 15, 201) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 13.6s [77/178] +pair ( 15, 202) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 9.3s [78/178] +pair ( 15, 203) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 12.6s [79/178] +pair ( 15, 204) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 SAT 2.0s [80/178] +pair ( 15, 205) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 8.3s [81/178] +pair ( 15, 206) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 13.5s [82/178] +pair ( 15, 208) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 6.6s [83/178] +pair ( 15, 209) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 13.0s [84/178] +pair ( 15, 210) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 20.1s [85/178] +pair ( 15, 211) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 7.1s [86/178] +pair ( 15, 212) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 18.5s [87/178] +pair ( 15, 213) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 9.3s [88/178] +pair ( 15, 214) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 9.8s [89/178] +pair ( 15, 215) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 10.1s [90/178] +pair ( 15, 232) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.3s [91/178] +pair ( 15, 234) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 10.8s [92/178] +pair ( 15, 235) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.3s [93/178] +pair ( 15, 238) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 7.6s [94/178] +pair ( 15, 239) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 8.0s [95/178] +pair ( 15, 242) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.6s [96/178] +pair ( 15, 393) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 SAT 0.4s [97/178] +pair ( 15, 395) |H1|= 4 |H2|= 4 |O|=52 orbit 2880 c=14 cadical153 SAT 0.1s [98/178] +pair ( 15, 396) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 7.3s [99/178] +pair ( 15, 398) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 21.2s [100/178] +pair ( 15, 405) |H1|= 4 |H2|= 6 |O|=53 orbit 1920 c=14 cadical153 UNSAT 7.9s [101/178] +pair ( 15, 406) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 7.6s [102/178] +pair ( 15, 407) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 4.6s [103/178] +pair ( 15, 408) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.8s [104/178] +pair ( 15, 409) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 3.4s [105/178] +pair ( 15, 410) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 9.6s [106/178] +pair ( 15, 411) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 8.4s [107/178] +pair ( 15, 412) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 6.6s [108/178] +pair ( 15, 442) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 4.6s [109/178] +pair ( 15, 443) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 7.6s [110/178] +pair ( 15, 444) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 6.5s [111/178] +pair ( 15, 445) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 8.1s [112/178] +pair ( 15, 450) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.2s [113/178] +pair ( 15, 451) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 9.8s [114/178] +pair ( 15, 454) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 8.4s [115/178] +pair ( 15, 455) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 7.5s [116/178] +pair ( 15, 462) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 7.6s [117/178] +pair ( 15, 463) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 6.3s [118/178] +pair ( 15, 474) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 5.3s [119/178] +pair ( 15, 476) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 4.2s [120/178] +pair ( 15, 477) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 6.4s [121/178] +pair ( 15, 478) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 10.2s [122/178] +pair ( 15, 481) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 8.3s [123/178] +pair ( 15, 484) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 9.1s [124/178] +pair ( 15, 488) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 12.1s [125/178] +pair ( 15,1116) |H1|= 4 |H2|= 4 |O|=52 orbit 2880 c=14 cadical153 SAT 0.6s [126/178] +pair ( 15,1117) |H1|= 4 |H2|= 4 |O|=52 orbit 2880 c=14 cadical153 SAT 1.5s [127/178] +pair ( 15,1120) |H1|= 4 |H2|= 6 |O|=50 orbit 5760 c=14 cadical153 SAT 1.1s [128/178] +pair ( 15,1121) |H1|= 4 |H2|= 6 |O|=50 orbit 5760 c=14 cadical153 SAT 0.1s [129/178] +pair ( 15,1128) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 5.4s [130/178] +pair ( 15,1129) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 5.6s [131/178] +pair ( 15,1132) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 5.0s [132/178] +pair ( 15,1133) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 4.7s [133/178] +pair ( 15,1148) |H1|= 4 |H2|= 6 |O|=53 orbit 1920 c=14 cadical153 UNSAT 8.2s [134/178] +pair ( 15,1149) |H1|= 4 |H2|= 6 |O|=53 orbit 1920 c=14 cadical153 UNSAT 3.5s [135/178] +pair ( 15,1150) |H1|= 4 |H2|= 4 |O|=55 orbit 960 c=14 cadical153 UNSAT 16.4s [136/178] +pair ( 15,1154) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 SAT 1.3s [137/178] +pair ( 15,1155) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 SAT 1.5s [138/178] +pair ( 15,1160) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 3.6s [139/178] +pair ( 15,1161) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 15.9s [140/178] +pair ( 15,1163) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 15.5s [141/178] +pair ( 15,1166) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 10.2s [142/178] +pair ( 15,1167) |H1|= 4 |H2|= 6 |O|=51 orbit 5760 c=14 cadical153 UNSAT 5.8s [143/178] +pair ( 15,1169) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 11.0s [144/178] +pair ( 15,1180) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 6.3s [145/178] +pair ( 15,1182) |H1|= 4 |H2|= 4 |O|=53 orbit 5760 c=14 cadical153 UNSAT 7.0s [146/178] +pair ( 15,1186) |H1|= 4 |H2|= 4 |O|=53 orbit 2880 c=14 cadical153 UNSAT 10.5s [147/178] +pair ( 15,1208) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 7.8s [148/178] +pair ( 15,1209) |H1|= 4 |H2|= 6 |O|=50 orbit 11520 c=14 cadical153 UNSAT 17.0s [149/178] +pair ( 15,1276) |H1|= 4 |H2|= 4 |O|=52 orbit 11520 c=14 cadical153 UNSAT 6.9s [150/178] +pair ( 15,1281) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 10.1s [151/178] +pair ( 15,1284) |H1|= 4 |H2|= 4 |O|=52 orbit 5760 c=14 cadical153 UNSAT 6.7s [152/178] +pair ( 19, 20) |H1|= 6 |H2|= 6 |O|=50 orbit 720 c=14 cadical153 SAT 0.8s [153/178] +pair ( 19, 21) |H1|= 6 |H2|= 6 |O|=50 orbit 1440 c=14 cadical153 UNSAT 5.3s [154/178] +pair ( 19, 33) |H1|= 6 |H2|= 6 |O|=51 orbit 960 c=14 cadical153 SAT 1.0s [155/178] +pair ( 19, 35) |H1|= 6 |H2|= 6 |O|=49 orbit 2880 c=14 cadical153 UNSAT 10.1s [156/178] +pair ( 19, 43) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 UNSAT 4.4s [157/178] +pair ( 19, 44) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 UNSAT 13.4s [158/178] +pair ( 19, 64) |H1|= 6 |H2|= 6 |O|=51 orbit 960 c=14 cadical153 UNSAT 10.2s [159/178] +pair ( 19, 65) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 SAT 0.7s [160/178] +pair ( 19, 66) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 UNSAT 6.2s [161/178] +pair ( 19, 67) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 UNSAT 6.4s [162/178] +pair ( 19, 72) |H1|= 6 |H2|= 6 |O|=51 orbit 1920 c=14 cadical153 UNSAT 3.1s [163/178] +pair ( 19, 74) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 SAT 0.9s [164/178] +pair ( 19, 75) |H1|= 6 |H2|= 6 |O|=49 orbit 2880 c=14 cadical153 UNSAT 3.7s [165/178] +pair ( 19, 79) |H1|= 6 |H2|= 6 |O|=49 orbit 5760 c=14 cadical153 UNSAT 2.8s [166/178] +pair ( 19, 83) |H1|= 6 |H2|= 6 |O|=51 orbit 960 c=14 cadical153 UNSAT 9.8s [167/178] +pair ( 19, 88) |H1|= 6 |H2|= 6 |O|=49 orbit 2880 c=14 cadical153 UNSAT 4.9s [168/178] +pair ( 19, 129) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 7.6s [169/178] +pair ( 19, 134) |H1|= 6 |H2|= 6 |O|=48 orbit 11520 c=14 cadical153 UNSAT 7.0s [170/178] +pair ( 19, 149) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 6.8s [171/178] +pair ( 19, 150) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 10.2s [172/178] +pair ( 19, 154) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 16.3s [173/178] +pair ( 19, 160) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 5.6s [174/178] +pair ( 19, 161) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 UNSAT 6.7s [175/178] +pair ( 19, 172) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 SAT 1.2s [176/178] +pair ( 19, 414) |H1|= 6 |H2|= 6 |O|=48 orbit 5760 c=14 cadical153 SAT 1.1s [177/178] +pair ( 19, 922) |H1|= 6 |H2|= 6 |O|=48 orbit 1440 c=14 cadical153 SAT 0.4s [178/178] +SAT pairs: 28 of 178 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/price16.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/price16.log new file mode 100644 index 0000000000..5c7ee073bc --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/price16.log @@ -0,0 +1 @@ +c=16 decoder valid; betas [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] sum 64 cheap totals 0 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/prove_c16.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/prove_c16.log new file mode 100644 index 0000000000..6b91d6ad44 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/prove_c16.log @@ -0,0 +1,22 @@ +level sizes: {1: 3, 2: 28, 3: 120, 4: 368, 5: 498, 6: 464, 7: 315, 8: 206, 9: 124, 10: 74, 11: 32, 12: 24, 13: 10, 14: 6, 15: 1, 16: 0} +(A) k= 1: 3 of 3 reps have an a-capable member +(A) k= 2: 19 of 28 reps have an a-capable member +(A) k= 3: 18 of 120 reps have an a-capable member +(A) k= 4: 3 of 368 reps have an a-capable member +(A) k= 5: 0 of 498 reps have an a-capable member +(A) k= 6: 0 of 464 reps have an a-capable member +(A) k= 7: 0 of 315 reps have an a-capable member +(A) k= 8: 0 of 206 reps have an a-capable member +(A) k= 9: 0 of 124 reps have an a-capable member +(A) k=10: 0 of 74 reps have an a-capable member +(A) k=11: 0 of 32 reps have an a-capable member +(A) k=12: 0 of 24 reps have an a-capable member +(A) k=13: 0 of 10 reps have an a-capable member +(A) k=14: 0 of 6 reps have an a-capable member +(A) k=15: 0 of 1 reps have an a-capable member +(B) k=11: max p* = 5 (need >= 11 for cost <= 42) +(B) k=12: max p* = 4 (need >= 10 for cost <= 42) +(B) k=13: max p* = 3 (need >= 9 for cost <= 42) +(B) k=14: max p* = 2 (need >= 8 for cost <= 42) +(B) k=15: max p* = 1 (need >= 7 for cost <= 42) +THEOREM: no Gaussian-E8 decoder with exactly 16 totals has sum beta_j <= 42 (780s) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/refine.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/refine.log new file mode 100644 index 0000000000..57ef47b57a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/refine.log @@ -0,0 +1,83 @@ +k= 9 T=(0, 1, 6, 127, 139, 152, 162, 372, 382) doublable 9 a-capable 0 -> excluded (2s) +k= 9 T=(0, 1, 6, 127, 139, 152, 162, 372, 492) doublable 9 a-capable 0 -> excluded (4s) +k= 9 T=(0, 1, 6, 127, 139, 152, 162, 492, 504) doublable 9 a-capable 0 -> excluded (6s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 372, 382) doublable 9 a-capable 0 -> excluded (7s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 372, 492) doublable 9 a-capable 0 -> excluded (9s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 372, 504) doublable 9 a-capable 0 -> excluded (11s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 382, 492) doublable 9 a-capable 0 -> excluded (12s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 382, 504) doublable 9 a-capable 0 -> excluded (14s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 392, 524) doublable 9 a-capable 0 -> excluded (15s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 402, 512) doublable 9 a-capable 0 -> excluded (17s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 402, 524) doublable 9 a-capable 0 -> excluded (18s) +k= 9 T=(0, 1, 6, 127, 139, 152, 272, 492, 504) doublable 9 a-capable 0 -> excluded (20s) +k= 9 T=(0, 1, 6, 127, 139, 272, 284, 372, 382) doublable 9 a-capable 0 -> excluded (22s) +k= 9 T=(0, 1, 6, 127, 139, 372, 392, 504, 512) doublable 9 a-capable 0 -> excluded (23s) +k= 9 T=(0, 1, 6, 127, 139, 372, 402, 504, 524) doublable 9 a-capable 0 -> excluded (25s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 249, 372, 382) doublable 10 a-capable 0 -> excluded (27s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 249, 372, 492) doublable 10 a-capable 0 -> excluded (29s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 249, 372, 504) doublable 10 a-capable 0 -> excluded (31s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 249, 382, 504) doublable 10 a-capable 0 -> excluded (33s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 372, 382, 492) doublable 10 a-capable 0 -> excluded (34s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 372, 382, 1115) doublable 6 a-capable 0 -> excluded (36s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 372, 492, 504) doublable 10 a-capable 0 -> excluded (38s) +k=10 T=(0, 1, 6, 127, 139, 152, 162, 372, 492, 1115) doublable 6 a-capable 0 -> excluded (40s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 372, 504) doublable 10 a-capable 0 -> excluded (42s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 382, 492) doublable 10 a-capable 0 -> excluded (44s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 382, 504) doublable 10 a-capable 0 -> excluded (45s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 402, 512) doublable 10 a-capable 0 -> excluded (47s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 402, 524) doublable 10 a-capable 0 -> excluded (49s) +k=10 T=(0, 1, 6, 127, 139, 152, 249, 272, 512, 524) doublable 10 a-capable 0 -> excluded (51s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 284, 382, 492) doublable 10 a-capable 0 -> excluded (53s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 372, 382, 1115) doublable 6 a-capable 0 -> excluded (55s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 372, 492, 1115) doublable 6 a-capable 0 -> excluded (57s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 372, 504, 1115) doublable 6 a-capable 0 -> excluded (58s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 392, 524, 1115) doublable 6 a-capable 0 -> excluded (60s) +k=10 T=(0, 1, 6, 127, 139, 152, 272, 402, 512, 1115) doublable 6 a-capable 0 -> excluded (62s) +k=10 T=(0, 1, 6, 127, 139, 372, 392, 504, 512, 1115) doublable 6 a-capable 0 -> excluded (64s) +k=10 T=(0, 1, 6, 127, 139, 372, 402, 504, 524, 1115) doublable 6 a-capable 0 -> excluded (66s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 259, 372, 382) doublable 7 a-capable 0 -> excluded (68s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 259, 372, 492) doublable 7 a-capable 0 -> excluded (71s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 259, 372, 504) doublable 7 a-capable 0 -> excluded (73s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 259, 492, 504) doublable 7 a-capable 0 -> excluded (75s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 272, 372, 382) doublable 11 a-capable 0 -> excluded (78s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 272, 372, 504) doublable 11 a-capable 0 -> excluded (80s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 272, 392, 402) doublable 11 a-capable 0 -> excluded (82s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 272, 392, 512) doublable 11 a-capable 0 -> excluded (85s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 249, 372, 382, 504) doublable 11 a-capable 0 -> excluded (87s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 372, 382, 492, 504) doublable 7 a-capable 0 -> excluded (89s) +k=11 T=(0, 1, 6, 127, 139, 152, 162, 372, 382, 492, 1115) doublable 7 a-capable 0 -> excluded (91s) +k=11 T=(0, 1, 6, 127, 139, 152, 249, 259, 272, 372, 504) doublable 7 a-capable 0 -> excluded (94s) +k=11 T=(0, 1, 6, 127, 139, 152, 249, 259, 272, 402, 524) doublable 7 a-capable 0 -> excluded (96s) +k=11 T=(0, 1, 6, 127, 139, 152, 249, 272, 284, 382, 492) doublable 11 a-capable 0 -> excluded (99s) +k=12 T=(0, 1, 6, 127, 139, 152, 162, 249, 259, 372, 492, 1115) doublable 4 a-capable 0 -> excluded (102s) +k=12 T=(0, 1, 6, 127, 139, 152, 162, 249, 272, 392, 512, 524) doublable 12 a-capable 0 -> excluded (106s) +k=12 T=(0, 1, 6, 127, 139, 152, 249, 259, 272, 372, 492, 1115) doublable 4 a-capable 0 -> excluded (109s) +k=12 T=(0, 1, 6, 127, 139, 152, 249, 272, 284, 372, 382, 492) doublable 12 a-capable 0 -> excluded (112s) +k=12 T=(0, 1, 6, 127, 139, 162, 249, 259, 272, 372, 492, 1115) doublable 4 a-capable 0 -> excluded (114s) +k=12 T=(0, 1, 6, 127, 139, 162, 249, 272, 284, 372, 382, 492) doublable 12 a-capable 0 -> excluded (117s) +k=12 T=(0, 1, 6, 127, 139, 162, 249, 272, 284, 372, 382, 504) doublable 12 a-capable 0 -> excluded (120s) +k=12 T=(0, 1, 6, 127, 139, 249, 392, 402, 492, 504, 512, 524) doublable 8 a-capable 0 -> excluded (123s) +k=12 T=(0, 1, 6, 127, 139, 249, 392, 402, 492, 504, 512, 1115) doublable 8 a-capable 0 -> excluded (127s) +k=12 T=(0, 1, 6, 127, 139, 259, 382, 402, 492, 504, 512, 524) doublable 12 a-capable 0 -> excluded (131s) +k=12 T=(0, 1, 6, 127, 139, 259, 392, 402, 492, 504, 512, 524) doublable 8 a-capable 0 -> excluded (135s) +k=12 T=(0, 1, 6, 127, 139, 372, 392, 402, 492, 504, 512, 524) doublable 8 a-capable 0 -> excluded (138s) +k=12 T=(0, 1, 6, 127, 139, 372, 392, 402, 492, 504, 512, 1115) doublable 8 a-capable 0 -> excluded (141s) +k=12 T=(0, 1, 6, 127, 139, 372, 392, 402, 492, 512, 524, 1115) doublable 4 a-capable 0 -> excluded (143s) +k=12 T=(0, 1, 6, 127, 139, 382, 392, 402, 492, 504, 512, 524) doublable 8 a-capable 0 -> excluded (146s) +k=12 T=(0, 1, 6, 127, 139, 382, 392, 402, 492, 504, 512, 1115) doublable 8 a-capable 0 -> excluded (148s) +k=12 T=(0, 1, 6, 127, 139, 382, 392, 402, 492, 512, 524, 1115) doublable 4 a-capable 0 -> excluded (151s) +k=13 T=(0, 1, 6, 127, 139, 249, 259, 392, 402, 492, 504, 512, 524) doublable 5 a-capable 0 -> excluded (154s) +k=13 T=(0, 1, 6, 127, 139, 249, 372, 392, 402, 492, 504, 512, 524) doublable 9 a-capable 0 -> excluded (158s) +k=13 T=(0, 1, 6, 127, 139, 249, 372, 392, 402, 492, 504, 512, 1115) doublable 9 a-capable 0 -> excluded (161s) +k=13 T=(0, 1, 6, 127, 139, 249, 382, 392, 402, 492, 504, 512, 524) doublable 9 a-capable 0 -> excluded (164s) +k=13 T=(0, 1, 6, 127, 139, 259, 372, 392, 402, 492, 504, 512, 524) doublable 9 a-capable 0 -> excluded (167s) +k=13 T=(0, 1, 6, 127, 139, 259, 382, 392, 402, 492, 504, 512, 524) doublable 9 a-capable 0 -> excluded (170s) +k=13 T=(0, 1, 6, 127, 139, 372, 382, 392, 402, 492, 504, 512, 524) doublable 5 a-capable 0 -> excluded (173s) +k=14 T=(0, 1, 6, 127, 139, 249, 259, 372, 392, 402, 492, 504, 512, 524) doublable 6 a-capable 0 -> excluded (177s) +k=14 T=(0, 1, 6, 127, 139, 249, 259, 372, 392, 402, 492, 512, 524, 1115) doublable 2 a-capable 0 -> excluded (181s) +k=14 T=(0, 1, 6, 127, 139, 249, 259, 382, 392, 402, 492, 504, 512, 524) doublable 6 a-capable 0 -> excluded (184s) +k=14 T=(0, 1, 6, 127, 139, 249, 259, 382, 392, 402, 492, 512, 524, 1115) doublable 2 a-capable 0 -> excluded (188s) +k=14 T=(0, 1, 6, 127, 139, 249, 372, 382, 392, 402, 492, 504, 512, 524) doublable 6 a-capable 0 -> excluded (191s) +k=14 T=(0, 1, 6, 127, 139, 259, 372, 382, 392, 402, 492, 504, 512, 524) doublable 6 a-capable 0 -> excluded (195s) +k=15 T=(0, 1, 6, 127, 139, 249, 259, 372, 382, 392, 402, 492, 504, 512, 524) doublable 3 a-capable 0 -> excluded (199s) +SURVIVING CONFIGURATIONS: 0 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/verify_run.log b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/verify_run.log new file mode 100644 index 0000000000..43822c853c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/logs/verify_run.log @@ -0,0 +1,44 @@ +Gauss v 60 w 4 nonorth degree [np.int64(44)] +LEVEL 1: 3 good orbit reps (3s) +LEVEL 2: 28 good orbit reps (131s) +LEVEL 3: 120 good orbit reps (157s) +LEVEL 4: 368 good orbit reps +LEVEL 5: 498 good orbit reps +LEVEL 6: 464 good orbit reps +LEVEL 7: 315 good orbit reps +LEVEL 8: 206 good orbit reps +LEVEL 9: 124 good orbit reps +LEVEL 10: 74 good orbit reps +LEVEL 11: 32 good orbit reps +LEVEL 12: 24 good orbit reps +LEVEL 13: 10 good orbit reps +LEVEL 14: 6 good orbit reps +LEVEL 15: 1 good orbit reps +LEVEL 16: 0 good orbit reps +EMPTY at level 16 => at most 15 such flats/totals for c=16, dims 3 +level sizes: {1: 3, 2: 28, 3: 120, 4: 368, 5: 498, 6: 464, 7: 315, 8: 206, 9: 124, 10: 74, 11: 32, 12: 24, 13: 10, 14: 6, 15: 1, 16: 0} +(A) k= 1: 3 of 3 reps have an a-capable member +(A) k= 2: 19 of 28 reps have an a-capable member +(A) k= 3: 18 of 120 reps have an a-capable member +(A) k= 4: 3 of 368 reps have an a-capable member +(A) k= 5: 0 of 498 reps have an a-capable member +(A) k= 6: 0 of 464 reps have an a-capable member +(A) k= 7: 0 of 315 reps have an a-capable member +(A) k= 8: 0 of 206 reps have an a-capable member +(A) k= 9: 0 of 124 reps have an a-capable member +(A) k=10: 0 of 74 reps have an a-capable member +(A) k=11: 0 of 32 reps have an a-capable member +(A) k=12: 0 of 24 reps have an a-capable member +(A) k=13: 0 of 10 reps have an a-capable member +(A) k=14: 0 of 6 reps have an a-capable member +(A) k=15: 0 of 1 reps have an a-capable member +(B) k=11: max p* = 5 (need >= 11 for cost <= 42) +(B) k=12: max p* = 4 (need >= 10 for cost <= 42) +(B) k=13: max p* = 3 (need >= 9 for cost <= 42) +(B) k=14: max p* = 2 (need >= 8 for cost <= 42) +(B) k=15: max p* = 1 (need >= 7 for cost <= 42) +THEOREM: no Gaussian-E8 decoder with exactly 16 totals has sum beta_j <= 42 +c=16 decoder valid; betas [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4] sum 64 cheap totals 0 +LEVEL 1: 0 good orbit reps +EMPTY at level 1 => at most 0 such flats/totals for c=15, dims 3 +work dir: /private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/s7/vf diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/acap_sanity.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/acap_sanity.py new file mode 100644 index 0000000000..e3a03f25df --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/acap_sanity.py @@ -0,0 +1,12 @@ +"""Sanity: how many level-k reps have a member that is 'a-capable' (some 2-flat Z inside it with every 3-flat through Z a valid substitute).""" +import sys; sys.path.insert(0, sys.path[0]) +from refine import good, inside, through, ok +for k in range(1, 16): + n = 0; mem = 0 + for T in good[k]: + T = list(T); c = 0 + for i in range(k): + subst = lambda h: sorted(T[:i] + [h] + T[i + 1:]) + if any(all(h == T[i] or (h not in T and ok(subst(h))) for h in through[z]) for z in inside[T[i]]): c += 1 + n += c > 0; mem += c + print('k=%2d reps %4d with a-capable member %4d (total a-capable members %d)' % (k, len(good[k]), n, mem), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/crosscheck.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/crosscheck.py new file mode 100644 index 0000000000..c0261eb1c1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/crosscheck.py @@ -0,0 +1,37 @@ +"""Re-derive every UNSAT pruning of the 3-flat level search (levels 2..maxlev) and re-solve it with another solver. +Only UNSAT verdicts can remove configurations, so only they need cross-checking (a wrong SAT would only keep more). +argv: c dims pkl maxlev solver nproc (first three as levels3.py, which reads them at import)""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from multiprocessing import Pool +import levels3 +from levels3 import gen, init, compl, sat_job +from sub_minrank import run +from levels2 import S + + +def job(args): + X, O, cc, solver = args + r, t, _ = run(S, O, cc, solver) + return X, r, t + + +if __name__ == '__main__': + good = levels3.good; maxlev = int(sys.argv[4]); solver = sys.argv[5]; npr = int(sys.argv[6]) + c = levels3.c + bad = 0; tot = 0; t0 = time.time() + for m in range(2, maxlev + 1): + with Pool(npr, initializer=init, initargs=(good[m - 1],)) as pool: + cand = set() + if m == 2: # all pair orbits (gen() prefilters by pair compatibility, so use the orbit list directly) + cand = {levels3.canon(levels3.P, [a, b]) for a, b, _ in pickle.load(open('pair_orbits.pkl', 'rb'))['reps']} + assert len(cand) == 178 + else: + for out in pool.imap_unordered(gen, sorted(good[m - 1]), chunksize=4): cand.update(out) + assert good[m] <= cand + uns = sorted(cand - good[m]) + print('level %d: %d candidates, %d good, %d UNSAT prunings to recheck with %s' % (m, len(cand), len(good[m]), len(uns), solver), flush=True) + for X, r, t in pool.imap_unordered(job, [(X, compl(X), c - m, solver) for X in uns]): + tot += 1; bad += bool(r) + print(' %s |O|=%d minrank<=%d: %s %.1fs' % (X, len(compl(X)), c - m, 'SAT (MISMATCH)' if r else 'UNSAT', t), flush=True) + print('CROSSCHECK %s: %d UNSAT prunings rechecked, %d mismatches (%.0fs)' % (solver, tot, bad, time.time() - t0), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/direct.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/direct.py new file mode 100644 index 0000000000..8046ec4104 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/direct.py @@ -0,0 +1,60 @@ +"""S7 step 3: full decoder SAT on all 60 lines with c totals, where for each flat Z_i of a configuration (i = 0..k-1), +total i has a whole side inside Z_i: sigma_i -> W[T,i] = 0 for T outside Z_i; not sigma_i -> U[T,i] = 0 for T outside Z_i. +Totals k..c-1 are free. sigma_0 = 1 by the global transpose symmetry (W,U) -> (U,W) (the pattern is symmetric). +No GL gauge (columns are distinguished). Free columns: lexicographic order is NOT imposed (keeps the encoding simple). +argv: c pkl level nproc [solver] [limit]: runs every orbit rep of good[level] in pkl.""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from multiprocessing import Pool +from pysat.formula import IDPool +from pysat.solvers import Solver +from lines import views + +S = views()['Gauss'] + + +def encode(Zs, c): + pool = IDPool(); cl = [] + W = [[pool.id(('w', T, j)) for j in range(c)] for T in range(60)] + U = [[pool.id(('u', T, j)) for j in range(c)] for T in range(60)] + sig = [pool.id(('s', i)) for i in range(len(Zs))] + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + for T in range(60): + for S_ in range(60): + if T == S_ or S.NO[T, S_]: + acc = AND(W[T][0], U[S_][0]) + for j in range(1, c): acc = XOR(acc, AND(W[T][j], U[S_][j])) + cl.append([acc] if T == S_ else [-acc]) + for i, Z in enumerate(Zs): + Zs_ = set(Z) + for T in range(60): + if T not in Zs_: + cl.append([-sig[i], -W[T][i]]); cl.append([sig[i], -U[T][i]]) + if Zs: cl.append([sig[0]]) + return cl, pool + + +def job(args): + X, Zs, c, solver = args + cl, pool = encode(Zs, c) + t = time.time() + with Solver(name=solver, bootstrap_with=cl) as s: + r = s.solve() + return X, r, time.time() - t + + +if __name__ == '__main__': + c = int(sys.argv[1]); d = pickle.load(open(sys.argv[2], 'rb')); lev = int(sys.argv[3]); npr = int(sys.argv[4]) + solver = sys.argv[5] if len(sys.argv) > 5 else 'cadical153' + lim = int(sys.argv[6]) if len(sys.argv) > 6 else None + fl = d['flats']; reps = sorted(d['good'][lev])[:lim] + jobs = [(X, [fl[f][1] for f in X], c, solver) for X in reps] + nsat = 0; t0 = time.time() + with Pool(npr) as p: + for k, (X, r, t) in enumerate(p.imap_unordered(job, jobs)): + nsat += bool(r) + print('%s sizes %s %s %.1fs [%d/%d] (%.0fs)' % (X, [len(fl[f][1]) for f in X], 'SAT' if r else 'UNSAT', t, k + 1, len(jobs), time.time() - t0), flush=True) + print('level %d c=%d %s: %d SAT of %d' % (lev, c, solver, nsat, len(jobs)), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/dumbbell.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/dumbbell.py new file mode 100644 index 0000000000..c5601f33f8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/dumbbell.py @@ -0,0 +1,112 @@ +"""S7 step 2c: cost-aware refinement of the 3-flat level search ("dumbbells"). + +Per total j (X_j = span R_j, Y_j = span A_j, Hermitian definite form on K^4, beta_j = dim X - dim(X cap Y^perp)): + full (4,4) beta = 4 + 'a' a side of dim <= 2 beta >= 0 (at most 2 such totals: 2-flat level search, lev_c16_d2.pkl) + 'p' (3,3), beta = 2 both X_j, Y_j are 3-flats containing a side, X_j != Y_j -> a DUMBBELL + 'q' otherwise cheap beta >= 3 (one 3-flat side; also (3,3) with beta = 3) +cost <= 42 with 16 totals: 4f + 2p + 3q <= 42, f = 16 - a - p - q <=> 3a + p >= 22 - k, k = a + p + q. +Every transversal (one 3-flat per cheap total: for p-totals either X_j or Y_j; for a-totals any 3-flat containing the +small side) is a valid level-k set (all hyperplanes distinct since one 3-flat cannot host two totals). +So a <= 42 decoder needs a level-k rep T (k = 9..15) and a choice of p members with partners Y (beta(t, Y) = 2) such that +all 2^p transversals are level-k sets, with 3*min(2, k-p) + p >= 22 - k. +This script computes, for every level-k rep, the maximum number p* of simultaneously doubled members.""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from levels2 import load_flats, canon_many, canon +from lines import views + +S = views()['Gauss'] +J = S.ops[1] + + +def hyperplane_bases(fl): + out = [] + for d, f in fl: + rows = np.array([v for T in f for v in (S.reps[T], J @ S.reps[T])], float) + u, s, vt = np.linalg.svd(rows) + r = int((s > 1e-9 * s[0]).sum()); assert r == 2 * d + out.append(vt[:r]) + return np.array(out) # NF x 6 x 8 orthonormal real bases + + +def beta_matrix(Bs): + """beta(X,Y) = real rank(Bx By^T)/2 for all pairs, by SVD; true rank-2 (real rank 4) pairs have sigma_5 ~ 1e-15.""" + NF = len(Bs); beta = np.zeros((NF, NF), np.int8) + for i in range(NF): + M = np.einsum('ik,njk->nij', Bs[i], Bs) + s = np.linalg.svd(M, compute_uv=False) + r = (s > 1e-7).sum(1) + amb = ((s > 1e-11) & (s < 1e-3)).any(1) + assert not amb.any(), 'ambiguous rank' + assert (r % 2 == 0).all() + beta[i] = r // 2 + return beta + + +def main(): + G, fl, P = load_flats('3') + NF = len(fl) + good = pickle.load(open(sys.argv[1], 'rb'))['good'] + t0 = time.time() + beta = beta_matrix(hyperplane_bases(fl)) + print('beta histogram over ordered pairs', np.bincount(beta.flatten()).tolist(), '(%.0fs)' % (time.time() - t0), flush=True) + comp = np.zeros((NF, NF), bool) + for (a, b) in good[2]: + comp[P[:, a], P[:, b]] = True; comp[P[:, b], P[:, a]] = True + feasible = [] + for k in range(9, 16): + need_best = None + stats = [] + for T in sorted(good[k]): + T = list(T) + partners = {} + for idx, t in enumerate(T): + rest = T[:idx] + T[idx + 1:] + ys = [y for y in range(NF) if y not in T and beta[t, y] == 2 and comp[y, rest].all()] + ok = [] + if ys: + cs = canon_many(P, rest, ys) + ok = [y for y, cf in zip(ys, cs) if cf in good[k]] + if ok: partners[idx] = ok + # max number of members doubled simultaneously, all transversals valid (backtracking) + best = [0] + mem = sorted(partners) + + def valid_all(choice): + # choice: dict idx -> partner; check every transversal + items = list(choice.items()) + for mask in range(1 << len(items)): + X = list(T) + for b, (idx, y) in enumerate(items): + if mask >> b & 1: X[idx] = y + if canon(P, X) not in good[k]: return False + return True + + def rec(pos, choice): + best[0] = max(best[0], len(choice)) + if len(choice) + (len(mem) - pos) <= best[0]: return + for q in range(pos, len(mem)): + idx = mem[q] + for y in partners[idx]: + if y in choice.values(): continue + # newly added transversals: those using y + choice[idx] = y + if valid_all(choice): rec(q + 1, choice) + del choice[idx] + rec(0, {}) + p = best[0] + # feasibility: exists p' <= p, a' <= min(2, k - p') with 3a' + p' >= 22 - k + feas = any(3 * min(2, k - pp) + pp >= 22 - k for pp in range(p + 1)) + stats.append((p, len(partners))) + if feas: feasible.append((k, tuple(T), p)) + from collections import Counter + print('k=%d reps %d (p*, #doublable members) histogram %s feasible %d (%.0fs)' % ( + k, len(good[k]), sorted(Counter(stats).items()), sum(1 for f in feasible if f[0] == k), time.time() - t0), flush=True) + pickle.dump({'feasible': feasible, 'beta': beta}, open('dumbbell_out.pkl', 'wb')) + print('FEASIBLE CONFIGURATIONS:', len(feasible), flush=True) + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/flats.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/flats.py new file mode 100644 index 0000000000..b064c81896 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/flats.py @@ -0,0 +1,26 @@ +"""All flats (line sets of K-subspaces spanned by configuration lines) of a view, with dims. Exact.""" +import sys, pickle; sys.path.insert(0,sys.path[0]) +from lines import * +def flats(S): + v=S.v; out={} + def close(idx): + d=S.arank(idx); return frozenset(T for T in range(v) if S.arank(list(idx)+[T])==d), d + level=[ (frozenset([T]),1) for T in range(v)] + for f,d in level: out[f]=d + frontier=list(out.items()) + while frontier: + new={} + for f,d in frontier: + if d>=S.w: continue + for T in range(v): + if T in f or any(T in g and f distinct). Level m+1 candidates: extend each level-m rep by a flat that is +pair-compatible with all members; keep if all m-subsets are level-m sets; then SAT-test the full union at c-(m+1). +argv: c maxlevel nproc. Writes levels_c{c}.pkl and prints per-level counts.""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from multiprocessing import Pool +from lines import views +from sub_minrank import run +from pairs import load + +S = views()['Gauss'] + + +def sat_job(args): + key, O, cc = args + if cc < 0: + return key, False, 0.0 + if len(O) == 0: + return key, True, 0.0 + if cc == 0: + return key, False, 0.0 # nonempty O needs rank >= 1 (B_TT = 1) + r, t, _ = run(S, O, cc, 'cadical153') + return key, r, t + + +if __name__ == '__main__': + c = int(sys.argv[1]); maxlev = int(sys.argv[2]); npr = int(sys.argv[3]) + G, hyp = load() + hid = {h: k for k, h in enumerate(hyp)} + P = np.array([[hid[tuple(sorted(g[i] for i in h))] for h in hyp] for g in G], dtype=np.int32) # |G| x 1500 + NF = len(hyp) + lines_of = [set(h) for h in hyp] + + def canon(fs): + A = np.sort(P[:, list(fs)], axis=1) + k = np.lexsort(A.T[::-1])[0] + return tuple(int(x) for x in A[k]) + + def compl(fs): + U = set().union(*(lines_of[f] for f in fs)) + return [T for T in range(60) if T not in U] + + # level 1: the 3 orbit reps, minrank 15 on complement (S6) -> valid iff 15 <= c-1 + reps1 = sorted({canon([f]) for f in range(NF)}) + good = {1: set(reps1) if 15 <= c - 1 else set()} + print('level 1 reps', len(good[1]), flush=True) + # level 2: from pairs.py orbit results (c=14 test) only valid for c=16; recompute generically via SAT + union_cache = {} + t0 = time.time() + with Pool(npr) as pool: + for m in range(2, maxlev + 1): + cand = set() + for R in good[m - 1]: + for f in range(NF): + if f in R: continue + cand.add(canon(list(R) + [f])) + # subset closure + cand2 = [] + for X in cand: + if all(canon(list(Y)) in good[m - 1] for Y in itertools.combinations(X, m - 1)): + cand2.append(X) + jobs = [] + for X in cand2: + O = compl(X) + jobs.append((X, O, c - m)) + print('level %d: candidates %d, after subset closure %d; SAT tests at rank <= %d' % (m, len(cand), len(jobs), c - m), flush=True) + good[m] = set(); n = 0 + for X, r, t in pool.imap_unordered(sat_job, jobs, chunksize=1): + n += 1 + if r: good[m].add(X) + if n % 50 == 0 or n == len(jobs): + print(' level %d progress %d/%d good %d (%.0fs)' % (m, n, len(jobs), len(good[m]), time.time() - t0), flush=True) + pickle.dump(good, open('levels_c%d.pkl' % c, 'wb')) + sizes = {} + for X in good[m]: + k = len(compl(X)); sizes[k] = sizes.get(k, 0) + 1 + print('LEVEL %d: %d good orbit reps; |O| histogram %s' % (m, len(good[m]), sorted(sizes.items())), flush=True) + if not good[m]: + print('EMPTY at level %d => at most %d cheap totals for c=%d' % (m, m - 1, c), flush=True) + break diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels2.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels2.py new file mode 100644 index 0000000000..5efc500057 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels2.py @@ -0,0 +1,110 @@ +"""S7 step 2 (fast, general): level search over sets Z = {Z_1..Z_m} of distinct flats (dims chosen by argv), up to the +line group (order 11520), such that EVERY subset J of Z satisfies + + minrank(decoder pattern on O_J) <= c - |J|, O_J = lines outside U_{i in J} Z_i. + +Why: if a decoder B = W U^T has c totals and Z_i contains a whole side (R_j or A_j) of total j_i (distinct totals), then +those |J| columns of W (or U) vanish on O_J, so B[O_J, O_J] has rank <= c - |J| (and obeys the pattern on O_J). +minrank is monotone under taking sub-line-sets, and Z = 3-flats is a relaxation of any proper flat. + +argv: c dims(e.g. '3' or '2' or '23') maxlevel nproc [tag] +Writes lev_.pkl = {m: set of canonical sets (tuples of flat ids into the 'flats' list)} and prints counts.""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from multiprocessing import Pool +from lines import views +from sub_minrank import run + +S = views()['Gauss'] +B = 12 # bits per flat id (< 4096) + + +def load_flats(dims): + G = pickle.load(open('orbits.pkl', 'rb'))['G'] + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + fl = sorted((d, tuple(sorted(f))) for f, d in F.items() if str(d) in dims) + fid = {f: k for k, (d, f) in enumerate(fl)} + P = np.array([[fid[tuple(sorted(g[i] for i in f))] for d, f in fl] for g in G], dtype=np.int64) + return G, fl, P + + +def keys(A): + """A: (..., |G|, m) sorted ids -> lexicographic key pair (k1, k2), 5 ids per key.""" + m = A.shape[-1] + k1 = np.zeros(A.shape[:-1], np.int64); k2 = np.zeros(A.shape[:-1], np.int64) + for i in range(m): + if i < 5: k1 = (k1 << B) | A[..., i] + else: k2 = (k2 << B) | A[..., i] + return k1, k2 + + +def canon_many(P, R, fs): + """canonical forms of R u {f} for each f in fs (vectorised). Canonical = lexicographically least sorted image.""" + out = [] + PR = P[:, list(R)] # |G| x m + for s in range(0, len(fs), 64): + ch = fs[s:s + 64] + A = np.concatenate([np.broadcast_to(PR, (len(ch),) + PR.shape), P[:, ch].T[:, :, None]], axis=2) + A = np.sort(A, axis=2) + k1, k2 = keys(A) + m1 = k1.min(1, keepdims=True) + k2m = np.where(k1 == m1, k2, np.iinfo(np.int64).max) + g = k2m.argmin(1) + for t in range(len(ch)): + out.append(tuple(int(x) for x in A[t, g[t]])) + return out + + +def canon(P, X): + X = list(X) + return canon_many(P, X[:-1], [X[-1]])[0] + + +def sat_job(args): + key, O, cc = args + if len(O) == 0: return key, True, 0.0 + if cc <= 0: return key, False, 0.0 # nonempty O has rank >= 1 + r, t, _ = run(S, O, cc, 'cadical153') + return key, r, t + + +def main(): + c = int(sys.argv[1]); dims = sys.argv[2]; maxlev = int(sys.argv[3]); npr = int(sys.argv[4]) + tag = sys.argv[5] if len(sys.argv) > 5 else 'c%d_d%s' % (c, dims) + G, fl, P = load_flats(dims) + NF = len(fl); assert NF < (1 << B) + lines_of = [set(f) for d, f in fl] + compl = lambda X: [T for T in range(60) if T not in set().union(*(lines_of[f] for f in X))] + t0 = time.time() + good = {0: {()}} + with Pool(npr) as pool: + for m in range(1, maxlev + 1): + cand = set() + for R in good[m - 1]: + fs = [f for f in range(NF) if f not in R] + cand.update(canon_many(P, R, fs) if R else [canon(P, [f]) for f in fs]) + cand2 = [X for X in cand + if m == 1 or all(canon(P, Y) in good[m - 1] for Y in itertools.combinations(X, m - 1))] + jobs = [(X, compl(X), c - m) for X in cand2] + print('level %d: candidates %d, subset-closed %d; testing minrank <= %d (%.0fs)' % ( + m, len(cand), len(jobs), c - m, time.time() - t0), flush=True) + good[m] = set(); n = 0 + for X, r, t in pool.imap_unordered(sat_job, jobs, chunksize=1): + n += 1 + if r: good[m].add(X) + if n % 100 == 0: print(' %d/%d good %d (%.0fs)' % (n, len(jobs), len(good[m]), time.time() - t0), flush=True) + pickle.dump({'good': good, 'flats': fl}, open('lev_%s.pkl' % tag, 'wb')) + hist = {} + for X in good[m]: + key = (tuple(sorted(len(fl[f][1]) for f in X)), len(compl(X))) + hist[key] = hist.get(key, 0) + 1 + print('LEVEL %d: %d good orbit reps (%.0fs)' % (m, len(good[m]), time.time() - t0), flush=True) + if len(hist) < 40: print(' (flat sizes, |O|) histogram:', sorted(hist.items()), flush=True) + if not good[m]: + print('EMPTY at level %d => at most %d such flats/totals for c=%d, dims %s' % (m, m - 1, c, dims), flush=True) + break + + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels3.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels3.py new file mode 100644 index 0000000000..a966313c40 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/levels3.py @@ -0,0 +1,62 @@ +"""Continuation of levels2.py (same definitions) with two exact speedups: + (1) extension flats f must be pair-compatible with every member (pair {Z_i, f} is a 2-subset, so it must be a level-2 set); + (2) canonicalisation and subset-closure checks run in the worker pool. +argv: c dims infile startlevel maxlevel nproc tag. Loads good[] from infile (written by levels2.py) and continues.""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from multiprocessing import Pool +from levels2 import load_flats, canon_many, canon, sat_job + +c = int(sys.argv[1]); dims = sys.argv[2] +G, fl, P = load_flats(dims) +NF = len(fl) +lines_of = [set(f) for d, f in fl] +good = pickle.load(open(sys.argv[3], 'rb'))['good'] +# pair compatibility matrix from level-2 orbit reps +comp = np.zeros((NF, NF), bool) +for (a, b) in good[2]: + comp[P[:, a], P[:, b]] = True; comp[P[:, b], P[:, a]] = True + + +def compl(X): + U = set().union(*(lines_of[f] for f in X)) + return [T for T in range(60) if T not in U] + + +def gen(R): + """canonical (m+1)-sets extending rep R that pass the full (m)-subset closure test, given global PREV.""" + fs = [f for f in range(NF) if f not in R and comp[f, list(R)].all()] + out = [] + for X in set(canon_many(P, R, fs)) if fs else []: + if all(canon(P, Y) in PREV for Y in itertools.combinations(X, len(X) - 1)): + out.append(X) + return out + + +def init(prev): + global PREV + PREV = prev + + +if __name__ == '__main__': + start = int(sys.argv[4]); maxlev = int(sys.argv[5]); npr = int(sys.argv[6]); tag = sys.argv[7] + t0 = time.time() + for m in range(start, maxlev + 1): + with Pool(npr, initializer=init, initargs=(good[m - 1],)) as pool: + cand = set() + for out in pool.imap_unordered(gen, sorted(good[m - 1]), chunksize=4): + cand.update(out) + jobs = [(X, compl(X), c - m) for X in sorted(cand)] + print('level %d: subset-closed candidates %d; testing minrank <= %d (%.0fs)' % (m, len(jobs), c - m, time.time() - t0), flush=True) + good[m] = set() + for X, r, t in pool.imap_unordered(sat_job, jobs, chunksize=1): + if r: good[m].add(X) + pickle.dump({'good': good, 'flats': fl}, open('lev_%s.pkl' % tag, 'wb')) + hist = {} + for X in good[m]: + k = len(compl(X)); hist[k] = hist.get(k, 0) + 1 + print('LEVEL %d: %d good orbit reps (%.0fs); |O| hist %s' % (m, len(good[m]), time.time() - t0, sorted(hist.items())), flush=True) + if not good[m]: + print('EMPTY at level %d => at most %d such flats/totals for c=%d, dims %s' % (m, m - 1, c, dims), flush=True) + break diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/lines.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/lines.py new file mode 100644 index 0000000000..fc4eac011d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/lines.py @@ -0,0 +1,166 @@ +"""Self-contained exact construction of the E8 root lines over Q(i), Q(omega) and the Hurwitz quaternions. + +Real model: the 240 E8 roots in doubled standard coordinates (norm 8; in {0,+-4,+-8}). + * Gaussian view : J = left multiplication by i on R^8 = H^2 (coords 1,i,j,k per block); J preserves E8. + Hermitian Gram hG(x,y) = ( + i )/4 in Z[i] (diag 2). + * Eisenstein view: rho = fixed-point-free order-3 isometry of E8 (product of Coxeter elements of 4 orthogonal A2's). + Sesquilinear Gram hE(x,y) = ( - w )/4 in Z[w] (w^2+w+1=0; diag 2+w, a unit at 2). + * Hurwitz view : left multiplication by i, j, k; quaternionic lines of real dim 4. +Elements of Z[i] / Z[w] are stored as integer pairs (a, b) = a + b*i / a + b*w. +Everything is exact integer arithmetic; asserts check every structural claim used downstream. +""" +import itertools +from fractions import Fraction +import numpy as np + + +def e8_roots(): + R = [] + for i, j in itertools.combinations(range(8), 2): + for si in (2, -2): + for sj in (2, -2): + r = [0] * 8; r[i], r[j] = si, sj; R.append(r) + for s in itertools.product((1, -1), repeat=8): + if s.count(-1) % 2 == 0: + R.append(list(s)) + R = np.array(R, dtype=np.int64) + assert len(R) == 240 and set((R * R).sum(1)) == {8} + return R + + +def rank_q(rows): + """exact rank over Q of an integer matrix (fraction-free Gaussian elimination).""" + M = [list(map(int, r)) for r in rows] + if not M: + return 0 + n, m = len(M), len(M[0]); r = 0 + for c in range(m): + p = next((i for i in range(r, n) if M[i][c] != 0), None) + if p is None: + continue + M[r], M[p] = M[p], M[r] + for i in range(r + 1, n): + if M[i][c]: + f, g = M[i][c], M[r][c] + M[i] = [g * a - f * b for a, b in zip(M[i], M[r])] + r += 1 + if r == n: + break + return r + + +def left_quat(): + Li = np.array([[0, -1, 0, 0], [1, 0, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) + Lj = np.array([[0, 0, -1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, -1, 0, 0]]) + Lk = Li @ Lj + blk = lambda M: np.kron(np.eye(2, dtype=np.int64), M).astype(np.int64) + return blk(Li), blk(Lj), blk(Lk) + + +def a2_rotation(roots): + """rho, an order-3 fixed-point-free isometry preserving E8 (returned as an exact integer matrix 4*rho).""" + chosen = [] + for a in roots: + if len(chosen) == 4: + break + if any(np.dot(a, x) != 0 for p in chosen for x in p): + continue + for b in roots: + if np.dot(a, b) == -4 and all(np.dot(a, x) == 0 and np.dot(b, x) == 0 for p in chosen for x in p): + chosen.append((a, b)); break + assert len(chosen) == 4 + g = np.eye(8, dtype=object) + for a, b in chosen: + sa = np.eye(8, dtype=object) * 8 - 2 * np.outer(a, a).astype(object) + sb = np.eye(8, dtype=object) * 8 - 2 * np.outer(b, b).astype(object) + g = g.dot(sa).dot(sb) + D = 8 ** 8 // 4 + assert all(x % D == 0 for x in g.flatten()) + g = (g // D).astype(np.int64) + I = np.eye(8, dtype=np.int64) + assert (g @ g @ g == 64 * I).all() and (g @ g + 4 * g + 16 * I == 0).all() + return g + + +def lines_over(roots, ops): + """group roots into A-lines, A spanned by the integer matrices ops (identity first; ops may be scaled by 4). + Returns representatives (first root met of each line, in root order).""" + S = {tuple(r) for r in roots} + reps, seen = [], set() + for r in roots: + if tuple(r) in seen: + continue + span = [B @ r for B in ops] + assert rank_q(span) == len(ops) + reps.append(r) + members = [s for s in roots if rank_q(span + [s]) == len(ops)] + for s in members: + seen.add(tuple(s)) + return np.array(reps) + + +class View: + """name, reps (v x 8), ops (real matrices spanning A, scaled so that ops[i] @ root is a root times scale[i]).""" + def __init__(self, name, reps, ops, d): + self.name, self.reps, self.ops, self.d = name, reps, ops, d + self.v = len(reps) + NO = np.zeros((self.v, self.v), bool) + for B in ops: + NO |= (reps @ B @ reps.T) != 0 + np.fill_diagonal(NO, False) + self.NO = NO # A-non-orthogonal pairs (the decoder pattern forces B_TS = 0 there) + self.w = self.arank(range(self.v)) + + def arank(self, idx): + rows = [B @ self.reps[i] for i in idx for B in self.ops] + r = rank_q(rows) if rows else 0 + assert r % self.d == 0 + return r // self.d + + def pair_rank(self, R, A): + """A-rank of the pairing span(R) x span(A) = real rank of the real Gram block / dim A (Theorem C cost).""" + BR = [B @ self.reps[i] for i in R for B in self.ops] + BA = [B @ self.reps[i] for i in A for B in self.ops] + if not BR or not BA: + return 0 + M = [[int(x @ y) for y in BA] for x in BR] + r = rank_q(M); assert r % self.d == 0 + return r // self.d + + +def views(): + roots = e8_roots(); I8 = np.eye(8, dtype=np.int64) + Li, Lj, Lk = left_quat() + S = {tuple(r) for r in roots} + for M in (Li, Lj, Lk): + assert all(tuple(M @ r) in S for r in roots) + g = a2_rotation(roots) # 4*rho + assert all(tuple(g @ r // 4) in S and ((g @ r) % 4 == 0).all() for r in roots) + rho = g # 4*rho (exact integer matrix); ranks are scale-free + out = {} + out['Gauss'] = View('Gauss', lines_over(roots, [I8, Li]), [I8, Li], 2) + out['Eisen'] = View('Eisen', lines_over(roots, [I8, rho]), [I8, rho], 2) + out['Hurwitz'] = View('Hurwitz', lines_over(roots, [I8, Li, Lj, Lk]), [I8, Li, Lj, Lk], 4) + out['_J'], out['_rho'] = Li, rho + return out + + +def gauss_gram(V, J): + """hG(r_T, r_S) = ( + i)/4 as integer pairs (a, b).""" + A = V @ V.T; Bm = V @ J @ V.T + assert (A % 4 == 0).all() and (Bm % 4 == 0).all() + return A // 4, Bm // 4 + + +def eisen_gram(V, rho4): + """hE(r_T, r_S) = ( - w)/4 as integer pairs (a, b) = a + b w (rho4 = 4*rho).""" + A = V @ V.T; Bm = V @ rho4 @ V.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + + +if __name__ == '__main__': + W = views() + for nm in ('Gauss', 'Eisen', 'Hurwitz'): + S = W[nm] + print(nm, 'v', S.v, 'w', S.w, 'nonorth degree', sorted(set(S.NO.sum(1)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/orbits.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/orbits.py new file mode 100644 index 0000000000..11e4c31837 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/orbits.py @@ -0,0 +1,53 @@ +"""Permutation group of the 60 Gaussian-E8 lines generated by the 60 complex reflections s_r s_{Jr}; orbits on the 1500 3-flats.""" +import sys, pickle +sys.path.insert(0, sys.path[0]) +import numpy as np +from lines import views, e8_roots + +def line_index(S): + J = S.ops[1]; idx = {} + for T, r in enumerate(S.reps): + for v in (r, -r, J @ r, -(J @ r)): idx[tuple(v)] = T + return idx + +def generators(S): + J = S.ops[1]; idx = line_index(S); gens = [] + for r in S.reps: + out = [] + for T in range(S.v): + x = S.reps[T] + for a in (r, J @ r): # real reflections in r and Jr (norm 8): x -> x - 2/8 a + x = x - (2 * int(x @ a) // 8) * a + out.append(idx[tuple(x)]) + assert sorted(out) == list(range(S.v)) + gens.append(tuple(out)) + return gens + +def closure(gens, n): + G = {tuple(range(n))}; frontier = list(G) + while frontier: + new = [] + for p in frontier: + for g in gens: + q = tuple(g[p[i]] for i in range(n)) + if q not in G: G.add(q); new.append(q) + frontier = new + return G + +if __name__ == '__main__': + S = views()['Gauss'] + gens = generators(S) + # check: generators preserve non-orthogonality + for g in gens: + P = np.array(g); assert (S.NO[np.ix_(P, P)] == S.NO).all() + G = closure(gens, S.v); print('group order on lines', len(G)) + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = sorted(sorted(f) for f, d in F.items() if d == 3) + hs = {frozenset(h) for h in hyp}; seen = set(); reps = [] + for h in hyp: + if frozenset(h) in seen: continue + orb = {frozenset(g[i] for i in h) for g in G} + assert orb <= hs + seen |= orb; reps.append((h, len(orb))) + print('orbits on 3-flats:', [(len(h), n) for h, n in reps], 'total', sum(n for _, n in reps)) + pickle.dump({'G': sorted(G), 'hyp_reps': reps}, open('orbits.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/pairs.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/pairs.py new file mode 100644 index 0000000000..17ef07df54 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/pairs.py @@ -0,0 +1,61 @@ +"""S7 step 2a: orbits of unordered pairs of distinct 3-flats under the line group (order 11520), and the minrank test +minrank(pattern on O) <= c - 2 with O = lines outside H1 u H2 (c = 16 -> test 14). + +Reduction (argued): a total j is 'cheap' (beta_j < 4) iff R_j or A_j lies in a 3-flat H_j. For any m cheap totals, +on O = complement of H_1 u ... u H_m those m columns of W (or U) vanish, so B[O,O] = W[O] U[O]^T has rank <= c - m. +Output: pairs.pkl {'pair_reps': [(H1, H2, orbit_size, minrank_le_14: bool)]}.""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from multiprocessing import Pool +from lines import views +from sub_minrank import run + +S = views()['Gauss'] + + +def load(): + G = pickle.load(open('orbits.pkl', 'rb'))['G'] + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = sorted(tuple(sorted(f)) for f, d in F.items() if d == 3) + assert len(hyp) == 1500 + return G, hyp + + +def act(g, h): + return tuple(sorted(g[i] for i in h)) + + +def pair_orbits(G, hyp): + """canonical orbit reps of unordered pairs {H1,H2}, by brute force: canonical form = min over G of sorted image pair.""" + hid = {h: k for k, h in enumerate(hyp)} + # permutation of flats induced by each group element + P = [[hid[act(g, h)] for h in hyp] for g in G] + seen = set(); reps = [] + n = len(hyp) + for a in range(n): + for b in range(a + 1, n): + if (a, b) in seen: continue + orb = set() + for p in P: + x, y = p[a], p[b] + orb.add((x, y) if x < y else (y, x)) + seen |= orb + reps.append((a, b, len(orb))) + assert sum(r[2] for r in reps) == n * (n - 1) // 2 + return reps, P + + +def job(args): + H, c, solver = args + Hs = set(H) + O = [T for T in range(60) if T not in Hs] + r, t, k = run(S, O, c, solver) + return H, c, solver, r, t, len(O) + + +if __name__ == '__main__': + G, hyp = load() + t = time.time() + reps, P = pair_orbits(G, hyp) + print('pair orbits', len(reps), 'time %.0fs' % (time.time() - t), flush=True) + pickle.dump({'reps': reps}, open('pair_orbits.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/price16.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/price16.py new file mode 100644 index 0000000000..d518940485 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/price16.py @@ -0,0 +1,18 @@ +"""Calibration: a SAT-found 16-total decoder (S6 clique-gauge encoding, no cost cap) is valid and costs >= 43 (here: its exact +Theorem-C cost, with the number of cheap totals).""" +import sys; sys.path.insert(0, sys.path[0]) +import numpy as np +from lines import views +from sub_minrank import encode +from pysat.solvers import Solver +S = views()['Gauss']; c = 16 +cl, pool, _ = encode(S, range(60), c) +with Solver(name='cadical153', bootstrap_with=cl) as s: + assert s.solve(); m = set(l for l in s.get_model() if l > 0) +W = np.array([[pool.id(('w', T, j)) in m for j in range(c)] for T in range(60)], int) +U = np.array([[pool.id(('u', T, j)) in m for j in range(c)] for T in range(60)], int) +B = W @ U.T % 2 +assert all(B[T, T] == 1 for T in range(60)) and not (B[S.NO] != 0).any(), 'invalid decoder' +beta = [S.pair_rank([T for T in range(60) if W[T, j]], [T for T in range(60) if U[T, j]]) for j in range(c)] +print('c=16 decoder valid; betas', beta, 'sum', sum(beta), 'cheap totals', sum(b < 4 for b in beta)) +assert sum(beta) >= 43 diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/prove_c16.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/prove_c16.py new file mode 100644 index 0000000000..7f19b82c36 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/prove_c16.py @@ -0,0 +1,88 @@ +"""S7 theorem: every Gaussian-E8 decoder with exactly 16 totals has Theorem-C cost >= 43. + +Inputs: lev_c16_d3_hi.pkl (3-flat level tables good[k], k = 1..16, from levels2.py / levels3.py; a level-k set is a set of +k distinct 3-flats every subset J of which has minrank(pattern on lines outside the union of J) <= 16 - |J|). + +Argument (see README): + k = #cheap totals (beta < 4). Each cheap total has a side in a proper flat, hence in a 3-flat; one 3-flat per cheap + total gives a level-k set (S6 lemma for distinctness), and any legal choice of 3-flats does. Types: + a: a side of dim <= 2 (inside a 2-flat Z): every 3-flat through Z is a legal choice; beta >= 0 + p: (3,3) with beta = 2: both X_j != Y_j legal (dumbbell); beta = 2 + q: rest (beta >= 3). + cost <= 42 <=> 3a + p >= 22 - k. + (A) a = 0 whenever k >= 5: no level-k rep (k >= 5) has an 'a-capable' member. [checked below] + (B) for k >= 11 the maximum number p* of jointly doubled members is < 22 - k. [checked below] + Arithmetic: k <= 4: 3a + p <= 3k < 22 - k. 5 <= k <= 10: a = 0, p <= k < 22 - k. k = 16: good[16] empty. +""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from levels2 import load_flats, canon, canon_many +from dumbbell import hyperplane_bases, beta_matrix + +G, fl3, P = load_flats('3') +_, fl2, _ = load_flats('2') +NF = len(fl3) +good = pickle.load(open(sys.argv[1] if len(sys.argv) > 1 else 'lev_c16_d3_hi.pkl', 'rb'))['good'] +set3 = [set(f) for d, f in fl3]; set2 = [set(f) for d, f in fl2] +through = [[h for h in range(NF) if set2[z] <= set3[h]] for z in range(len(fl2))] +inside = [[z for z in range(len(fl2)) if set2[z] <= set3[h]] for h in range(NF)] +cache = {} + + +def ok(X): + X = tuple(sorted(X)) + if len(set(X)) < len(X): return False + if X not in cache: cache[X] = canon(P, X) in good[len(X)] + return cache[X] + + +def a_capable(T, i): + return any(all(h == T[i] or (h not in T and ok(T[:i] + [h] + T[i + 1:])) for h in through[z]) for z in inside[T[i]]) + + +def pstar(T, beta, cap): + k = len(T) + part = {i: [y for y in range(NF) if y not in T and beta[T[i], y] == 2 and ok(T[:i] + [y] + T[i + 1:])] for i in range(k)} + mem = sorted(i for i in part if part[i]); best = [0] + + def valid(choice): + items = list(choice.items()) + for mask in range(1 << len(items)): + X = list(T) + for b, (i, y) in enumerate(items): + if mask >> b & 1: X[i] = y + if not ok(X): return False + return True + + def rec(pos, choice): + best[0] = max(best[0], len(choice)) + if best[0] >= cap or len(choice) + len(mem) - pos <= best[0]: return + for q in range(pos, len(mem)): + i = mem[q] + for y in part[i]: + if y in choice.values(): continue + choice[i] = y + if valid(choice): rec(q + 1, choice) + del choice[i] + rec(0, {}) + return best[0] + + +if __name__ == '__main__': + t0 = time.time() + assert len(good[1]) == 3 and not good[16] + print('level sizes:', {k: len(good[k]) for k in sorted(good) if k}, flush=True) + for k in range(1, 16): + n = sum(1 for T in good[k] if any(a_capable(list(T), i) for i in range(k))) + print('(A) k=%2d: %4d of %4d reps have an a-capable member' % (k, n, len(good[k])), flush=True) + if k >= 5: assert n == 0 + beta = beta_matrix(hyperplane_bases(fl3)) + for k in range(11, 16): + ps = [pstar(list(T), beta, 22 - k) for T in sorted(good[k])] + print('(B) k=%2d: max p* = %d (need >= %d for cost <= 42)' % (k, max(ps), 22 - k), flush=True) + assert max(ps) < 22 - k + for k in range(0, 17): + if k <= 4: assert 3 * k < 22 - k + elif k <= 10: assert k < 22 - k + print('THEOREM: no Gaussian-E8 decoder with exactly 16 totals has sum beta_j <= 42 (%.0fs)' % (time.time() - t0), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/refine.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/refine.py new file mode 100644 index 0000000000..d25baf408d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/refine.py @@ -0,0 +1,97 @@ +"""S7 step 2d: exact joint refinement of the configurations surviving dumbbell.py. + +For a level-k rep T (one 3-flat per cheap total) assign each member a type: + 'a' (a side of dim <= 2): choose a 2-flat Z inside T[i] containing that side; EVERY 3-flat H containing Z is a legal choice + for this total, so every transversal using any such H must be a level-k set. At most 2 'a' members, and if 2, the + pair (Z1, Z2) must be a level-2 set of the 2-flat search (lev_c16_d2.pkl). + 'p' (dumbbell): partner Y with beta(T[i], Y) = 2; both T[i] and Y are legal. + 'q': only T[i]. +Requirement from cost <= 42: 3*#a + #p >= 22 - k. ALL transversals of the joint choice must be level-k sets. +(A side of dim 1, a line l, is inside some 2-flat Z inside T[i], and every 3-flat through Z contains l, so 'a' with Z is +a relaxation of the line case.) Prints the configurations that survive (should be none for exclusion).""" +import sys, time, pickle, itertools +sys.path.insert(0, sys.path[0]) +import numpy as np +from levels2 import load_flats, canon + +G, fl3, P3 = load_flats('3') +_, fl2, P2 = load_flats('2') +NF = len(fl3) +good = pickle.load(open('lev_c16_d3_hi.pkl', 'rb'))['good'] +good2 = pickle.load(open('lev_c16_d2.pkl', 'rb'))['good'] +dd = pickle.load(open('dumbbell_out.pkl', 'rb')) +beta = dd['beta']; feasible = dd['feasible'] +set3 = [set(f) for d, f in fl3]; set2 = [set(f) for d, f in fl2] +through = [[h for h in range(NF) if set2[z] <= set3[h]] for z in range(len(fl2))] +inside = [[z for z in range(len(fl2)) if set2[z] <= set3[h]] for h in range(NF)] +cache = {} + + +def ok(X): + X = tuple(X) + if len(set(X)) < len(X): return False + if X not in cache: cache[X] = canon(P3, X) in good[len(X)] + return cache[X] + + +def joint_ok(T, opts): + """opts[i] = list of legal 3-flats for member i; every transversal must be a level-k set.""" + for X in itertools.product(*opts): + if not ok(sorted(X)): return False + return True + + +def analyse(k, T): + T = list(T) + need = 22 - k + subst = lambda i, h: sorted(T[:i] + [h] + T[i + 1:]) + part = {i: [y for y in range(NF) if y not in T and beta[T[i], y] == 2 and ok(subst(i, y))] for i in range(k)} + aopt = {i: [z for z in inside[T[i]] if all(h == T[i] or (h not in T and ok(subst(i, h))) for h in through[z])] for i in range(k)} + sols = [] + # choose A (<= 2 members with a 2-flat), then dumbbells among the rest with backtracking + for na in range(0, 3): + if 3 * na + (k - na) < need: continue + for A in itertools.combinations(range(k), na): + if any(not aopt[i] for i in A): continue + for Zs in itertools.product(*[aopt[i] for i in A]): + if na == 2: + if Zs[0] == Zs[1] or canon(P2, list(Zs)) not in good2[2]: continue + base = [[T[i]] for i in range(k)] + for i, z in zip(A, Zs): base[i] = through[z] + if not joint_ok(T, base): continue + rest = [i for i in range(k) if i not in A and part[i]] + pneed = need - 3 * na + if pneed <= 0: + sols.append((A, Zs, {})); continue + # backtracking over dumbbells + found = [] + def rec(pos, choice): + if found: return + if len(choice) >= pneed: + found.append(dict(choice)); return + if len(choice) + len(rest) - pos < pneed: return + for q in range(pos, len(rest)): + i = rest[q] + for y in part[i]: + if y in [v for v in choice.values()]: continue + opts = [list(o) for o in base] + for j, yy in list(choice.items()) + [(i, y)]: opts[j] = [T[j], yy] + if joint_ok(T, opts): + choice[i] = y; rec(q + 1, choice); del choice[i] + if found: return + rec(0, {}) + if found: sols.append((A, Zs, found[0])) + if sols: return sols, part, aopt + return sols, part, aopt + + +if __name__ == '__main__': + t0 = time.time(); surv = [] + for k, T, p in feasible: + sols, part, aopt = analyse(k, T) + na = sum(1 for i in aopt if aopt[i]) + print('k=%2d T=%s doublable %d a-capable %d -> %s (%.0fs)' % ( + k, T, sum(1 for i in part if part[i]), na, 'SURVIVES %s' % (sols[0],) if sols else 'excluded', time.time() - t0), flush=True) + if sols: surv.append((k, T, sols[0])) + pickle.dump(surv, open('refine_out.pkl', 'wb')) + print('SURVIVING CONFIGURATIONS:', len(surv), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/sub_minrank.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/sub_minrank.py new file mode 100644 index 0000000000..2f9383e0c2 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/sub_minrank.py @@ -0,0 +1,50 @@ +"""F2 minrank test of the Gaussian-E8 decoder pattern induced on a line subset O (asymmetric B = W U^T, clique gauge).""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from lines import views +from pysat.formula import IDPool +from pysat.solvers import Solver +import networkx as nx + +def encode(S, O, c, gauge=True): + O = list(O); pool = IDPool(); cl = [] + U = {T: [pool.id(('u', T, j)) for j in range(c)] for T in O} + W = {T: [pool.id(('w', T, j)) for j in range(c)] for T in O} + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + def xorlist(ls): + acc = ls[0] + for l in ls[1:]: acc = XOR(acc, l) + return acc + for T in O: + for S_ in O: + if T == S_ or S.NO[T, S_]: + b = xorlist([AND(W[T][j], U[S_][j]) for j in range(c)]) + cl.append([b] if T == S_ else [-b]) + K = [] + if gauge: + g = nx.Graph([(i, j) for i in O for j in O if i < j and S.NO[i, j]]); g.add_nodes_from(O) # S7: isolated lines too + K = sorted(max(nx.find_cliques(g), key=len))[:c] + for i, T in enumerate(K): + for j in range(c): + cl.append([U[T][j]] if j == i else [-U[T][j]]); cl.append([W[T][j]] if j == i else [-W[T][j]]) + return cl, pool, len(K) + +def run(S, O, c, solver='cadical153'): + cl, pool, k = encode(S, O, c) + t = time.time() + with Solver(name=solver, bootstrap_with=cl) as s: r = s.solve() + return r, time.time() - t, k + +if __name__ == '__main__': + S = views()['Gauss'] + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = [sorted(f) for f, d in F.items() if d == 3] + c = int(sys.argv[1]) if len(sys.argv) > 1 else 14 + print('full 60, c', c, run(S, range(60), c), flush=True) + for size in (15, 6, 4): + H = next(h for h in hyp if len(h) == size) + O = [T for T in range(60) if T not in H] + print('|H|', size, '|O|', len(O), 'c', c, run(S, O, c), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/test_pairs.py b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/test_pairs.py new file mode 100644 index 0000000000..ad5d796d4b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/scripts/test_pairs.py @@ -0,0 +1,19 @@ +"""Run minrank(O) <= c tests on complements of unions of pair-orbit reps. argv: c solver nproc.""" +import sys, time, pickle +sys.path.insert(0, sys.path[0]) +from multiprocessing import Pool +from pairs import load, job + +if __name__ == '__main__': + c = int(sys.argv[1]); solver = sys.argv[2]; npr = int(sys.argv[3]) + G, hyp = load() + reps = pickle.load(open('pair_orbits.pkl', 'rb'))['reps'] + jobs = [(tuple(sorted(set(hyp[a]) | set(hyp[b]))), c, solver) for a, b, _ in reps] + res = {} + with Pool(npr) as p: + for k, ((U, c_, s, r, t, nO), (a, b, n)) in enumerate(zip(p.imap(job, jobs), reps)): + res[(a, b)] = r + print('pair (%4d,%4d) |H1|=%2d |H2|=%2d |O|=%2d orbit %5d c=%d %s %s %.1fs [%d/%d]' % ( + a, b, len(hyp[a]), len(hyp[b]), nO, n, c, s, 'SAT' if r else 'UNSAT', t, k + 1, len(jobs)), flush=True) + pickle.dump(res, open('pair_res_c%d_%s.pkl' % (c, solver), 'wb')) + print('SAT pairs:', sum(res.values()), 'of', len(res), flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/verify.sh new file mode 100755 index 0000000000..99ef81e07c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s7-gaussian-c16/verify.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# S7 verification: rebuild the 3-flat level tables from scratch, then check the theorem. ~15-25 min, 8 processes. +# Needs python with numpy, networkx, python-sat on PATH. CROSS=1 adds the glucose4 re-solve of all UNSAT prunings (~15 min). +set -e +export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 +HERE=$(cd "$(dirname "$0")" && pwd); W=${WORK:-$(mktemp -d)} +cp "$HERE"/scripts/*.py "$HERE"/data/flats_Gauss.pkl "$HERE"/data/orbits.pkl "$HERE"/data/pair_orbits.pkl "$W"/ +cd "$W" +python lines.py | head -1 +python levels2.py 16 3 3 8 vf | grep -E '^(LEVEL|EMPTY)' +python levels3.py 16 3 lev_vf.pkl 4 16 8 vf2 | grep -E '^(LEVEL|EMPTY)' | sed 's/ (.*//' +cp lev_vf2.pkl lev_c16_d3_hi.pkl +python prove_c16.py lev_vf2.pkl | sed 's/ (.*s)$//' +python price16.py +python levels2.py 15 3 1 1 calib15 | grep -E '^(LEVEL|EMPTY)' | sed 's/ (.*//' +if [ -n "$CROSS" ]; then python crosscheck.py 16 3 lev_vf2.pkl 8 glucose4 8 | grep CROSSCHECK | sed 's/ (.*//'; fi +echo "work dir: $W" From d9f74946210588fe4085411ea254c30fb7f11e09 Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Wed, 14 Oct 2026 23:06:54 -0400 Subject: [PATCH 16/17] Add Gaussian E8 all-c bound cost >= max(40, 60-c) and exclusion of c = 18 Co-Authored-By: Claude Opus 5.5 --- .../README.md | 24 ++- .../s8-gaussian-all-c/README.md | 113 ++++++++++++ .../s8-gaussian-all-c/expected.txt | 18 ++ .../s8-gaussian-all-c/logs/check_c21cover.log | 1 + .../s8-gaussian-all-c/logs/cover_ilp.log | 1 + .../s8-gaussian-all-c/logs/lp2.log | 11 ++ .../s8-gaussian-all-c/logs/tight_ilp.log | 6 + .../s8-gaussian-all-c/logs/verify_run.log | 19 ++ .../s8-gaussian-all-c/scripts/c18.py | 41 +++++ .../scripts/check_c21cover.py | 11 ++ .../s8-gaussian-all-c/scripts/cover_ilp.py | 30 ++++ .../s8-gaussian-all-c/scripts/facets.py | 23 +++ .../s8-gaussian-all-c/scripts/flats.py | 26 +++ .../s8-gaussian-all-c/scripts/hits_table.py | 78 ++++++++ .../s8-gaussian-all-c/scripts/lines.py | 166 ++++++++++++++++++ .../s8-gaussian-all-c/scripts/lp2.py | 38 ++++ .../s8-gaussian-all-c/scripts/tight_ilp.py | 22 +++ .../s8-gaussian-all-c/scripts/types.py | 18 ++ .../s8-gaussian-all-c/verify.sh | 14 ++ 19 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/expected.txt create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/check_c21cover.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/cover_ilp.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/lp2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/tight_ilp.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/verify_run.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/c18.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/check_c21cover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/cover_ilp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/facets.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/hits_table.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lp2.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/tight_ilp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/types.py create mode 100755 research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 425895e9bf..4b9bda9486 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -192,4 +192,26 @@ Together with §13, a Gaussian E8 decoder that beats real E8 (cost ≤ 42) needs - Side result: at most 2 totals can have a side of dim ≤ 2. - Related scoped results: #767, #2066. -**Open:** c ≥ 17. The same machinery applies, with 3a + p ≥ 26 − k and level tables at rank ≤ 17 − |J|. +**Caveat (found in §15):** the fast canonical key in `levels2.py` can overflow int64 for k > 10, so in principle a pruning could be wrong. No mismatch was observed, and §15's Theorem 1 re-proves the c = 16 result independently (cost ≥ 44) without level tables or SAT. + +## 15. Gaussian E8 for every c: cost ≥ max(40, 60 − c), and c = 18 excluded (`s8-gaussian-all-c/`; `verify.sh` about 6 min, exact arithmetic) +- **Theorem 1 (checked).** + - On all 2271² flat pairs, the maximum clique hits by rank are 0, 192, 384, 512 and 640 for ranks 0–4. Their concave envelope has two facets: h ≤ 192·R and **h ≤ 128·R + 128**. + - Summing the second facet over the c totals: 7680 ≤ 128·cost + 128·c, so **cost ≥ 60 − c**. + - Hence every decoder with c ≤ 17 costs ≥ 43. This **settles c = 17** and re-proves §14 (c = 16 gives ≥ 44) without SAT. +- **Theorem 2 (checked): c = 18 is excluded.** + - At c = 18 and cost ≤ 42, every clique is hit exactly 12 times and every total is tight. + - All 1,081 tight flat pairs miss exactly the cliques M·x_t, where x_t is a set of pairwise-orthogonal lines. + - So Mμ = 6·**1** with μ integral. M is the 640×60 clique/line incidence matrix, of exact rank 60, so μ = ½·**1**: a contradiction. +- **Theorem 3 (checked): the tight face cost = 60 − c never occurs.** It would need μ = (c − 12)/12. Hence c = 19 requires cost ≥ 42 (only cost = 42 is open) and c = 20 requires cost ≥ 41. +- **Facts for all c (argued).** + - Cost-0 totals can be dropped, so WLOG c ≤ 42. + - At most 3 totals are full rank and at most 6 have β ≥ 3. + - At most 2(c − 18) totals have rank 1. +- **Why counting alone stops here.** + - The LP over cliques of every size, lines, flat complements and a free c-term gives exactly max(40, 60 − c). + - An **integral** cover exists from c = 21: 21 dumbbells (ℓ^⊥, m^⊥) with ℓ ⊥ m, of total cost 42, hit every clique 12–16 times (checked). + - So excluding c ≥ 21 needs the F2 decoder pattern itself, not counting. + - CP-SAT covering ILPs for c = 19 and c = 20 returned UNKNOWN. + +**Status of the Gaussian question.** A Gaussian E8 decoder that beats real E8 (cost ≤ 42) needs **19 ≤ c ≤ 42** totals of nonzero cost. At c = 19 only cost exactly 42 is open, and at c = 20 only 41–42. diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/README.md b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/README.md new file mode 100644 index 0000000000..30d4737fce --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/README.md @@ -0,0 +1,113 @@ +# S8 — Gaussian E8: no decoder with at most 18 totals costs ≤ 42 + +Context: #620 §10 (S5: Σβ ≥ 40 for every decoder), §13 (S6: every 15-total decoder costs 60) and §14 (S7: no 16-total decoder costs ≤ 42). +- The setting is the Gaussian view of E8: 60 Q(i)-lines in K⁴, with the F2 decoder pattern (B_TT = 1, and B_TS = 0 on K-non-orthogonal pairs). +- The cost is the Theorem-C cost Σ_j β_j. +- To beat real E8 a decoder needs Σβ ≤ 42. + +Labels: +- **checked**: `verify.sh` recomputes the claim, with exact integers and certified rank lower bounds. +- **argued**: the proof is in this README. + +## Results + +**Theorem 1 (two counting facets; argued + checked).** Every Gaussian-E8 decoder with c totals has + + Σβ ≥ max(40, 60 − c). + +So every decoder with c ≤ 17 totals costs ≥ 43. This re-proves §14 (c = 16) with the stronger bound 44, and settles **c = 17**. No level tables or SAT are needed. + +**Theorem 2 (c = 18; argued + checked).** No 18-total decoder costs ≤ 42. + +**Theorem 3 (tight face).** A decoder with Σβ = 60 − c exists for no c: for 15 ≤ c ≤ 20, μ = (c − 12)/12 is non-integral, and c ≤ 14 or c ≥ 21 is impossible (minrank 15; cost ≥ 40). In particular: +- c = 19 needs cost ≥ 42, so the only value open there is cost exactly 42; +- c = 20 needs cost ≥ 41. + +**Corollary.** +- A total of cost 0 touches only free entries, so it can be dropped. That gives a decoder with fewer totals and the same cost. +- Therefore a Gaussian decoder that beats real E8 has **between 19 and 42 totals of nonzero cost**. +- The upper end holds because each such total costs ≥ 1. +- **The all-c theorem is NOT proved. The open range is 19 ≤ c ≤ 42.** See "Gap" below. + +## Proof + +### 0. Data (checked: `hits_table.py`, which is S5's `gauss_bound40.py` plus a pickle dump) +For all 2271² pairs of flats (X, Y), the table holds: +- the exact clique hits h(X, Y): the number of the 640 twelve-line cliques of pairwise non-orthogonal lines that meet both X and Y; +- a certified lower bound R(X, Y) on the K-rank of the pairing, computed as an exact integer rank mod 2³¹ − 1. + +### 1. Theorem 1 +**Per clique (S5, argued).** On a clique C the pattern forces B[C, C] = I₁₂. A total contributes to this block only if it *hits* C, meaning R_j ∩ C ≠ ∅ and A_j ∩ C ≠ ∅, and then it contributes a rank-1 matrix. So N_C, the number of totals hitting C, satisfies N_C ≥ 12. Summing over the 640 cliques gives Σ_j hit_j ≥ 7680. + +**Per total (checked, `facets.py`).** Let X_j, Y_j be the spans of the two sides of total j. Then +- hit_j ≤ h(X_j, Y_j), and β_j ≥ R(X_j, Y_j); +- by rank, the largest h is 0, 192, 384, 512, 640 for R = 0, 1, 2, 3, 4; +- the upper concave envelope of these values has two facets, so on **every** flat pair + + h ≤ 192·R and h ≤ 128·R + 128. + +Summing over totals gives 7680 ≤ 192·Σβ and 7680 ≤ 128·Σβ + 128·c. ∎ + +The two facets give two slack identities, each term of which is ≥ 0: +- (D) Σ_j d_j + E = 192·cost − 7680, where d_j = 192β_j − hit_j; +- (E) Σ_j e_j + E = 128·(cost + c) − 7680, where e_j = 128β_j + 128 − hit_j; +- in both, E = Σ_C (N_C − 12). + +### 2. Theorem 2 (`c18.py`) +1. **Tightness.** At c = 18 and cost ≤ 42, the right side of (E) is ≤ 0. Hence cost = 42, E = 0 (every clique is hit **exactly** 12 times), and every e_j = 0. +2. **The span pair is tight.** hit_j ≤ h(X_j, Y_j) ≤ 128·R + 128 ≤ 128β_j + 128 = hit_j, so (X_j, Y_j) is a *tight* flat pair, and total j hits exactly the cliques that its span pair hits. +3. **The tight pairs (checked).** There are 1081 of them, and they are exactly: + - (ℓ^⊥, m^⊥) with ℓ ⊥ m, β = 2; + - (ℓ^⊥, ℓ^⊥), β = 3; + - (ℓ^⊥, K⁴) and (K⁴, ℓ^⊥), β = 3; + - (K⁴, K⁴), β = 4. + + Here ℓ^⊥ is a 15-line 3-flat. For each tight pair t, the set of cliques it **misses** is exactly {C : C meets x_t}, where x_t is a set of pairwise orthogonal lines ({ℓ, m}, {ℓ} or ∅). In matrix form, 1 − h_t = M·x_t, where M is the 640 × 60 clique/line incidence matrix. +4. **Equation.** So 12·**1** = N = Σ_j h_j = 18·**1** − M·μ, with μ = Σ_j x_{t(j)} ∈ Z₊⁶⁰. That is, Mμ = 6·**1**. +5. **Contradiction.** M has exact rank 60 (checked, sympy), and M·(½·**1**) = 6·**1** because every clique has 12 lines. So μ = ½·**1**, which is not integral. ∎ + +**Theorem 3.** The same argument applies whenever cost = 60 − c. That case needs (E) = 0, and with cost ≥ 40 it forces c ≤ 20. Then Mμ = (c − 12)·**1**, so μ = (c − 12)/12. For c = 19, 20 this is 7/12 and 2/3, neither integral. + +### 3. Consequences for 19 ≤ c ≤ 42 (argued) +- From (D), a total with β ≥ 3 has d ≥ 64 and a full total has d = 128 (checked: the smallest d at R = 3 is 64, at R = 4 it is 128). So there are at most 3 full totals and at most 6 totals with β ≥ 3, **for every c**. +- From (E), a rank-1 total has e ≥ 64. So there are at most 2(c − 18) rank-1 totals, while cost ≤ 42 needs at least 2c − 42 of them. +- At c = 19 with cost 42, the slack in (E) is only 128. + +## Gap: what is not proved, and why +- **Linear counting cannot go further.** `lp2.py` is an LP over uniform orbit weights. It uses cliques of every size (7, 8, 10, 11, 12), single lines, complements of all flats (minrank 15), the whole set, and a free c-term μ·c. Its value is exactly max(40, 60 − c) (float LP, recorded). So linear counting with these sets is exhausted; what goes further must be integrality, as in Theorem 2, or F2 structure. +- **Level tables do not scale.** The S7 level-table route does not reach c ≥ 19. Every multiset of at most c − 15 three-flats is trivially a level set, so the bottom levels alone have about 1500^(c−15)/11520 orbits. The hyperplane-only version at c = 18 grew as 1, 5, 21, 129, 921, 7917 reps at levels 1–6. +- **Integer counting is not enough either from c = 21 on (checked).** + - `tight_ilp.py 21` (CP-SAT) found 21 dumbbell totals (ℓ^⊥, m^⊥) with ℓ ⊥ m. They cost 42 in total, and they hit every 12-clique at least 12 times (between 12 and 16 times). + - `check_c21cover.py` re-checks this exactly; see `logs/check_c21cover.log`. + - So clique counting, even with integrality, cannot exclude c ≥ 21. That range needs the F2 pattern itself, for example S7-style rank-drop or level arguments on the dumbbell hyperplanes, or a SAT search. +- **c = 19 and 20 are open.** + - c = 19: cost exactly 42 with (E)-slack 128. + - c = 20: cost 41 or 42. + - CP-SAT returned UNKNOWN within its time limit on two runs: + - the full covering ILP over 124,841 rank-maximal types (`types.py`, `cover_ilp.py`, all c, 900 s); + - the tight-types-only version (`tight_ilp.py 19/20`, 300 s each). + + See `logs/`. + +## Validation +- c = 15: Theorem 1 gives ≥ 45, consistent with §13's exact 60. +- c = 16: it gives ≥ 44, consistent with §14's ≥ 43. +- The identity decoder (60 single-line totals, cost 60) meets 7680 = Σ hits exactly, with 128 hits per total, and satisfies both facets. +- §14's machinery was rerun from scratch with a multiset-aware level search (`levels_ms.py` in the work dir, exact multi-key canonical form). It reproduced S7's level counts 3, 28, 120, 368 for k = 1–4; the run was stopped after that because it is superseded. +- **Caveat on S7 (not affecting any S8 claim).** S7's `levels2.keys` packs ids 6, 7, … into one int64 with 12-bit shifts. That overflows for k > 10, so the canonical form there is a min over a truncated key. False prunes would need two distinct images to tie on the truncated key. None were observed, but none were excluded either. Theorem 1 re-proves §14 without the tables. +- No new SAT/UNSAT verdicts are used, so no Glucose cross-check is needed. All checks are exact integer computations, plus a sympy rank. + +## Files (`scripts/`) +| file | what | +|---|---| +| `lines.py`, `flats.py` | S5's exact construction of the lines and flats | +| `hits_table.py` | S5's `gauss_bound40.py`, which also dumps `hits_table.pkl` | +| `facets.py` | Theorem 1: both facets on all flat pairs; the tight pairs | +| `c18.py` | Theorems 2 and 3: tight-pair miss sets equal M·x_t; rank M = 60; the parity contradiction | +| `lp2.py` | the LP showing linear counting stops at max(40, 60 − c) | +| `types.py`, `cover_ilp.py`, `tight_ilp.py` | integer clique-covering relaxations (exploratory; CP-SAT) | +| `check_c21cover.py` | exact check of the c = 21, cost-42 integral cover | + +```sh +PATH=$SP/venv/bin:$PATH sh verify.sh # ~6 min, 1 process; compare with expected.txt +``` diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/expected.txt b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/expected.txt new file mode 100644 index 0000000000..a2aefc4ba0 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/expected.txt @@ -0,0 +1,18 @@ +PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all 2271x2271 flat pairs, histogram [5450, 369780, 2631390, 2150820, 1] +PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks) +RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43) +PASS facet1: hits <= 192*rank on all 5157441 flat pairs +PASS facet2: hits <= 128*rank + 128 on all 5157441 flat pairs +max hits by certified rank: {0: 0, 1: 192, 2: 384, 3: 512, 4: 640} +tight pairs (hits = 128 rank + 128): [(('dim3/15lines', 'dim3/15lines', 2), 900), (('dim3/15lines', 'dim3/15lines', 3), 60), (('dim3/15lines', 'dim4/60lines', 3), 60), (('dim4/60lines', 'dim3/15lines', 3), 60), (('dim4/60lines', 'dim4/60lines', 4), 1)] +c = 15: cost >= 45 (> 42: excluded) +c = 16: cost >= 44 (> 42: excluded) +c = 17: cost >= 43 (> 42: excluded) +c = 18: cost >= 42 +c = 19: cost >= 41 +PASS clique/line incidence M (640 x 60) has exact rank 60; M(1/2) = 6 +PASS every tight flat pair t has 1 - h_t = M x_t with x_t pairwise-orthogonal lines: [((15, 15, 2, 2), 900), ((15, 15, 3, 1), 60), ((15, 60, 3, 1), 60), ((60, 15, 3, 1), 60), ((60, 60, 4, 0), 1)] +RESULT c18: M mu = 6*1 has the unique rational solution mu = 1/2 (non-integral) => no 18-total decoder costs <= 42 +RESULT tight c=19: cost = 41 would need mu = 7/12 (non-integral) => cost >= 42 +RESULT tight c=20: cost = 40 would need mu = 2/3 (non-integral) => cost >= 41 +c = 21 dumbbell cover: cost 42 min clique hits 12 max 16 PASS diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/check_c21cover.log b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/check_c21cover.log new file mode 100644 index 0000000000..aab245b59b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/check_c21cover.log @@ -0,0 +1 @@ +c = 21 dumbbell cover: cost 42 min clique hits 12 max 16 PASS diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/cover_ilp.log b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/cover_ilp.log new file mode 100644 index 0000000000..cd72b5cc5c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/cover_ilp.log @@ -0,0 +1 @@ +status UNKNOWN 906s diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/lp2.log b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/lp2.log new file mode 100644 index 0000000000..a6825ce4c5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/lp2.log @@ -0,0 +1,11 @@ +Counter({10: 2304, 11: 1920, 8: 1920, 7: 1920, 12: 640}) +['clq7', 'clq8', 'clq10', 'clq11', 'clq12', 'line', 'cmp1', 'cmp2', 'cmp3', 'all'] +rows 313 +17 43.0 {'clq12': np.float64(0.00781)} mu 1.0 +18 42.0 {'clq12': np.float64(0.00781)} mu 1.0 +19 41.0 {'clq12': np.float64(0.00781)} mu 1.0 +20 40.0 {'clq12': np.float64(0.00521)} mu -0.0 +22 40.0 {'clq12': np.float64(0.00521)} mu -0.0 +25 40.0 {'clq12': np.float64(0.00521)} mu -0.0 +30 40.0 {'clq12': np.float64(0.00521)} mu -0.0 +42 40.0 {'clq12': np.float64(0.00521)} mu 0.0 diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/tight_ilp.log b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/tight_ilp.log new file mode 100644 index 0000000000..f59f25e8eb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/tight_ilp.log @@ -0,0 +1,6 @@ +c 21 status OPTIMAL +D [((0, 13), 1), ((4, 22), 1), ((5, 50), 1), ((6, 19), 1), ((6, 28), 1), ((7, 38), 1), ((8, 19), 1), ((11, 56), 1), ((12, 17), 1), ((18, 37), 1), ((18, 58), 1), ((20, 30), 1), ((21, 33), 1), ((25, 26), 1), ((35, 55), 1), ((36, 45), 1), ((37, 47), 1), ((41, 44), 1), ((42, 51), 1), ((44, 51), 1), ((48, 53), 1)] S [] F 0 +c 20 status UNKNOWN +c 19 status UNKNOWN + +[exited with code 0] diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/verify_run.log b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/verify_run.log new file mode 100644 index 0000000000..d26d5cead6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/logs/verify_run.log @@ -0,0 +1,19 @@ +PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all 2271x2271 flat pairs, histogram [5450, 369780, 2631390, 2150820, 1] +PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks) +RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43) +PASS facet1: hits <= 192*rank on all 5157441 flat pairs +PASS facet2: hits <= 128*rank + 128 on all 5157441 flat pairs +max hits by certified rank: {0: 0, 1: 192, 2: 384, 3: 512, 4: 640} +tight pairs (hits = 128 rank + 128): [(('dim3/15lines', 'dim3/15lines', 2), 900), (('dim3/15lines', 'dim3/15lines', 3), 60), (('dim3/15lines', 'dim4/60lines', 3), 60), (('dim4/60lines', 'dim3/15lines', 3), 60), (('dim4/60lines', 'dim4/60lines', 4), 1)] +c = 15: cost >= 45 (> 42: excluded) +c = 16: cost >= 44 (> 42: excluded) +c = 17: cost >= 43 (> 42: excluded) +c = 18: cost >= 42 +c = 19: cost >= 41 +PASS clique/line incidence M (640 x 60) has exact rank 60; M(1/2) = 6 +PASS every tight flat pair t has 1 - h_t = M x_t with x_t pairwise-orthogonal lines: [((15, 15, 2, 2), 900), ((15, 15, 3, 1), 60), ((15, 60, 3, 1), 60), ((60, 15, 3, 1), 60), ((60, 60, 4, 0), 1)] +RESULT c18: M mu = 6*1 has the unique rational solution mu = 1/2 (non-integral) => no 18-total decoder costs <= 42 +RESULT tight c=19: cost = 41 would need mu = 7/12 (non-integral) => cost >= 42 +RESULT tight c=20: cost = 40 would need mu = 2/3 (non-integral) => cost >= 41 +c = 21 dumbbell cover: cost 42 min clique hits 12 max 16 PASS +work dir: /private/tmp/claude-501/-Users-joelpulikkan-integer-mult-bounds/3231f0a2-7112-4dee-8ef4-f1fca97ae281/scratchpad/s8/vw diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/c18.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/c18.py new file mode 100644 index 0000000000..ec1bd133bb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/c18.py @@ -0,0 +1,41 @@ +"""S8: no 18-total Gaussian-E8 decoder costs <= 42. Input hits_table.pkl (see facets.py). + +Argument. e_j := 128 beta_j + 128 - hit_j >= 0 for every total (facet2), and + sum_j e_j + E = 128 cost + 128 c - 7680, E = sum_C (N_C - 12) >= 0 (N_C = #totals hitting clique C on both sides). +At c = 18 and cost <= 42 the right side is <= 0, so cost = 42, E = 0 (every clique hit EXACTLY 12 times) and every total +is tight: hit(R_j,A_j) = 128 beta_j + 128. Since hit(R,A) <= hit(X,Y) <= 128 R(X,Y) + 128 <= 128 beta + 128 for the spans +(X,Y), the span pair is a tight flat pair and the cliques hit by total j are exactly those hit by (X_j,Y_j). +Checked below: for every tight flat pair t the set of cliques NOT hit is {C : C meets x_t} for a set x_t of pairwise +orthogonal lines (so that 1 - h_t = M x_t, M = 640 x 60 clique/line incidence). Then + 12 * 1 = N = sum_j h_j = 18 * 1 - M mu, mu = sum_j x_{t(j)} in Z_{>=0}^60, i.e. M mu = 6 * 1. +M has rank 60 (checked, exact) and M (1/2 * 1) = 6 * 1 (every clique has 12 lines), so mu = 1/2 * 1: not integral. +Contradiction. +Same argument on the tight face for any c (cost = 60 - c, needs c <= 20 since cost >= 40): M mu = (c-12) 1, mu = (c-12)/12; +for c = 19 (cost 41) and c = 20 (cost 40) this is non-integral, so c = 19 needs cost >= 42 and c = 20 cost >= 41.""" +import pickle, numpy as np, sympy +from lines import views +S = views()['Gauss']; NO = S.NO +d = pickle.load(open('hits_table.pkl', 'rb')); fl = d['fl']; R = d['R']; H = d['hits']; C = d['C'] +M = np.zeros((640, 60), np.int64) +for k, c in enumerate(C): M[k, c] = 1 +assert (M.sum(1) == 12).all() +assert sympy.Matrix(M).rank() == 60 +print('PASS clique/line incidence M (640 x 60) has exact rank 60; M(1/2) = 6') +L = [set(f) for f, dd in fl] +tight = np.argwhere(H == 128 * R + 128) +from collections import Counter +kinds = Counter() +for a, b in tight: + X, Y = L[a], L[b] + h = np.array([bool(X & set(c)) and bool(Y & set(c)) for c in C]) + x = sorted(T for T in range(60) if all(not h[k] for k, c in enumerate(C) if T in c)) # lines all of whose cliques are missed + assert all(not NO[s, t] for s in x for t in x if s != t) # pairwise orthogonal => cliques meet x at most once + assert ((M[:, x].sum(1) if x else np.zeros(640, int)) == (~h).astype(int)).all() # 1 - h = M x exactly + kinds[(len(L[a]), len(L[b]), int(R[a, b]), len(x))] += 1 +print('PASS every tight flat pair t has 1 - h_t = M x_t with x_t pairwise-orthogonal lines:', sorted(kinds.items())) +print('RESULT c18: M mu = 6*1 has the unique rational solution mu = 1/2 (non-integral) => no 18-total decoder costs <= 42') +# Tight face in general: cost = 60 - c forces the same structure with M mu = (c - 12) * 1, mu = (c - 12)/12 * 1. +for c in (19, 20): + from fractions import Fraction as Fr + mu = Fr(c - 12, 12) + print('RESULT tight c=%d: cost = %d would need mu = %s (non-integral) => cost >= %d' % (c, 60 - c, mu, 61 - c if 61 - c > 40 else 41)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/check_c21cover.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/check_c21cover.py new file mode 100644 index 0000000000..9237a61705 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/check_c21cover.py @@ -0,0 +1,11 @@ +"""Exact check: the 21 dumbbell totals (l^perp, m^perp), l ⊥ m (beta = 2 each, cost 42) found by tight_ilp.py 21 hit every +12-clique >= 12 times, i.e. the clique-covering relaxation is integrally feasible at c = 21, cost 42.""" +import pickle, numpy as np +from lines import views +S=views()['Gauss']; NO=S.NO +C=pickle.load(open('hits_table.pkl','rb'))['C'] +D=[(0,13),(4,22),(5,50),(6,19),(6,28),(7,38),(8,19),(11,56),(12,17),(18,37),(18,58),(20,30),(21,33),(25,26),(35,55),(36,45),(37,47),(41,44),(42,51),(44,51),(48,53)] +perp=lambda l: {T for T in range(60) if T!=l and not NO[l,T]} +assert all(not NO[a,b] and a!=b for a,b in D) +N=[sum(1 for a,b in D if perp(a)&set(c) and perp(b)&set(c)) for c in C] +print('c = 21 dumbbell cover: cost', 2*len(D), 'min clique hits', min(N), 'max', max(N), 'PASS' if min(N)>=12 else 'FAIL') diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/cover_ilp.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/cover_ilp.py new file mode 100644 index 0000000000..ab503dfc43 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/cover_ilp.py @@ -0,0 +1,30 @@ +"""Clique-covering ILP relaxation (c-free): integer multiplicities n_t of rank-maximal flat-pair types with deficit <= 384, +every 12-clique covered >= 12 times, sum rank*n <= 42. Feasible => relaxation cannot close all c; infeasible => all c closed.""" +import pickle, numpy as np, time, sys +from ortools.sat.python import cp_model +d=pickle.load(open('hits_table.pkl','rb')); fl=d['fl']; C=d['C'] +T=pickle.load(open('types.pkl','rb')); ts=T['types']; R=T['R']; D=T['D'] +L=np.zeros((len(fl),60),np.int64) +for i,(f,dd) in enumerate(fl): L[i,sorted(f)]=1 +inc=np.zeros((60,640),np.int64) +for k,c in enumerate(C): inc[c,k]=1 +meet=(L@inc>0) +m=cp_model.CpModel(); n=[] +for k,(a,b) in enumerate(ts): + ub=42//int(R[k]) if D[k]==0 else min(42//int(R[k]),384//int(D[k])) + n.append(m.NewIntVar(0,ub,'n%d'%k)) +cols=[np.nonzero(meet[a]&meet[b])[0] for a,b in ts] +rows=[[] for _ in range(640)] +for k,cl in enumerate(cols): + for q in cl: rows[q].append(k) +for q in range(640): m.Add(sum(n[k] for k in rows[q])>=12) +m.Add(sum(int(R[k])*n[k] for k in range(len(n)))<=int(sys.argv[1]) if len(sys.argv)>1 else 42) +m.Add(sum(int(D[k])*n[k] for k in range(len(n)))<=384) +s=cp_model.CpSolver(); s.parameters.num_workers=8; s.parameters.max_time_in_seconds=float(sys.argv[2]) if len(sys.argv)>2 else 900 +t=time.time(); r=s.Solve(m); print('status',s.StatusName(r),'%.0fs'%(time.time()-t)) +if r in (cp_model.FEASIBLE,cp_model.OPTIMAL): + sol=[(k,s.Value(n[k])) for k in range(len(n)) if s.Value(n[k])] + print('solution: totals',sum(v for k,v in sol),'cost',sum(int(R[k])*v for k,v in sol)) + from collections import Counter + print(Counter((len(fl[ts[k][0]][0]),len(fl[ts[k][1]][0]),int(R[k]),int(D[k])) for k,v in sol for _ in range(v))) + pickle.dump(sol,open('cover_sol.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/facets.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/facets.py new file mode 100644 index 0000000000..56cc4c00cd --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/facets.py @@ -0,0 +1,23 @@ +"""S8 counting lemma. Input: hits_table.pkl (from hits_table.py = S5 gauss_bound40.py + dump): for all 2271^2 flat pairs +(X, Y): exact clique hits h(X,Y) (number of the 640 twelve-cliques meeting both X and Y) and a certified LOWER bound +R(X,Y) on the Theorem-C pairing rank. + +Checks: h <= 192 R (S5) and h <= 128 R + 128 (new) on every pair. +Since a total j with spans X_j, Y_j has hit_j <= h(X_j, Y_j) and beta_j >= R(X_j, Y_j), and every clique is hit >= 12 +times, 7680 <= sum_j hit_j <= sum_j min(192 beta_j, 128 beta_j + 128) => cost >= 40 and cost >= 60 - c. +Also prints the tight pairs (h = 128 R + 128) and the deficit facts used for c = 18.""" +import pickle, numpy as np +from collections import Counter +d = pickle.load(open('hits_table.pkl', 'rb')); fl = d['fl']; R = d['R']; H = d['hits'] +assert len(fl) == 2271 and len(d['C']) == 640 +assert (H <= 192 * R).all() +print('PASS facet1: hits <= 192*rank on all %d flat pairs' % H.size) +assert (H <= 128 * R + 128).all() +print('PASS facet2: hits <= 128*rank + 128 on all %d flat pairs' % H.size) +print('max hits by certified rank:', {int(r): int(H[R == r].max()) for r in range(5)}) +desc = lambda i: 'dim%d/%dlines' % (fl[i][1], len(fl[i][0])) +tight = np.argwhere(H == 128 * R + 128) +print('tight pairs (hits = 128 rank + 128):', sorted(Counter((desc(a), desc(b), int(R[a, b])) for a, b in tight).items())) +for c in range(15, 20): + lb = max(40, -(-(7680 - 128 * c) // 128)) + print('c = %d: cost >= %d%s' % (c, lb, ' (> 42: excluded)' if lb > 42 else '')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/flats.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/flats.py new file mode 100644 index 0000000000..b064c81896 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/flats.py @@ -0,0 +1,26 @@ +"""All flats (line sets of K-subspaces spanned by configuration lines) of a view, with dims. Exact.""" +import sys, pickle; sys.path.insert(0,sys.path[0]) +from lines import * +def flats(S): + v=S.v; out={} + def close(idx): + d=S.arank(idx); return frozenset(T for T in range(v) if S.arank(list(idx)+[T])==d), d + level=[ (frozenset([T]),1) for T in range(v)] + for f,d in level: out[f]=d + frontier=list(out.items()) + while frontier: + new={} + for f,d in frontier: + if d>=S.w: continue + for T in range(v): + if T in f or any(T in g and f= 40 (x_K >= 2/3). + +Argument. C ranges over the 640 twelve-line sets of pairwise K-non-orthogonal lines. On C x C the pattern forces +B[C,C] = I_12 (diagonal 1, all off-diagonal pairs non-orthogonal), and each total contributes a rank-1 F2 matrix there, +so at least 12 totals hit C on both sides (R_j & C != {} and A_j & C != {}). Summing: sum_j hit(R_j, A_j) >= 12*640. +For fixed spans X = span R_j, Y = span A_j the hit count is largest for R = lines(X), A = lines(Y) (flats), and this +script shows hit(X, Y) <= 192 * rank(X, Y) for all 2271^2 flat pairs, rank = K-rank of the pairing (Theorem C cost). +Hence sum_j beta_j >= 12*640/192 = 40. Binding pair: X = 6-line 2-flat containing l, Y = the 15-line hyperplane l^perp +(pairing rank 1, 192 hits). Mixing all clique sizes, singletons, non-orthogonal pairs and the whole set in an LP over +uniform orbit weights does not improve 40 (gauss_lp3.py in the work dir). +Ranks: exact integer pairing matrices, rank mod p = 2^31-1 (a certified lower bound on the rank over Q).""" +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from flats import flats +import networkx as nx + +def main(): + W = views(); S = W['Gauss']; J = W['_J']; v = S.v + F = flats(S); fl = sorted(F.items(), key=lambda t: (t[1], len(t[0]), sorted(t[0]))) + n = len(fl); assert n == 2271 + g = nx.Graph([(i, j) for i in range(v) for j in range(i+1, v) if S.NO[i, j]]) + C = [c for c in nx.find_cliques(g) if len(c) == 12] + assert len(C) == 640 and all(S.NO[a, b] for c in C for a, b in itertools.combinations(c, 2)) + Lo = np.zeros((n, v), np.int64) + for i, (f, d) in enumerate(fl): Lo[i, sorted(f)] = 1 + inc = np.zeros((v, len(C)), np.int64) + for k, c in enumerate(C): inc[c, k] = 1 + meet = (Lo @ inc > 0).astype(np.int64); hits = meet @ meet.T # exact integers + # exact integer bases (vectors r_T, J r_T), pairing ranks by batched elimination mod p = 2^31-1 + # (rank mod p <= rank over Q, so these are certified LOWER bounds; the claim only needs lower bounds) + def ibasis(f): + rows = [] + for T in sorted(f): + for x in (S.reps[T], J @ S.reps[T]): + if rank_q(rows + [x]) > len(rows): rows.append(x) + return np.array(rows, np.int64) + Bs = [ibasis(f) for f, d in fl] + assert all(len(b) == 2 * d for b, (f, d) in zip(Bs, fl)) + P = 2147483647 + def modrank(A): + A = A % P; N, r, c = A.shape; used = np.zeros((N, r), bool); idx = np.arange(N) + for col in range(c): + cand = (A[:, :, col] != 0) & ~used; has = cand.any(1) + if not has.any(): continue + pr = cand.argmax(1); it = idx[has]; prow = A[it, pr[has]] + inv = np.array([pow(int(x), P - 2, P) for x in prow[:, col]], np.int64) if len(it) < 64 else modinv(prow[:, col]) + prow = (prow * inv[:, None]) % P + fac = A[it, :, col].copy(); fac[np.arange(len(it)), pr[has]] = 0 + A[it] = (A[it] - (fac[:, :, None] * prow[:, None, :]) % P) % P + used[it, pr[has]] = True + return used.sum(1) + def modinv(x): + res = np.ones_like(x); base = x % P; e = P - 2 + while e: + if e & 1: res = (res * base) % P + base = (base * base) % P; e >>= 1 + return res + R = np.zeros((n, n), np.int64) + groups = {} + for i, b in enumerate(Bs): groups.setdefault(len(b), []).append(i) + for da, ia in groups.items(): + A = np.stack([Bs[i] for i in ia]) + for db, ib in groups.items(): + Bm = np.stack([Bs[i] for i in ib]) + M = np.einsum('aik,bjk->abij', A, Bm).reshape(-1, da, db) + r = modrank(M).reshape(len(ia), len(ib)); assert (r % 2 == 0).all() + R[np.ix_(ia, ib)] = r // 2 + import pickle; pickle.dump({'fl':fl,'R':R,'hits':hits,'C':C},open('hits_table.pkl','wb')) + print(f'PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all {n}x{n} flat pairs, histogram {np.bincount(R.flatten()).tolist()}') + assert (hits <= 192 * R).all() + worst = np.argwhere((hits == 192 * R) & (R > 0)) + print('PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks)') + a, b = worst[0] + print(f' binding example: |X| = {len(fl[a][0])} (dim {fl[a][1]}), |Y| = {len(fl[b][0])} (dim {fl[b][1]}), rank {R[a,b]}, hits {hits[a,b]}; {len(worst)} binding pairs') + print('RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43)') + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lines.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lines.py new file mode 100644 index 0000000000..fc4eac011d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lines.py @@ -0,0 +1,166 @@ +"""Self-contained exact construction of the E8 root lines over Q(i), Q(omega) and the Hurwitz quaternions. + +Real model: the 240 E8 roots in doubled standard coordinates (norm 8; in {0,+-4,+-8}). + * Gaussian view : J = left multiplication by i on R^8 = H^2 (coords 1,i,j,k per block); J preserves E8. + Hermitian Gram hG(x,y) = ( + i )/4 in Z[i] (diag 2). + * Eisenstein view: rho = fixed-point-free order-3 isometry of E8 (product of Coxeter elements of 4 orthogonal A2's). + Sesquilinear Gram hE(x,y) = ( - w )/4 in Z[w] (w^2+w+1=0; diag 2+w, a unit at 2). + * Hurwitz view : left multiplication by i, j, k; quaternionic lines of real dim 4. +Elements of Z[i] / Z[w] are stored as integer pairs (a, b) = a + b*i / a + b*w. +Everything is exact integer arithmetic; asserts check every structural claim used downstream. +""" +import itertools +from fractions import Fraction +import numpy as np + + +def e8_roots(): + R = [] + for i, j in itertools.combinations(range(8), 2): + for si in (2, -2): + for sj in (2, -2): + r = [0] * 8; r[i], r[j] = si, sj; R.append(r) + for s in itertools.product((1, -1), repeat=8): + if s.count(-1) % 2 == 0: + R.append(list(s)) + R = np.array(R, dtype=np.int64) + assert len(R) == 240 and set((R * R).sum(1)) == {8} + return R + + +def rank_q(rows): + """exact rank over Q of an integer matrix (fraction-free Gaussian elimination).""" + M = [list(map(int, r)) for r in rows] + if not M: + return 0 + n, m = len(M), len(M[0]); r = 0 + for c in range(m): + p = next((i for i in range(r, n) if M[i][c] != 0), None) + if p is None: + continue + M[r], M[p] = M[p], M[r] + for i in range(r + 1, n): + if M[i][c]: + f, g = M[i][c], M[r][c] + M[i] = [g * a - f * b for a, b in zip(M[i], M[r])] + r += 1 + if r == n: + break + return r + + +def left_quat(): + Li = np.array([[0, -1, 0, 0], [1, 0, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) + Lj = np.array([[0, 0, -1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, -1, 0, 0]]) + Lk = Li @ Lj + blk = lambda M: np.kron(np.eye(2, dtype=np.int64), M).astype(np.int64) + return blk(Li), blk(Lj), blk(Lk) + + +def a2_rotation(roots): + """rho, an order-3 fixed-point-free isometry preserving E8 (returned as an exact integer matrix 4*rho).""" + chosen = [] + for a in roots: + if len(chosen) == 4: + break + if any(np.dot(a, x) != 0 for p in chosen for x in p): + continue + for b in roots: + if np.dot(a, b) == -4 and all(np.dot(a, x) == 0 and np.dot(b, x) == 0 for p in chosen for x in p): + chosen.append((a, b)); break + assert len(chosen) == 4 + g = np.eye(8, dtype=object) + for a, b in chosen: + sa = np.eye(8, dtype=object) * 8 - 2 * np.outer(a, a).astype(object) + sb = np.eye(8, dtype=object) * 8 - 2 * np.outer(b, b).astype(object) + g = g.dot(sa).dot(sb) + D = 8 ** 8 // 4 + assert all(x % D == 0 for x in g.flatten()) + g = (g // D).astype(np.int64) + I = np.eye(8, dtype=np.int64) + assert (g @ g @ g == 64 * I).all() and (g @ g + 4 * g + 16 * I == 0).all() + return g + + +def lines_over(roots, ops): + """group roots into A-lines, A spanned by the integer matrices ops (identity first; ops may be scaled by 4). + Returns representatives (first root met of each line, in root order).""" + S = {tuple(r) for r in roots} + reps, seen = [], set() + for r in roots: + if tuple(r) in seen: + continue + span = [B @ r for B in ops] + assert rank_q(span) == len(ops) + reps.append(r) + members = [s for s in roots if rank_q(span + [s]) == len(ops)] + for s in members: + seen.add(tuple(s)) + return np.array(reps) + + +class View: + """name, reps (v x 8), ops (real matrices spanning A, scaled so that ops[i] @ root is a root times scale[i]).""" + def __init__(self, name, reps, ops, d): + self.name, self.reps, self.ops, self.d = name, reps, ops, d + self.v = len(reps) + NO = np.zeros((self.v, self.v), bool) + for B in ops: + NO |= (reps @ B @ reps.T) != 0 + np.fill_diagonal(NO, False) + self.NO = NO # A-non-orthogonal pairs (the decoder pattern forces B_TS = 0 there) + self.w = self.arank(range(self.v)) + + def arank(self, idx): + rows = [B @ self.reps[i] for i in idx for B in self.ops] + r = rank_q(rows) if rows else 0 + assert r % self.d == 0 + return r // self.d + + def pair_rank(self, R, A): + """A-rank of the pairing span(R) x span(A) = real rank of the real Gram block / dim A (Theorem C cost).""" + BR = [B @ self.reps[i] for i in R for B in self.ops] + BA = [B @ self.reps[i] for i in A for B in self.ops] + if not BR or not BA: + return 0 + M = [[int(x @ y) for y in BA] for x in BR] + r = rank_q(M); assert r % self.d == 0 + return r // self.d + + +def views(): + roots = e8_roots(); I8 = np.eye(8, dtype=np.int64) + Li, Lj, Lk = left_quat() + S = {tuple(r) for r in roots} + for M in (Li, Lj, Lk): + assert all(tuple(M @ r) in S for r in roots) + g = a2_rotation(roots) # 4*rho + assert all(tuple(g @ r // 4) in S and ((g @ r) % 4 == 0).all() for r in roots) + rho = g # 4*rho (exact integer matrix); ranks are scale-free + out = {} + out['Gauss'] = View('Gauss', lines_over(roots, [I8, Li]), [I8, Li], 2) + out['Eisen'] = View('Eisen', lines_over(roots, [I8, rho]), [I8, rho], 2) + out['Hurwitz'] = View('Hurwitz', lines_over(roots, [I8, Li, Lj, Lk]), [I8, Li, Lj, Lk], 4) + out['_J'], out['_rho'] = Li, rho + return out + + +def gauss_gram(V, J): + """hG(r_T, r_S) = ( + i)/4 as integer pairs (a, b).""" + A = V @ V.T; Bm = V @ J @ V.T + assert (A % 4 == 0).all() and (Bm % 4 == 0).all() + return A // 4, Bm // 4 + + +def eisen_gram(V, rho4): + """hE(r_T, r_S) = ( - w)/4 as integer pairs (a, b) = a + b w (rho4 = 4*rho).""" + A = V @ V.T; Bm = V @ rho4 @ V.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + + +if __name__ == '__main__': + W = views() + for nm in ('Gauss', 'Eisen', 'Hurwitz'): + S = W[nm] + print(nm, 'v', S.v, 'w', S.w, 'nonorth degree', sorted(set(S.NO.sum(1)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lp2.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lp2.py new file mode 100644 index 0000000000..2f24885d2a --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/lp2.py @@ -0,0 +1,38 @@ +import pickle, numpy as np, networkx as nx +from scipy.optimize import linprog +from lines import views +S=views()['Gauss']; NO=S.NO +d=pickle.load(open('hits_table.pkl','rb')); fl=d['fl']; R=d['R']; H=d['hits'] +n=len(fl); v=60 +L=np.zeros((n,v),np.int64) +for i,(f,dd) in enumerate(fl): L[i,sorted(f)]=1 +dims=np.array([dd for f,dd in fl]) +g=nx.Graph([(i,j) for i in range(v) for j in range(i+1,v) if NO[i,j]]) +cl=list(nx.find_cliques(g)) +fams={} +def famhits(sets): + inc=np.zeros((v,len(sets)),np.int64) + for k,s in enumerate(sets): inc[list(s),k]=1 + meet=(L@inc>0).astype(np.int64); return meet@meet.T +from collections import Counter +print(Counter(len(c) for c in cl)) +for s in sorted(set(len(c) for c in cl)): + sets=[c for c in cl if len(c)==s]; fams['clq%d'%s]=(famhits(sets),s,len(sets)) +fams['line']=(famhits([[T] for T in range(v)]),1,60) +for dd in (1,2,3): + idx=np.where(dims==dd)[0] + inc=((L@L[idx].T)==L.sum(1)[:,None]).astype(np.int64) + a=inc.sum(1); N=len(idx)-a[:,None]-a[None,:]+inc@inc.T + fams['cmp%d'%dd]=(N,15,len(idx)) +fams['all']=(((L.sum(1)[:,None]>0)&(L.sum(1)[None,:]>0)).astype(np.int64),15,1) +names=list(fams); print(names) +mask=R>0 +rows=np.stack([fams[k][0][mask] for k in names],1); rr=R[mask] +u=np.unique(np.concatenate([rows,rr[:,None]],1),axis=0); print('rows',len(u)) +dem=np.array([fams[k][1]*fams[k][2] for k in names],float) +# vars: y (per family, uniform), mu>=0 ; constraint rows: hits.y - mu <= r ; maximize dem.y - c*mu +for c in (17,18,19,20,22,25,30,42): + obj=np.concatenate([-dem,[c]]) + A=np.concatenate([u[:,:-1],-np.ones((len(u),1))],1) + res=linprog(obj,A_ub=A,b_ub=u[:,-1],bounds=[(0,None)]*(len(names)+1),method='highs') + y=res.x; print(c, round(-res.fun,4), {names[i]:round(y[i],5) for i in range(len(names)) if y[i]>1e-9}, 'mu',round(y[-1],4)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/tight_ilp.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/tight_ilp.py new file mode 100644 index 0000000000..6f6e19c668 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/tight_ilp.py @@ -0,0 +1,22 @@ +"""Clique covering using only tight types (dumbbells (l^perp,m^perp) l⊥m beta 2; (l^perp,l^perp)/(l^perp,K4) beta 3; (K4,K4) beta 4), +for a fixed number c of totals: misses per clique = sum over chosen totals of |C ∩ x_t| <= c - 12, cost <= 42. +argv: c""" +import pickle, sys, numpy as np +from ortools.sat.python import cp_model +from lines import views +S=views()['Gauss']; NO=S.NO +C=pickle.load(open('hits_table.pkl','rb'))['C'] +c=int(sys.argv[1]) +pairs=[(a,b) for a in range(60) for b in range(a+1,60) if not NO[a,b]] +m=cp_model.CpModel() +nD={p:m.NewIntVar(0,21,'d%d_%d'%p) for p in pairs}; nS={l:m.NewIntVar(0,14,'s%d'%l) for l in range(60)}; nF=m.NewIntVar(0,3,'f') +m.Add(sum(nD.values())+sum(nS.values())+nF==c) +m.Add(2*sum(nD.values())+3*sum(nS.values())+4*nF<=42) +mu={l:[] for l in range(60)} +for (a,b),v in nD.items(): mu[a].append(v); mu[b].append(v) +for l,v in nS.items(): mu[l].append(v) +for cl in C: m.Add(sum(v for l in cl for v in mu[l])<=c-12) +s=cp_model.CpSolver(); s.parameters.num_workers=8; s.parameters.max_time_in_seconds=300 +r=s.Solve(m); print('c',c,'status',s.StatusName(r)) +if r in (cp_model.OPTIMAL,cp_model.FEASIBLE): + print('D',[(p,s.Value(v)) for p,v in nD.items() if s.Value(v)],'S',[(l,s.Value(v)) for l,v in nS.items() if s.Value(v)],'F',s.Value(nF)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/types.py b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/types.py new file mode 100644 index 0000000000..16e2eab88f --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/scripts/types.py @@ -0,0 +1,18 @@ +"""Rank-maximal flat-pair types with deficit <= 384 (any total of a cost<=42 decoder can be replaced, for the clique-covering +relaxation, by its span pair, then by a rank-maximal enlargement: hits only grow, rank (cost) unchanged).""" +import pickle, numpy as np +d=pickle.load(open('hits_table.pkl','rb')); fl=d['fl']; R=d['R']; H=d['hits']; C=d['C'] +n=len(fl); L=[set(f) for f,dd in fl] +sup=[[j for j in range(n) if L[i] < L[j]] for i in range(n)] +D=192*R-H +ok=(R>0)&(D<=384) +enlX=np.zeros((n,n),bool); enlY=np.zeros((n,n),bool) +for i in range(n): + if sup[i]: + enlX[i]=(R[sup[i],:]==R[i][None,:]).any(0) + enlY[:,i]=(R[:,sup[i]]==R[:,i][:,None]).any(1) +mx=ok&~enlX&~enlY +ts=np.argwhere(mx); print('types',len(ts)) +from collections import Counter +print(Counter((len(fl[a][0]),len(fl[b][0]),int(R[a,b]),int(D[a,b])) for a,b in ts).most_common()) +pickle.dump({'types':ts,'R':R[mx],'D':D[mx]},open('types.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/verify.sh new file mode 100755 index 0000000000..649678a685 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s8-gaussian-all-c/verify.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# S8 verification (~6 min, 1 process; prints PASS/RESULT lines identical to expected.txt): rebuild the S5 flat-pair table (exact hits, certified ranks), then check +# the two counting facets (cost >= max(40, 60 - c)) and the c = 18 parity argument. Compare with expected.txt. +# Needs python 3.11 with numpy, scipy, sympy, networkx on PATH. +set -e +export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 +HERE=$(cd "$(dirname "$0")" && pwd); W=${WORK:-$(mktemp -d)} +cp "$HERE"/scripts/*.py "$W"/ +cd "$W" +python hits_table.py | grep -E '^(PASS|RESULT)' +python facets.py +python c18.py +python check_c21cover.py +echo "work dir: $W" From a2b34f010d20b3ed04d07a54dce0b7f8ec12925f Mon Sep 17 00:00:00 2001 From: Joel Pulikkan Date: Thu, 15 Oct 2026 12:27:57 -0400 Subject: [PATCH 17/17] Add Gaussian E8 mu-packing theorem, exclusion of c = 19, and partial c = 20 Co-Authored-By: Claude Opus 5.5 --- .../README.md | 12 ++ .../s9-gaussian-c19/README.md | 126 +++++++++++++ .../s9-gaussian-c19/expected.txt | 19 ++ .../s9-gaussian-c19/logs/cases_19_42.log | 13 ++ .../s9-gaussian-c19/logs/cases_19_42_feas.log | 7 + .../s9-gaussian-c19/logs/cases_19_42_opt2.log | 3 + .../s9-gaussian-c19/logs/cases_20_41.log | 10 ++ .../logs/cases_20_42_light.log | 5 + .../s9-gaussian-c19/logs/cp_19_20.log | 1 + .../s9-gaussian-c19/logs/mu_cpsat_7.log | 1 + .../s9-gaussian-c19/logs/mu_cpsat_8.log | 1 + .../s9-gaussian-c19/logs/mu_ilp_9_12.log | 8 + .../s9-gaussian-c19/scripts/branch1.py | 42 +++++ .../s9-gaussian-c19/scripts/branch_cp.py | 24 +++ .../s9-gaussian-c19/scripts/check_c21cover.py | 11 ++ .../s9-gaussian-c19/scripts/core.py | 47 +++++ .../s9-gaussian-c19/scripts/dom.py | 34 ++++ .../s9-gaussian-c19/scripts/f2check.py | 93 ++++++++++ .../s9-gaussian-c19/scripts/flats.py | 26 +++ .../s9-gaussian-c19/scripts/hits_table.py | 78 ++++++++ .../s9-gaussian-c19/scripts/lift_prep.py | 27 +++ .../s9-gaussian-c19/scripts/lines.py | 166 ++++++++++++++++++ .../s9-gaussian-c19/scripts/mu_cpsat.py | 10 ++ .../s9-gaussian-c19/scripts/mu_ilp.py | 10 ++ .../s9-gaussian-c19/scripts/orbits.py | 53 ++++++ .../s9-gaussian-c19/scripts/pair_ilp.py | 55 ++++++ .../s9-gaussian-c19/scripts/pairkinds.py | 23 +++ .../s9-gaussian-c19/scripts/tight_diag.py | 30 ++++ .../s9-gaussian-c19/scripts/try_cand.py | 6 + .../s9-gaussian-c19/scripts/types_e.py | 27 +++ .../s9-gaussian-c19/scripts/vsingle.py | 13 ++ .../s9-gaussian-c19/verify.sh | 41 +++++ 32 files changed, 1022 insertions(+) create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/README.md create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/expected.txt create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_feas.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_opt2.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_41.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_42_light.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cp_19_20.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_7.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_8.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_ilp_9_12.log create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch1.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch_cp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/check_c21cover.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/core.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/dom.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/f2check.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/flats.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/hits_table.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lift_prep.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lines.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_cpsat.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_ilp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/orbits.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pair_ilp.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pairkinds.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/tight_diag.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/try_cand.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/types_e.py create mode 100644 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/vsingle.py create mode 100755 research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/verify.sh diff --git a/research/e8-decoder-floors-and-cold14-notes/README.md b/research/e8-decoder-floors-and-cold14-notes/README.md index 4b9bda9486..36177f741f 100644 --- a/research/e8-decoder-floors-and-cold14-notes/README.md +++ b/research/e8-decoder-floors-and-cold14-notes/README.md @@ -215,3 +215,15 @@ Together with §13, a Gaussian E8 decoder that beats real E8 (cost ≤ 42) needs - CP-SAT covering ILPs for c = 19 and c = 20 returned UNKNOWN. **Status of the Gaussian question.** A Gaussian E8 decoder that beats real E8 (cost ≤ 42) needs **19 ≤ c ≤ 42** totals of nonzero cost. At c = 19 only cost exactly 42 is open, and at c = 20 only 41–42. + +## 16. Gaussian E8: c = 19 excluded; c = 20 partial (`s9-gaussian-c19/`, 2026-10-15) +- **μ-packing theorem (checked: HiGHS exact-optimal, and CP-SAT for capacities 7 and 8).** Tight totals miss exactly the cliques M·x_t (§15). Over μ ∈ ℤ≥0^60 with Mμ ≤ cap, the maximum |μ| for cap = 6..12 is **28, 32, 36, 42, 48, 52, 60**; the LP allows 5·cap. Consequences: + - an all-tight decoder costs ≥ 44 for every c ≤ 20; + - at c = 21 the requirement equals the maximum (42), which is exactly S8's 21-dumbbell cover. +- **c = 19 is excluded (checked, HiGHS MIP dual bounds; floating point, not exact certificates).** Non-tight totals are handled by a case split over the 27 rank-maximal type orbits with deficit ≤ 256. All 20 lifted cases are excluded: each best value is ≤ 32 with a dual bound ≤ 33, against the 34 needed. **Hence no 19-total decoder costs ≤ 42.** +- **c = 20 is partial.** + - At cost ≤ 41: 6 cases are excluded and cases 6 and 9 timed out. + - At cost ≤ 42: cases −1 and 21 are excluded; cases 19, 22 and 23 timed out, and the heavy cases (20k–54k columns) were not run. +- **c ≥ 21 (argued, partly checked).** Counting is exhausted. However, the c = 21 covers that counting allows are **F2-infeasible** (0.1 s SAT). The minimal core is a non-orthogonal pair {a, b}: one total covers both diagonal entries while another gives only an off-diagonal entry. S9 classified the exact 2×2 support lemma into 17 minimal feasible patterns; `pair_ilp.py` (all-dumbbell plus the lemma) is written but not run. + +**Status of the Gaussian question:** a decoder that beats real E8 needs **20 ≤ c ≤ 42**. c = 20 is partly excluded, and c ≥ 21 has a candidate F2 route. diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/README.md b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/README.md new file mode 100644 index 0000000000..eb74ceb4eb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/README.md @@ -0,0 +1,126 @@ +# S9 — Gaussian E8: integral clique packing closes c = 19 (and part of c = 20); c ≥ 21 needs F2 structure + +Context: #620 §13–§15 (S6, S7, S8). Setting: the Gaussian view of E8, 60 Q(i)-lines in K⁴, F2 decoder pattern +(B_TT = 1, B_TS = 0 on K-non-orthogonal pairs), minrank 15, line group of order 11520, Theorem-C cost Σβ_j. +Beating real E8 needs Σβ ≤ 42. Before S9: cost ≥ max(40, 60 − c); c ≤ 18 excluded; c = 19 open only at cost 42; +c = 20 open at 41–42; c ≥ 21 open (and S8 found an integral clique cover at c = 21, so counting alone cannot close c ≥ 21). + +Labels: **checked** = recomputed by `verify.sh` (exact integers / certified ranks for the data; HiGHS MIP and CP-SAT for +the integer programs, see "Trust" below); **argued** = proof in this README. + +## Status (PARTIAL — written 2026-10-15 01:40 EDT, solver jobs still running) +- **Proven (checked):** the μ-packing lemma (§1) and its corollary: a decoder whose totals are all tight costs ≥ 44 at c = 18–20 and ≥ 42 at c = 21. +- **c = 19, cost 42: 17 of 20 cases excluded. Three cases are still running:** orbits 17, 18 and 20. Their logs are (HiGHS, optimisation form) and (CP-SAT on case 20). The 10 cases that went to the time limit in optimisation form all had dual bounds ≤ 33 < 34, and analogous cases gave dual bound 33 within 1200 s. **c = 19 is NOT yet closed.** +- **c = 20:** cost 41 has cases −1 and 2 excluded; cases 4, 5 and 6 are running (), and cases 9 and 11–23 were never run. Cost 42 has cases −1 and 21 excluded; case 22 is running (, sequential −1, 21, 22, 23, 19, 20). The heavy cases at cost 42 (orbits 1, 2, 4–6 and 9–18, 20k–54k columns) were never run; they look out of reach without a better formulation. +- **c ≥ 21:** counting is exhausted (proven). The F2 pattern is what fails on the counting covers (2×2 support lemma, §3). This is not a proof for any c ≥ 21. + +## 1. The μ-packing lemma (checked: `mu_ilp.py` HiGHS optimal, `mu_cpsat.py` CP-SAT cross-check) +M = 640 × 60 clique/line incidence (the 640 twelve-cliques of pairwise non-orthogonal lines; each line lies in 128). + + f(k) := max { |μ| : μ ∈ Z≥0⁶⁰, Mμ ≤ k·1 } = 28, 32, 36, 42, 48, 52, 60 for k = 6, …, 12, + +while the LP value is 5k (μ = k/12·1). CP-SAT independently: k = 7, |μ| ≥ 33 INFEASIBLE; k = 8, |μ| ≥ 37 INFEASIBLE. +The integrality gap (3 at k = 7, 4 at k = 8) is what S8's linear counting could not see. + +**Corollary (all-tight decoders; argued).** S8: a tight total t misses exactly the cliques M·x_t with x_t pairwise +orthogonal lines and gain g_t = 4 − β_t = |x_t|. N_C ≥ 12 gives Σ_j M x_j ≤ (c − 12)·1, so Σ g ≤ f(c − 12): +cost ≥ 4c − f(c − 12) = 44 (c = 18), 44 (c = 19), 44 (c = 20), 42 (c = 21). The c = 21 value is attained by +S8's 21-dumbbell cover, so counting is exactly exhausted there. + +## 2. Lifted packing relaxation for all decoders (argued; data checked) +For a decoder with c totals and cost ≤ cm: +1. **WLOG β_j ≥ 1** (S8: a cost-0 total can be dropped; c − 1 is already excluded at the same cost). +2. **Types.** Replace total j by a rank-maximal enlargement t of its span pair (X_j, Y_j): certified rank R_t ≤ β_j, + its miss set m_t (cliques not met on both sides) ⊆ the total's miss set, and e_t = 128R_t + 128 − hits_t ≤ e_j. + All rank-maximal types with 1 ≤ R ≤ 3 have e ≤ 132; deduplicated by (R, miss set) there are **58,340**, in **27 orbits** + under the line group (`types_e.py`, `dom.py`). +3. **Packing.** N_C ≥ 12 ⇔ Σ_j m_j ≤ (c − 12)·1. Cost ≤ cm ⇔ Σ_j g_j ≥ 4c − cm with g = 4 − β ≤ g_t. + Identity (E) of S8: Σ_j e_j + E = 128(cm + c) − 7680 =: slack, so Σ_j e_t(j) ≤ slack. +4. **Lifting.** Z_t = lines all of whose 128 cliques are missed by t; x_t = a maximum pairwise-orthogonal subset (α_t = |x_t|). + A clique contains at most one line of x_t, so M x_t ≤ m_t. If α_t ≥ g_t ("non-deficient") replace t by x_t (fewer + misses, gain ≥ g_t): these totals only enter through μ = Σ x_t ∈ Z≥0⁶⁰. "Deficient" types (δ_t = g_t − α_t > 0) keep their + exact miss set. Every deficient type has e_t ≥ 16 (`lift_prep.py`; table below). +5. **Case split (symmetry).** No deficient total: Σ g ≤ f(c − 12). Otherwise apply a group element so that the + deficient total of **largest e** is its orbit representative t₁; every other deficient total then has + e ≤ min(e₁, slack − e₁). Each case is the ILP M μ + m_{t₁} + Σ n_t m_t ≤ (c−12)·1, Σ n_t e_t ≤ slack − e₁, + |μ| + g_{t₁} + Σ n_t g_t ≥ 4c − cm (μ, n integer) — `branch1.py` (HiGHS; "feas" adds the last row as a + constraint, "opt" maximises the left side and reports the dual bound). + +**Theorem 1 (c = 19), partial.** Cases −1, 2, 4, 5, 6, 9–16, 19, 21, 22 and 23 are excluded: HiGHS proved them infeasible, or the optimisation form gave a dual bound ≤ 33 < 34. Cases 17, 18 and 20 are still running. If they are excluded, then no 19-total decoder costs ≤ 42 (logs: ). + +**Theorem 2 (c = 20), partial.** Cost ≤ 41: cases −1 and 2 are excluded. Cost ≤ 42: cases −1 and 21 are excluded. Everything else is open or running (see Status). + +Single-deficient diagnostics (`vsingle.py`, c = 19): with one deficient total fixed to each orbit representative, +max(|μ| + g) ≤ 33 < 34 for every orbit, i.e. deficient totals always cost more clique capacity than they gain. + +| orbit | R | e | α | δ | size | | orbit | R | e | α | δ | size | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 0 | 1 | 128 | 3 | 0 | 60 | 14 | 2 | 84 | 0 | 2 | 5760 | +| 1 | 1 | 132 | 2 | 1 | 2880 | 15 | 2 | 70 | 0 | 2 | 5760 | +| 2 | 1 | 100 | 2 | 1 | 1440 | 16 | 2 | 70 | 0 | 2 | 5760 | +| 3 | 2 | 128 | 2 | 0 | 360 | 17 | 2 | 76 | 1 | 1 | 2880 | +| 4 | 1 | 84 | 1 | 2 | 1920 | 18 | 3 | 76 | 0 | 1 | 960 | +| 5 | 1 | 76 | 2 | 1 | 960 | 19 | 2 | 24 | 0 | 2 | 720 | +| 6 | 2 | 76 | 1 | 1 | 320 | 20 | 2 | 34 | 0 | 2 | 5760 | +| 7 | 1 | 64 | 3 | 0 | 180 | 21 | 2 | 16 | 0 | 2 | 1440 | +| 8 | 2 | 64 | 2 | 0 | 30 | 22 | 2 | 20 | 1 | 1 | 2880 | +| 9 | 2 | 108 | 0 | 2 | 2880 | 23 | 3 | 20 | 0 | 1 | 480 | +| 10 | 2 | 114 | 0 | 2 | 5760 | 24 | 2 | 0 | 2 | 0 | 90 | +| 11 | 2 | 112 | 0 | 2 | 2880 | 25 | 2 | 0 | 2 | 0 | 360 | +| 12 | 2 | 112 | 0 | 2 | 2880 | 26 | 3 | 0 | 1 | 0 | 60 | +| 13 | 2 | 112 | 0 | 2 | 2880 | | | | | | | + +## 3. c ≥ 21: counting is exhausted; the F2 pattern is what fails (checked: `f2check.py`, `try_cand.py`, `core.py`, `tight_diag.py`) +- For c ≥ 21 the lifted relaxation is feasible already with μ alone (f(9) = 42 = 4·21 − 42, f(10) = 48 ≥ 46, …): no counting + argument of this kind can exclude c ≥ 21. +- `f2check.py` solves the actual F2 problem with every total confined to given line sets (R_j ⊆ X_j, A_j ⊆ Y_j, optional + alternatives such as both orientations of a dumbbell). Calibration: the identity decoder (60 singleton totals) is SAT and + re-checks as a valid decoder. +- **The counting covers are F2-infeasible.** S8's 21-dumbbell cover (fixed orientation: UNSAT immediately — some line has + no total covering its diagonal) and the cover found by `tight_diag.py` (21 dumbbells satisfying clique counting AND + diagonal coverage; free orientation per dumbbell): UNSAT in 0.1 s. +- **Minimal reason** (`core.py`, UNSAT core over per-line assumptions + deletion): a single non-orthogonal pair {23, 26}. + On it the decoder needs B[{a,b},{a,b}] = I₂. Exactly one total covers both diagonals, and the only other total hitting the pair + can give just one off-diagonal entry. Counting is satisfied (2 totals hit the pair), but I₂ − e_a e_bᵀ has rank 2. +- **Exact 2×2 support lemma** (`pairkinds.py`): classify each total on a non-orthogonal pair {a,b} by its kind + (X∩{a,b}, Y∩{a,b}). I₂ is reachable iff the multiset of kinds contains one of **17 minimal patterns**: + 16 two-element patterns (two totals that can supply the two diagonal entries) and the triple + {(ab,ab), (a→b), (b→a)}. This is a local F2 constraint that counting (N_K ≥ |K|) does not capture. + +## Remaining open cases +- c = 19 (cost 42): cases 17, 18 and 20 (running). +- c = 20, cost 41: cases 4, 5 and 6 (running), and 9, 11–23 (not run). +- c = 20, cost 42: every case except −1 and 21. Case 22 is running. +- 21 ≤ c ≤ 42: everything. Suggested next step: a cut loop that adds the exact 2×2 lemma constraints (, written but not run) plus F2 cores to the type ILP. + +## Trust and validation +- Inputs are exact (lines, flats, cliques, hits) with certified rank lower bounds (S5/S8 `hits_table.py`). The relaxation + only uses R as a lower bound on β and hits as upper bounds, so every step is in the safe direction. +- The integer programs are solved in floating point by HiGHS (scipy 1.x `milp`) and have no proof certificates. The μ optima + were cross-checked with CP-SAT. The case ILPs have not been cross-checked independently; one CP-SAT run () is in progress. +- Calibrations (must be feasible, are feasible): c = 21, cost ≤ 42, case "no deficient" (μ = 42); c = 19, cost ≤ 44, + same case. The F2 checker accepts the identity decoder. +- Not used: level tables (S7), the S6 sweep. + +## Files (`scripts/`) +| file | role | +|---|---| +| `lines.py`, `flats.py`, `hits_table.py`, `orbits.py` | S5/S6/S8 exact data: lines, 2271 flats, 640 cliques, hits, certified ranks, the order-11520 group | +| `mu_ilp.py`, `mu_cpsat.py` | μ-packing optima f(k) (HiGHS) and CP-SAT cross-check | +| `types_e.py`, `dom.py`, `lift_prep.py` | type catalog (58,340), orbits (27) and dominance, lifting data (x_t, δ_t) | +| `branch1.py` | one case ILP of the lifted relaxation (`feas` / optimisation form) | +| `vsingle.py` | single-deficient diagnostics | +| `f2check.py`, `try_cand.py`, `core.py`, `tight_diag.py`, `pairkinds.py`, `check_c21cover.py` | F2 confinement SAT, UNSAT cores, tight covering ILP with diagonal coverage, 2×2 lemma, S8's c = 21 cover | + +```sh +PATH=$SP/venv/bin:$PATH sh verify.sh # data + μ lemma + calibrations + F2 checks (~15 min) +FULL=1 PAR=6 PATH=$SP/venv/bin:$PATH sh verify.sh # + every case ILP of Theorems 1-2 (hours) +``` + +## Addendum (2026-10-15, after the agent stopped): final case results +- **c = 19: all 20 lifted cases EXCLUDED.** The last three (cases 17, 18 and 20, 11,280 columns each) finished in the optimisation form with a HiGHS MIP dual bound of 33 < 34 needed, with best incumbents 31, 31 and 30, at about 2,400 s each (`logs/cases_19_42_opt2.log`). CP-SAT on case 20 returned UNKNOWN (`logs/cp_19_20.log`); the HiGHS bound is the one used. **Hence no 19-total Gaussian decoder costs ≤ 42.** HiGHS dual bounds are floating-point MIP bounds; they are not exact certificates. +- **c = 20, partial.** + - At cost ≤ 41: cases −1, 2, 10, 11, 12 and 13 are EXCLUDED. Cases 6 and 9 hit the time limit (OPEN). + - At cost ≤ 42: cases −1 and 21 are EXCLUDED. Cases 19, 22 and 23 hit the time limit (OPEN). + - The remaining heavy cases were not run. See `logs/cases_20_41.log` and `logs/cases_20_42_light.log`. +- `verify.sh` has not been rerun from scratch. diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/expected.txt b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/expected.txt new file mode 100644 index 0000000000..cd207039bf --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/expected.txt @@ -0,0 +1,19 @@ +# completed parts only (non-FULL verify.sh; values recorded from the work-dir runs, not yet from a fresh verify run) +mu-packing: cap 6 max |mu| = 28 (LP 30) +mu-packing: cap 7 max |mu| = 32 (LP 35) +mu-packing: cap 8 max |mu| = 36 (LP 40) +mu-packing: cap 9 max |mu| = 42 (LP 45) +mu-packing: cap 10 max |mu| = 48 (LP 50) +mu-packing: cap 11 max |mu| = 52 (LP 55) +mu-packing: cap 12 max |mu| = 60 (LP 60) +cap 7 |mu|>= 33 INFEASIBLE +cap 8 |mu|>= 37 INFEASIBLE +types: 58340 (R,miss)-distinct rank-maximal types, 27 orbits, 23 non-dominated +calibration c=21 cost<=42 case -1: FEASIBLE ; c=19 cost<=44 case -1: FEASIBLE +identity True True +S8 dumbbell cover, fixed orientation: False +c = 21 dumbbell cover: cost 42 min clique hits 12 max 16 PASS +free orientation: False +minimal infeasible O: 2 [23, 26] +17 minimal feasible kind-multisets: +# case ILPs: see logs/cases_*.log (c=19: 17/20 EXCLUDED; c=20/41: -1,2 EXCLUDED; c=20/42: -1,21 EXCLUDED) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42.log new file mode 100644 index 0000000000..77fc0249f3 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42.log @@ -0,0 +1,13 @@ +c 19 cost<= 42 case -1 e1 None cols 0 status 0 best 31.999999999999616 dual_bound 31.999999999999616 need 34 EXCLUDED 5s +c 19 cost<= 42 case 10 e1 114 cols 0 status 0 best 30.0 dual_bound 30.0 need 34 EXCLUDED 4s +c 19 cost<= 42 case 2 e1 100 cols 5520 status 0 best 31.999999999934616 dual_bound 31.999999999934616 need 34 EXCLUDED 88s +c 19 cost<= 42 case 11 e1 112 cols 1440 status 0 best 31.000000000000007 dual_bound 31.000000000000007 need 34 EXCLUDED 84s +c 19 cost<= 42 case 9 e1 108 cols 4800 status 0 best 31.000000000000014 dual_bound 31.000000000000014 need 34 EXCLUDED 312s +c 19 cost<= 42 case 13 e1 112 cols 1440 status 0 best 29.999999999999993 dual_bound 29.999999999999993 need 34 EXCLUDED 264s +c 19 cost<= 42 case 12 e1 112 cols 1440 status 0 best 31.0 dual_bound 31.0 need 34 EXCLUDED 273s +c 19 cost<= 42 case 4 e1 84 cols 11280 status 1 best 30.999999999999915 dual_bound 33.0 need 34 EXCLUDED 1201s +c 19 cost<= 42 case 5 e1 76 cols 11280 status 1 best 30.99999999999773 dual_bound 33.0 need 34 EXCLUDED 1202s +c 19 cost<= 42 case 6 e1 76 cols 11280 status 1 best 30.999999999998764 dual_bound 33.0 need 34 EXCLUDED 1202s +c 19 cost<= 42 case 14 e1 84 cols 11280 status 1 best 31.00000000000057 dual_bound 33.0 need 34 EXCLUDED 1201s +c 19 cost<= 42 case 15 e1 70 cols 11280 status 1 best 31.000000000010186 dual_bound 32.0 need 34 EXCLUDED 1201s +c 19 cost<= 42 case 16 e1 70 cols 11280 status 1 best 30.00000000000007 dual_bound 33.0 need 34 EXCLUDED 1201s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_feas.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_feas.log new file mode 100644 index 0000000000..8a17423ce5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_feas.log @@ -0,0 +1,7 @@ +c 19 cost<= 42 case 21 e1 16 cols 1440 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 26s +c 19 cost<= 42 case 22 e1 20 cols 4800 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 266s +c 19 cost<= 42 case 23 e1 20 cols 4800 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 267s +c 19 cost<= 42 case 19 e1 24 cols 5520 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 365s +c 19 cost<= 42 case 20 e1 34 cols 11280 feasibility form: status 1 Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) OPEN 2109s +c 19 cost<= 42 case 17 e1 76 cols 11280 feasibility form: status 1 Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) OPEN 2203s +c 19 cost<= 42 case 18 e1 76 cols 11280 feasibility form: status 1 Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) OPEN 2212s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_opt2.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_opt2.log new file mode 100644 index 0000000000..14d2cac35c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_19_42_opt2.log @@ -0,0 +1,3 @@ +c 19 cost<= 42 case 20 e1 34 cols 11280 status 1 best 30.000000000000192 dual_bound 33.0 need 34 EXCLUDED 2401s +c 19 cost<= 42 case 18 e1 76 cols 11280 status 1 best 31.00000000000765 dual_bound 33.0 need 34 EXCLUDED 2402s +c 19 cost<= 42 case 17 e1 76 cols 11280 status 1 best 31.000000000001165 dual_bound 33.0 need 34 EXCLUDED 2402s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_41.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_41.log new file mode 100644 index 0000000000..04102afaa5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_41.log @@ -0,0 +1,10 @@ +c 20 cost<= 41 case -1 e1 None cols 0 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 1s +c 20 cost<= 41 case 2 e1 100 cols 5520 feasibility form: status 2 The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) EXCLUDED 679s +c 20 cost<= 41 case 4 e1 84 cols 11280 feasibility form: status 1 Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) OPEN 10438s +c 20 cost<= 41 case 5 e1 76 cols 11280 feasibility form: status 1 Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) OPEN 10442s +c=20 cost<=41 case 10 e1 114 cols 0 -> EXCLUDED (infeasible) 0s +c=20 cost<=41 case 11 e1 112 cols 1440 -> EXCLUDED (infeasible) 316s +c=20 cost<=41 case 12 e1 112 cols 1440 -> EXCLUDED (infeasible) 683s +c=20 cost<=41 case 13 e1 112 cols 1440 -> EXCLUDED (infeasible) 2932s +c=20 cost<=41 case 9 e1 108 cols 4800 -> OPEN: Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) 12882s +c=20 cost<=41 case 6 e1 76 cols 11280 -> OPEN: Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) 31851s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_42_light.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_42_light.log new file mode 100644 index 0000000000..5c6a33307c --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cases_20_42_light.log @@ -0,0 +1,5 @@ +c=20 cost<=42 case -1 e1 None cols 0 -> EXCLUDED (infeasible) 3s +c=20 cost<=42 case 21 e1 16 cols 1440 -> EXCLUDED (infeasible) 683s +c=20 cost<=42 case 22 e1 20 cols 4800 -> OPEN: Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) 3814s +c=20 cost<=42 case 23 e1 20 cols 4800 -> OPEN: Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) 5047s +c=20 cost<=42 case 19 e1 24 cols 5520 -> OPEN: Time limit reached. (HiGHS Status 13: model_status is Time limit reached; primal_status is None) 13885s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cp_19_20.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cp_19_20.log new file mode 100644 index 0000000000..e0ccfa5edb --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/cp_19_20.log @@ -0,0 +1 @@ +CP-SAT c=19 cost<=42 case 20 e1 34 cols 11280 -> UNKNOWN 2400s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_7.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_7.log new file mode 100644 index 0000000000..b1c95628be --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_7.log @@ -0,0 +1 @@ +cap 7 |mu|>= 33 INFEASIBLE 3s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_8.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_8.log new file mode 100644 index 0000000000..40e7f74f07 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_cpsat_8.log @@ -0,0 +1 @@ +cap 8 |mu|>= 37 INFEASIBLE 170s diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_ilp_9_12.log b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_ilp_9_12.log new file mode 100644 index 0000000000..36d96aff25 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/logs/mu_ilp_9_12.log @@ -0,0 +1,8 @@ +12 0 Optimization terminated successfully. (HiGHS Status 7: Optimal) 60.000000000000014 bound -60.000000000000014 0s +[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +10 0 Optimization terminated successfully. (HiGHS Status 7: Optimal) 48.00000000000016 bound -48.00000000000016 3s +[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0] +9 0 Optimization terminated successfully. (HiGHS Status 7: Optimal) 41.99999999999998 bound -41.99999999999998 5s +[0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1] +11 0 Optimization terminated successfully. (HiGHS Status 7: Optimal) 52.00000000000023 bound -52.00000000000023 6s +[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch1.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch1.py new file mode 100644 index 0000000000..2cc25369d5 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch1.py @@ -0,0 +1,42 @@ +"""Exact case split for c totals, cost <= cm (relaxation 'lifted packing'). Non-tight totals with delta>0 ('deficient'); +case 0: none -> pure mu ILP. Otherwise WLOG (line group) the deficient total of largest e is the orbit representative t1; +the other deficient totals have e <= min(e1, slack - e1). Each case: max |mu| + sum delta over +M mu + y_t1 + sum n_t y_t <= cap, sum n_t e_t <= slack - e1, mu in Z^60, n in Z. Exclusion iff every case < 4c - cm. +argv: c cm case(orbit index or -1) timelimit""" +import pickle, sys, numpy as np, time +from scipy.optimize import milp, LinearConstraint, Bounds +from scipy.sparse import csr_matrix, hstack, vstack +c=int(sys.argv[1]); cm=int(sys.argv[2]); case=int(sys.argv[3]); tl=float(sys.argv[4]) +cap=c-12; slack=128*(cm+c)-7680; need=4*c-cm +C=pickle.load(open('hits_table.pkl','rb'))['C'] +M=np.zeros((640,60)) +for k,cl in enumerate(C): M[k,cl]=1 +T=pickle.load(open('types_e256.pkl','rb')); L=pickle.load(open('lift.pkl','rb')); dom=pickle.load(open('dom.pkl','rb')); orb=dom['orb'] +fixed=np.zeros(640); base=0; e1=None +if case>=0: + r=dom['reps'][case]; a,b,R,e1,m=T[r]; x,d=L[r]; assert d>0 and e1<=slack + fixed=m-M[:,x].sum(1); base=d+len(x) # t1's own x lines are not in mu: count alpha directly + cols=[i for i in range(len(T)) if L[i][1]>0 and T[i][3]<=min(e1,slack-e1)] +else: cols=[] +n=len(cols) +Y=csr_matrix(np.array([T[i][4]-M[:,L[i][0]].sum(1) for i in cols]).T) if n else csr_matrix((640,0)) +dl=np.array([L[i][1]+len(L[i][0]) for i in cols],float) # gain g_t = alpha + delta (x lines counted here, not in mu) +el=np.array([T[i][3] for i in cols],float) +# note: for deficient types we count their x lines inside their own column (load M x + y = m_t): exact miss vector +Ycol=csr_matrix(np.array([T[i][4] for i in cols],float).T) if n else csr_matrix((640,0)) +obj=-np.concatenate([np.ones(60),dl]) +FEAS=len(sys.argv)>5 and sys.argv[5]=='feas' # feasibility form: add row |mu| + sum g >= need - base +A=vstack([hstack([csr_matrix(M),Ycol]),csr_matrix(np.concatenate([np.zeros(60),el])[None,:]),csr_matrix(obj[None,:])]) +ub=np.concatenate([cap-T[dom['reps'][case]][4] if case>=0 else np.full(640,cap),[slack-(e1 or 0)],[-(need-base) if FEAS else np.inf]]) +t0=time.time() +res=milp(obj,constraints=[LinearConstraint(A,-np.inf,ub)],integrality=np.ones(60+n),bounds=Bounds(0,cap),options={'time_limit':tl}) +val=None if res.x is None else -res.fun+base +bound=-res.mip_dual_bound+base if getattr(res,'mip_dual_bound',None) is not None else None +if FEAS: + print('c=%d cost<=%d case %d e1 %s cols %d -> %s'%(c,cm,case,e1,n,'EXCLUDED (infeasible)' if res.status==2 else ('FEASIBLE' if res.x is not None else 'OPEN: '+res.message)),'%.0fs'%(time.time()-t0),flush=True) + if res.x is not None: xx=np.round(res.x).astype(int); print(' mu',xx[:60].tolist(),'deficient',[(cols[k],int(orb[cols[k]]),xx[60+k]) for k in range(n) if xx[60+k]]) + sys.exit() +ok=(res.status==0 and val %s (opt: best %s, dual bound %s, need %d)'%(c,cm,case,e1,n,'EXCLUDED' if ok else 'OPEN',None if val is None else round(val),None if bound is None else round(bound,3),need),'%.0fs'%(time.time()-t0),flush=True) +if res.x is not None and val>=need: + xx=np.round(res.x).astype(int); print(' mu',xx[:60].tolist(),'deficient',[(cols[k],int(orb[cols[k]]),xx[60+k]) for k in range(n) if xx[60+k]]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch_cp.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch_cp.py new file mode 100644 index 0000000000..964c2722f4 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/branch_cp.py @@ -0,0 +1,24 @@ +"""CP-SAT version of branch1.py feasibility form (same case split, same columns). argv: c cm case timelimit workers""" +import pickle, sys, numpy as np, time +from ortools.sat.python import cp_model +c=int(sys.argv[1]); cm=int(sys.argv[2]); case=int(sys.argv[3]); tl=float(sys.argv[4]); wk=int(sys.argv[5]) +cap=c-12; slack=128*(cm+c)-7680; need=4*c-cm +C=pickle.load(open('hits_table.pkl','rb'))['C'] +T=pickle.load(open('types_e256.pkl','rb')); L=pickle.load(open('lift.pkl','rb')); dom=pickle.load(open('dom.pkl','rb')) +base=0; e1=0; load0=np.zeros(640,int) +if case>=0: + r=dom['reps'][case]; a,b,R,e1,m=T[r]; x,d=L[r]; assert d>0 and e1<=slack + base=4-R; load0=m.astype(int) + cols=[i for i in range(len(T)) if L[i][1]>0 and T[i][3]<=min(e1,slack-e1)] +else: cols=[] +md=cp_model.CpModel() +mu=[md.NewIntVar(0,cap,'m%d'%t) for t in range(60)] +nv=[md.NewIntVar(0,min(cap,(slack-e1)//T[i][3]),'n%d'%i) for i in cols] +for q,cl in enumerate(C): + terms=[mu[t] for t in cl]+[nv[k] for k,i in enumerate(cols) if T[i][4][q]] + md.Add(sum(terms)<=cap-int(load0[q])) +md.Add(sum(T[i][3]*nv[k] for k,i in enumerate(cols))<=slack-e1) +md.Add(sum(mu)+sum((4-T[i][2])*nv[k] for k,i in enumerate(cols))>=need-base) +s=cp_model.CpSolver(); s.parameters.num_workers=wk; s.parameters.max_time_in_seconds=tl +t=time.time(); res=s.Solve(md) +print('CP-SAT c=%d cost<=%d case %d e1 %s cols %d -> %s'%(c,cm,case,e1,len(cols),{'INFEASIBLE':'EXCLUDED (infeasible)'}.get(s.StatusName(res),s.StatusName(res))),'%.0fs'%(time.time()-t),flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/check_c21cover.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/check_c21cover.py new file mode 100644 index 0000000000..9237a61705 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/check_c21cover.py @@ -0,0 +1,11 @@ +"""Exact check: the 21 dumbbell totals (l^perp, m^perp), l ⊥ m (beta = 2 each, cost 42) found by tight_ilp.py 21 hit every +12-clique >= 12 times, i.e. the clique-covering relaxation is integrally feasible at c = 21, cost 42.""" +import pickle, numpy as np +from lines import views +S=views()['Gauss']; NO=S.NO +C=pickle.load(open('hits_table.pkl','rb'))['C'] +D=[(0,13),(4,22),(5,50),(6,19),(6,28),(7,38),(8,19),(11,56),(12,17),(18,37),(18,58),(20,30),(21,33),(25,26),(35,55),(36,45),(37,47),(41,44),(42,51),(44,51),(48,53)] +perp=lambda l: {T for T in range(60) if T!=l and not NO[l,T]} +assert all(not NO[a,b] and a!=b for a,b in D) +N=[sum(1 for a,b in D if perp(a)&set(c) and perp(b)&set(c)) for c in C] +print('c = 21 dumbbell cover: cost', 2*len(D), 'min clique hits', min(N), 'max', max(N), 'PASS' if min(N)>=12 else 'FAIL') diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/core.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/core.py new file mode 100644 index 0000000000..ab92a4032e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/core.py @@ -0,0 +1,47 @@ +"""Minimal line set O on which the candidate's confined decoder is infeasible (UNSAT core over per-line assumptions + deletion).""" +from pysat.formula import IDPool +from pysat.solvers import Solver +from f2check import NO +def build(opts): + pool=IDPool(); cl=[]; W={}; U={} + act=[pool.id(('a',T)) for T in range(60)] + for j,alts in enumerate(opts): + sels=[pool.id(('s',j,k)) for k in range(len(alts))]; cl.append(sels) + for a in range(len(sels)): + for b in range(a+1,len(sels)): cl.append([-sels[a],-sels[b]]) + for T in set().union(*[set(X) for X,Y in alts]): + W[T,j]=pool.id(('w',T,j)); cl.append([-W[T,j]]+[sels[k] for k,(X,Y) in enumerate(alts) if T in X]) + for T in set().union(*[set(Y) for X,Y in alts]): + U[T,j]=pool.id(('u',T,j)); cl.append([-U[T,j]]+[sels[k] for k,(X,Y) in enumerate(alts) if T in Y]) + def AND(a,b): + z=pool.id(('and',a,b)); cl.extend([[-z,a],[-z,b],[z,-a,-b]]); return z + def XOR(a,b): + z=pool.id(('xor',a,b)); cl.extend([[-z,a,b],[-z,-a,-b],[z,-a,b],[z,a,-b]]); return z + for T in range(60): + for S_ in range(60): + if not (T==S_ or NO[T,S_]): continue + terms=[AND(W[T,j],U[S_,j]) for j in range(len(opts)) if (T,j) in W and (S_,j) in U] + g=[-act[T]]+([-act[S_]] if S_!=T else []) + if not terms: + if T==S_: cl.append(g) + continue + acc=terms[0] + for t in terms[1:]: acc=XOR(acc,t) + cl.append(g+([acc] if T==S_ else [-acc])) + return cl,act +def min_core(opts,solver='cadical153'): + cl,act=build(opts) + with Solver(name=solver,bootstrap_with=cl) as s: + if s.solve(assumptions=act): return None + core=sorted(act.index(x) for x in s.get_core()) + cur=list(core) + for T in list(core): + trial=[x for x in cur if x!=T] + if not s.solve(assumptions=[act[x] for x in trial]): cur=trial + return cur +if __name__=='__main__': + import sys + perp=lambda l:[T for T in range(60) if T!=l and not NO[l,T]] + D=eval(sys.argv[1]) + O=min_core([[(perp(a),perp(b)),(perp(b),perp(a))] for a,b in D]) + print('minimal infeasible O:',len(O),O) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/dom.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/dom.py new file mode 100644 index 0000000000..9f43338113 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/dom.py @@ -0,0 +1,34 @@ +"""Dominance reduction of the miss-set type catalog: t is dominated if some t' has g' >= g and m' subset of m (t' != t). +Orbits under the line group (acting on cliques) are used to test one representative per orbit.""" +import pickle, numpy as np +from collections import Counter +T=pickle.load(open('types_e256.pkl','rb')) +G=np.array(pickle.load(open('orbits.pkl','rb'))['G']) +C=pickle.load(open('hits_table.pkl','rb'))['C'] +ci={frozenset(c):k for k,c in enumerate(C)} +Cperm=np.array([[ci[frozenset(int(g[t]) for t in c)] for c in C] for g in G]) # (|G|,640) +mis=np.array([t[4] for t in T]); g=np.array([4-t[2] for t in T]); e=np.array([t[3] for t in T]) +key={ (int(g[i]),mis[i].tobytes()):i for i in range(len(T))} +orb=-np.ones(len(T),int); reps=[] +for i in range(len(T)): + if orb[i]>=0: continue + reps.append(i) + idx=np.nonzero(mis[i])[0] + for p in Cperm: + m2=np.zeros(640,bool); m2[p[idx]]=True + j=key.get((int(g[i]),m2.tobytes())) + assert j is not None + orb[j]=len(reps)-1 +print('types',len(T),'orbits',len(reps)) +size=mis.sum(1) +dominated=[] +for r in reps: + # t' with g'>=g, m' subset m, t' != r + sub=~(mis[:, ~mis[r]].any(1)) # m' within m_r + cand=np.nonzero(sub & (g>=g[r]) & (np.arange(len(T))!=r))[0] + dominated.append(len(cand)>0) +keep=np.array([not dominated[orb[i]] for i in range(len(T))]) +print('orbit reps non-dominated',sum(not d for d in dominated)) +for k,r in enumerate(reps): + print(k,'R',T[r][2],'e',T[r][3],'|m|',int(size[r]),'orbit',int((orb==k).sum()),'dominated' if dominated[k] else 'KEEP') +pickle.dump({'orb':orb,'reps':reps,'keep':keep,'Cperm':Cperm},open('dom.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/f2check.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/f2check.py new file mode 100644 index 0000000000..9444d66270 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/f2check.py @@ -0,0 +1,93 @@ +"""F2 feasibility of a decoder whose totals are confined to given line sets: R_j <= X_j, A_j <= Y_j. +B = sum_j w_j u_j^T must have B_TT = 1 and B_TS = 0 for K-non-orthogonal T != S. Returns SAT model or None.""" +import sys, itertools +from pysat.formula import IDPool +from pysat.solvers import Solver +from lines import views +S = views()['Gauss']; NO = S.NO + +def encode(pairs, extra_or=None): + pool = IDPool(); cl = [] + W = {}; U = {} + for j, (X, Y) in enumerate(pairs): + for T in X: W[T, j] = pool.id(('w', T, j)) + for T in Y: U[T, j] = pool.id(('u', T, j)) + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + for T in range(60): + for S_ in range(60): + if not (T == S_ or NO[T, S_]): continue + terms = [AND(W[T, j], U[S_, j]) for j in range(len(pairs)) if (T, j) in W and (S_, j) in U] + if not terms: + if T == S_: return None, None, None, None + continue + acc = terms[0] + for t in terms[1:]: acc = XOR(acc, t) + cl.append([acc] if T == S_ else [-acc]) + return cl, pool, W, U + +def solve(pairs, solver='cadical153'): + cl, pool, W, U = encode(pairs) + if cl is None: return False, None + with Solver(name=solver, bootstrap_with=cl) as s: + r = s.solve() + if not r: return False, None + mod = set(x for x in s.get_model() if x > 0) + R = [sorted(T for T in X if W[T, j] in mod) for j, (X, Y) in enumerate(pairs)] + A = [sorted(T for T in Y if U[T, j] in mod) for j, (X, Y) in enumerate(pairs)] + return True, (R, A) + +def check_decoder(R, A): + import numpy as np + B = np.zeros((60, 60), int) + for r, a in zip(R, A): + for T in r: + for S_ in a: B[T, S_] ^= 1 + ok = all(B[T, T] == 1 for T in range(60)) and all(B[T, S_] == 0 for T in range(60) for S_ in range(60) if T != S_ and NO[T, S_]) + return ok + +if __name__ == '__main__': + # calibration: identity decoder (60 singleton totals) and the 21-dumbbell cover of S8 + ok, sol = solve([([T], [T]) for T in range(60)]); print('identity', ok, check_decoder(*sol)) + perp = lambda l: [T for T in range(60) if T != l and not NO[l, T]] + D = [(0,13),(4,22),(5,50),(6,19),(6,28),(7,38),(8,19),(11,56),(12,17),(18,37),(18,58),(20,30),(21,33),(25,26),(35,55),(36,45),(37,47),(41,44),(42,51),(44,51),(48,53)] + import time; t = time.time() + ok, sol = solve([(perp(a), perp(b)) for a, b in D]); print('S8 dumbbell cover, fixed orientation:', ok, '%.1fs' % (time.time() - t)) + +def solve_opts(opts, solver='cadical153', assumptions=()): + """opts[j] = list of (X, Y) alternatives for total j (one is selected).""" + pool = IDPool(); cl = [] + W = {}; U = {}; SEL = {} + for j, alts in enumerate(opts): + sels = [pool.id(('s', j, k)) for k in range(len(alts))] + cl.append(sels) + for a in range(len(sels)): + for b in range(a + 1, len(sels)): cl.append([-sels[a], -sels[b]]) + SEL[j] = sels + allX = set().union(*[set(X) for X, Y in alts]); allY = set().union(*[set(Y) for X, Y in alts]) + for T in allX: + W[T, j] = pool.id(('w', T, j)); cl.append([-W[T, j]] + [sels[k] for k, (X, Y) in enumerate(alts) if T in X]) + for T in allY: + U[T, j] = pool.id(('u', T, j)); cl.append([-U[T, j]] + [sels[k] for k, (X, Y) in enumerate(alts) if T in Y]) + def AND(a, b): + z = pool.id(('and', a, b)); cl.extend([[-z, a], [-z, b], [z, -a, -b]]); return z + def XOR(a, b): + z = pool.id(('xor', a, b)); cl.extend([[-z, a, b], [-z, -a, -b], [z, -a, b], [z, a, -b]]); return z + for T in range(60): + for S_ in range(60): + if not (T == S_ or NO[T, S_]): continue + terms = [AND(W[T, j], U[S_, j]) for j in range(len(opts)) if (T, j) in W and (S_, j) in U] + if not terms: + if T == S_: return False, None + continue + acc = terms[0] + for t in terms[1:]: acc = XOR(acc, t) + cl.append([acc] if T == S_ else [-acc]) + with Solver(name=solver, bootstrap_with=cl) as s: + if not s.solve(): return False, None + mod = set(x for x in s.get_model() if x > 0) + R = [sorted(T for (T, jj), v in W.items() if jj == j and v in mod) for j in range(len(opts))] + A = [sorted(T for (T, jj), v in U.items() if jj == j and v in mod) for j in range(len(opts))] + return True, (R, A) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/flats.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/flats.py new file mode 100644 index 0000000000..b064c81896 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/flats.py @@ -0,0 +1,26 @@ +"""All flats (line sets of K-subspaces spanned by configuration lines) of a view, with dims. Exact.""" +import sys, pickle; sys.path.insert(0,sys.path[0]) +from lines import * +def flats(S): + v=S.v; out={} + def close(idx): + d=S.arank(idx); return frozenset(T for T in range(v) if S.arank(list(idx)+[T])==d), d + level=[ (frozenset([T]),1) for T in range(v)] + for f,d in level: out[f]=d + frontier=list(out.items()) + while frontier: + new={} + for f,d in frontier: + if d>=S.w: continue + for T in range(v): + if T in f or any(T in g and f= 40 (x_K >= 2/3). + +Argument. C ranges over the 640 twelve-line sets of pairwise K-non-orthogonal lines. On C x C the pattern forces +B[C,C] = I_12 (diagonal 1, all off-diagonal pairs non-orthogonal), and each total contributes a rank-1 F2 matrix there, +so at least 12 totals hit C on both sides (R_j & C != {} and A_j & C != {}). Summing: sum_j hit(R_j, A_j) >= 12*640. +For fixed spans X = span R_j, Y = span A_j the hit count is largest for R = lines(X), A = lines(Y) (flats), and this +script shows hit(X, Y) <= 192 * rank(X, Y) for all 2271^2 flat pairs, rank = K-rank of the pairing (Theorem C cost). +Hence sum_j beta_j >= 12*640/192 = 40. Binding pair: X = 6-line 2-flat containing l, Y = the 15-line hyperplane l^perp +(pairing rank 1, 192 hits). Mixing all clique sizes, singletons, non-orthogonal pairs and the whole set in an LP over +uniform orbit weights does not improve 40 (gauss_lp3.py in the work dir). +Ranks: exact integer pairing matrices, rank mod p = 2^31-1 (a certified lower bound on the rank over Q).""" +import sys, itertools; sys.path.insert(0, sys.path[0]) +from lines import * +from flats import flats +import networkx as nx + +def main(): + W = views(); S = W['Gauss']; J = W['_J']; v = S.v + F = flats(S); fl = sorted(F.items(), key=lambda t: (t[1], len(t[0]), sorted(t[0]))) + n = len(fl); assert n == 2271 + g = nx.Graph([(i, j) for i in range(v) for j in range(i+1, v) if S.NO[i, j]]) + C = [c for c in nx.find_cliques(g) if len(c) == 12] + assert len(C) == 640 and all(S.NO[a, b] for c in C for a, b in itertools.combinations(c, 2)) + Lo = np.zeros((n, v), np.int64) + for i, (f, d) in enumerate(fl): Lo[i, sorted(f)] = 1 + inc = np.zeros((v, len(C)), np.int64) + for k, c in enumerate(C): inc[c, k] = 1 + meet = (Lo @ inc > 0).astype(np.int64); hits = meet @ meet.T # exact integers + # exact integer bases (vectors r_T, J r_T), pairing ranks by batched elimination mod p = 2^31-1 + # (rank mod p <= rank over Q, so these are certified LOWER bounds; the claim only needs lower bounds) + def ibasis(f): + rows = [] + for T in sorted(f): + for x in (S.reps[T], J @ S.reps[T]): + if rank_q(rows + [x]) > len(rows): rows.append(x) + return np.array(rows, np.int64) + Bs = [ibasis(f) for f, d in fl] + assert all(len(b) == 2 * d for b, (f, d) in zip(Bs, fl)) + P = 2147483647 + def modrank(A): + A = A % P; N, r, c = A.shape; used = np.zeros((N, r), bool); idx = np.arange(N) + for col in range(c): + cand = (A[:, :, col] != 0) & ~used; has = cand.any(1) + if not has.any(): continue + pr = cand.argmax(1); it = idx[has]; prow = A[it, pr[has]] + inv = np.array([pow(int(x), P - 2, P) for x in prow[:, col]], np.int64) if len(it) < 64 else modinv(prow[:, col]) + prow = (prow * inv[:, None]) % P + fac = A[it, :, col].copy(); fac[np.arange(len(it)), pr[has]] = 0 + A[it] = (A[it] - (fac[:, :, None] * prow[:, None, :]) % P) % P + used[it, pr[has]] = True + return used.sum(1) + def modinv(x): + res = np.ones_like(x); base = x % P; e = P - 2 + while e: + if e & 1: res = (res * base) % P + base = (base * base) % P; e >>= 1 + return res + R = np.zeros((n, n), np.int64) + groups = {} + for i, b in enumerate(Bs): groups.setdefault(len(b), []).append(i) + for da, ia in groups.items(): + A = np.stack([Bs[i] for i in ia]) + for db, ib in groups.items(): + Bm = np.stack([Bs[i] for i in ib]) + M = np.einsum('aik,bjk->abij', A, Bm).reshape(-1, da, db) + r = modrank(M).reshape(len(ia), len(ib)); assert (r % 2 == 0).all() + R[np.ix_(ia, ib)] = r // 2 + import pickle; pickle.dump({'fl':fl,'R':R,'hits':hits,'C':C},open('hits_table.pkl','wb')) + print(f'PASS gauss.ranks: certified (mod p) pairing-rank lower bounds for all {n}x{n} flat pairs, histogram {np.bincount(R.flatten()).tolist()}') + assert (hits <= 192 * R).all() + worst = np.argwhere((hits == 192 * R) & (R > 0)) + print('PASS gauss.hits: hit(X,Y) <= 192 * rank(X,Y) on every flat pair (exact hits, certified ranks)') + a, b = worst[0] + print(f' binding example: |X| = {len(fl[a][0])} (dim {fl[a][1]}), |Y| = {len(fl[b][0])} (dim {fl[b][1]}), rank {R[a,b]}, hits {hits[a,b]}; {len(worst)} binding pairs') + print('RESULT gauss: every decoder has sum_j beta_j >= 12*640/192 = 40 => x_K >= 2/3 (threshold to exclude: 43)') + +if __name__ == '__main__': + main() diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lift_prep.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lift_prep.py new file mode 100644 index 0000000000..fd0f131260 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lift_prep.py @@ -0,0 +1,27 @@ +"""Per type: Z_t (lines all of whose cliques are missed), x_t max pairwise-orthogonal subset, delta, y_t. Cached to lift.pkl""" +import pickle, numpy as np +from f2check import NO +C=pickle.load(open('hits_table.pkl','rb'))['C'] +M=np.zeros((640,60),bool) +for k,cl in enumerate(C): M[k,cl]=True +T=pickle.load(open('types_e256.pkl','rb')) +orth=~NO; np.fill_diagonal(orth,False) +cache={} +def maxorth(Z): + k=tuple(Z) + if k in cache: return cache[k] + best=[] + def ext(cl,cand): + nonlocal best + if len(cl)>len(best): best=cl + for i,v in enumerate(cand): ext(cl+[v],[w for w in cand[i+1:] if orth[v,w]]) + ext([],list(Z)); cache[k]=best; return best +mis=np.array([t[4] for t in T]) +Zall=~((~mis[:,:,None])&M[None,:,:]).any(1) # (n,60): no hit clique through line +out=[] +for i,(a,b,R,e,m) in enumerate(T): + x=maxorth(list(np.nonzero(Zall[i])[0])); d=(4-R)-len(x) + out.append((x,d)) +pickle.dump(out,open('lift.pkl','wb')) +from collections import Counter +print(Counter((T[i][2],T[i][3],len(x),d) for i,(x,d) in enumerate(out))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lines.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lines.py new file mode 100644 index 0000000000..fc4eac011d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/lines.py @@ -0,0 +1,166 @@ +"""Self-contained exact construction of the E8 root lines over Q(i), Q(omega) and the Hurwitz quaternions. + +Real model: the 240 E8 roots in doubled standard coordinates (norm 8; in {0,+-4,+-8}). + * Gaussian view : J = left multiplication by i on R^8 = H^2 (coords 1,i,j,k per block); J preserves E8. + Hermitian Gram hG(x,y) = ( + i )/4 in Z[i] (diag 2). + * Eisenstein view: rho = fixed-point-free order-3 isometry of E8 (product of Coxeter elements of 4 orthogonal A2's). + Sesquilinear Gram hE(x,y) = ( - w )/4 in Z[w] (w^2+w+1=0; diag 2+w, a unit at 2). + * Hurwitz view : left multiplication by i, j, k; quaternionic lines of real dim 4. +Elements of Z[i] / Z[w] are stored as integer pairs (a, b) = a + b*i / a + b*w. +Everything is exact integer arithmetic; asserts check every structural claim used downstream. +""" +import itertools +from fractions import Fraction +import numpy as np + + +def e8_roots(): + R = [] + for i, j in itertools.combinations(range(8), 2): + for si in (2, -2): + for sj in (2, -2): + r = [0] * 8; r[i], r[j] = si, sj; R.append(r) + for s in itertools.product((1, -1), repeat=8): + if s.count(-1) % 2 == 0: + R.append(list(s)) + R = np.array(R, dtype=np.int64) + assert len(R) == 240 and set((R * R).sum(1)) == {8} + return R + + +def rank_q(rows): + """exact rank over Q of an integer matrix (fraction-free Gaussian elimination).""" + M = [list(map(int, r)) for r in rows] + if not M: + return 0 + n, m = len(M), len(M[0]); r = 0 + for c in range(m): + p = next((i for i in range(r, n) if M[i][c] != 0), None) + if p is None: + continue + M[r], M[p] = M[p], M[r] + for i in range(r + 1, n): + if M[i][c]: + f, g = M[i][c], M[r][c] + M[i] = [g * a - f * b for a, b in zip(M[i], M[r])] + r += 1 + if r == n: + break + return r + + +def left_quat(): + Li = np.array([[0, -1, 0, 0], [1, 0, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) + Lj = np.array([[0, 0, -1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, -1, 0, 0]]) + Lk = Li @ Lj + blk = lambda M: np.kron(np.eye(2, dtype=np.int64), M).astype(np.int64) + return blk(Li), blk(Lj), blk(Lk) + + +def a2_rotation(roots): + """rho, an order-3 fixed-point-free isometry preserving E8 (returned as an exact integer matrix 4*rho).""" + chosen = [] + for a in roots: + if len(chosen) == 4: + break + if any(np.dot(a, x) != 0 for p in chosen for x in p): + continue + for b in roots: + if np.dot(a, b) == -4 and all(np.dot(a, x) == 0 and np.dot(b, x) == 0 for p in chosen for x in p): + chosen.append((a, b)); break + assert len(chosen) == 4 + g = np.eye(8, dtype=object) + for a, b in chosen: + sa = np.eye(8, dtype=object) * 8 - 2 * np.outer(a, a).astype(object) + sb = np.eye(8, dtype=object) * 8 - 2 * np.outer(b, b).astype(object) + g = g.dot(sa).dot(sb) + D = 8 ** 8 // 4 + assert all(x % D == 0 for x in g.flatten()) + g = (g // D).astype(np.int64) + I = np.eye(8, dtype=np.int64) + assert (g @ g @ g == 64 * I).all() and (g @ g + 4 * g + 16 * I == 0).all() + return g + + +def lines_over(roots, ops): + """group roots into A-lines, A spanned by the integer matrices ops (identity first; ops may be scaled by 4). + Returns representatives (first root met of each line, in root order).""" + S = {tuple(r) for r in roots} + reps, seen = [], set() + for r in roots: + if tuple(r) in seen: + continue + span = [B @ r for B in ops] + assert rank_q(span) == len(ops) + reps.append(r) + members = [s for s in roots if rank_q(span + [s]) == len(ops)] + for s in members: + seen.add(tuple(s)) + return np.array(reps) + + +class View: + """name, reps (v x 8), ops (real matrices spanning A, scaled so that ops[i] @ root is a root times scale[i]).""" + def __init__(self, name, reps, ops, d): + self.name, self.reps, self.ops, self.d = name, reps, ops, d + self.v = len(reps) + NO = np.zeros((self.v, self.v), bool) + for B in ops: + NO |= (reps @ B @ reps.T) != 0 + np.fill_diagonal(NO, False) + self.NO = NO # A-non-orthogonal pairs (the decoder pattern forces B_TS = 0 there) + self.w = self.arank(range(self.v)) + + def arank(self, idx): + rows = [B @ self.reps[i] for i in idx for B in self.ops] + r = rank_q(rows) if rows else 0 + assert r % self.d == 0 + return r // self.d + + def pair_rank(self, R, A): + """A-rank of the pairing span(R) x span(A) = real rank of the real Gram block / dim A (Theorem C cost).""" + BR = [B @ self.reps[i] for i in R for B in self.ops] + BA = [B @ self.reps[i] for i in A for B in self.ops] + if not BR or not BA: + return 0 + M = [[int(x @ y) for y in BA] for x in BR] + r = rank_q(M); assert r % self.d == 0 + return r // self.d + + +def views(): + roots = e8_roots(); I8 = np.eye(8, dtype=np.int64) + Li, Lj, Lk = left_quat() + S = {tuple(r) for r in roots} + for M in (Li, Lj, Lk): + assert all(tuple(M @ r) in S for r in roots) + g = a2_rotation(roots) # 4*rho + assert all(tuple(g @ r // 4) in S and ((g @ r) % 4 == 0).all() for r in roots) + rho = g # 4*rho (exact integer matrix); ranks are scale-free + out = {} + out['Gauss'] = View('Gauss', lines_over(roots, [I8, Li]), [I8, Li], 2) + out['Eisen'] = View('Eisen', lines_over(roots, [I8, rho]), [I8, rho], 2) + out['Hurwitz'] = View('Hurwitz', lines_over(roots, [I8, Li, Lj, Lk]), [I8, Li, Lj, Lk], 4) + out['_J'], out['_rho'] = Li, rho + return out + + +def gauss_gram(V, J): + """hG(r_T, r_S) = ( + i)/4 as integer pairs (a, b).""" + A = V @ V.T; Bm = V @ J @ V.T + assert (A % 4 == 0).all() and (Bm % 4 == 0).all() + return A // 4, Bm // 4 + + +def eisen_gram(V, rho4): + """hE(r_T, r_S) = ( - w)/4 as integer pairs (a, b) = a + b w (rho4 = 4*rho).""" + A = V @ V.T; Bm = V @ rho4 @ V.T + assert (A % 4 == 0).all() and (Bm % 16 == 0).all() + return A // 4, -(Bm // 16) + + +if __name__ == '__main__': + W = views() + for nm in ('Gauss', 'Eisen', 'Hurwitz'): + S = W[nm] + print(nm, 'v', S.v, 'w', S.w, 'nonorth degree', sorted(set(S.NO.sum(1)))) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_cpsat.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_cpsat.py new file mode 100644 index 0000000000..0a408ac9e6 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_cpsat.py @@ -0,0 +1,10 @@ +"""Independent cross-check of the mu-packing optimum with CP-SAT: is there mu in Z>=0^60 with M mu <= cap and |mu| >= target?""" +import pickle, sys, numpy as np, time +from ortools.sat.python import cp_model +C=pickle.load(open('hits_table.pkl','rb'))['C'] +cap=int(sys.argv[1]); target=int(sys.argv[2]); wk=int(sys.argv[3]) if len(sys.argv)>3 else 2 +m=cp_model.CpModel(); mu=[m.NewIntVar(0,cap,'m%d'%i) for i in range(60)] +for cl in C: m.Add(sum(mu[t] for t in cl)<=cap) +m.Add(sum(mu)>=target) +s=cp_model.CpSolver(); s.parameters.num_workers=wk; s.parameters.max_time_in_seconds=3000 +t=time.time(); r=s.Solve(m); print('cap',cap,'|mu|>=',target,s.StatusName(r),'%.0fs'%(time.time()-t),flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_ilp.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_ilp.py new file mode 100644 index 0000000000..2c37ad6662 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/mu_ilp.py @@ -0,0 +1,10 @@ +import pickle, sys, numpy as np, time +from scipy.optimize import milp, LinearConstraint, Bounds +C=pickle.load(open('hits_table.pkl','rb'))['C'] +M=np.zeros((640,60)); +for k,c in enumerate(C): M[k,c]=1 +cap=int(sys.argv[1]) +t=time.time() +r=milp(-np.ones(60),constraints=[LinearConstraint(M,-np.inf,cap)],integrality=np.ones(60),bounds=Bounds(0,cap),options={'disp':False,'time_limit':float(sys.argv[2])}) +print(cap, r.status, r.message, -r.fun if r.x is not None else None, 'bound', getattr(r,'mip_dual_bound',None), '%.0fs'%(time.time()-t)) +if r.x is not None: print(np.round(r.x).astype(int).tolist()) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/orbits.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/orbits.py new file mode 100644 index 0000000000..11e4c31837 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/orbits.py @@ -0,0 +1,53 @@ +"""Permutation group of the 60 Gaussian-E8 lines generated by the 60 complex reflections s_r s_{Jr}; orbits on the 1500 3-flats.""" +import sys, pickle +sys.path.insert(0, sys.path[0]) +import numpy as np +from lines import views, e8_roots + +def line_index(S): + J = S.ops[1]; idx = {} + for T, r in enumerate(S.reps): + for v in (r, -r, J @ r, -(J @ r)): idx[tuple(v)] = T + return idx + +def generators(S): + J = S.ops[1]; idx = line_index(S); gens = [] + for r in S.reps: + out = [] + for T in range(S.v): + x = S.reps[T] + for a in (r, J @ r): # real reflections in r and Jr (norm 8): x -> x - 2/8 a + x = x - (2 * int(x @ a) // 8) * a + out.append(idx[tuple(x)]) + assert sorted(out) == list(range(S.v)) + gens.append(tuple(out)) + return gens + +def closure(gens, n): + G = {tuple(range(n))}; frontier = list(G) + while frontier: + new = [] + for p in frontier: + for g in gens: + q = tuple(g[p[i]] for i in range(n)) + if q not in G: G.add(q); new.append(q) + frontier = new + return G + +if __name__ == '__main__': + S = views()['Gauss'] + gens = generators(S) + # check: generators preserve non-orthogonality + for g in gens: + P = np.array(g); assert (S.NO[np.ix_(P, P)] == S.NO).all() + G = closure(gens, S.v); print('group order on lines', len(G)) + F = pickle.load(open('flats_Gauss.pkl', 'rb')) + hyp = sorted(sorted(f) for f, d in F.items() if d == 3) + hs = {frozenset(h) for h in hyp}; seen = set(); reps = [] + for h in hyp: + if frozenset(h) in seen: continue + orb = {frozenset(g[i] for i in h) for g in G} + assert orb <= hs + seen |= orb; reps.append((h, len(orb))) + print('orbits on 3-flats:', [(len(h), n) for h, n in reps], 'total', sum(n for _, n in reps)) + pickle.dump({'G': sorted(G), 'hyp_reps': reps}, open('orbits.pkl', 'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pair_ilp.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pair_ilp.py new file mode 100644 index 0000000000..f48491c52e --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pair_ilp.py @@ -0,0 +1,55 @@ +"""c = 21, all totals tight (then all 21 are dumbbells, oriented): clique packing + diagonal + exact 2x2 support lemma on +every non-orthogonal pair. argv: c timelimit""" +import pickle, sys, numpy as np, time +from scipy.optimize import milp, LinearConstraint, Bounds +from scipy.sparse import lil_matrix, csr_matrix +from f2check import NO +c=int(sys.argv[1]); tl=float(sys.argv[2]); cap=c-12 +C=pickle.load(open('hits_table.pkl','rb'))['C']; PK=pickle.load(open('pairkinds.pkl','rb')); kinds=PK['kinds']; minimal=PK['minimal'] +orth=lambda a,b: a!=b and not NO[a,b] +perp=lambda l: set(T for T in range(60) if orth(T,l)) +ALL=set(range(60)) +cols=[] +for a in range(60): + for b in range(60): + if orth(a,b): cols.append((2,(a,b),perp(a),perp(b))) +for l in range(60): cols+= [(3,(l,),perp(l),perp(l)),(3,(l,),perp(l),ALL),(3,(l,),ALL,perp(l))] +cols.append((4,(),ALL,ALL)) +n=len(cols); pairs=[(a,b) for a in range(60) for b in range(a+1,60) if NO[a,b]] +nz=len(pairs)*len(minimal); N=n+nz +rows=[]; lb=[]; ub=[] +A=lil_matrix((640+60+len(pairs)*(1+3*len(minimal))+2,N)); r=0 +for q,cl in enumerate(C): + for k,(bt,x,X,Y) in enumerate(cols): + v=len(set(x)&set(cl)) + if v: A[r,k]=v + lb.append(-np.inf); ub.append(cap); r+=1 +for T in range(60): + for k,(bt,x,X,Y) in enumerate(cols): + if T in X and T in Y: A[r,k]=1 + lb.append(1); ub.append(np.inf); r+=1 +kid={kd:i for i,kd in enumerate(kinds)} +for p,(a,b) in enumerate(pairs): + kcols={} + for k,(bt,x,X,Y) in enumerate(cols): + xs=tuple(i for i,t in enumerate((a,b)) if t in X); ys=tuple(i for i,t in enumerate((a,b)) if t in Y) + if xs and ys: kcols.setdefault(kid[(xs,ys)],[]).append(k) + for i,m in enumerate(minimal): + z=n+p*len(minimal)+i; A[r,z]=1 + lb.append(1); ub.append(np.inf); r+=1 + for i,m in enumerate(minimal): + z=n+p*len(minimal)+i + for kd in set(m): + for k in kcols.get(kd,[]): A[r,k]=1 + A[r,z]=-m.count(kd); lb.append(0); ub.append(np.inf); r+=1 +for k in range(n): A[r,k]=1 +lb.append(c); ub.append(c); r+=1 +for k,(bt,x,X,Y) in enumerate(cols): A[r,k]=bt +lb.append(-np.inf); ub.append(42); r+=1 +A=csr_matrix(A[:r]) +print('cols',n,'binaries',nz,'rows',r,flush=True) +t=time.time() +res=milp(np.zeros(N),constraints=[LinearConstraint(A,lb,ub)],integrality=np.ones(N),bounds=Bounds(0,np.concatenate([np.full(n,c),np.ones(nz)])),options={'time_limit':tl}) +print('c',c,'all-tight + 2x2 lemma:',res.status,res.message,'%.0fs'%(time.time()-t),flush=True) +if res.x is not None: + x=np.round(res.x[:n]).astype(int); print([(cols[k][1],cols[k][0],x[k]) for k in np.nonzero(x)[0]]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pairkinds.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pairkinds.py new file mode 100644 index 0000000000..83ece23c7d --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/pairkinds.py @@ -0,0 +1,23 @@ +"""2x2 support lemma data: kinds (Xs, Ys) with Xs, Ys nonempty subsets of {a,b} (a,b non-orthogonal lines). +A total of kind (Xs,Ys) contributes w u^T with supp w <= Xs, supp u <= Ys. B[{a,b},{a,b}] = I_2 is required. +Brute force: minimal multisets of kinds (size <= 4) for which I_2 is reachable. Feasibility is monotone under adding totals.""" +import itertools, pickle +import numpy as np +subs=[(0,),(1,),(0,1)] +kinds=[(x,y) for x in subs for y in subs] +def vecs(s): return [v for v in itertools.product((0,1),repeat=2) if all(v[i]==0 for i in range(2) if i not in s)] +I=np.eye(2,dtype=int) +def feasible(ms): + opts=[[(np.array(w),np.array(u)) for w in vecs(kinds[k][0]) for u in vecs(kinds[k][1])] for k in ms] + for ch in itertools.product(*opts): + S=sum((np.outer(w,u) for w,u in ch),np.zeros((2,2),int))%2 + if (S==I).all(): return True + return False +minimal=[] +for size in range(1,5): + for ms in itertools.combinations_with_replacement(range(9),size): + if any(all(ms.count(k)>=m.count(k) for k in set(m)) for m in minimal): continue + if feasible(ms): minimal.append(ms) +print(len(minimal),'minimal feasible kind-multisets:') +for m in minimal: print(' ',[kinds[k] for k in m]) +pickle.dump({'kinds':kinds,'minimal':minimal},open('pairkinds.pkl','wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/tight_diag.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/tight_diag.py new file mode 100644 index 0000000000..db08ab41c8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/tight_diag.py @@ -0,0 +1,30 @@ +"""Tight-types-only covering ILP (S8 tight_ilp) + diagonal coverage: every line T lies in X_j and Y_j of some total. +argv: c timelimit""" +import pickle, sys, numpy as np, time +from scipy.optimize import milp, LinearConstraint, Bounds +from lines import views +S=views()['Gauss']; NO=S.NO +C=pickle.load(open('hits_table.pkl','rb'))['C'] +c=int(sys.argv[1]); tl=float(sys.argv[2]) +orth=lambda a,b: a!=b and not NO[a,b] +pairs=[(a,b) for a in range(60) for b in range(a+1,60) if orth(a,b)] +# columns: dumbbells (beta2, x={a,b}), singles l (beta3, x={l}), full (beta4) +cols=[] +for a,b in pairs: cols.append((2,[a,b],[T for T in range(60) if orth(T,a) and orth(T,b)])) +for l in range(60): cols.append((3,[l],[T for T in range(60) if orth(T,l)])) +cols.append((4,[],list(range(60)))) +n=len(cols); A=[];lb=[];ub=[] +M=np.zeros((640,n)) +for k,(beta,x,cov) in enumerate(cols): + for q,cl in enumerate(C): M[q,k]=len(set(x)&set(cl)) +A.append(M); lb+= [-np.inf]*640; ub+=[c-12]*640 +D=np.zeros((60,n)) +for k,(beta,x,cov) in enumerate(cols): D[cov,k]=1 +A.append(D); lb+=[1]*60; ub+=[np.inf]*60 +A.append(np.ones((1,n))); lb.append(c); ub.append(c) +A.append(np.array([[cl[0] for cl in cols]])); lb.append(-np.inf); ub.append(42) +t=time.time() +r=milp(np.array([cl[0] for cl in cols],float),constraints=[LinearConstraint(np.vstack(A),lb,ub)],integrality=np.ones(n),bounds=Bounds(0,c),options={'time_limit':tl}) +print('c',c,'status',r.status,r.message,'cost',r.fun,'%.0fs'%(time.time()-t)) +if r.x is not None: + x=np.round(r.x).astype(int); print([(cols[k][0],cols[k][1],x[k]) for k in np.nonzero(x)[0]]) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/try_cand.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/try_cand.py new file mode 100644 index 0000000000..9366778a4b --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/try_cand.py @@ -0,0 +1,6 @@ +import sys, time +from f2check import * +perp = lambda l: [T for T in range(60) if T != l and not NO[l, T]] +D=eval(sys.argv[1]) +t=time.time(); ok,sol=solve_opts([[(perp(a),perp(b)),(perp(b),perp(a))] for a,b in D]); print('free orientation:',ok,'%.1fs'%(time.time()-t)) +if ok: print('decoder check',check_decoder(*sol)) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/types_e.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/types_e.py new file mode 100644 index 0000000000..99ea12fb19 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/types_e.py @@ -0,0 +1,27 @@ +"""Rank-maximal flat-pair types (1<=R<=3) with e = 128R+128-h <= EMAX; dedupe by (miss set, R). Output types_e{EMAX}.pkl""" +import pickle, sys, numpy as np +from collections import Counter +EMAX=int(sys.argv[1]) +d=pickle.load(open('hits_table.pkl','rb')); fl=d['fl']; R=d['R']; H=d['hits']; C=d['C'] +n=len(fl); L=[set(f) for f,dd in fl] +E=128*R+128-H +sup=[[j for j in range(n) if L[i] < L[j]] for i in range(n)] +enlX=np.zeros((n,n),bool); enlY=np.zeros((n,n),bool) +for i in range(n): + if sup[i]: + enlX[i]=(R[sup[i],:]==R[i][None,:]).any(0) + enlY[:,i]=(R[:,sup[i]]==R[:,i][:,None]).any(1) +ok=(R>=1)&(R<=3)&(E<=EMAX)&~enlX&~enlY +ts=np.argwhere(ok) +Lo=np.zeros((n,60),np.int64) +for i,(f,dd) in enumerate(fl): Lo[i,sorted(f)]=1 +inc=np.zeros((60,640),np.int64) +for k,c in enumerate(C): inc[c,k]=1 +meet=(Lo@inc>0) +miss={} +for a,b in ts: + m=~(meet[a]&meet[b]); key=(int(R[a,b]),np.packbits(m).tobytes()) + if key not in miss: miss[key]=(int(a),int(b)) +print('EMAX',EMAX,'rank-max pairs',len(ts),'distinct (R,miss)',len(miss)) +print(Counter((len(fl[a][0]),len(fl[b][0]),int(R[a,b]),int(E[a,b])) for (r,k),(a,b) in miss.items()).most_common()) +pickle.dump([(a,b,r,int(E[a,b]),np.unpackbits(np.frombuffer(k,np.uint8))[:640].astype(bool)) for (r,k),(a,b) in miss.items()],open('types_e%d.pkl'%EMAX,'wb')) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/vsingle.py b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/vsingle.py new file mode 100644 index 0000000000..996fa243f1 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/scripts/vsingle.py @@ -0,0 +1,13 @@ +import pickle,sys,numpy as np +from scipy.optimize import milp, LinearConstraint, Bounds +cap=int(sys.argv[1]) +C=pickle.load(open('hits_table.pkl','rb'))['C'] +M=np.zeros((640,60)) +for k,cl in enumerate(C): M[k,cl]=1 +T=pickle.load(open('types_e256.pkl','rb')); L=pickle.load(open('lift.pkl','rb')); dom=pickle.load(open('dom.pkl','rb')) +for k,r in enumerate(dom['reps']): + a,b,R,e,m=T[r]; x,d=L[r] + if d<=0: continue + y=m-M[:,x].sum(1) + res=milp(-np.ones(60),constraints=[LinearConstraint(M,-np.inf,cap-y)],integrality=np.ones(60),bounds=Bounds(0,cap),options={'time_limit':300}) + print('orbit',k,'R',R,'e',e,'alpha',len(x),'delta',d,'V',-res.fun,'V+alpha+delta',-res.fun+len(x)+d,flush=True) diff --git a/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/verify.sh b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/verify.sh new file mode 100755 index 0000000000..201f2084a8 --- /dev/null +++ b/research/e8-decoder-floors-and-cold14-notes/s9-gaussian-c19/verify.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# S9 verification. Rebuilds every input from scratch (exact lines/flats/cliques, certified ranks), then +# (1) the mu-packing optima f(k) (HiGHS) + CP-SAT cross-checks, +# (2) the type catalog (58,340 types), its 27 orbits and the lifting data, +# (3) calibrations of the case ILP (must be FEASIBLE) and the F2 confinement checks of the c = 21 counting covers, +# (4) [FULL=1] every case ILP of Theorems 1-2 (HiGHS; hours; PAR parallel jobs, default 6). +# Lines printed (times stripped) should match expected.txt (FULL part: expected_full.txt). +# Needs python 3.11 + numpy scipy sympy networkx python-sat ortools on PATH. +set -e +export OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 +HERE=$(cd "$(dirname "$0")" && pwd); W=${WORK:-$(mktemp -d)}; PAR=${PAR:-6} +cp "$HERE"/scripts/*.py "$W"/; cd "$W" +strip() { sed -E 's/ [0-9]+s$//'; } +python flats.py Gauss > /dev/null +python orbits.py +python hits_table.py | grep -E '^(PASS|RESULT)' +python types_e.py 256 | head -1 +python dom.py | head -2 +python lift_prep.py > /dev/null && echo "lift.pkl built" +for cap in 6 7 8 9 10 11 12; do python mu_ilp.py $cap 600 | head -1 | awk '{printf "mu-packing: cap %s max |mu| = %d (LP %d)\n",$1,int($(NF-3)+0.5),5*$1}'; done +python mu_cpsat.py 7 33 2 | strip +python mu_cpsat.py 8 37 2 | strip +echo "calibrations (must be FEASIBLE):" +python branch1.py 21 42 -1 600 feas | head -1 | strip +python branch1.py 19 44 -1 600 feas | head -1 | strip +python pairkinds.py | head -1 +python f2check.py +python check_c21cover.py +python try_cand.py "[(1,54),(3,4),(5,59),(6,18),(7,19),(8,57),(20,32),(21,31),(23,24),(25,51),(28,58),(29,52),(33,56),(35,48),(36,50),(38,53),(39,46),(40,55),(41,47),(42,44),(43,45)]" | strip +python core.py "[(1,54),(3,4),(5,59),(6,18),(7,19),(8,57),(20,32),(21,31),(23,24),(25,51),(28,58),(29,52),(33,56),(35,48),(36,50),(38,53),(39,46),(40,55),(41,47),(42,44),(43,45)]" +if [ -n "$FULL" ]; then + for cc in "19 42" "20 41" "20 42"; do + set -- $cc + CASES=$(python -c " +import pickle; T=pickle.load(open('types_e256.pkl','rb')); L=pickle.load(open('lift.pkl','rb')); d=pickle.load(open('dom.pkl','rb')) +s=128*($2+$1)-7680 +print(' '.join(['-1']+[str(k) for k,r in enumerate(d['reps']) if L[r][1]>0 and T[r][3]<=s]))") + echo "$CASES" | tr ' ' '\n' | xargs -P $PAR -I{} python branch1.py $1 $2 {} ${TL:-14400} $MODE | strip | sort -t' ' -k4,4n + done +fi +echo "work dir: $W"