"""Test Conjecture 3.6: for every reduced dual of a minimal counterexample, every proper 3-edge-coloring has a face with two same-color edges that share a Kempe cycle with the merged edge. We can't run on actual counterexamples (none exist by the 4CT). Instead we test on the structural surrogates: proper 3-edge-colorings of reduced duals that *satisfy* chord-apex (Lemma 2.6) and the Kempe-cycle condition (Lemma 2.7). These are the kinds of colorings a counterexample's reduced dual would be forced to admit. For each such (G, face, i_red, phi), check whether some face F of H_1 and some pair of edges (e1, e2) in partial F satisfy: - phi(e1) = phi(e2) - e1, e2, and merged all lie on a common {a,b}-Kempe cycle. If conjecture FAILS for some coloring, we have empirical evidence against. If every coloring passes, evidence in favour. Run with: sage experiments/check_conj_face_kempe.py """ from sage.all import Graph from sage.graphs.graph_generators import graphs 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 apply_reduction(G, face, i, v_n_label): boundary = [u for (u, v) in face] if len(set(boundary)) != 5: return None A = [] for B_k in boundary: outer = [w for w in G.neighbor_iterator(B_k) if w not in boundary] if len(outer) != 1: return None A.append(outer[0]) if len(set(A)) != 5 or A[(i+3) % 5] == A[(i+4) % 5]: return None H = G.copy() for v in boundary: H.delete_vertex(v) H.add_vertex(v_n_label) side_0 = (v_n_label, A[i]) spike = (v_n_label, A[(i+1) % 5]) side_1 = (v_n_label, A[(i+2) % 5]) merged = (A[(i+3) % 5], A[(i+4) % 5]) H.add_edges([side_0, spike, side_1, merged]) if H.has_multiple_edges() or H.has_loops(): return None if not H.is_planar(set_embedding=True): return None if not all(H.degree(v) == 3 for v in H.vertex_iterator()): return None return { 'H': H, 'named': { 'spike': frozenset(spike), 'side_0': frozenset(side_0), 'side_1': frozenset(side_1), 'merged': frozenset(merged), }, } def proper_3_edge_colorings(G): edges = list(G.edges(labels=False)) n = len(edges) adj = [[] for _ in range(n)] for i in range(n): u, v = edges[i][0], edges[i][1] for j in range(i): x, y = edges[j][0], edges[j][1] if u in (x, y) or v in (x, y): adj[i].append(j); adj[j].append(i) coloring = [-1] * n results = [] def back(k): if k == n: results.append(tuple(coloring)) return for c in range(3): if all(coloring[j] != c for j in adj[k]): coloring[k] = c back(k + 1) coloring[k] = -1 back(0) return edges, results def kempe_cycle(edges, coloring, start_idx, color_pair): a, b = color_pair if coloring[start_idx] not in (a, b): return set() in_sub = set(i for i in range(len(edges)) if coloring[i] in (a, b)) visited = {start_idx} stack = [start_idx] while stack: cur = stack.pop() u, v = edges[cur][0], edges[cur][1] for j in in_sub: if j in visited: continue x, y = edges[j][0], edges[j][1] if u in (x, y) or v in (x, y): visited.add(j); stack.append(j) return visited def edge_idx(edges, e_frozen): for i, e in enumerate(edges): if frozenset((e[0], e[1])) == e_frozen: return i return None def matches_chord_apex_kempe(edges, col, named): idx = {role: edge_idx(edges, ns) for role, ns in named.items()} if any(v is None for v in idx.values()): return False c_spike = col[idx['spike']] c_merged = col[idx['merged']] if c_spike != c_merged: return False c_s0 = col[idx['side_0']]; c_s1 = col[idx['side_1']] kc0 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s0)) if idx['side_0'] not in kc0 or idx['merged'] not in kc0: return False kc1 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s1)) if idx['side_1'] not in kc1 or idx['merged'] not in kc1: return False return True def conjecture_holds_for(H, edges, col, named): """Returns (F, e1, e2, kc) if some face F of H has two same-colour edges (e1, e2), neither equal to merged, both on a Kempe cycle kc through merged, AND exactly one edge of partial F lies between e1 and e2 along one of the two arcs of partial F. Else None.""" merged_idx = edge_idx(edges, named['merged']) c_merged = col[merged_idx] kempe_cycles = [] for c_prime in range(3): if c_prime == c_merged: continue kc = kempe_cycle(edges, col, merged_idx, (c_merged, c_prime)) kempe_cycles.append(kc) for face in H.faces(): face_edge_indices = [] for u, v in face: ei = edge_idx(edges, frozenset((u, v))) if ei is not None: face_edge_indices.append(ei) n_face = len(face_edge_indices) for i in range(n_face): for j in range(i + 1, n_face): e1, e2 = face_edge_indices[i], face_edge_indices[j] if e1 == merged_idx or e2 == merged_idx: continue if col[e1] != col[e2]: continue # exactly one edge between e1 and e2 in one arc gap_a = (j - i - 1) gap_b = (n_face - 2 - gap_a) if gap_a != 1 and gap_b != 1: continue for kc in kempe_cycles: if e1 in kc and e2 in kc: return face, e1, e2, kc return None def main(): for n in [12, 14, 15]: print(f"=== n = {n} ===") try: triangulations = list(graphs.triangulations(n, minimum_degree=5)) except Exception as ex: print(f" cannot enumerate: {ex}"); continue for tri_idx, G in enumerate(triangulations, start=1): G.is_planar(set_embedding=True) D = dual_of(G); D.is_planar(set_embedding=True) for face_no, face in enumerate( f for f in D.faces() if len(f) == 5): 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)] if not cand: continue n_pass = sum(1 for c in cand if conjecture_holds_for(H, edges, c, named) is not None) n_fail = len(cand) - n_pass status = "PASSED" if n_fail == 0 else "*** FAILED ***" print(f" n={n} tri#{tri_idx} face#{face_no} i_red={i_red}: " f"{len(cand)} chord-apex+Kempe colorings; " f"{n_pass} satisfy conjecture, {n_fail} fail. " f"{status}") if __name__ == '__main__': main()