Files
math-research/papers/face_monochromatic_pairs/experiments/check_heawood_on_kempe.py
T
didericis b72c38b8ce face_monochromatic_pairs: diagnostic scripts for Path 4 (Heawood
constancy on V(K_b) U V(K_c))

Three empirical checks on all chord-apex+Kempe colourings up to
n = 20 (142,812 colourings):

1. check_heawood_on_kempe.py
   - Sum_v h_phi(v): not zero in general; 17.6% of colourings have
     sum 0, the rest range in {+-4, +-8, +-12, +-16, +-20, +-24}.
     So the global "Heawood sum = 0" identity fails.
   - h_phi constant on V(K_b) U V(K_c): NEVER (0/142,812). This is
     the central empirical result -- by Lemma 5.3's contrapositive
     it gives an empirical proof of Conjecture 5.1 on these
     surrogates.

2. check_heawood_per_kempe_cycle.py
   - Sum_{V(K_b)} h_phi and sum_{V(K_c)} h_phi range widely (-20 to
     +20), with only ~23% zero. So the "Heawood sum on each Kempe
     cycle = 0" identity also fails -- the per-cycle sum is not the
     right invariant.

3. check_heawood_pair_mismatch.py
   - For each of 16 named-vertex pairs (v_n with each A_j, A_j with
     A_k for j, k in {i, ..., i+4}), counts how often h_phi differs.
     No pair is *always* differing -- the closest are consecutive
     pairs (A_j, A_{j+1}) at ~75% diff. So the Heawood mismatch
     enforcing non-constancy on V(K_b) U V(K_c) is diffuse, not at
     a fixed pair.

Together these results confirm Path 4 (Conjecture 5.1 reduces via
Lemma 5.3 to showing h_phi non-constant on V(K_b) U V(K_c)) but
rule out the simplest single-pair-identity proof; the structural
obstruction lives elsewhere (likely a topological/cycle-winding
argument or a chord-apex/Kempe-spike colour cascade).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 23:18:52 -04:00

189 lines
6.8 KiB
Python

"""Empirically test, on every chord-apex+Kempe colouring of every
reduced dual we can enumerate:
(a) Does the Heawood-sum identity sum_v h_phi(v) = 0 hold?
(b) Is h_phi ever constant on V(K_b) U V(K_c), the union of the two
Kempe cycles through the merged edge?
(b) is the precise hypothesis of Lemma 5.3's conclusion. If (b) is
*never* witnessed on any chord-apex+Kempe colouring, then by the
contrapositive of Lemma 5.3 we have empirical verification of
Conjecture 5.1 on the chord-apex+Kempe surrogates.
Run with: sage experiments/check_heawood_on_kempe.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_3_8_scaled import (
apply_reduction,
proper_3_edge_colorings,
matches_chord_apex_kempe,
kempe_cycle_set,
edge_idx,
)
def dual_of(G):
G.is_planar(set_embedding=True)
faces = G.faces()
edge_to_faces = {}
for fi, face in enumerate(faces):
for u, v in face:
edge_to_faces.setdefault(frozenset((u, v)), []).append(fi)
return Graph(
[(fs[0], fs[1]) for fs in edge_to_faces.values() if len(fs) == 2],
multiedges=False, loops=False)
def heawood_numbers(H, edges, col_list):
"""Return dict v -> h_phi(v) in {+1, -1}.
The Heawood number is +1 iff the clockwise cyclic colour order at v
is an even cyclic permutation of (0, 1, 2).
"""
H.is_planar(set_embedding=True)
emb = H.get_embedding() # v -> list of neighbours in CW order
edge_color = {frozenset(e): col_list[i] for i, e in enumerate(edges)}
out = {}
for v in H.vertex_iterator():
nbrs = emb[v]
if len(nbrs) != 3:
raise RuntimeError(f"vertex {v} not cubic: {len(nbrs)}")
cs = tuple(edge_color[frozenset((v, w))] for w in nbrs)
# cs is a permutation of (0, 1, 2). Check parity of its cyclic
# equivalence class compared to (0, 1, 2).
# (0,1,2), (1,2,0), (2,0,1) are even cyclic -> +1
# (0,2,1), (2,1,0), (1,0,2) are odd cyclic -> -1
if cs in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]:
out[v] = +1
elif cs in [(0, 2, 1), (2, 1, 0), (1, 0, 2)]:
out[v] = -1
else:
raise RuntimeError(f"bad colour tuple at {v}: {cs}")
return out
def vertices_of_kempe(edges, kc_edge_indices):
"""Vertices touched by the edges with indices in kc_edge_indices."""
vs = set()
for i in kc_edge_indices:
u, v = edges[i][0], edges[i][1]
vs.add(u); vs.add(v)
return vs
def test_one(D, name=""):
"""Run the empirical check on one cubic plane graph D = G'."""
D.is_planar(set_embedding=True)
n_col = 0
n_sum_zero = 0
n_constant_union = 0
sum_distribution = {}
examples = [] # (n_pent_face, i_red, coloring index, sum_h, constant?)
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
# Compute Heawood numbers
try:
h = heawood_numbers(H, edges, col)
except RuntimeError:
continue
# Part (a): sum_v h(v)
s = sum(h.values())
sum_distribution[s] = sum_distribution.get(s, 0) + 1
if s == 0:
n_sum_zero += 1
# Part (b): is h constant on V(K_b) U V(K_c)?
merged_idx = edge_idx(edges, named['merged'])
a = col[merged_idx]
K_unions = set()
for b in range(3):
if b == a: continue
kc = kempe_cycle_set(edges, col, merged_idx, (a, b))
K_unions |= vertices_of_kempe(edges, kc)
h_on_union = {h[v] for v in K_unions}
constant = (len(h_on_union) == 1)
if constant:
n_constant_union += 1
if len(examples) < 5:
examples.append((len(K_unions), s, list(h_on_union)[0]))
return n_col, n_sum_zero, n_constant_union, sum_distribution, examples
def main(max_n=20, time_budget_per_n=1800):
print(f"Checking Heawood identities on chord-apex+Kempe colourings, "
f"n in [12, {max_n}]\n")
total_col = 0
total_sum_zero = 0
total_constant = 0
overall_dist = {}
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_sum_zero_n = 0
n_constant_n = 0
dist_n = {}
examples_n = []
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)
n_col_i, n_sz_i, n_cu_i, dist_i, exs = test_one(D, name=f"n{n}t{tri_idx}")
n_col_n += n_col_i
n_sum_zero_n += n_sz_i
n_constant_n += n_cu_i
for k, v in dist_i.items():
dist_n[k] = dist_n.get(k, 0) + v
if exs and len(examples_n) < 5:
examples_n.extend(exs[:5 - len(examples_n)])
elapsed = time.time() - start
print(f"n={n}: {n_col_n} colourings "
f"sum=0: {n_sum_zero_n}/{n_col_n} "
f"constant on V(K_b)UV(K_c): {n_constant_n}/{n_col_n} "
f"sum-dist: {sorted(dist_n.items())} [{elapsed:.0f}s]")
if examples_n and n_constant_n:
print(f" examples of constant-union colourings: {examples_n}")
sys.stdout.flush()
total_col += n_col_n
total_sum_zero += n_sum_zero_n
total_constant += n_constant_n
for k, v in dist_n.items():
overall_dist[k] = overall_dist.get(k, 0) + v
print()
print("=" * 78)
print(f"Grand totals (n in [12, {max_n}]):")
print(f" colourings tested: {total_col}")
print(f" sum_v h(v) = 0: {total_sum_zero} ({100*total_sum_zero/max(1,total_col):.2f}%)")
print(f" h constant on V(K_b)UV(K_c): {total_constant} ({100*total_constant/max(1,total_col):.2f}%)")
print(f" sum distribution: {sorted(overall_dist.items())}")
if __name__ == '__main__':
main()