4ceae9c68a
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>
133 lines
4.9 KiB
Python
133 lines
4.9 KiB
Python
"""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()
|