dual_decomposition: iterated-reduction algorithm + Kempe/chord-apex search

- Add section 3 with Algorithm 3.1 (iterated reduction with protected edges)
  and remarks on invariants and chord-apex applicability.
- Add fig:iterated-reduction-trace illustrating the algorithm on G' =
  dodecahedron (G' -> H_1 -> H_2 -> terminate).
- experiments/iterated_reduction.py: Sage implementation of the algorithm.
- experiments/draw_iterated_reduction.py: produces the 3 trace figures.
- experiments/check_dodecahedron_kempe.py: enumerate proper 3-edge-colorings
  of the dodecahedron's reduced dual and check the chord-apex + Kempe-cycle
  conditions (0 of 36 colorings satisfy all three).
- experiments/search_kempe_property.py: search across min-deg-5
  triangulations; the n = 14 first plantri triangulation is the smallest hit
  (reduced dual has 20 v, 30 e).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 12:40:38 -04:00
parent 192ad33bd2
commit c987259c14
11 changed files with 1057 additions and 55 deletions
@@ -0,0 +1,186 @@
"""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()
@@ -0,0 +1,219 @@
"""Draw the iterated reduction algorithm's trace on the dodecahedron.
Produces three figures:
fig_alg_step0.png -- G' (dodecahedron) with F_v (inner pentagon) shaded.
fig_alg_step1.png -- H_1 (post step 1), 3-edge-coloured; 4 protected edges.
fig_alg_step2.png -- H_2 (post step 2), 3-edge-coloured; 8 protected edges;
algorithm terminates.
Run with: sage experiments/draw_iterated_reduction.py
"""
from sage.all import Graph
from sage.graphs.graph_coloring import edge_coloring
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import math
import os
OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
C = ['#dc2626', '#16a34a', '#2563eb'] # proper-edge-colour palette
GRAY = '#9ca3af'
DARK = '#374151'
HIGHLIGHT = '#fef3c7'
def dodecahedron_positions():
pos = {}
R = {'a': 1.0, 'b': 2.2, 'c': 3.6, 'd': 4.8}
for i in range(5):
for fam in ('a', 'b'):
th = math.radians(90 - 72 * i)
pos[(fam, i)] = (R[fam] * math.cos(th), R[fam] * math.sin(th))
for fam in ('c', 'd'):
th = math.radians(90 - 72 * i - 36)
pos[(fam, i)] = (R[fam] * math.cos(th), R[fam] * math.sin(th))
return pos
def build_dodecahedron():
edges = []
for i in range(5):
edges.append((('a', i), ('a', (i + 1) % 5)))
edges.append((('a', i), ('b', i)))
edges.append((('b', i), ('c', i)))
edges.append((('b', i), ('c', (i - 1) % 5)))
edges.append((('c', i), ('d', i)))
edges.append((('d', i), ('d', (i + 1) % 5)))
G = Graph(edges, multiedges=False, loops=False)
G.is_planar(set_embedding=True)
return G
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 = []
for B_k in boundary:
outer = [w for w in G.neighbor_iterator(B_k) if w not in boundary]
if len(outer) != 1:
break
externals.append(frozenset([B_k, outer[0]]))
A.append(outer[0])
else:
if not any(e in protected for e in boundary_edges + externals):
return boundary, externals, A
return None
def valid_indices(f_vec):
out = []
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:
out.append(i)
return out
def draw(ax, G, pos, *, coloring=None, protected=None,
shade_face=None):
if shade_face:
poly = [pos[v] for v in shade_face]
ax.add_patch(Polygon(poly, closed=True, facecolor=HIGHLIGHT,
edgecolor='none', zorder=0))
protected = protected or set()
for u, v in G.edges(labels=False):
e = frozenset([u, v])
c = C[coloring[e]] if coloring is not None else GRAY
lw = 3.8 if e in protected else 1.4
(x0, y0), (x1, y1) = pos[u], pos[v]
ax.plot([x0, x1], [y0, y1], color=c, lw=lw, zorder=2)
for v in G.vertices(sort=False):
x, y = pos[v]
if isinstance(v, tuple) and v[0] == 'v_n':
t = v[1]
ax.scatter(x, y, s=320, color=HIGHLIGHT, marker='s',
edgecolors='black', linewidths=1.2, zorder=4)
ax.annotate(f'$v_n^{{({t})}}$', (x, y),
textcoords='offset points', xytext=(16, 16),
ha='left', fontsize=14, fontweight='bold',
color=DARK, zorder=6,
bbox=dict(boxstyle='round,pad=0.2', fc='white',
ec=DARK, lw=0.6))
else:
ax.scatter(x, y, s=70, color=DARK, zorder=3)
ax.set_aspect('equal')
ax.axis('off')
def main():
G = build_dodecahedron()
pos = dodecahedron_positions()
F_v = [('a', i) for i in range(5)]
# ----- Step 0: G' with F_v shaded -----
fig, ax = plt.subplots(figsize=(8, 8))
draw(ax, G, pos, shade_face=F_v)
fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step0.png'),
dpi=170, bbox_inches='tight')
plt.close(fig)
# ----- Step 1: Definition 2.1 at F_v with i_1 = 0 -----
safe = find_safe_pentagonal_face(G, set())
boundary_1, externals_1, A_1 = safe
G1 = G.copy()
for v in boundary_1:
G1.delete_vertex(v)
v_n_1 = ('v_n', 1)
G1.add_vertex(v_n_1)
G1.add_edge(v_n_1, A_1[0])
G1.add_edge(v_n_1, A_1[1])
G1.add_edge(v_n_1, A_1[2])
G1.add_edge(A_1[3], A_1[4])
G1.is_planar(set_embedding=True)
pos1 = {v: p for v, p in pos.items() if v not in boundary_1}
cx = (pos[A_1[0]][0] + pos[A_1[1]][0] + pos[A_1[2]][0]) / 3
cy = (pos[A_1[0]][1] + pos[A_1[1]][1] + pos[A_1[2]][1]) / 3
pos1[v_n_1] = (cx * 0.55, cy * 0.55)
cols = edge_coloring(G1, value_only=False)
coloring = {}
for k, edge_list in enumerate(cols):
for u, v in edge_list:
coloring[frozenset([u, v])] = k
E = {
frozenset([v_n_1, A_1[1]]), # spike
frozenset([v_n_1, A_1[0]]), # side-0
frozenset([v_n_1, A_1[2]]), # side-1
frozenset([A_1[3], A_1[4]]), # merged
}
fig, ax = plt.subplots(figsize=(8, 8))
draw(ax, G1, pos1, coloring=coloring, protected=E)
fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step1.png'),
dpi=170, bbox_inches='tight')
plt.close(fig)
# ----- Step 2: reduce at the only remaining safe face (outer pentagon) -----
safe = find_safe_pentagonal_face(G1, E)
if safe is None:
print("ERROR: expected an outer pentagonal face but none found.")
return
boundary_2, externals_2, A_2 = safe
f_vec = [coloring[e] for e in externals_2]
choices = valid_indices(f_vec)
if not choices:
print(f"ERROR: f-vector {f_vec} has no valid index.")
return
i_t = choices[0]
G2 = G1.copy()
for v in boundary_2:
G2.delete_vertex(v)
v_n_2 = ('v_n', 2)
G2.add_vertex(v_n_2)
G2.add_edge(v_n_2, A_2[i_t])
G2.add_edge(v_n_2, A_2[(i_t + 1) % 5])
G2.add_edge(v_n_2, A_2[(i_t + 2) % 5])
G2.add_edge(A_2[(i_t + 3) % 5], A_2[(i_t + 4) % 5])
G2.is_planar(set_embedding=True)
coloring2 = {e: c for e, c in coloring.items()
if not any(u in boundary_2 for u in e)}
side_0_2 = frozenset([v_n_2, A_2[i_t]])
spike_2 = frozenset([v_n_2, A_2[(i_t + 1) % 5]])
side_1_2 = frozenset([v_n_2, A_2[(i_t + 2) % 5]])
merged_2 = frozenset([A_2[(i_t + 3) % 5], A_2[(i_t + 4) % 5]])
coloring2[side_0_2] = coloring[externals_2[i_t]]
coloring2[spike_2] = coloring[externals_2[(i_t + 1) % 5]]
coloring2[side_1_2] = coloring[externals_2[(i_t + 2) % 5]]
coloring2[merged_2] = coloring[externals_2[(i_t + 3) % 5]]
pos2 = {v: p for v, p in pos1.items() if v not in boundary_2}
nbrs = [A_2[i_t], A_2[(i_t + 1) % 5], A_2[(i_t + 2) % 5]]
cx = sum(pos2[a][0] for a in nbrs) / 3
cy = sum(pos2[a][1] for a in nbrs) / 3
r = math.hypot(cx, cy)
# v_n^{(2)} lies outside the surviving graph (the deleted d's were outermost)
target_r = 5.0
pos2[v_n_2] = (cx * target_r / r, cy * target_r / r)
E |= {side_0_2, spike_2, side_1_2, merged_2}
fig, ax = plt.subplots(figsize=(8, 8))
draw(ax, G2, pos2, coloring=coloring2, protected=E)
fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step2.png'),
dpi=170, bbox_inches='tight')
plt.close(fig)
print(f"Wrote fig_alg_step{{0,1,2}}.png to {OUT_DIR}")
if __name__ == '__main__':
main()
@@ -0,0 +1,238 @@
"""Iterated reduced-dual reduction with protected edges (Sage).
Implements Algorithm 3.1 from paper.tex:
0. G' = dual of G (a triangulation conjectured to be a minimal
counterexample).
1. Apply Definition 2.1 to G' at a pentagonal face to get H_1; find a
proper 3-edge-colouring phi_1 of H_1.
2. Initialise protected edge set E := {spike, side-0, side-1, merged} from
step 1.
3. Iterate: find a pentagonal face of H_{t-1} whose 10 incident edges are
disjoint from E, pick a valid index i_t (Lemma 2.4), apply
Definition 2.1, extend phi_{t-1} to phi_t, and add the four new named
edges to E.
4. Terminate when no safe pentagonal face exists.
We run on the icosahedron-dodecahedron pair as a concrete trace; the
icosahedron is 4-colourable, so the dodecahedron is 3-edge-colourable and
the algorithm terminates combinatorially.
Run with:
sage experiments/iterated_reduction.py
"""
from sage.all import Graph
from sage.graphs.graph_coloring import edge_coloring
# ---------------------------------------------------------------------------
# Build G' = dodecahedron with the same naming as experiments/reduced_dual.py:
# vertex families a (inner pentagon, will play the role of partial F_v's
# boundary vertices for the first reduction), b, c, d.
# ---------------------------------------------------------------------------
def build_dodecahedron():
edges = []
for i in range(5):
edges.append((('a', i), ('a', (i + 1) % 5))) # inner pentagon
edges.append((('a', i), ('b', i))) # spoke a-b
edges.append((('b', i), ('c', i))) # b-c
edges.append((('b', i), ('c', (i - 1) % 5))) # b-c (other side)
edges.append((('c', i), ('d', i))) # spoke c-d
edges.append((('d', i), ('d', (i + 1) % 5))) # outer pentagon
G = Graph(edges, multiedges=False, loops=False)
assert G.is_planar(set_embedding=True), "dodecahedron not planar?"
return G
# ---------------------------------------------------------------------------
# 3-edge-colour a cubic plane graph; return None if not 3-edge-colourable.
# ---------------------------------------------------------------------------
def proper_3_coloring(G):
classes = edge_coloring(G, value_only=False)
if len(classes) > 3:
return None
out = {}
for k, edge_list in enumerate(classes):
for u, v in edge_list:
out[frozenset([u, v])] = k
return out
# ---------------------------------------------------------------------------
# Face search: pentagonal face whose 10 incident edges are outside `protected`.
# Returns (boundary, externals, A) or None.
# ---------------------------------------------------------------------------
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]
# the external edge at each boundary vertex (the one not on the face)
externals = []
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:
# In a cubic graph each face-boundary vertex has exactly one
# external edge; otherwise something is off.
break
externals.append(frozenset([B_k, outer[0]]))
A.append(outer[0])
else:
if not any(e in protected for e in boundary_edges + externals):
return boundary, externals, A
return None
# ---------------------------------------------------------------------------
# Lemma 2.4: any proper 3-edge-colouring forces the external vector at a
# pentagonal face to have shape (a, b, c, c, c) up to cyclic rotation. The
# valid index i for the reduction is one where positions i+3, i+4 host the
# doubled colour and positions i, i+1, i+2 host three distinct colours.
# ---------------------------------------------------------------------------
def valid_indices(f_vec):
out = []
for i in range(5):
if f_vec[(i + 3) % 5] != f_vec[(i + 4) % 5]:
continue
triple = {f_vec[i], f_vec[(i + 1) % 5], f_vec[(i + 2) % 5]}
if len(triple) == 3:
out.append(i)
return out
# ---------------------------------------------------------------------------
# Apply Definition 2.1 at (F, i): delete the 5 boundary vertices, add v_n
# connected to A[i], A[i+1], A[i+2], add the chord A[i+3]-A[i+4]. Extend the
# colouring: every surviving edge keeps its colour; each new edge takes the
# colour of the deleted external at the same A_k (the unique third colour at
# A_k under the surviving edges).
# ---------------------------------------------------------------------------
def apply_reduction(G, boundary, externals, A, coloring, i, v_n_label):
G_new = G.copy()
for v in boundary:
G_new.delete_vertex(v)
G_new.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])
G_new.add_edges([side_0, spike, side_1, merged])
assert G_new.is_planar(set_embedding=True), "reduction broke planarity"
coloring_new = {
e: c for e, c in coloring.items() if not any(u in boundary for u in e)
}
coloring_new[frozenset(side_0)] = coloring[externals[i]]
coloring_new[frozenset(spike)] = coloring[externals[(i + 1) % 5]]
coloring_new[frozenset(side_1)] = coloring[externals[(i + 2) % 5]]
coloring_new[frozenset(merged)] = coloring[externals[(i + 3) % 5]]
named = {
'spike': frozenset(spike),
'side_0': frozenset(side_0),
'side_1': frozenset(side_1),
'merged': frozenset(merged),
}
return G_new, coloring_new, named
# ---------------------------------------------------------------------------
# Sanity check: verify that a coloring is proper (each vertex sees 3 colours).
# ---------------------------------------------------------------------------
def is_proper_3_coloring(G, coloring):
for v in G.vertex_iterator():
seen = set()
for u in G.neighbor_iterator(v):
seen.add(coloring[frozenset([u, v])])
if len(seen) != G.degree(v):
return False
return True
# ---------------------------------------------------------------------------
# Main driver.
# ---------------------------------------------------------------------------
def run_algorithm(max_iterations=50, verbose=True):
if verbose:
print("STEP 0: G' = dodecahedron (dual of the icosahedron)")
G_prime = build_dodecahedron()
if verbose:
print(f" |V(G')| = {G_prime.order()}, |E(G')| = {G_prime.size()}")
# ---- STEP 1: apply Definition 2.1 to G' at the inner pentagon, i_1 = 0
face_data = find_safe_pentagonal_face(G_prime, set())
if face_data is None:
print(" No pentagonal face in G'.")
return
boundary, externals, A = face_data
H = G_prime.copy()
for v in boundary:
H.delete_vertex(v)
v_n_label = ('v_n', 1)
H.add_vertex(v_n_label)
side_0 = (v_n_label, A[0])
spike = (v_n_label, A[1])
side_1 = (v_n_label, A[2])
merged = (A[3], A[4])
H.add_edges([side_0, spike, side_1, merged])
assert H.is_planar(set_embedding=True)
coloring = proper_3_coloring(H)
if coloring is None:
print(" H_1 is not 3-edge-colourable; algorithm halts.")
return
assert is_proper_3_coloring(H, coloring)
if verbose:
print(f"STEP 1: H_1 = reduced dual at the first face, i_1 = 0")
print(f" |V(H_1)| = {H.order()}, |E(H_1)| = {H.size()}; "
f"3-edge-coloured.")
# ---- STEP 2: initialise the protected set
E = {
frozenset(spike),
frozenset(side_0),
frozenset(side_1),
frozenset(merged),
}
if verbose:
print(f"STEP 2: |E_protected| = {len(E)}")
# ---- STEP 3-4: iterate
for t in range(2, max_iterations + 1):
face_data = find_safe_pentagonal_face(H, E)
if face_data is None:
if verbose:
print(f"\nTerminated at iteration t = {t}: "
f"no pentagonal face avoids E.")
break
boundary, externals, A = face_data
f_vec = [coloring[e] for e in externals]
choices = valid_indices(f_vec)
if not choices:
print(f" iter t = {t}: f-vector {f_vec} has no valid index "
f"(Lemma 2.4 should preclude this --- bug!)")
break
i_t = choices[0]
v_n_label = ('v_n', t)
H, coloring, named = apply_reduction(
H, boundary, externals, A, coloring, i_t, v_n_label)
assert is_proper_3_coloring(H, coloring)
E |= set(named.values())
if verbose:
print(f" iter t = {t:>2}: f = {f_vec}, chose i_t = {i_t}; "
f"|V| = {H.order():>2}, |E_graph| = {H.size():>2}, "
f"|E_protected| = {len(E):>2}")
else:
if verbose:
print(f"\nReached max_iterations = {max_iterations}.")
if verbose:
print(f"\nFinal H: |V| = {H.order()}, |E| = {H.size()}, "
f"|E_protected| = {len(E)}")
if __name__ == '__main__':
run_algorithm()
@@ -0,0 +1,225 @@
"""Search for a proper 3-edge-coloring of a reduced dual satisfying:
(i) color(spike) == color(merged)
(ii) the {c_spike, c_side_0}-Kempe cycle through the spike contains
side_0 AND merged
(iii) the {c_spike, c_side_1}-Kempe cycle through the spike contains
side_1 AND merged
Iterates over all min-degree-5 simple planar triangulations on n vertices
(via plantri through Sage), then every pentagonal face of the dual G', then
every index i in {0,...,4} for the reduced-dual construction, then every
proper 3-edge-coloring of the resulting reduced dual. Stops on the first hit.
The icosahedron (n = 12) is the only n = 12 triangulation with min degree 5
and was already known to fail (see check_dodecahedron_kempe.py).
Run with:
sage experiments/search_kempe_property.py
"""
from sage.all import Graph
from sage.graphs.graph_generators import graphs
import sys
import time
def dual_of(G):
"""Combinatorial dual of a planar G with the embedding set on G."""
faces = G.faces()
edge_to_faces = {}
for fi, face in enumerate(faces):
for u, v in face:
e = frozenset((u, v))
edge_to_faces.setdefault(e, []).append(fi)
dual_edges = []
for e, fs in edge_to_faces.items():
if len(fs) == 2:
dual_edges.append((fs[0], fs[1]))
return Graph(dual_edges, multiedges=False, loops=False)
def apply_reduction(G, face, i):
"""Apply Definition 2.1 at the pentagonal `face` (list of directed edges)
with index i, returning (H, named) or (None, None) if the construction
breaks."""
boundary = [u for (u, v) in face]
if len(set(boundary)) != 5:
return None, 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, None
A.append(outer[0])
if len(set(A)) != 5:
return None, None
H = G.copy()
for v in boundary:
H.delete_vertex(v)
v_n = '__v_n__'
H.add_vertex(v_n)
side_0 = (v_n, A[i % 5])
spike = (v_n, A[(i + 1) % 5])
side_1 = (v_n, A[(i + 2) % 5])
merged = (A[(i + 3) % 5], A[(i + 4) % 5])
# If merged would be a self-loop or duplicate of an existing edge, skip
if A[(i + 3) % 5] == A[(i + 4) % 5]:
return None, None
H.add_edges([side_0, spike, side_1, merged])
if H.has_multiple_edges() or H.has_loops():
return None, None
if not H.is_planar(set_embedding=True):
return None, None
# Cubic check
if not all(H.degree(v) == 3 for v in H.vertex_iterator()):
return None, None
named = {
'spike': frozenset(spike),
'side_0': frozenset(side_0),
'side_1': frozenset(side_1),
'merged': frozenset(merged),
}
return H, named
def proper_3_edge_colorings(G):
edges = list(G.edges(labels=False))
n_edges = len(edges)
adj = [[] for _ in range(n_edges)]
for i in range(n_edges):
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_edges
def back(k):
if k == n_edges:
yield tuple(coloring)
return
for c in range(3):
if all(coloring[j] != c for j in adj[k]):
coloring[k] = c
yield from back(k + 1)
coloring[k] = -1
return edges, back(0)
def kempe_cycle(edges, coloring, start_idx, color_pair):
a, b = color_pair
if coloring[start_idx] not in (a, b):
return None
in_sub = [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 matches(edges, col, named_idx):
c_spike = col[named_idx['spike']]
c_merged = col[named_idx['merged']]
if c_spike != c_merged:
return False
c_s0 = col[named_idx['side_0']]
c_s1 = col[named_idx['side_1']]
kc0 = kempe_cycle(edges, col, named_idx['spike'], (c_spike, c_s0))
if named_idx['side_0'] not in kc0 or named_idx['merged'] not in kc0:
return False
kc1 = kempe_cycle(edges, col, named_idx['spike'], (c_spike, c_s1))
if named_idx['side_1'] not in kc1 or named_idx['merged'] not in kc1:
return False
return True
def report(edges, col, named_idx, named, info):
print()
print("*** MATCH FOUND ***")
for k, v in info.items():
print(f" {k} = {v}")
print("Named edges and their colours:")
for role in ('spike', 'side_0', 'side_1', 'merged'):
e = edges[named_idx[role]]
print(f" {role:7s}: edge {tuple(e)}, colour {col[named_idx[role]]}")
print(f"Full coloring (indexed by edge order):")
for i, e in enumerate(edges):
print(f" [{col[i]}] {tuple(e)}")
def search(max_n=16, time_budget_sec=600):
start = time.time()
n = 12
while n <= max_n:
elapsed = time.time() - start
if elapsed > time_budget_sec:
print(f"\nTime budget {time_budget_sec}s exhausted at n = {n}.")
return
print(f"\n=== n = {n} === (elapsed {elapsed:.1f}s)")
tcount = 0
total_reductions = 0
total_colorings = 0
try:
it = graphs.triangulations(n, minimum_degree=5)
except Exception as ex:
print(f" cannot enumerate triangulations: {ex}")
n += 1
continue
for G in it:
tcount += 1
if not G.is_planar(set_embedding=True):
continue
D = dual_of(G)
if not D.is_planar(set_embedding=True):
continue
faces_D = D.faces()
pentagonal = [f for f in faces_D if len(f) == 5]
for face in pentagonal:
for i_red in range(5):
H, named = apply_reduction(D, face, i_red)
if H is None:
continue
total_reductions += 1
edges, gen = proper_3_edge_colorings(H)
# Find indices of named edges
named_idx = {}
for ii, e in enumerate(edges):
es = frozenset((e[0], e[1]))
for role, ns in named.items():
if es == ns:
named_idx[role] = ii
if len(named_idx) != 4:
continue
for col in gen:
total_colorings += 1
if matches(edges, col, named_idx):
report(edges, col, named_idx, named, {
'n': n,
'triangulation_index': tcount,
'face_size': len(face),
'i_red': i_red,
'reduced_dual_V': H.order(),
'reduced_dual_E': H.size(),
})
return
sys.stdout.flush()
print(f" n = {n}: {tcount} triangulation(s), "
f"{total_reductions} reductions, "
f"{total_colorings} colorings; no hit.")
n += 1
print(f"\nSearch exhausted up to n = {max_n}.")
if __name__ == '__main__':
search()
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

@@ -13,9 +13,15 @@
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces The proof of Lemma\nonbreakingspace 2.6\hbox {}, illustrated for $i = 0$ on $G' = $ the dodecahedron. Top: under the assumption $W \neq Y$, propriety at $v_n$ forces $W \in \{X, Z\}$. Bottom: in either case the lift to $G'$ has externals satisfying the hypothesis of Lemma\nonbreakingspace 2.4\hbox {}, which colours $\partial F_v$ to extend $\psi $ to a proper $3$-edge-colouring of $G'$.}}{5}{}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces The proof of Lemma\nonbreakingspace 2.6\hbox {}, illustrated for $i = 0$ on $G' = $ the dodecahedron. Top: under the assumption $W \neq Y$, propriety at $v_n$ forces $W \in \{X, Z\}$. Bottom: in either case the lift to $G'$ has externals satisfying the hypothesis of Lemma\nonbreakingspace 2.4\hbox {}, which colours $\partial F_v$ to extend $\psi $ to a proper $3$-edge-colouring of $G'$.}}{5}{}\protected@file@percent }
\newlabel{fig:chord-apex-proof}{{2}{5}} \newlabel{fig:chord-apex-proof}{{2}{5}}
\newlabel{lem:kempe-spike}{{2.7}{6}} \newlabel{lem:kempe-spike}{{2.7}{6}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{3}{An iterated reduction}}{7}{}\protected@file@percent }
\newlabel{alg:iterated-reduction}{{3.1}{7}}
\newlabel{rem:alg-invariants}{{3.2}{7}}
\newlabel{rem:alg-chord-apex}{{3.3}{7}}
\newlabel{tocindent-1}{0pt} \newlabel{tocindent-1}{0pt}
\newlabel{tocindent0}{0pt} \newlabel{tocindent0}{0pt}
\newlabel{tocindent1}{17.77782pt} \newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt} \newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt} \newlabel{tocindent3}{0pt}
\gdef \@abspage@last{7} \@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces Algorithm\nonbreakingspace 3.1\hbox {} on $G' = $ dodecahedron (dual of the icosahedron). \emph {Left:} $G'$ (20 vertices, 30 edges), with $F_v$ (the inner pentagon) shaded as the face chosen for the first reduction. \emph {Centre:} $H_1$ (16 vertices, 24 edges) after step\nonbreakingspace (1) with $i_1 = 0$, $3$-edge-coloured by Sage; the four edges around $v_n^{(1)}$ in $E$ are drawn thicker. \emph {Right:} $H_2$ (12 vertices, 18 edges) after step\nonbreakingspace (3) with $i_t = 0$; the only safe pentagonal face in $H_1$ was the outer pentagon, whose deletion produces $v_n^{(2)}$ and a second chord, giving eight protected edges. No safe pentagonal face remains, so the algorithm terminates. The generating script is \texttt {experiments/draw\_iterated\_reduction.py}.}}{8}{}\protected@file@percent }
\newlabel{fig:iterated-reduction-trace}{{3}{8}}
\gdef \@abspage@last{8}
@@ -1,4 +1,4 @@
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 23 MAY 2026 02:40 This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 23 MAY 2026 03:21
entering extended mode entering extended mode
restricted \write18 enabled. restricted \write18 enabled.
%&-line parsing enabled. %&-line parsing enabled.
@@ -144,26 +144,26 @@ File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
(./paper.aux) (./paper.aux)
\openout1 = `paper.aux'. \openout1 = `paper.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 25. LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 25. LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 25. LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 25. LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 25. LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 25. LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 25. LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 26.
LaTeX Font Info: ... okay on input line 25. LaTeX Font Info: ... okay on input line 26.
LaTeX Font Info: Trying to load font information for U+msa on input line 25. LaTeX Font Info: Trying to load font information for U+msa on input line 26.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
File: umsa.fd 2013/01/14 v3.01 AMS symbols A File: umsa.fd 2013/01/14 v3.01 AMS symbols A
) )
LaTeX Font Info: Trying to load font information for U+msb on input line 25. LaTeX Font Info: Trying to load font information for U+msb on input line 26.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
@@ -192,7 +192,7 @@ File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
e e
)) ))
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
Overfull \hbox (41.917pt too wide) in paragraph at lines 143--145 Overfull \hbox (41.917pt too wide) in paragraph at lines 144--146
[]\OT1/cmr/m/n/10 List the five degree-$2$ ver-tices in clock-wise or-der aroun []\OT1/cmr/m/n/10 List the five degree-$2$ ver-tices in clock-wise or-der aroun
d $\OML/cmm/m/it/10 F$ \OT1/cmr/m/n/10 as $\OML/cmm/m/it/10 A \OT1/cmr/m/n/10 = d $\OML/cmm/m/it/10 F$ \OT1/cmr/m/n/10 as $\OML/cmm/m/it/10 A \OT1/cmr/m/n/10 =
(\OML/cmm/m/it/10 A[]; A[]; A[]; A[]; A[]\OT1/cmr/m/n/10 )$. (\OML/cmm/m/it/10 A[]; A[]; A[]; A[]; A[]\OT1/cmr/m/n/10 )$.
@@ -201,22 +201,22 @@ d $\OML/cmm/m/it/10 F$ \OT1/cmr/m/n/10 as $\OML/cmm/m/it/10 A \OT1/cmr/m/n/10 =
<fig_reduced_dual_step1.png, id=17, 517.79329pt x 499.08812pt> <fig_reduced_dual_step1.png, id=17, 517.79329pt x 499.08812pt>
File: fig_reduced_dual_step1.png Graphic file (type png) File: fig_reduced_dual_step1.png Graphic file (type png)
<use fig_reduced_dual_step1.png> <use fig_reduced_dual_step1.png>
Package pdftex.def Info: fig_reduced_dual_step1.png used on input line 163. Package pdftex.def Info: fig_reduced_dual_step1.png used on input line 164.
(pdftex.def) Requested size: 172.79846pt x 166.55775pt. (pdftex.def) Requested size: 172.79846pt x 166.55775pt.
<fig_reduced_dual_step2.png, id=19, 490.16064pt x 483.35876pt> <fig_reduced_dual_step2.png, id=19, 490.16064pt x 483.35876pt>
File: fig_reduced_dual_step2.png Graphic file (type png) File: fig_reduced_dual_step2.png Graphic file (type png)
<use fig_reduced_dual_step2.png> <use fig_reduced_dual_step2.png>
Package pdftex.def Info: fig_reduced_dual_step2.png used on input line 164. Package pdftex.def Info: fig_reduced_dual_step2.png used on input line 165.
(pdftex.def) Requested size: 172.79846pt x 170.39505pt. (pdftex.def) Requested size: 172.79846pt x 170.39505pt.
<fig_reduced_dual_step3.png, id=20, 490.16064pt x 483.35876pt> <fig_reduced_dual_step3.png, id=20, 490.16064pt x 483.35876pt>
File: fig_reduced_dual_step3.png Graphic file (type png) File: fig_reduced_dual_step3.png Graphic file (type png)
<use fig_reduced_dual_step3.png> <use fig_reduced_dual_step3.png>
Package pdftex.def Info: fig_reduced_dual_step3.png used on input line 165. Package pdftex.def Info: fig_reduced_dual_step3.png used on input line 166.
(pdftex.def) Requested size: 172.79846pt x 170.39505pt. (pdftex.def) Requested size: 172.79846pt x 170.39505pt.
<fig_reduced_dual_step4.png, id=21, 490.16064pt x 486.3346pt> <fig_reduced_dual_step4.png, id=21, 490.16064pt x 486.3346pt>
File: fig_reduced_dual_step4.png Graphic file (type png) File: fig_reduced_dual_step4.png Graphic file (type png)
<use fig_reduced_dual_step4.png> <use fig_reduced_dual_step4.png>
Package pdftex.def Info: fig_reduced_dual_step4.png used on input line 166. Package pdftex.def Info: fig_reduced_dual_step4.png used on input line 167.
(pdftex.def) Requested size: 172.79846pt x 171.44409pt. (pdftex.def) Requested size: 172.79846pt x 171.44409pt.
LaTeX Warning: `h' float specifier changed to `ht'. LaTeX Warning: `h' float specifier changed to `ht'.
@@ -226,50 +226,78 @@ uced_dual_step3.png> <./fig_reduced_dual_step4.png>]
<fig_chord_apex_step1.png, id=34, 505.03976pt x 502.06393pt> <fig_chord_apex_step1.png, id=34, 505.03976pt x 502.06393pt>
File: fig_chord_apex_step1.png Graphic file (type png) File: fig_chord_apex_step1.png Graphic file (type png)
<use fig_chord_apex_step1.png> <use fig_chord_apex_step1.png>
Package pdftex.def Info: fig_chord_apex_step1.png used on input line 287. Package pdftex.def Info: fig_chord_apex_step1.png used on input line 288.
(pdftex.def) Requested size: 251.9989pt x 250.5104pt. (pdftex.def) Requested size: 251.9989pt x 250.5104pt.
<fig_chord_apex_step2.png, id=35, 490.16064pt x 499.51323pt> <fig_chord_apex_step2.png, id=35, 490.16064pt x 499.51323pt>
File: fig_chord_apex_step2.png Graphic file (type png) File: fig_chord_apex_step2.png Graphic file (type png)
<use fig_chord_apex_step2.png> <use fig_chord_apex_step2.png>
Package pdftex.def Info: fig_chord_apex_step2.png used on input line 288. Package pdftex.def Info: fig_chord_apex_step2.png used on input line 289.
(pdftex.def) Requested size: 172.79846pt x 176.08986pt. (pdftex.def) Requested size: 172.79846pt x 176.08986pt.
<fig_chord_apex_step3.png, id=36, 490.16064pt x 499.51323pt> <fig_chord_apex_step3.png, id=36, 490.16064pt x 499.51323pt>
File: fig_chord_apex_step3.png Graphic file (type png) File: fig_chord_apex_step3.png Graphic file (type png)
<use fig_chord_apex_step3.png> <use fig_chord_apex_step3.png>
Package pdftex.def Info: fig_chord_apex_step3.png used on input line 289. Package pdftex.def Info: fig_chord_apex_step3.png used on input line 290.
(pdftex.def) Requested size: 172.79846pt x 176.08986pt. (pdftex.def) Requested size: 172.79846pt x 176.08986pt.
LaTeX Warning: `h' float specifier changed to `ht'. LaTeX Warning: `h' float specifier changed to `ht'.
[4] [5 <./fig_chord_apex_step1.png> <./fig_chord_apex_step2.png> <./fig_chord_a [4] [5 <./fig_chord_apex_step1.png> <./fig_chord_apex_step2.png> <./fig_chord_a
pex_step3.png>] [6] [7] (./paper.aux) ) pex_step3.png>] [6]
Here is how much of TeX's memory you used: Overfull \hbox (4.76643pt too wide) in paragraph at lines 432--439
3043 strings out of 478268 \OT1/cmr/m/n/10 which $\OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[
43156 string characters out of 5846347 ]\OT1/cmr/m/n/10 ) = \OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\
343146 words of memory out of 5000000 OT1/cmr/m/n/10 )$ and $\OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[
21084 multiletter control sequences out of 15000+600000 ]\OT1/cmr/m/n/10 )\OML/cmm/m/it/10 ; '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\O
476364 words of font info for 55 fonts, out of 8000000 for 9000 T1/cmr/m/n/10 )\OML/cmm/m/it/10 ; '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\OT1/
1302 hyphenation exceptions out of 8191 cmr/m/n/10 )$
69i,8n,76p,664b,298s stack positions out of 10000i,1000n,20000p,200000b,200000s []
</usr/local/texlive/2022/texmf-dist/font
s/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts <fig_alg_step0.png, id=49, 462.528pt x 440.42188pt>
/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts File: fig_alg_step0.png Graphic file (type png)
/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/ <use fig_alg_step0.png>
type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/t Package pdftex.def Info: fig_alg_step0.png used on input line 478.
ype1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ (pdftex.def) Requested size: 115.20264pt x 109.69525pt.
e1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1 <fig_alg_step1.png, id=50, 462.528pt x 440.42188pt>
/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu File: fig_alg_step1.png Graphic file (type png)
blic/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi <use fig_alg_step1.png>
c/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public Package pdftex.def Info: fig_alg_step1.png used on input line 479.
/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/a (pdftex.def) Requested size: 115.20264pt x 109.69525pt.
msfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am <fig_alg_step2.png, id=51, 480.80806pt x 371.1277pt>
sfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf File: fig_alg_step2.png Graphic file (type png)
onts/symbols/msam10.pfb> <use fig_alg_step2.png>
Output written on paper.pdf (7 pages, 755306 bytes). Package pdftex.def Info: fig_alg_step2.png used on input line 480.
PDF statistics: (pdftex.def) Requested size: 115.20264pt x 88.91956pt.
108 PDF objects out of 1000 (max. 8388607) [7] [8 <./fig_alg_step0.png> <./fig_alg_step1.png> <./fig_alg_step2.png>]
58 compressed objects within 1 object stream (./paper.aux) )
0 named destinations out of 1000 (max. 500000) Here is how much of TeX's memory you used:
36 words of extra memory for PDF output out of 10000 (max. 10000000) 3075 strings out of 478268
43781 string characters out of 5846347
343216 words of memory out of 5000000
21112 multiletter control sequences out of 15000+600000
476532 words of font info for 56 fonts, out of 8000000 for 9000
1302 hyphenation exceptions out of 8191
69i,8n,76p,918b,298s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/
cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
m/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
m/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm
/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/
cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cm
mi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr1
0.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.p
fb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb>
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></u
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></us
r/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/
local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/l
ocal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/loc
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb></usr/loca
l/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (8 pages, 983037 bytes).
PDF statistics:
132 PDF objects out of 1000 (max. 8388607)
69 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000)
51 words of extra memory for PDF output out of 10000 (max. 10000000)
@@ -16,6 +16,7 @@
\theoremstyle{definition} \theoremstyle{definition}
\newtheorem{definition}[theorem]{Definition} \newtheorem{definition}[theorem]{Definition}
\newtheorem{example}[theorem]{Example} \newtheorem{example}[theorem]{Example}
\newtheorem{algorithm}[theorem]{Algorithm}
\theoremstyle{remark} \theoremstyle{remark}
\newtheorem{remark}[theorem]{Remark} \newtheorem{remark}[theorem]{Remark}
@@ -391,4 +392,103 @@ distinct colours, contradicting Lemma~\ref{lem:chord-apex} applied to
$\varphi'$. $\varphi'$.
\end{proof} \end{proof}
\section{An iterated reduction}
The reduced-dual construction in Definition~\ref{def:reduced-dual} can be
iterated: starting from a proper $3$-edge-colouring $\varphi_1$ of a reduced
dual $\widehat{G}'_{v,i}$, we apply the construction again to that graph at
a pentagonal face whose ten incident edges avoid the four named edges from
the first reduction, extending $\varphi_1$ across the new reduction. The
protected edges accumulate into a set $E$ that grows by four per iteration,
and the process terminates when $E$ has blocked every pentagonal face.
\begin{algorithm}[Iterated reduction with protected edges]
\label{alg:iterated-reduction}
Let $G$ be a triangulation we assume to be a minimal counterexample to the
Four Colour Theorem. The algorithm produces a sequence $H_1, H_2, \dots$ of
cubic plane graphs, proper $3$-edge-colourings $\varphi_t$ of $H_t$, and a
growing set $E$ of protected edges.
\begin{enumerate}
\item[(0)] Form $G' := \mathrm{dual}(G)$, a cubic plane graph.
\item[(1)] Choose a degree-$5$ vertex $v$ of $G$ (equivalently a pentagonal
face $F_v$ of $G'$) and an index $i_1 \in \{0, \dots, 4\}$. Apply
Definition~\ref{def:reduced-dual} to form
$H_1 := \widehat{G'}_{v, i_1}$, and fix any proper
$3$-edge-colouring $\varphi_1$ of $H_1$ (one exists by the minimality
of $G$).
\item[(2)] Initialise $E := \{\text{spike}, \text{side-}0, \text{side-}1,
\text{merged}\}$, the four named edges of the reduction in (1).
\item[(3)] (Iterate.) At step $t \geq 2$, given $H_{t-1}$, $\varphi_{t-1}$,
and $E \subseteq E(H_{t-1})$:
\begin{enumerate}
\item[(a)] Find a pentagonal face $F$ of $H_{t-1}$ whose ten
incident edges --- the five boundary edges of $\partial F$
and the five external edges at $\partial F$ --- are all
outside $E$. If no such $F$ exists, terminate.
\item[(b)] By Lemma~\ref{lem:pentagonal-externals} applied to
$H_{t-1}$ at $F$ under $\varphi_{t-1}$, the external vector
has shape $(a, b, c, c, c)$ up to cyclic rotation. Choose an
index $i_t$ for which
$\varphi_{t-1}(f_{i_t + 3}) = \varphi_{t-1}(f_{i_t + 4})$ and
$\varphi_{t-1}(f_{i_t}), \varphi_{t-1}(f_{i_t + 1}),
\varphi_{t-1}(f_{i_t + 2})$ are three distinct colours.
\item[(c)] Apply Definition~\ref{def:reduced-dual} to $H_{t-1}$ at
$(F, i_t)$ to form $H_t$.
\item[(d)] Extend $\varphi_{t-1}$ to a proper $3$-edge-colouring
$\varphi_t$ of $H_t$: every surviving edge keeps its
$\varphi_{t-1}$-colour, and each new edge takes the unique
colour completing the palette at its endpoint (consistent
across both endpoints of the chord by the choice of $i_t$).
\item[(e)] Add the four named edges of the step-$t$ reduction to
$E$.
\end{enumerate}
\item[(4)] Repeat (3) until termination.
\end{enumerate}
\end{algorithm}
\begin{remark}
\label{rem:alg-invariants}
At each iteration, $|V(H_t)| = |V(H_{t-1})| - 4$ and
$|E(H_t)| = |E(H_{t-1})| - 6$, so $H_t$ shrinks at a fixed rate; the
protected set $|E|$ grows by exactly four; and every protected edge survives
all subsequent reductions. Since the graph is finite, termination is
guaranteed. By Lemma~\ref{lem:pentagonal-externals}, step~(b) never fails:
some valid $i_t$ always exists for any pentagonal face under any proper
colouring. Termination is therefore combinatorial: it occurs precisely when
$E$ touches every pentagonal face of $H_{t-1}$.
\end{remark}
\begin{remark}
\label{rem:alg-chord-apex}
Lemma~\ref{lem:chord-apex} applies only at $t = 1$, when $H_1$ is a reduced
dual of $G'$. For $t \geq 2$, $H_t$ is a reduced dual of $H_{t-1}$ rather than
of $G'$, and $H_{t-1}$ is itself $3$-edge-colourable, so the
non-$3$-edge-colourability argument that drives Lemma~\ref{lem:chord-apex}
does not carry over. Whether the constraints accumulated in $E$ propagate
any further structure to $\varphi_t$ for $t \geq 2$ is left open.
\end{remark}
\begin{figure}[h]
\centering
\includegraphics[width=0.32\textwidth]{fig_alg_step0.png}\hfill
\includegraphics[width=0.32\textwidth]{fig_alg_step1.png}\hfill
\includegraphics[width=0.32\textwidth]{fig_alg_step2.png}
\caption{Algorithm~\ref{alg:iterated-reduction} on $G' = $ dodecahedron
(dual of the icosahedron). \emph{Left:} $G'$ (20 vertices, 30 edges), with
$F_v$ (the inner pentagon) shaded as the face chosen for the first reduction.
\emph{Centre:} $H_1$ (16 vertices, 24 edges) after step~(1) with $i_1 = 0$,
$3$-edge-coloured by Sage; the four edges around $v_n^{(1)}$ in $E$ are drawn
thicker. \emph{Right:} $H_2$ (12 vertices, 18 edges) after step~(3) with
$i_t = 0$; the only safe pentagonal face in $H_1$ was the outer pentagon,
whose deletion produces $v_n^{(2)}$ and a second chord, giving eight protected
edges. No safe pentagonal face remains, so the algorithm terminates. The
generating script is \texttt{experiments/draw\_iterated\_reduction.py}.}
\label{fig:iterated-reduction-trace}
\end{figure}
\end{document} \end{document}