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

216 lines
8.8 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.
"""Check whether sub-case (ii.B) of Case (b) of the Flank-covering
lemma (n_i = 6) ever arises in chord-apex+Kempe colourings.
Sub-case (ii.B) is the configuration in which the structural
propagation argument in the partial proof has a real gap:
- n_i = 6: F_flank_{i, i+1}^♭ has 2 intermediates P_1, P_2 on the
F-arc from A_i to A_{i+1}.
- Case (b): varphi(A_i P_1) = c_1.
- Sub-case (ii): varphi(P_1 P_2) = c_0.
- Derived: varphi(A_{i+1} P_2) = c_1, varphi(P_2 O_2) = c,
varphi(P_1 O_1) = c, varphi(A_i Q) = c.
In this sub-case the lemma's local argument (that the cycle at P_2
passes from P_2 to P_1) needs P_2 ∈ V(K_b), which isn't forced from
the local data: the {c, c_0}-Kempe cycle through P_2 might not be K_b.
We check: across all chord-apex+Kempe colourings of all reduced duals
with |V(G)| ≤ 20, does sub-case (ii.B) ever occur? If yes, in how many
of those configurations is P_1 actually in V(K_b) V(K_c) (i.e., the
conclusion of the lemma still holds even though our proof gap is
real)?
Run with: sage experiments/check_subcase_iib.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_flank_arc_intermediates(H, named, i, side):
"""Return the ordered F-arc from A_i (or A_{i+1}) along F's
boundary as a list of vertices.
side='lower': arc from A_i to A_{i+1} (= F_flank_{i, i+1}^♭).
side='upper': arc from A_{i+1} to A_{i+2} (= F_flank_{i+1, i+2}^♭).
We use the planar embedding of H to walk around the
face containing v_n + side_0 + spike (lower) or v_n + spike + side_1
(upper).
"""
H.is_planar(set_embedding=True)
spike = named['spike'] # frozenset({A_{i+1}, v_n})
side_0 = named['side_0']
side_1 = named['side_1']
# The flank face contains v_n + spike + side_0/side_1 + the arc edges
target_edges = set()
if side == 'lower':
target_edges = {side_0, spike}
else:
target_edges = {side_1, spike}
for face in H.faces():
# face is list of (u, v) edges (or tuples of vertex pairs)
face_edges_set = {frozenset(e) for e in face}
if target_edges.issubset(face_edges_set):
# Trace the boundary, return the vertices in cyclic order
verts = []
for e in face:
verts.append(e[0])
return verts
return None
def test_one(D):
D.is_planar(set_embedding=True)
n_col = 0
n_subcase_iib = 0
n_subcase_iib_p1_covered = 0 # of those, how many have P_1 ∈ V(K_b) V(K_c)
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
# We need to identify P_1, P_2, A_i, A_{i+1}, etc.
# Recover the named vertices from `named`.
v_n = 9999
# named['side_0'] = {A_i, v_n}, named['spike'] = {A_{i+1}, v_n}
# side_1 = {A_{i+2}, v_n}, merged = {A_{i+3}, A_{i+4}}
A_i = next(iter(named['side_0'] - {v_n}))
A_ip1 = next(iter(named['spike'] - {v_n}))
A_ip2 = next(iter(named['side_1'] - {v_n}))
A_ip3, A_ip4 = sorted(named['merged'])
# Get flank arc for lower (A_i → A_{i+1})
# The face F_flank_{i, i+1}^♭ has boundary v_n - A_i - ... - A_{i+1} - v_n
# Find this face:
target_edges = {named['side_0'], named['spike']}
arc_verts = None
for f in H.faces():
f_edges = {frozenset(e) for e in f}
if target_edges.issubset(f_edges):
arc_verts = [e[0] for e in f]
break
if arc_verts is None or len(arc_verts) != 5:
# Not the n_i = 6 case (length 5 flank face)
continue
# Identify P_1, P_2 along the arc
# The cycle visits v_n, A_i, P_1, P_2, A_{i+1} in some order
# (possibly reversed). Find the index of v_n.
if v_n not in arc_verts: continue
k = arc_verts.index(v_n)
cyc = arc_verts[k:] + arc_verts[:k] # rotate so v_n is first
# cyc should be [v_n, A_i, P_1, P_2, A_{i+1}] or
# [v_n, A_{i+1}, P_2, P_1, A_i]
if cyc[1] == A_i:
P_1, P_2 = cyc[2], cyc[3]
assert cyc[4] == A_ip1
elif cyc[1] == A_ip1:
P_2, P_1 = cyc[2], cyc[3]
assert cyc[4] == A_i
else:
continue
# Check sub-case (ii.B): φ(A_i P_1) = c_1, φ(P_1 P_2) = c_0
# c = color of merged/spike
merged_idx = edge_idx(edges, named['merged'])
c = col[merged_idx]
# side_0 has color c_0
side_0_idx = edge_idx(edges, named['side_0'])
c_0 = col[side_0_idx]
# side_1 has color c_1
side_1_idx = edge_idx(edges, named['side_1'])
c_1 = col[side_1_idx]
# Get edge colours of (A_i, P_1) and (P_1, P_2)
e_AiP1 = edge_idx(edges, frozenset((A_i, P_1)))
e_P1P2 = edge_idx(edges, frozenset((P_1, P_2)))
if e_AiP1 is None or e_P1P2 is None:
continue
if col[e_AiP1] != c_1 or col[e_P1P2] != c_0:
continue
# Sub-case (ii.B) detected
n_subcase_iib += 1
# Check if P_1 ∈ V(K_b) V(K_c)
a = c
other = [x for x in range(3) if x != a]
# K_b = {a, b_color} Kempe cycle through merged
kc_b = kempe_cycle_set(edges, col, merged_idx, (a, other[0]))
kc_c = kempe_cycle_set(edges, col, merged_idx, (a, other[1]))
V_b = vertices_of_kempe(edges, kc_b)
V_c = vertices_of_kempe(edges, kc_c)
if P_1 in V_b or P_1 in V_c:
n_subcase_iib_p1_covered += 1
return n_col, n_subcase_iib, n_subcase_iib_p1_covered
def main(max_n=20, time_budget_per_n=1800):
print("Check: how often does sub-case (ii.B) of Case (b) of\n"
"Lemma 'Flank covering, n_i = 6' actually arise?\n"
f"n_G in [12, {max_n}]\n")
grand_col = 0
grand_sub = 0
grand_sub_covered = 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; n_sub_n = 0; n_cov_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, ns, ncov = test_one(D)
n_col_n += ni; n_sub_n += ns; n_cov_n += ncov
elapsed = time.time() - start
print(f"n={n}: {n_col_n} col., {n_sub_n} sub-case (ii.B) "
f"({100*n_sub_n/max(n_col_n, 1):.2f}% of col.); "
f"{n_cov_n} of those have P_1 ∈ V(K_b)V(K_c) "
f"[{elapsed:.0f}s]")
sys.stdout.flush()
grand_col += n_col_n
grand_sub += n_sub_n
grand_sub_covered += n_cov_n
print()
print("=" * 70)
print(f"Grand totals: {grand_col} chord-apex+Kempe colourings")
print(f" sub-case (ii.B) detected: {grand_sub} "
f"({100*grand_sub/max(grand_col, 1):.2f}%)")
if grand_sub > 0:
print(f" of those, P_1 ∈ V(K_b)V(K_c): {grand_sub_covered} "
f"({100*grand_sub_covered/max(grand_sub, 1):.2f}%)")
gap_open = grand_sub - grand_sub_covered
print(f" of those, P_1 NOT in V(K_b)V(K_c): {gap_open}")
else:
print(" Sub-case (ii.B) never arises empirically.")
print(" → the proof gap is structurally INACTIVE")
print(" → the n_i = 6 lemma's conclusion is empirically tight")
if __name__ == '__main__':
main()