Files
math-research/papers/face_monochromatic_pairs/experiments/check_dodecahedron_kempe.py
T
didericis 41227c6a0f papers: rename folders and retitle
- Main paper: dual_decomposition_minimal_counterexamples/ ->
  face_monochromatic_pairs/. Title is now
  "Face-Monochromatic Pairs and the Four Colour Theorem".
- Companion paper: dual_decomposition_iterated_reduction/ ->
  iterated_reduction_in_reduced_dual/. Title is now
  "An Iterated Reduction in the Reduced Dual". Its prose and bibliography
  cite the parent under the new title.
- Update one absolute sys.path reference inside
  check_conj_face_kempe_n15.py that pointed at the old folder.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 15:04:15 -04:00

187 lines
7.2 KiB
Python

"""Check whether the dodecahedron's reduced dual admits a proper
3-edge-colouring with:
(i) colour(spike) == colour(merged), AND
(ii) the {c_spike, c_side0}-Kempe cycle through the spike contains both
side-0 and the merged edge, AND
(iii) the {c_spike, c_side1}-Kempe cycle through the spike contains both
side-1 and the merged edge.
Definitions follow Algorithm 3.1 in paper.tex with G = icosahedron, G' =
dodecahedron, i_1 = 0; the reduced dual has 16 vertices and 24 edges.
Lemmas 2.6 and 2.7 force these properties for ANY proper 3-edge-colouring
of the reduced dual of a *minimal counterexample's* dual. The dodecahedron
is the dual of the icosahedron --- which IS 4-colourable, so the lemmas do
not apply and the question is non-trivial.
"""
from sage.all import Graph
def build_reduced_dodecahedron(i_red=0):
edges = []
for k in range(5):
edges.append((('b', k), ('c', k)))
edges.append((('b', k), ('c', (k - 1) % 5)))
edges.append((('c', k), ('d', k)))
edges.append((('d', k), ('d', (k + 1) % 5)))
v_n = 'v_n'
edges.append((v_n, ('b', i_red % 5)))
edges.append((v_n, ('b', (i_red + 1) % 5)))
edges.append((v_n, ('b', (i_red + 2) % 5)))
edges.append((('b', (i_red + 3) % 5), ('b', (i_red + 4) % 5)))
return Graph(edges, multiedges=False, loops=False)
def enumerate_proper_3_edge_colorings(G):
"""Return (edge_list, list_of_colorings) where each coloring is a list of
3-edge-colours indexed by edge_list."""
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(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(edges, coloring, start_edge_idx, color_pair):
"""Return the set of edge-indices in the Kempe cycle containing
edges[start_edge_idx] in the subgraph of edges with colour in
color_pair."""
a, b = color_pair
in_sub = [i for i in range(len(edges)) if coloring[i] in (a, b)]
if start_edge_idx not in in_sub:
return None
visited = {start_edge_idx}
stack = [start_edge_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 main():
G = build_reduced_dodecahedron(i_red=0)
print(f"|V| = {G.order()}, |E| = {G.size()}")
edges, colorings = enumerate_proper_3_edge_colorings(G)
print(f"Proper 3-edge-colorings total: {len(colorings)}")
v_n = 'v_n'
spike_set = frozenset({v_n, ('b', 1)})
side_0_set = frozenset({v_n, ('b', 0)})
side_1_set = frozenset({v_n, ('b', 2)})
merged_set = frozenset({('b', 3), ('b', 4)})
idx = {}
for i, e in enumerate(edges):
s = frozenset((e[0], e[1]))
if s == spike_set: idx['spike'] = i
if s == side_0_set: idx['side_0'] = i
if s == side_1_set: idx['side_1'] = i
if s == merged_set: idx['merged'] = i
n_chord_apex = 0 # spike == merged
n_kc0_ok = 0 # condition (ii)
n_kc1_ok = 0 # condition (iii)
n_all_ok = 0 # all three
example = None
for col in colorings:
c_spike = col[idx['spike']]
c_merged = col[idx['merged']]
c_s0 = col[idx['side_0']]
c_s1 = col[idx['side_1']]
if c_spike != c_merged:
continue
n_chord_apex += 1
kc0 = kempe_cycle_edges(edges, col, idx['spike'], (c_spike, c_s0))
kc1 = kempe_cycle_edges(edges, col, idx['spike'], (c_spike, c_s1))
kc0_ok = kc0 is not None and idx['side_0'] in kc0 and idx['merged'] in kc0
kc1_ok = kc1 is not None and idx['side_1'] in kc1 and idx['merged'] in kc1
n_kc0_ok += int(kc0_ok)
n_kc1_ok += int(kc1_ok)
if kc0_ok and kc1_ok:
n_all_ok += 1
if example is None:
example = (col, kc0, kc1)
print()
print(f"Colorings with spike == merged (chord-apex condition): {n_chord_apex}")
print(f" ... + {{c, c_0}}-Kempe cycle through spike/side-0/merged: {n_kc0_ok}")
print(f" ... + {{c, c_1}}-Kempe cycle through spike/side-1/merged: {n_kc1_ok}")
print(f" ... ALL THREE conditions: {n_all_ok}")
if example is not None:
col, kc0, kc1 = example
c_spike = col[idx['spike']]
print()
print("Example coloring (one of {} matching):".format(n_all_ok))
for role in ('spike', 'side_0', 'side_1', 'merged'):
print(f" {role:7s}: colour {col[idx[role]]}, edge {tuple(edges[idx[role]])}")
print(f" spike colour = merged colour = {c_spike}")
print(f" Kempe cycle {{c, c_0}} (through spike): {len(kc0)} edges")
for i in sorted(kc0):
print(f" [c={col[i]}] {edges[i]}")
print(f" Kempe cycle {{c, c_1}} (through spike): {len(kc1)} edges")
for i in sorted(kc1):
print(f" [c={col[i]}] {edges[i]}")
else:
# Dissect a sample chord-apex coloring that fails the Kempe condition
print()
print("Sample chord-apex coloring (fails the Kempe-chain condition):")
for col in colorings:
if col[idx['spike']] != col[idx['merged']]:
continue
c_spike = col[idx['spike']]
c_s0 = col[idx['side_0']]
c_s1 = col[idx['side_1']]
for role in ('spike', 'side_0', 'side_1', 'merged'):
print(f" {role:7s}: colour {col[idx[role]]}, edge {tuple(edges[idx[role]])}")
kc0 = kempe_cycle_edges(edges, col, idx['spike'], (c_spike, c_s0))
kc1 = kempe_cycle_edges(edges, col, idx['spike'], (c_spike, c_s1))
kc_m0 = kempe_cycle_edges(edges, col, idx['merged'], (c_spike, c_s0))
kc_m1 = kempe_cycle_edges(edges, col, idx['merged'], (c_spike, c_s1))
print(f" spike's {{c, c_0}}-Kempe cycle has {len(kc0)} edges; "
f"side_0 in it = {idx['side_0'] in kc0}, "
f"merged in it = {idx['merged'] in kc0}")
print(f" merged's {{c, c_0}}-Kempe cycle has {len(kc_m0)} edges; "
f"disjoint from spike's? {kc0.isdisjoint(kc_m0)}")
print(f" spike's {{c, c_1}}-Kempe cycle has {len(kc1)} edges; "
f"side_1 in it = {idx['side_1'] in kc1}, "
f"merged in it = {idx['merged'] in kc1}")
print(f" merged's {{c, c_1}}-Kempe cycle has {len(kc_m1)} edges; "
f"disjoint from spike's? {kc1.isdisjoint(kc_m1)}")
break
if __name__ == '__main__':
main()