Files
didericis 4ceae9c68a face_monochromatic_pairs: rename check_conj_3_8_scaled → check_conj_final_scaled; add n=21-24 test
Rename the shared helper module to a number-resistant name. Update
all 26 dependent scripts via sed.

Add experiments/test_n_21_to_24.py — extends the empirical check
beyond |V(G)| ≤ 20 to n_G ∈ [21, 24]. Checks per chord-apex+Kempe
colouring:
  (1) h_φ constant on V(K_b)? (counterexample to Corollary 5.4)
  (2) h_φ constant on V(K_b) ∪ V(K_c)? (counterexample to Conj 5.1)
  (3) Deciding face exists?

Writes results incrementally to test_n_21_to_24_results.jsonl (one
JSON line per triangulation, plus n-level and grand summaries).
Emits PROGRESS lines every 10 minutes (default) to stdout for live
monitoring.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 08:01:29 -04:00

133 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Check what fraction of V(Ĝ'_{v,i}) is covered by V(K_b) V(K_c)
across all chord-apex+Kempe colourings of all reduced duals up to
|V(G)| ≤ 18.
This is the key prerequisite for the Heawood-face-sum proof attempt:
if V(K_b) V(K_c) = V(reduced dual) in many cases, then under
constancy on V(K_b) V(K_c) every face of the reduced dual must
satisfy ε * |f| ≡ 0 (mod 3); since ε = ±1, this forces |f| ≡ 0 mod 3
for every face -- and any reduced dual with a face of length 4, 5, or
7 (etc., not multiple of 3) would yield an immediate contradiction.
Run with: sage experiments/check_kb_kc_coverage.py
"""
import os
import sys
import time
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,
kempe_cycle_set,
edge_idx,
)
from check_heawood_on_kempe import (
dual_of, heawood_numbers, vertices_of_kempe,
)
def test_one(D):
D.is_planar(set_embedding=True)
n_col = 0
coverage_dist = {} # |V(K_b)V(K_c)| / |V| as ratio (rounded) -> count
full_coverage = 0
face_lengths_at_full = {} # face length distribution when full coverage
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)]
n_v = H.order()
face_lens = sorted([len(f) for f in H.faces()])
for col in cand:
n_col += 1
merged_idx = edge_idx(edges, named['merged'])
a = col[merged_idx]
bs = [c for c in range(3) if c != a]
kc_b = kempe_cycle_set(edges, col, merged_idx, (a, bs[0]))
kc_c = kempe_cycle_set(edges, col, merged_idx, (a, bs[1]))
V_b = vertices_of_kempe(edges, kc_b)
V_c = vertices_of_kempe(edges, kc_c)
cov = len(V_b | V_c)
cov_ratio = cov / n_v
bucket = round(cov_ratio * 20) / 20 # bucket in 5% increments
coverage_dist[bucket] = coverage_dist.get(bucket, 0) + 1
if cov == n_v:
full_coverage += 1
key = tuple(face_lens)
face_lengths_at_full[key] = (
face_lengths_at_full.get(key, 0) + 1)
return n_col, coverage_dist, full_coverage, face_lengths_at_full
def main(max_n=18, time_budget_per_n=300):
print(f"V(K_b) V(K_c) coverage / V(Ĝ'_{{v,i}}) "
f"on chord-apex+Kempe colourings, n_G ∈ [12, {max_n}]\n")
grand_col = 0
grand_dist = {}
grand_full = 0
grand_full_faces = {}
for n in range(12, max_n + 1):
start = time.time()
try:
triangulations = list(graphs.triangulations(n, minimum_degree=5))
except Exception as ex:
print(f"n={n}: cannot enumerate ({ex})")
continue
n_col_n = 0
n_full_n = 0
for tri_idx, G in enumerate(triangulations):
if time.time() - start > time_budget_per_n:
print(f" n={n}: timeout at tri {tri_idx}/{len(triangulations)}")
break
G.is_planar(set_embedding=True)
D = dual_of(G)
ni, dist, full, full_faces = test_one(D)
n_col_n += ni
n_full_n += full
for k, v in dist.items():
grand_dist[k] = grand_dist.get(k, 0) + v
for k, v in full_faces.items():
grand_full_faces[k] = grand_full_faces.get(k, 0) + v
elapsed = time.time() - start
print(f"n={n}: {n_col_n} col., {n_full_n} full-coverage "
f"({100 * n_full_n / max(n_col_n, 1):.1f}%) [{elapsed:.0f}s]")
sys.stdout.flush()
grand_col += n_col_n
grand_full += n_full_n
print()
print("=" * 70)
print(f"Grand totals: {grand_col} chord-apex+Kempe colourings")
print(f" full coverage (V(K_b)V(K_c) = V): {grand_full} "
f"({100 * grand_full / max(grand_col, 1):.2f}%)")
print()
print(" Coverage ratio distribution "
"(|V(K_b)V(K_c)| / |V(Ĝ')|):")
for r in sorted(grand_dist):
c = grand_dist[r]
pct = 100 * c / max(grand_col, 1)
bar = '#' * max(1, int(pct))
print(f" {r:5.2f}: {c:>7} ({pct:5.2f}%) {bar[:50]}")
if grand_full > 0:
print()
print(" Face-length distributions among full-coverage cases:")
for key in sorted(grand_full_faces, key=lambda k: -grand_full_faces[k]):
print(f" {key}: {grand_full_faces[key]}")
if __name__ == '__main__':
main()