"""For the 1,314 bad chord-apex+Kempe colourings, check whether S-vertices form connected subgraphs (= are adjacent to each other), which would explain why their pentagon-coverage is below 3|S|. For each bad colouring: - Compute |S| and the induced subgraph H[S]. - # connected components of H[S]. - Edges in H[S] (= S-vertex pairs sharing an edge). - For each pair of adjacent S-vertices: how many G'-pentagons they share (= "overlap" that reduces pigeonhole bound). Run with: sage experiments/check_S_adjacency.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 test_one(D): D.is_planar(set_embedding=True) bad_count = 0 S_components_dist = {} # (|S|, # connected components in H[S]) -> count S_edges_dist = {} # (|S|, # edges in H[S]) -> count pentagon_overlap_dist = {} # (|S|, max overlap) -> count 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 for col in cand: target = {named['side_0'], named['spike']} lower_flank = None for f in H.faces(): if target.issubset({frozenset(e) for e in f}): lower_flank = f; break if lower_flank is None or len(lower_flank) != 5: continue arc_verts = [e[0] for e in lower_flank] if v_n not in arc_verts: continue k = arc_verts.index(v_n) cyc = arc_verts[k:] + arc_verts[:k] A_i = next(iter(named['side_0'] - {v_n})) A_ip1 = next(iter(named['spike'] - {v_n})) if cyc[1] == A_i and cyc[4] == A_ip1: P_1, P_2 = cyc[2], cyc[3] elif cyc[1] == A_ip1 and cyc[4] == A_i: P_2, P_1 = cyc[2], cyc[3] else: continue merged_idx = edge_idx(edges, named['merged']) c_col = col[merged_idx] c_0_col = col[edge_idx(edges, named['side_0'])] c_1_col = col[edge_idx(edges, named['side_1'])] 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_col or col[e_P1P2] != c_0_col: continue a = c_col other = [x for x in range(3) if x != a] 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) V_union = V_b | V_c S = set(H.vertices()) - V_union if P_1 in V_union: continue bad_count += 1 S_size = len(S) # H[S] = induced subgraph HS = H.subgraph(S) comps = HS.connected_components() key = (S_size, len(comps)) S_components_dist[key] = S_components_dist.get(key, 0) + 1 # Edges in H[S] n_edges = HS.size() key2 = (S_size, n_edges) S_edges_dist[key2] = S_edges_dist.get(key2, 0) + 1 # Pentagon overlap: for each pair of adjacent S-vertices, # count their shared G'-pentagons. max_overlap = 0 for u in S: for v_other in H.neighbors(u): if v_other not in S or v_other <= u: continue # Find G'-pentagons containing both u and v_other n_shared = 0 for f in H.faces(): if len(f) != 5: continue verts = {a for a, b in f} | {b for a, b in f} if u in verts and v_other in verts: n_shared += 1 max_overlap = max(max_overlap, n_shared) key3 = (S_size, max_overlap) pentagon_overlap_dist[key3] = ( pentagon_overlap_dist.get(key3, 0) + 1) return (bad_count, S_components_dist, S_edges_dist, pentagon_overlap_dist) def main(max_n=20, time_budget_per_n=1800): print("Connectivity structure of S-vertices in bad colourings.\n") grand_bad = 0 grand_comps = {} grand_edges = {} grand_overlap = {} 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_bad_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}") break G.is_planar(set_embedding=True) D = dual_of(G) nb, sc, se, po = test_one(D) n_bad_n += nb for k, v in sc.items(): grand_comps[k] = grand_comps.get(k, 0) + v for k, v in se.items(): grand_edges[k] = grand_edges.get(k, 0) + v for k, v in po.items(): grand_overlap[k] = grand_overlap.get(k, 0) + v elapsed = time.time() - start print(f"n={n}: {n_bad_n} bad colourings [{elapsed:.0f}s]") sys.stdout.flush() grand_bad += n_bad_n print() print("=" * 70) print(f"Total bad colourings: {grand_bad}") print("\n(|S|, # connected components in H[S]) distribution:") for k in sorted(grand_comps): c = grand_comps[k] print(f" |S|={k[0]}, #comps={k[1]}: {c}") print("\n(|S|, # edges in H[S]) distribution:") for k in sorted(grand_edges): c = grand_edges[k] print(f" |S|={k[0]}, #edges={k[1]}: {c}") print("\n(|S|, max # shared pentagons across adjacent S-pairs):") for k in sorted(grand_overlap): c = grand_overlap[k] print(f" |S|={k[0]}, max_overlap={k[1]}: {c}") if __name__ == '__main__': main()