Files
math-research/papers/face_monochromatic_pairs/experiments/check_deciding_face.py
T
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

162 lines
6.0 KiB
Python
Raw 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.
"""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()