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

225 lines
9.0 KiB
Python

"""For each chord-apex+Kempe colouring, dissect WHERE the constancy
hypothesis fails:
(1) Is h_phi constant on V(K_b) alone? On V(K_c) alone? On the
intersection V(K_b) cap V(K_c)?
(2) Per colouring, distribution of (#+1, #-1) shared vertices.
(3) For each colouring, identify the "minority Heawood" shared
vertices (= those whose h differs from the more frequent value
on V(K_b) cup V(K_c)). How many are there? Are they
concentrated at specific structural positions (v_n, A_{i+1},
A_{i+3}, A_{i+4})?
(4) Is h(v_n) always equal to / always different from the majority?
Under the constancy hypothesis, all of these distributions would be
trivial (everything same Heawood, 0 minority vertices). Their
non-trivial empirical structure exposes where Path 4's "trap"
mechanism would have to apply.
Run with: sage experiments/check_constancy_obstruction.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,
trace_kempe_cycle,
edge_idx,
kempe_cycle_set,
)
from check_heawood_on_kempe import (
dual_of, heawood_numbers, vertices_of_kempe,
)
def named_vertices(named, v_n=9999):
"""Recover A_i, A_{i+1}, ..., A_{i+4}."""
def other(fs, v):
return next(iter(fs - {v}))
A_i = other(named['side_0'], v_n)
A_i1 = other(named['spike'], v_n)
A_i2 = other(named['side_1'], v_n)
A_i3, A_i4 = sorted(named['merged'])
return A_i, A_i1, A_i2, A_i3, A_i4
def test_one(D):
D.is_planar(set_embedding=True)
n_col = 0
rec = {
'const_kb': 0, 'const_kc': 0, 'const_cap': 0,
# (#+1, #-1) on V(K_b) cup V(K_c) histogram
'plus_minus_dist': {},
# # minority Heawood vertices on V(K_b) cup V(K_c)
'minority_count': {},
# Is v_n minority? majority?
'v_n_pos': {'minority': 0, 'majority': 0, 'tie': 0},
# Position-tallies of minority for each "named" vertex
'minority_at': {'v_n': 0, 'A_i': 0, 'A_i1': 0, 'A_i2': 0,
'A_i3': 0, 'A_i4': 0},
# Is h(v_n) == h(A_{i+3}) == h(A_{i+4}) always?
'v_n_eq_merged_endpts': 0,
# Is h constant on the 6 named vertices {v_n, A_0..A_4}?
'const_on_named6': 0,
}
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)]
v_n = 9999
A_i, A_i1, A_i2, A_i3, A_i4 = named_vertices(named, v_n)
for col in cand:
n_col += 1
try:
h = heawood_numbers(H, edges, col)
except RuntimeError:
continue
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
V_cap = V_b & V_c
h_b_vals = {h[v] for v in V_b}
h_c_vals = {h[v] for v in V_c}
h_cap_vals = {h[v] for v in V_cap}
if len(h_b_vals) == 1: rec['const_kb'] += 1
if len(h_c_vals) == 1: rec['const_kc'] += 1
if len(h_cap_vals) == 1: rec['const_cap'] += 1
plus = sum(1 for v in V_union if h[v] == 1)
minus = sum(1 for v in V_union if h[v] == -1)
rec['plus_minus_dist'][(plus, minus)] = \
rec['plus_minus_dist'].get((plus, minus), 0) + 1
# Majority sign on union
if plus > minus:
maj = 1
elif minus > plus:
maj = -1
else:
maj = 0
minority = sum(1 for v in V_union if h[v] != maj and maj != 0)
if maj == 0:
minority = min(plus, minus)
rec['minority_count'][minority] = \
rec['minority_count'].get(minority, 0) + 1
# v_n's position
hv = h.get(v_n)
if hv is not None and maj != 0:
if hv == maj:
rec['v_n_pos']['majority'] += 1
else:
rec['v_n_pos']['minority'] += 1
else:
rec['v_n_pos']['tie'] += 1
# Named vertices: minority count
for name, vv in [('v_n', v_n), ('A_i', A_i),
('A_i1', A_i1), ('A_i2', A_i2),
('A_i3', A_i3), ('A_i4', A_i4)]:
if vv in V_union and maj != 0 and h[vv] != maj:
rec['minority_at'][name] += 1
# h(v_n) == h(A_{i+3}) == h(A_{i+4}) ?
if h.get(v_n) == h.get(A_i3) == h.get(A_i4):
rec['v_n_eq_merged_endpts'] += 1
# h constant on the 6 named vertices?
named6 = [v_n, A_i, A_i1, A_i2, A_i3, A_i4]
named6_vals = {h[vv] for vv in named6 if vv in h}
if len(named6_vals) == 1:
rec['const_on_named6'] += 1
return n_col, rec
def merge_into(g, r):
for k in ('const_kb', 'const_kc', 'const_cap',
'v_n_eq_merged_endpts', 'const_on_named6'):
g[k] += r[k]
for k in ('plus_minus_dist', 'minority_count'):
for kk, vv in r[k].items():
g[k][kk] = g[k].get(kk, 0) + vv
for sub in ('v_n_pos', 'minority_at'):
for kk, vv in r[sub].items():
g[sub][kk] = g[sub].get(kk, 0) + vv
def main(max_n=18, time_budget_per_n=1800):
print(f"Constancy obstruction analysis, n in [12, {max_n}]\n")
grand = {
'const_kb': 0, 'const_kc': 0, 'const_cap': 0,
'plus_minus_dist': {}, 'minority_count': {},
'v_n_pos': {'minority': 0, 'majority': 0, 'tie': 0},
'minority_at': {'v_n': 0, 'A_i': 0, 'A_i1': 0, 'A_i2': 0,
'A_i3': 0, 'A_i4': 0},
'v_n_eq_merged_endpts': 0,
'const_on_named6': 0,
}
grand_col = 0
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
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, ri = test_one(D)
n_col_n += ni
merge_into(grand, ri)
elapsed = time.time() - start
print(f"n={n}: {n_col_n} col., [{elapsed:.0f}s]")
sys.stdout.flush()
grand_col += n_col_n
print()
print("=" * 78)
print(f"Grand totals (n in [12, {max_n}], {grand_col} colourings)")
print(f"\n h constant on V(K_b): {grand['const_kb']}/{grand_col}")
print(f" h constant on V(K_c): {grand['const_kc']}/{grand_col}")
print(f" h constant on V(K_b) cap V(K_c): {grand['const_cap']}/{grand_col}")
print(f" h constant on {{v_n, A_0..A_4}}: "
f"{grand['const_on_named6']}/{grand_col}")
print(f" h(v_n) == h(A_{{i+3}}) == h(A_{{i+4}}): "
f"{grand['v_n_eq_merged_endpts']}/{grand_col}")
print(f"\n Minority count (= #vertices on V(K_b) cup V(K_c) with "
f"non-majority h) distribution:")
for k, v in sorted(grand['minority_count'].items()):
pct = 100 * v / max(1, grand_col)
print(f" {k:>3}: {v} ({pct:.2f}%)")
print(f"\n v_n status on V(K_b) cup V(K_c):")
print(f" in majority: {grand['v_n_pos']['majority']} "
f"({100*grand['v_n_pos']['majority']/max(1,grand_col):.2f}%)")
print(f" in minority: {grand['v_n_pos']['minority']} "
f"({100*grand['v_n_pos']['minority']/max(1,grand_col):.2f}%)")
print(f" tie: {grand['v_n_pos']['tie']} "
f"({100*grand['v_n_pos']['tie']/max(1,grand_col):.2f}%)")
print(f"\n How often each named vertex is in the minority:")
for name, c in grand['minority_at'].items():
print(f" {name:>5}: {c}/{grand_col} "
f"({100*c/max(1,grand_col):.2f}%)")
if __name__ == '__main__':
main()