41227c6a0f
- 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>
187 lines
7.2 KiB
Python
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()
|