face_mono: extend Conjecture 5.26 to n_G ≤ 22

Adds experiments/test_conj_5_26_n_21_22.py, a clause-4 checker that
re-uses find_all_36_witnesses + check_clause_4 from
check_conj_final_scaled.py and runs them on n = 21, 22 with
incremental JSONL output and a 10-minute PROGRESS heartbeat.

Results (139 min wall, single thread):
  n=21: 192 tri, 392,370 colourings w/ clause-1–3 witness, all pass
  n=22: 651 tri, 1,786,314 colourings w/ clause-1–3 witness, all pass
  total at n ≤ 22: 2,321,496 / 2,321,496 (combined with the existing
  142,812 at n ≤ 20 from check_conj_final_scaled.py)

Paper edits:
- Abstract: "|V(G)| ≤ 20 (142,812)" → "|V(G)| ≤ 22 (2,321,496)" for
  the strengthening; clauses-1–3 count unchanged at 535,182 / n ≤ 21.
- Intro paragraph: matching update.
- Remark rem:conj-3-8-empirical table: added n=21 and n=22 rows; new
  total ($n \le 22$) = 959 triangulations, 2,321,496 colourings.
- Updated script reference in that remark to point at
  check_conj_final_scaled.py + test_conj_5_26_n_21_22.py.

COMMENTARY.md summary table: Conjecture 5.26 row bumped to
2,321,496 / 2,321,496 (n ≤ 22).

Also commits the test_*_results.jsonl artifacts (with per-tri
records + n-summaries + grand summary) for reproducibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 12:27:58 -04:00
parent d36c2513cc
commit 74030a5b8f
9 changed files with 2738 additions and 51 deletions
@@ -0,0 +1,199 @@
"""Test Conjecture 5.26 (the strengthening, clauses 14) on
chord-apex+Kempe colourings of reduced duals for n in [21, 22].
For each (G, v, i, φ) chord-apex+Kempe colouring, enumerate every
witness (F, e_1, e_2) satisfying clauses 13, then check whether
at least one such witness also satisfies clause 4. The conjecture
states: SOME witness for each colouring passes clause 4.
A "passing colouring" = ∃ witness with clauses 14 all satisfied.
A "failing colouring" = ALL its witnesses fail clause 4.
The known result for n ≤ 20 is 142,812 / 142,812 (pass) via the
existing check_conj_final_scaled.py. This script extends to
n = 21 and n = 22, with incremental JSONL output and a
10-minute PROGRESS heartbeat.
Run with: sage experiments/test_conj_5_26_n_21_22.py [n_min] [n_max]
"""
import json
import os
import sys
import time
from pathlib import Path
from sage.all import Graph
from sage.graphs.graph_generators import graphs
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from check_conj_final_scaled import (
apply_reduction,
proper_3_edge_colorings,
matches_chord_apex_kempe,
find_all_36_witnesses,
check_clause_4,
)
from check_heawood_on_kempe import dual_of
RESULTS_FILE = Path(HERE) / '..' / 'test_conj_5_26_n_21_22_results.jsonl'
def test_one(D):
"""For dual D, iterate reductions + chord-apex+Kempe colourings.
Returns counters and any failing examples."""
D.is_planar(set_embedding=True)
n_col_with_witness = 0
n_pass = 0
n_fail = 0
failures = [] # only failing colourings get recorded
for face in D.faces():
if len(face) != 5:
continue
for i_red in range(5):
res = apply_reduction(D, face, i_red, 9999)
if res is None:
continue
H = res['H']; named = res['named']
H.is_planar(set_embedding=True)
edges, colorings = proper_3_edge_colorings(H)
cand = [c for c in colorings
if matches_chord_apex_kempe(edges, c, named)]
for col in cand:
witnesses = find_all_36_witnesses(H, edges, list(col), named)
if not witnesses:
continue
n_col_with_witness += 1
ok = False
for w in witnesses:
try:
if check_clause_4(H, edges, list(col), named, w):
ok = True
break
except Exception:
# Defensive: treat exception as "this witness fails"
# and keep trying others.
pass
if ok:
n_pass += 1
else:
n_fail += 1
failures.append({
'i_red': i_red,
'n_witnesses': len(witnesses),
})
return n_col_with_witness, n_pass, n_fail, failures
def write_result(record):
with open(RESULTS_FILE, 'a') as f:
f.write(json.dumps(record) + '\n')
f.flush()
def main(n_min=21, n_max=22, progress_every_seconds=600):
started_at = time.time()
print(f"Testing Conjecture 5.26 (clauses 14) on n in [{n_min}, {n_max}]")
print(f"Writing incremental results to {RESULTS_FILE}")
print(f"PROGRESS lines every {progress_every_seconds}s")
sys.stdout.flush()
if RESULTS_FILE.exists():
bak = RESULTS_FILE.with_suffix(RESULTS_FILE.suffix + '.bak')
RESULTS_FILE.rename(bak)
print(f"Previous results moved to {bak}")
RESULTS_FILE.touch()
grand_col = 0
grand_pass = 0
grand_fail = 0
last_progress = time.time()
for n in range(n_min, n_max + 1):
n_start = time.time()
print(f"\n=== n_G = {n} ===")
sys.stdout.flush()
try:
triangulations_iter = graphs.triangulations(n, minimum_degree=5)
except Exception as ex:
print(f"n={n}: cannot enumerate ({ex})")
continue
n_col_n = 0
n_pass_n = 0
n_fail_n = 0
tri_idx = 0
for G in triangulations_iter:
G.is_planar(set_embedding=True)
D = dual_of(G)
tri_start = time.time()
nc, np_, nf, fails = test_one(D)
tri_elapsed = time.time() - tri_start
n_col_n += nc
n_pass_n += np_
n_fail_n += nf
record = {
'n_G': n, 'tri_idx': tri_idx,
'n_col_with_witness': nc,
'n_pass': np_,
'n_fail': nf,
'tri_elapsed_s': round(tri_elapsed, 2),
'wall_elapsed_s': round(time.time() - started_at, 2),
'failures': fails,
}
write_result(record)
now = time.time()
if now - last_progress >= progress_every_seconds:
wall_min = (now - started_at) / 60
print(f"PROGRESS: wall {wall_min:.1f} min; "
f"n={n} tri {tri_idx+1} done ({tri_elapsed:.1f}s); "
f"running totals @n={n}: col={n_col_n}, "
f"pass={n_pass_n}, fail={n_fail_n}")
sys.stdout.flush()
last_progress = now
tri_idx += 1
n_elapsed = time.time() - n_start
summary = {
'type': 'n_summary',
'n_G': n, 'n_tri': tri_idx,
'n_col_with_witness': n_col_n,
'n_pass': n_pass_n,
'n_fail': n_fail_n,
'n_elapsed_s': round(n_elapsed, 2),
'wall_elapsed_s': round(time.time() - started_at, 2),
}
write_result(summary)
print(f"\nn={n}: {tri_idx} triangulations, {n_col_n} colourings "
f"with witnesses [{n_elapsed:.0f}s]")
print(f" pass clause 4: {n_pass_n}")
print(f" fail clause 4: {n_fail_n}")
sys.stdout.flush()
grand_col += n_col_n
grand_pass += n_pass_n
grand_fail += n_fail_n
total_elapsed = time.time() - started_at
final = {
'type': 'grand_summary',
'n_min': n_min, 'n_max': n_max,
'grand_col_with_witness': grand_col,
'grand_pass': grand_pass,
'grand_fail': grand_fail,
'wall_elapsed_s': round(total_elapsed, 2),
}
write_result(final)
print("\n" + "=" * 70)
print(f"DONE: n in [{n_min}, {n_max}], {grand_col} colourings with "
f"clause-1-3 witness in {total_elapsed/60:.1f} min")
print(f" pass: {grand_pass}")
print(f" fail: {grand_fail}")
if grand_fail == 0 and grand_col > 0:
print(f"✓ Conjecture 5.26 EMPIRICALLY EXTENDED to n_G ≤ {n_max}")
if __name__ == '__main__':
n_min = int(sys.argv[1]) if len(sys.argv) > 1 else 21
n_max = int(sys.argv[2]) if len(sys.argv) > 2 else 22
main(n_min=n_min, n_max=n_max)