"""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()