diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/draw_iterated_reduction_n14.py b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_iterated_reduction_n14.py new file mode 100644 index 0000000..7b9c71a --- /dev/null +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_iterated_reduction_n14.py @@ -0,0 +1,520 @@ +"""Draw the iterated reduction trace on the smallest triangulation where +the chord-apex + Kempe-cycle property is satisfied: the first min-degree-5 +plantri triangulation on n = 14 vertices, found by search_kempe_property.py. + +Overwrites fig_alg_step{0,1,2}.png in the paper directory with this +triangulation's trace (replacing the dodecahedron version). + +Run with: sage experiments/draw_iterated_reduction_n14.py +""" +from sage.all import Graph +from sage.graphs.graph_generators import graphs +import matplotlib.pyplot as plt +from matplotlib.patches import Polygon +import math +import os + + +def tutte_layout(G_sage, avoid_verts=None, iterations=300): + """Tutte's barycentric embedding: pick the largest face whose vertex set + avoids `avoid_verts` as the outer face, place its vertices on a regular + polygon, then iterate each interior vertex to the barycenter of its + neighbors. For 3-connected planar graphs this converges to the unique + straight-line planar embedding with the chosen outer face --- balanced + by construction and free of edge crossings.""" + avoid = set(avoid_verts or ()) + candidates = [] + for face in G_sage.faces(): + verts = [u for (u, v) in face] + if not (set(verts) & avoid): + candidates.append(verts) + if not candidates: + outer = [u for (u, v) in max(G_sage.faces(), key=len)] + else: + outer = max(candidates, key=len) + n_outer = len(outer) + pos = {} + for k, v in enumerate(outer): + ang = 2 * math.pi * k / n_outer + math.pi / 2 + pos[v] = (math.cos(ang), math.sin(ang)) + interior = [v for v in G_sage.vertex_iterator() if v not in pos] + for v in interior: + pos[v] = (0.0, 0.0) + for _ in range(iterations): + new_pos = dict(pos) + for v in interior: + nbrs = list(G_sage.neighbor_iterator(v)) + sx = sum(pos[w][0] for w in nbrs) / len(nbrs) + sy = sum(pos[w][1] for w in nbrs) / len(nbrs) + new_pos[v] = (sx, sy) + pos = new_pos + return pos + +OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +C = ['#dc2626', '#16a34a', '#2563eb'] +GRAY = '#9ca3af' +DARK = '#374151' +HIGHLIGHT = '#fef3c7' +SHADE = '#fef3c7' + + +def dual_of(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, 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 % 5]) + 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 + named = { + 'spike': frozenset(spike), + 'side_0': frozenset(side_0), + 'side_1': frozenset(side_1), + 'merged': frozenset(merged), + } + return H, named, boundary, A + + +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 + in_sub = [i for i in range(len(edges)) if coloring[i] in (a, b)] + if start_idx not in in_sub: + return None + 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_property(edges, col, named): + idx = {} + for ii, e in enumerate(edges): + es = frozenset((e[0], e[1])) + for role, ns in named.items(): + if es == ns: + idx[role] = ii + if len(idx) != 4: + 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 find_first_match(): + """Iterate over (G, face, i_red, coloring) and return the first hit.""" + for G in graphs.triangulations(14, minimum_degree=5): + if not G.is_planar(set_embedding=True): + continue + D = dual_of(G) + D.is_planar(set_embedding=True) + for face in D.faces(): + if len(face) != 5: + continue + for i_red in range(5): + res = apply_reduction(D, face, i_red, '__v_n_1__') + if res is None: + continue + H, named, boundary, A = res + edges, gen = proper_3_edge_colorings(H) + for col in gen: + if matches_property(edges, col, named): + coloring_dict = {frozenset((e[0], e[1])): c + for e, c in zip(edges, col)} + return G, D, face, i_red, H, named, boundary, A, coloring_dict + return None + + +def draw_graph(ax, G, pos, *, coloring=None, protected=None, + shade_vertices=None, vn_labels=None): + if shade_vertices: + poly = [pos[v] for v in shade_vertices] + ax.add_patch(Polygon(poly, closed=True, facecolor=SHADE, + edgecolor='none', zorder=0)) + protected = protected or set() + vn_labels = vn_labels or {} + for u, v, _ in G.edges(): + e = frozenset([u, v]) + c = C[coloring[e]] if (coloring is not None and e in coloring) 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 v in vn_labels: + ax.scatter(x, y, s=320, color=HIGHLIGHT, marker='s', + edgecolors='black', linewidths=1.2, zorder=4) + ax.annotate(vn_labels[v], (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=60, color=DARK, zorder=3) + ax.set_aspect('equal') + ax.axis('off') + + +def main(): + print("Searching for the first match at n = 14 ...") + result = find_first_match() + if result is None: + print("No match found at n = 14.") + return + G14, D, face, i_red, H1, named1, boundary1, A1, coloring1 = result + print(f"Found at i_red = {i_red}") + print(f" G (n=14): |V|={G14.order()}, |E|={G14.size()}, " + f"min_deg={min(G14.degree())}") + print(f" D = G': |V|={D.order()}, |E|={D.size()}") + print(f" H_1: |V|={H1.order()}, |E|={H1.size()}") + + # Relabel H_1 in place so all vertex labels are comparable integers + # (Sage's planar layout and face enumeration need comparable labels). + # Translate coloring1 and named1 accordingly. + H1_relabel_map = {v: i for i, v in enumerate(H1.vertex_iterator())} + H1.relabel(perm=H1_relabel_map, inplace=True) + vn1_int = H1_relabel_map['__v_n_1__'] + coloring1 = {frozenset(H1_relabel_map[u] for u in e): c + for e, c in coloring1.items()} + named1 = {role: frozenset(H1_relabel_map[u] for u in e) + for role, e in named1.items()} + + D.is_planar(set_embedding=True) + D_layout = tutte_layout(D, avoid_verts=set(u for (u, v) in face)) + H1.is_planar(set_embedding=True) + H1_layout = tutte_layout(H1, avoid_verts={vn1_int}) + + boundary_face_verts = [u for (u, v) in face] + fig, ax = plt.subplots(figsize=(8, 8)) + draw_graph(ax, D, D_layout, shade_vertices=boundary_face_verts) + fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step0.png'), + dpi=170, bbox_inches='tight') + plt.close(fig) + print("Wrote fig_alg_step0.png") + + E = set(named1.values()) + fig, ax = plt.subplots(figsize=(8, 8)) + draw_graph(ax, H1, H1_layout, coloring=coloring1, protected=E, + vn_labels={vn1_int: '$v_n^{(1)}$'}) + fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step1.png'), + dpi=170, bbox_inches='tight') + plt.close(fig) + print("Wrote fig_alg_step1.png") + + # ----- Step 2: try to continue ----- + H1.is_planar(set_embedding=True) + chosen2 = None + for face2 in H1.faces(): + if len(face2) != 5: + continue + boundary2 = [u for (u, v) in face2] + boundary2_edges = [frozenset([u, v]) for (u, v) in face2] + externals2 = [] + A2 = [] + valid_face = True + for B_k in boundary2: + outer = [w for w in H1.neighbor_iterator(B_k) if w not in boundary2] + if len(outer) != 1: + valid_face = False + break + externals2.append(frozenset([B_k, outer[0]])) + A2.append(outer[0]) + if not valid_face: + continue + if any(e in E for e in boundary2_edges + externals2): + continue + # find valid i_t + f_vec = [coloring1[e] for e in externals2] + for i_t in range(5): + if f_vec[(i_t + 3) % 5] != f_vec[(i_t + 4) % 5]: + continue + if len({f_vec[i_t], f_vec[(i_t + 1) % 5], f_vec[(i_t + 2) % 5]}) != 3: + continue + if A2[(i_t + 3) % 5] == A2[(i_t + 4) % 5]: + continue + chosen2 = (face2, i_t, boundary2, externals2, A2) + break + if chosen2 is not None: + break + + if chosen2 is None: + # algorithm terminates at H_1 + fig, ax = plt.subplots(figsize=(8, 8)) + ax.text(0.5, 0.5, + "Algorithm terminates at $H_1$:\n" + "no pentagonal face of $H_1$ has all\n" + "ten incident edges outside $E$.", + ha='center', va='center', fontsize=18, color=DARK, + transform=ax.transAxes, + bbox=dict(boxstyle='round,pad=0.6', fc=HIGHLIGHT, + ec=DARK, lw=1.0)) + ax.set_aspect('equal') + ax.axis('off') + fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step2.png'), + dpi=170, bbox_inches='tight') + plt.close(fig) + print("Wrote fig_alg_step2.png (termination card)") + print(" Algorithm terminates at H_1: no safe pentagonal face.") + return + + face2, i_t, boundary2, externals2, A2 = chosen2 + print(f"Step 2: safe face found, i_t = {i_t}") + + H2 = H1.copy() + for v in boundary2: + H2.delete_vertex(v) + # use a fresh int label for v_n^(2) + v_n_2 = max(H1.vertices(sort=False)) + 1 + H2.add_vertex(v_n_2) + side_0_2 = (v_n_2, A2[i_t]) + spike_2 = (v_n_2, A2[(i_t + 1) % 5]) + side_1_2 = (v_n_2, A2[(i_t + 2) % 5]) + merged_2 = (A2[(i_t + 3) % 5], A2[(i_t + 4) % 5]) + H2.add_edges([side_0_2, spike_2, side_1_2, merged_2]) + H2.is_planar(set_embedding=True) + + coloring2 = {e: c for e, c in coloring1.items() + if not any(u in boundary2 for u in e)} + coloring2[frozenset(side_0_2)] = coloring1[externals2[i_t]] + coloring2[frozenset(spike_2)] = coloring1[externals2[(i_t + 1) % 5]] + coloring2[frozenset(side_1_2)] = coloring1[externals2[(i_t + 2) % 5]] + coloring2[frozenset(merged_2)] = coloring1[externals2[(i_t + 3) % 5]] + + E |= {frozenset(side_0_2), frozenset(spike_2), + frozenset(side_1_2), frozenset(merged_2)} + + H2_layout = tutte_layout(H2, avoid_verts={vn1_int, v_n_2}) + fig, ax = plt.subplots(figsize=(8, 8)) + draw_graph(ax, H2, H2_layout, coloring=coloring2, protected=E, + vn_labels={vn1_int: '$v_n^{(1)}$', + v_n_2: '$v_n^{(2)}$'}) + fig.savefig(os.path.join(OUT_DIR, 'fig_alg_step2.png'), + dpi=170, bbox_inches='tight') + plt.close(fig) + print(f"Wrote fig_alg_step2.png: H_2 with |V|={H2.order()}, " + f"|E|={H2.size()}, |protected|={len(E)}") + + # --- continue running to completion, checking Kempe condition each step -- + print() + print("=" * 72) + print("Running algorithm to completion, checking chord-apex + Kempe at " + "each step.") + print("=" * 72) + # Step 1 status (by construction this is the matching coloring) + cond1 = check_step_conditions(H1, coloring1, named1) + print(f" step t = 1: |V|={H1.order():>3}, |E_graph|={H1.size():>3}, " + f"|E_prot|= 4 (initial)" + f" | chord-apex: {cond1['chord_apex']}, " + f"side_0-Kempe: {cond1['kc_side_0']}, " + f"side_1-Kempe: {cond1['kc_side_1']}") + run_to_completion_from(H2, coloring2, E, + {'spike': frozenset(spike_2), + 'side_0': frozenset(side_0_2), + 'side_1': frozenset(side_1_2), + 'merged': frozenset(merged_2)}, + start_t=2) + + +def check_step_conditions(H, coloring, named): + """Given an H_t and the *just-added* spike/side_0/side_1/merged, check + whether chord-apex and the two Kempe-cycle conditions hold.""" + edges = list(H.edges(labels=False)) + edges_fs = [frozenset((u, v)) for (u, v) in edges] + col = [coloring[e] for e in edges_fs] + idx = {role: edges_fs.index(e) for role, e in named.items()} + c_spike = col[idx['spike']] + c_merged = col[idx['merged']] + chord_apex = (c_spike == c_merged) + if not chord_apex: + return {'chord_apex': False, 'kc_side_0': False, 'kc_side_1': False} + c_s0 = col[idx['side_0']] + c_s1 = col[idx['side_1']] + kc0 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s0)) + kc1 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s1)) + kc_side_0 = (idx['side_0'] in kc0 and idx['merged'] in kc0) + kc_side_1 = (idx['side_1'] in kc1 and idx['merged'] in kc1) + return {'chord_apex': True, 'kc_side_0': kc_side_0, 'kc_side_1': kc_side_1} + + +def find_safe_face(H, protected): + """Return (face, externals, A) for some safe pentagonal face avoiding + `protected`, or None.""" + for face in H.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 = [] + valid = True + for B_k in boundary: + outer = [w for w in H.neighbor_iterator(B_k) if w not in boundary] + if len(outer) != 1: + valid = False + break + externals.append(frozenset([B_k, outer[0]])) + A.append(outer[0]) + if not valid: + continue + if any(e in protected for e in boundary_edges + externals): + continue + return face, boundary, externals, A + return None + + +def run_to_completion_from(H, coloring, E, last_named, start_t): + """Continue iterating from H_{start_t}. The 'last_named' dict carries + the spike/side/merged of step `start_t` so we can report its Kempe + status. Print a row per step.""" + t = start_t + print(f" step t = {t}: |V|={H.order():>3}, |E_graph|={H.size():>3}, " + f"|E_prot|={len(E):>3}", end='') + cond = check_step_conditions(H, coloring, last_named) + print(f" | chord-apex: {cond['chord_apex']}, " + f"side_0-Kempe: {cond['kc_side_0']}, " + f"side_1-Kempe: {cond['kc_side_1']}") + + while True: + H.is_planar(set_embedding=True) + res = find_safe_face(H, E) + if res is None: + print(f" step t = {t + 1}: no safe pentagonal face --> " + f"algorithm terminates at H_{t}.") + return + face, boundary, externals, A = res + f_vec = [coloring[e] for e in externals] + i_t = None + 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 + i_t = i + break + if i_t is None: + print(f" step t = {t + 1}: f = {f_vec}, no valid index --> " + f"terminate (Lemma 2.4 violation? Probably a parallel-edge " + f"or other degenerate case).") + return + t += 1 + v_n_new = max(H.vertices(sort=False)) + 1 if all( + isinstance(v, int) for v in H.vertex_iterator()) else f'vn{t}' + H_new = H.copy() + for v in boundary: + H_new.delete_vertex(v) + H_new.add_vertex(v_n_new) + side_0 = (v_n_new, A[i_t]) + spike = (v_n_new, A[(i_t + 1) % 5]) + side_1 = (v_n_new, 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]) + 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)] = coloring[externals[i_t]] \ + if frozenset(externals[i_t]) in coloring else f_vec[i_t] + # safer: directly use f_vec + 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()) + cond = check_step_conditions(H, coloring, named) + print(f" step t = {t}: |V|={H.order():>3}, |E_graph|={H.size():>3}, " + f"|E_prot|={len(E):>3}, i_t = {i_t}", end='') + print(f" | chord-apex: {cond['chord_apex']}, " + f"side_0-Kempe: {cond['kc_side_0']}, " + f"side_1-Kempe: {cond['kc_side_1']}") + + +if __name__ == '__main__': + main() diff --git a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step0.png b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step0.png index 173879a..68f0f39 100644 Binary files a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step0.png and b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step0.png differ diff --git a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step1.png b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step1.png index 022f173..ffcf72b 100644 Binary files a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step1.png and b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step1.png differ diff --git a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step2.png b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step2.png index 77372f5..dd67455 100644 Binary files a/papers/dual_decomposition_minimal_counterexamples/fig_alg_step2.png and b/papers/dual_decomposition_minimal_counterexamples/fig_alg_step2.png differ diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.aux b/papers/dual_decomposition_minimal_counterexamples/paper.aux index 0ea60b6..3c47f89 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.aux +++ b/papers/dual_decomposition_minimal_counterexamples/paper.aux @@ -22,6 +22,7 @@ \newlabel{tocindent1}{17.77782pt} \newlabel{tocindent2}{0pt} \newlabel{tocindent3}{0pt} -\@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 } +\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces Algorithm\nonbreakingspace 3.1\hbox {} on $G'=\mathrm {dual}(G)$, where $G$ is the first min-degree-$5$ plantri triangulation on $14$ vertices and $\varphi _1$ is a specific proper $3$-edge-colouring of $H_1$ that satisfies both the chord-apex condition (Lemma\nonbreakingspace 2.6\hbox {}) and the Kempe-cycle condition (Lemma\nonbreakingspace 2.7\hbox {}), found by \texttt {experiments/search\_kempe\_property.py}. \emph {Left:} $G'$ ($24$ vertices, $36$ edges) with the chosen pentagonal face shaded. \emph {Centre:} $H_1$ ($20$ vertices, $30$ edges) after step\nonbreakingspace (1) with $i_1 = 1$, $3$-edge-coloured by $\varphi _1$; the four edges around $v_n^{(1)}$ in $E$ are drawn thicker, and the spike and merged edges share the colour green. \emph {Right:} $H_2$ ($16$ vertices, $24$ edges) after step\nonbreakingspace (3) with $i_t = 3$; eight edges are protected, and the algorithm terminates one step later (no remaining safe pentagonal face in $H_2$). The generating script is \texttt {experiments/draw\_iterated\_reduction\_n14.py}; layouts are Tutte barycentric embeddings with the outer face picked to keep $v_n^{(1)}, v_n^{(2)}$ in the interior.}}{8}{}\protected@file@percent } \newlabel{fig:iterated-reduction-trace}{{3}{8}} +\newlabel{conj:no-all-distinct-coloring}{{3.4}{8}} \gdef \@abspage@last{8} diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.log b/papers/dual_decomposition_minimal_counterexamples/paper.log index 2532749..3d46aed 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.log +++ b/papers/dual_decomposition_minimal_counterexamples/paper.log @@ -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 03:21 +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 23 MAY 2026 13:19 entering extended mode restricted \write18 enabled. %&-line parsing enabled. @@ -144,26 +144,26 @@ File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX) (./paper.aux) \openout1 = `paper.aux'. -LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 26. -LaTeX Font Info: ... okay on input line 26. -LaTeX Font Info: Trying to load font information for U+msa on input line 26. +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 27. +LaTeX Font Info: ... okay on input line 27. +LaTeX Font Info: Trying to load font information for U+msa on input line 27. (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd 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 26. +LaTeX Font Info: Trying to load font information for U+msb on input line 27. (/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 )) [1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] -Overfull \hbox (41.917pt too wide) in paragraph at lines 144--146 +Overfull \hbox (41.917pt too wide) in paragraph at lines 145--147 []\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 = (\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 = File: fig_reduced_dual_step1.png Graphic file (type png) -Package pdftex.def Info: fig_reduced_dual_step1.png used on input line 164. +Package pdftex.def Info: fig_reduced_dual_step1.png used on input line 165. (pdftex.def) Requested size: 172.79846pt x 166.55775pt. File: fig_reduced_dual_step2.png Graphic file (type png) -Package pdftex.def Info: fig_reduced_dual_step2.png used on input line 165. +Package pdftex.def Info: fig_reduced_dual_step2.png used on input line 166. (pdftex.def) Requested size: 172.79846pt x 170.39505pt. File: fig_reduced_dual_step3.png Graphic file (type png) -Package pdftex.def Info: fig_reduced_dual_step3.png used on input line 166. +Package pdftex.def Info: fig_reduced_dual_step3.png used on input line 167. (pdftex.def) Requested size: 172.79846pt x 170.39505pt. File: fig_reduced_dual_step4.png Graphic file (type png) -Package pdftex.def Info: fig_reduced_dual_step4.png used on input line 167. +Package pdftex.def Info: fig_reduced_dual_step4.png used on input line 168. (pdftex.def) Requested size: 172.79846pt x 171.44409pt. LaTeX Warning: `h' float specifier changed to `ht'. @@ -226,17 +226,17 @@ uced_dual_step3.png> <./fig_reduced_dual_step4.png>] File: fig_chord_apex_step1.png Graphic file (type png) -Package pdftex.def Info: fig_chord_apex_step1.png used on input line 288. +Package pdftex.def Info: fig_chord_apex_step1.png used on input line 289. (pdftex.def) Requested size: 251.9989pt x 250.5104pt. File: fig_chord_apex_step2.png Graphic file (type png) -Package pdftex.def Info: fig_chord_apex_step2.png used on input line 289. +Package pdftex.def Info: fig_chord_apex_step2.png used on input line 290. (pdftex.def) Requested size: 172.79846pt x 176.08986pt. File: fig_chord_apex_step3.png Graphic file (type png) -Package pdftex.def Info: fig_chord_apex_step3.png used on input line 290. +Package pdftex.def Info: fig_chord_apex_step3.png used on input line 291. (pdftex.def) Requested size: 172.79846pt x 176.08986pt. @@ -244,7 +244,7 @@ LaTeX Warning: `h' float specifier changed to `ht'. [4] [5 <./fig_chord_apex_step1.png> <./fig_chord_apex_step2.png> <./fig_chord_a pex_step3.png>] [6] -Overfull \hbox (4.76643pt too wide) in paragraph at lines 432--439 +Overfull \hbox (4.76643pt too wide) in paragraph at lines 433--440 \OT1/cmr/m/n/10 which $\OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[ ]\OT1/cmr/m/n/10 ) = \OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\ OT1/cmr/m/n/10 )$ and $\OML/cmm/m/it/10 '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[ @@ -253,31 +253,60 @@ T1/cmr/m/n/10 )\OML/cmm/m/it/10 ; '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\OT1/ cmr/m/n/10 )$ [] - + File: fig_alg_step0.png Graphic file (type png) -Package pdftex.def Info: fig_alg_step0.png used on input line 478. -(pdftex.def) Requested size: 115.20264pt x 109.69525pt. - +Package pdftex.def Info: fig_alg_step0.png used on input line 479. +(pdftex.def) Requested size: 115.20264pt x 132.48134pt. + File: fig_alg_step1.png Graphic file (type png) -Package pdftex.def Info: fig_alg_step1.png used on input line 479. -(pdftex.def) Requested size: 115.20264pt x 109.69525pt. - +Package pdftex.def Info: fig_alg_step1.png used on input line 480. +(pdftex.def) Requested size: 115.20264pt x 132.48134pt. + File: fig_alg_step2.png Graphic file (type png) -Package pdftex.def Info: fig_alg_step2.png used on input line 480. -(pdftex.def) Requested size: 115.20264pt x 88.91956pt. +Package pdftex.def Info: fig_alg_step2.png used on input line 481. +(pdftex.def) Requested size: 115.20264pt x 132.48134pt. + +Underfull \hbox (badness 4391) in paragraph at lines 498--498 +\OT1/cmr/m/sc/10 Figure 3.\OT1/cmr/m/n/10 Algorithm 3.1[] on $\OML/cmm/m/it/10 +G[] \OT1/cmr/m/n/10 = [](\OML/cmm/m/it/10 G\OT1/cmr/m/n/10 )$, where $\OML/cmm/ +m/it/10 G$ + [] + + +Underfull \hbox (badness 3623) in paragraph at lines 498--498 +\OT1/cmr/m/n/10 is the first min-degree-$5$ plantri tri-an-gu-la-tion on $14$ v +er- + [] + + +Underfull \hbox (badness 3179) in paragraph at lines 498--498 +\OT1/cmr/m/n/10 tices and $\OML/cmm/m/it/10 '[]$ \OT1/cmr/m/n/10 is a spe-cific + proper $3$-edge-colouring of $\OML/cmm/m/it/10 H[]$ + [] + + +Underfull \hbox (badness 3209) in paragraph at lines 498--498 +\OT1/cmr/m/n/10 that sat-is-fies both the chord-apex con-di-tion (Lemma 2.6[]) + [] + + +Underfull \hbox (badness 6094) in paragraph at lines 498--498 +\OT1/cmr/m/n/10 and the Kempe-cycle con-di-tion (Lemma 2.7[]), found by + [] + [7] [8 <./fig_alg_step0.png> <./fig_alg_step1.png> <./fig_alg_step2.png>] (./paper.aux) ) Here is how much of TeX's memory you used: - 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 + 3079 strings out of 478268 + 43848 string characters out of 5846347 + 344259 words of memory out of 5000000 + 21116 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 + 69i,8n,76p,1306b,298s stack positions out of 10000i,1000n,20000p,200000b,200000s -Output written on paper.pdf (8 pages, 983037 bytes). +r/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy5.pfb> +Output written on paper.pdf (8 pages, 969703 bytes). PDF statistics: - 132 PDF objects out of 1000 (max. 8388607) - 69 compressed objects within 1 object stream + 137 PDF objects out of 1000 (max. 8388607) + 72 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) diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.pdf b/papers/dual_decomposition_minimal_counterexamples/paper.pdf index b52536f..bb72059 100644 Binary files a/papers/dual_decomposition_minimal_counterexamples/paper.pdf and b/papers/dual_decomposition_minimal_counterexamples/paper.pdf differ diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.tex b/papers/dual_decomposition_minimal_counterexamples/paper.tex index dd45950..3a2326c 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.tex +++ b/papers/dual_decomposition_minimal_counterexamples/paper.tex @@ -12,6 +12,7 @@ \newtheorem{lemma}[theorem]{Lemma} \newtheorem{corollary}[theorem]{Corollary} \newtheorem{proposition}[theorem]{Proposition} +\newtheorem{conjecture}[theorem]{Conjecture} \theoremstyle{definition} \newtheorem{definition}[theorem]{Definition} @@ -478,17 +479,36 @@ any further structure to $\varphi_t$ for $t \geq 2$ is left open. \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}.} +\caption{Algorithm~\ref{alg:iterated-reduction} on $G'=\mathrm{dual}(G)$, where +$G$ is the first min-degree-$5$ plantri triangulation on $14$ vertices and +$\varphi_1$ is a specific proper $3$-edge-colouring of $H_1$ that satisfies +both the chord-apex condition (Lemma~\ref{lem:chord-apex}) and the Kempe-cycle +condition (Lemma~\ref{lem:kempe-spike}), found by +\texttt{experiments/search\_kempe\_property.py}. \emph{Left:} $G'$ +($24$ vertices, $36$ edges) with the chosen pentagonal face shaded. +\emph{Centre:} $H_1$ ($20$ vertices, $30$ edges) after step~(1) with +$i_1 = 1$, $3$-edge-coloured by $\varphi_1$; the four edges around +$v_n^{(1)}$ in $E$ are drawn thicker, and the spike and merged edges share +the colour green. \emph{Right:} $H_2$ ($16$ vertices, $24$ edges) after +step~(3) with $i_t = 3$; eight edges are protected, and the algorithm +terminates one step later (no remaining safe pentagonal face in $H_2$). +The generating script is +\texttt{experiments/draw\_iterated\_reduction\_n14.py}; layouts are Tutte +barycentric embeddings with the outer face picked to keep $v_n^{(1)}, +v_n^{(2)}$ in the interior.} \label{fig:iterated-reduction-trace} \end{figure} +\begin{conjecture} +\label{conj:no-all-distinct-coloring} +Let $G$ be a maximal planar graph of minimum degree $\geq 5$, and let +$H_{t^*}$ be the final reduced graph produced by some terminating execution +of Algorithm~\ref{alg:iterated-reduction} on $G$, with the corresponding +sequence of named pairs $(\mathrm{spike}_t, \mathrm{merged}_t)$ for +$t = 1, \dots, t^*$. Then $G$ is a minimal counterexample to the Four Colour +Theorem if and only if there is no proper $3$-edge-colouring of $H_{t^*}$ +under which $\mathrm{spike}_t$ and $\mathrm{merged}_t$ receive different +colours for every $t \in \{1, \dots, t^*\}$. +\end{conjecture} + \end{document}