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>
189 lines
6.8 KiB
Python
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_final_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()
|