"""Probe: even when phi_t* is in a Kempe class with no all-distinct, does H_t* itself admit an all-distinct coloring in SOME (other) Kempe class? Reuses the Conj 3.6 counterexample: n=14, tri#1, i_red=0, phi_1 from the chord-apex+Kempe colorings. Enumerates every proper 3-edge-coloring of H_t*, marks the Kempe classes, and for each class reports whether it contains an all-distinct coloring. Run with: sage experiments/check_all_distinct_exists.py """ from sage.all import Graph from sage.graphs.graph_generators import graphs import sys 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) dual_edges = [(fs[0], fs[1]) for fs in edge_to_faces.values() if len(fs) == 2] return Graph(dual_edges, multiedges=False, loops=False) 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 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: return None if 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, 'A': A, 'named': { 'spike': frozenset(spike), 'side_0': frozenset(side_0), 'side_1': frozenset(side_1), 'merged': frozenset(merged), }, } def find_safe_pentagonal_face(G, protected): for face in G.faces(): if len(face) != 5: continue boundary = [u for (u, v) in face] boundary_edges = [frozenset([u, v]) for (u, v) in face] externals = [] A = [] ok = True for B_k in boundary: outer = [w for w in G.neighbor_iterator(B_k) if w not in boundary] if len(outer) != 1: ok = False break externals.append(frozenset([B_k, outer[0]])) A.append(outer[0]) if not ok: continue if any(e in protected for e in boundary_edges + externals): continue return boundary, externals, A return None def valid_index(f_vec, A): for i in range(5): if f_vec[(i + 3) % 5] != f_vec[(i + 4) % 5]: continue if len({f_vec[i], f_vec[(i + 1) % 5], f_vec[(i + 2) % 5]}) != 3: continue if A[(i + 3) % 5] == A[(i + 4) % 5]: continue return i return None def run_algorithm_to_completion(H, phi_1_dict, named_step1, vn_start=10000): H = H.copy() coloring = dict(phi_1_dict) all_named = [named_step1] E = set(named_step1.values()) next_vn = vn_start while True: H.is_planar(set_embedding=True) result = find_safe_pentagonal_face(H, E) if result is None: break boundary, externals, A = result f_vec = [coloring[e] for e in externals] i_t = valid_index(f_vec, A) if i_t is None: break v_n = next_vn next_vn += 1 H_new = H.copy() for v in boundary: H_new.delete_vertex(v) H_new.add_vertex(v_n) side_0 = (v_n, A[i_t]) spike = (v_n, A[(i_t + 1) % 5]) side_1 = (v_n, A[(i_t + 2) % 5]) merged = (A[(i_t + 3) % 5], A[(i_t + 4) % 5]) H_new.add_edges([side_0, spike, side_1, merged]) if H_new.has_multiple_edges() or H_new.has_loops(): break if not H_new.is_planar(set_embedding=True): break H = H_new coloring = {e: c for e, c in coloring.items() if not any(u in boundary for u in e)} coloring[frozenset(side_0)] = f_vec[i_t] coloring[frozenset(spike)] = f_vec[(i_t + 1) % 5] coloring[frozenset(side_1)] = f_vec[(i_t + 2) % 5] coloring[frozenset(merged)] = f_vec[(i_t + 3) % 5] named = { 'spike': frozenset(spike), 'side_0': frozenset(side_0), 'side_1': frozenset(side_1), 'merged': frozenset(merged), } E |= set(named.values()) all_named.append(named) return H, coloring, all_named def is_all_distinct(edges, col, all_named): for named in all_named: i_s = edge_idx(edges, named['spike']) i_m = edge_idx(edges, named['merged']) if i_s is None or i_m is None: return False if col[i_s] == col[i_m]: return False return True def main(): print("Reconstructing Conj 3.6 counterexample: n=14, tri#1, i_red=0") for G in graphs.triangulations(14, minimum_degree=5): D = dual_of(G) D.is_planar(set_embedding=True) chosen = None for face in D.faces(): if len(face) != 5: continue res = apply_reduction(D, face, 0, 9999) if res is None: continue H_1, named_1 = res['H'], res['named'] edges_1, colorings_1 = proper_3_edge_colorings(H_1) cand = [c for c in colorings_1 if matches_chord_apex_kempe(edges_1, c, named_1)] if cand: chosen = (face, res, cand) break if chosen is None: continue face, res, cand = chosen H_1, named_1 = res['H'], res['named'] edges_1, _ = proper_3_edge_colorings(H_1) if not cand: continue phi_1 = cand[0] phi_1_dict = {frozenset((e[0], e[1])): c for e, c in zip(edges_1, phi_1)} H_final, phi_final_dict, all_named = run_algorithm_to_completion( H_1, phi_1_dict, named_1) edges_f, colorings_f = proper_3_edge_colorings(H_final) print(f" H_t*: |V|={H_final.order()}, |E|={H_final.size()}, " f"{len(colorings_f)} proper 3-edge-colorings, " f"{len(all_named)} named-edge steps.") # Kempe equivalence classes via union-find col_idx = {c: i for i, c in enumerate(colorings_f)} parent = list(range(len(colorings_f))) def find(x): while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x def union(x, y): rx, ry = find(x), find(y) if rx != ry: parent[rx] = ry for c_idx, col in enumerate(colorings_f): for pair in [(0, 1), (0, 2), (1, 2)]: visited = set() for start in range(len(edges_f)): if col[start] not in pair or start in visited: continue cyc = kempe_cycle(edges_f, col, start, pair) visited |= cyc a, b = pair new_col = list(col) for k in cyc: if new_col[k] == a: new_col[k] = b elif new_col[k] == b: new_col[k] = a new_col = tuple(new_col) if new_col != col and new_col in col_idx: union(c_idx, col_idx[new_col]) roots = {} for i in range(len(colorings_f)): r = find(i) roots.setdefault(r, []).append(i) # phi_t* identification phi_final = tuple(phi_final_dict[frozenset((e[0], e[1]))] for e in edges_f) phi_idx = colorings_f.index(phi_final) phi_root = find(phi_idx) print() print(f" Found {len(roots)} Kempe equivalence classes:") for r, members in sorted(roots.items(), key=lambda kv: -len(kv[1])): ad = [m for m in members if is_all_distinct(edges_f, colorings_f[m], all_named)] label = " (contains phi_t*)" if r == phi_root else "" print(f" class size {len(members)}: " f"{len(ad)} all-distinct colorings{label}") return if __name__ == '__main__': main()