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>
162 lines
6.0 KiB
Python
162 lines
6.0 KiB
Python
"""For every chord-apex+Kempe colouring of every reduced dual (up to
|
||
|V(G)| ≤ 18), check whether there exists a "deciding face": a face f
|
||
of the reduced dual whose boundary lies entirely in V(K_b) ∪ V(K_c)
|
||
and whose length is not divisible by 3.
|
||
|
||
If a deciding face exists, then under the (hypothetical) constancy
|
||
hypothesis h_φ ≡ ε on V(K_b) ∪ V(K_c), Heawood's face-sum identity
|
||
gives ε · |f| ≡ 0 (mod 3), which forces ε ≡ 0 since |f| ≢ 0 (mod 3) --
|
||
contradicting ε ∈ {±1}. So existence of a deciding face in every
|
||
chord-apex+Kempe colouring of every reduced dual would *prove*
|
||
Conjecture 5.1 via the contrapositive of Lemma 5.3.
|
||
|
||
This script reports, per (n_G, colouring): does a deciding face exist?
|
||
And if not, why not (insufficient coverage, all in-coverage faces have
|
||
length ≡ 0 mod 3, etc.).
|
||
|
||
Run with: sage experiments/check_deciding_face.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, vertices_of_kempe,
|
||
)
|
||
|
||
|
||
def find_deciding_face(H, V_union):
|
||
"""Return (face, |face| mod 3) of a deciding face (all boundary in
|
||
V_union, length not divisible by 3). None if no such face exists."""
|
||
for face in H.faces():
|
||
verts = {u for (u, v) in face} | {v for (u, v) in face}
|
||
if not verts.issubset(V_union):
|
||
continue
|
||
L = len(face)
|
||
if L % 3 != 0:
|
||
return face, L, L % 3
|
||
return None
|
||
|
||
|
||
def test_one(D):
|
||
D.is_planar(set_embedding=True)
|
||
n_col = 0
|
||
n_with_deciding = 0
|
||
no_deciding_examples = []
|
||
deciding_lengths = {} # |f| of deciding face -> count
|
||
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:
|
||
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)
|
||
V_union = V_b | V_c
|
||
deciding = find_deciding_face(H, V_union)
|
||
if deciding is not None:
|
||
n_with_deciding += 1
|
||
_, L, mod_r = deciding
|
||
deciding_lengths[L] = deciding_lengths.get(L, 0) + 1
|
||
else:
|
||
cov_ratio = len(V_union) / H.order()
|
||
in_cov_face_lens = sorted([
|
||
len(f) for f in H.faces()
|
||
if {u for (u, v) in f} | {v for (u, v) in f}
|
||
<= V_union])
|
||
if len(no_deciding_examples) < 3:
|
||
no_deciding_examples.append({
|
||
'cov_ratio': cov_ratio,
|
||
'in_cov_face_lens': in_cov_face_lens,
|
||
'V_union_size': len(V_union),
|
||
'V_total': H.order(),
|
||
})
|
||
return n_col, n_with_deciding, deciding_lengths, no_deciding_examples
|
||
|
||
|
||
def main(max_n=20, time_budget_per_n=1800):
|
||
print(f"Deciding-face existence on chord-apex+Kempe colourings, "
|
||
f"n_G ∈ [12, {max_n}]\n")
|
||
grand_col = 0
|
||
grand_dec = 0
|
||
grand_lens = {}
|
||
grand_no_dec_examples = []
|
||
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_dec_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, ndec, lens, no_dec_ex = test_one(D)
|
||
n_col_n += ni
|
||
n_dec_n += ndec
|
||
for k, v in lens.items():
|
||
grand_lens[k] = grand_lens.get(k, 0) + v
|
||
if len(grand_no_dec_examples) < 5:
|
||
grand_no_dec_examples.extend(
|
||
no_dec_ex[:5 - len(grand_no_dec_examples)])
|
||
elapsed = time.time() - start
|
||
pct = 100 * n_dec_n / max(n_col_n, 1)
|
||
print(f"n={n}: {n_col_n} col., {n_dec_n} with deciding face "
|
||
f"({pct:.2f}%) [{elapsed:.0f}s]")
|
||
sys.stdout.flush()
|
||
grand_col += n_col_n
|
||
grand_dec += n_dec_n
|
||
|
||
print()
|
||
print("=" * 70)
|
||
print(f"Grand totals: {grand_col} chord-apex+Kempe colourings")
|
||
pct = 100 * grand_dec / max(grand_col, 1)
|
||
print(f" with deciding face: {grand_dec} ({pct:.2f}%)")
|
||
print(f" without : {grand_col - grand_dec} "
|
||
f"({100 - pct:.2f}%)")
|
||
if grand_lens:
|
||
print()
|
||
print(" Deciding face length distribution:")
|
||
for L in sorted(grand_lens):
|
||
print(f" |f| = {L} (mod 3 = {L % 3}): {grand_lens[L]}")
|
||
if grand_no_dec_examples:
|
||
print()
|
||
print(" Sample colourings WITHOUT a deciding face:")
|
||
for ex in grand_no_dec_examples[:5]:
|
||
print(f" coverage = {ex['V_union_size']}/{ex['V_total']} "
|
||
f"({100*ex['cov_ratio']:.0f}%); in-coverage face "
|
||
f"lengths = {ex['in_cov_face_lens']}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|