diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_3_8_scaled.py b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_3_8_scaled.py new file mode 100644 index 0000000..96bc638 --- /dev/null +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_3_8_scaled.py @@ -0,0 +1,426 @@ +"""Test Conjecture 3.8 (strengthening of 3.6 with the {b,c}-Kempe / 3-colour +constraint on the new 4-edge face) on all min-degree-5 triangulations up to +n = 18. + +For each (G, F_v, i_red, varphi) with varphi a chord-apex + Kempe coloring, +we look for a witness (F, e_1, e_2) of Conjecture 3.6 (clauses 1-3 including +the 4-edge-face criterion). Then we: + - subdivide e_1, e_2 by X_1, X_2, + - add the new edge X_1 X_2, + - recolour the (subdivided) Kempe cycle alternately starting from merged + so propriety holds and the new edge takes the third colour, + - identify f_n (the 4-edge face containing the new edge), and + - test clause 4: + EITHER partial(f_n) uses all 3 colours, + OR the {b, c}-Kempe cycle through X_1 X_2 has only X_1 X_2 itself in + partial(f_n). +Aggregates per-n. + +Run with: sage experiments/check_conj_3_8_scaled.py +""" +from sage.all import Graph +from sage.graphs.graph_generators import graphs +import sys +import time + + +def dual_of(G): + G.is_planar(set_embedding=True) + faces = G.faces() + edge_to_faces = {} + for fi, face in enumerate(faces): + for u, v in face: + edge_to_faces.setdefault(frozenset((u, v)), []).append(fi) + return Graph( + [(fs[0], fs[1]) for fs in edge_to_faces.values() if len(fs) == 2], + 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 or A[(i+3) % 5] == A[(i+4) % 5]: return None + H = G.copy() + for v in boundary: H.delete_vertex(v) + H.add_vertex(v_n_label) + side_0 = (v_n_label, A[i]) + spike = (v_n_label, A[(i+1) % 5]) + side_1 = (v_n_label, A[(i+2) % 5]) + merged = (A[(i+3) % 5], A[(i+4) % 5]) + H.add_edges([side_0, spike, side_1, merged]) + if H.has_multiple_edges() or H.has_loops(): return None + if not H.is_planar(set_embedding=True): return None + if not all(H.degree(v) == 3 for v in H.vertex_iterator()): return None + return {'H': H, 'named': {'spike': frozenset(spike), + 'side_0': frozenset(side_0), 'side_1': frozenset(side_1), + 'merged': frozenset(merged)}} + + +def proper_3_edge_colorings(G): + edges = list(G.edges(labels=False)) + n = len(edges) + adj = [[] for _ in range(n)] + for i in range(n): + u, v = edges[i][0], edges[i][1] + for j in range(i): + x, y = edges[j][0], edges[j][1] + if u in (x, y) or v in (x, y): + adj[i].append(j); adj[j].append(i) + coloring = [-1] * n + results = [] + def back(k): + if k == n: + results.append(tuple(coloring)); return + for c in range(3): + if all(coloring[j] != c for j in adj[k]): + coloring[k] = c + back(k + 1) + coloring[k] = -1 + back(0) + return edges, results + + +def kempe_cycle_set(edges, coloring, start_idx, color_pair): + a, b = color_pair + if coloring[start_idx] not in (a, b): return set() + in_sub = set(i for i in range(len(edges)) if coloring[i] in (a, b)) + visited = {start_idx}; stack = [start_idx] + while stack: + cur = stack.pop() + u, v = edges[cur][0], edges[cur][1] + for j in in_sub: + if j in visited: continue + x, y = edges[j][0], edges[j][1] + if u in (x, y) or v in (x, y): + visited.add(j); stack.append(j) + return visited + + +def edge_idx(edges, e_frozen): + for i, e in enumerate(edges): + if frozenset((e[0], e[1])) == e_frozen: + return i + return None + + +def trace_kempe_cycle(edges, col_list, start_idx, color_pair): + cycle_set = kempe_cycle_set(edges, col_list, start_idx, color_pair) + incident_at = {} + for ei in cycle_set: + u, v = edges[ei][0], edges[ei][1] + incident_at.setdefault(u, []).append(ei) + incident_at.setdefault(v, []).append(ei) + start_u, start_v = edges[start_idx][0], edges[start_idx][1] + walk = [(start_idx, start_v)] + cur_e = start_idx + cur_leave = start_v + while True: + nbrs = incident_at[cur_leave] + if len(nbrs) != 2: break + nxt = nbrs[0] if nbrs[1] == cur_e else nbrs[1] + u2, v2 = edges[nxt][0], edges[nxt][1] + leave_next = v2 if u2 == cur_leave else u2 + if nxt == start_idx: break + walk.append((nxt, leave_next)) + cur_e = nxt + cur_leave = leave_next + return walk + + +def matches_chord_apex_kempe(edges, col, named): + idx = {role: edge_idx(edges, ns) for role, ns in named.items()} + if any(v is None for v in idx.values()): return False + c_spike = col[idx['spike']] + c_merged = col[idx['merged']] + if c_spike != c_merged: return False + c_s0 = col[idx['side_0']]; c_s1 = col[idx['side_1']] + kc0 = kempe_cycle_set(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_set(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_all_36_witnesses(H, edges, col_list, named): + """Yield every (F, e_1, e_2) satisfying clauses 1-3 of Conjecture 3.6.""" + merged_idx = edge_idx(edges, named['merged']) + c_merged = col_list[merged_idx] + kempe_cycles = [] + for c_prime in range(3): + if c_prime == c_merged: continue + kc = kempe_cycle_set(edges, col_list, merged_idx, (c_merged, c_prime)) + kempe_cycles.append((c_prime, kc)) + H.is_planar(set_embedding=True) + out = [] + for face in H.faces(): + face_edge_indices = [] + for u, v in face: + ei = edge_idx(edges, frozenset((u, v))) + if ei is not None: + face_edge_indices.append(ei) + n_face = len(face_edge_indices) + for i in range(n_face): + for j in range(i + 1, n_face): + e1, e2 = face_edge_indices[i], face_edge_indices[j] + if e1 == merged_idx or e2 == merged_idx: continue + if col_list[e1] != col_list[e2]: continue + gap_a = (j - i - 1) + gap_b = (n_face - 2 - gap_a) + if gap_a != 1 and gap_b != 1: continue + for c_prime, kc in kempe_cycles: + if e1 in kc and e2 in kc: + if gap_a == 1: + e_F = face_edge_indices[i + 1] + else: + e_F = face_edge_indices[(j + 1) % n_face] + out.append({ + 'face_edges': face_edge_indices, + 'e1': e1, 'e2': e2, 'e_F': e_F, + 'kc_color_pair': (c_merged, c_prime), + }) + return out + + +def check_clause_4(H, edges, col_list, named, witness): + """Construct the modified H + recoloring, identify f_n, check clause 4.""" + e1, e2 = witness['e1'], witness['e2'] + e_F = witness['e_F'] + a = col_list[e1] # color of e_1 = e_2 + merged_idx = edge_idx(edges, named['merged']) + cyc_a, cyc_b = witness['kc_color_pair'] # = (c_merged, c_other) + # a is the colour of e_1, e_2 on the Kempe cycle. By chord-apex, + # c_merged is the color of merged = the color of all "a"-edges on the + # cycle. The cycle alternates between c_merged = a and c_other = b. + b = cyc_b + c = ({0, 1, 2} - {a, b}).pop() + + # Subdivided cycle K' alternates blue/green starting from merged = blue + walk = trace_kempe_cycle(edges, col_list, merged_idx, (cyc_a, cyc_b)) + walk_edges = [w[0] for w in walk] + leave_at = [w[1] for w in walk] + + # K' position counter. For each old cycle edge e in cyclic order, position + # increments by 1 normally; for e1/e2, increments by 2 (two halves). + # For e1, we record (entry_half_color, exit_half_color) where entry_half + # is the half adjacent to entry_vertex and exit_half adjacent to leaving. + e1_entry_color = e1_exit_color = None + e2_entry_color = e2_exit_color = None + e1_entry_vertex = e1_exit_vertex = None + e2_entry_vertex = e2_exit_vertex = None + other_new_colors = {} # ei -> new color (for cycle edges other than e1/e2) + pos = 0 + for k, ei in enumerate(walk_edges): + leaving = leave_at[k] + u, v = edges[ei][0], edges[ei][1] + entry = v if leaving == u else u + if ei == e1: + c_entry = cyc_b if pos % 2 == 0 else cyc_a + pos += 1 + c_exit = cyc_b if pos % 2 == 0 else cyc_a + pos += 1 + e1_entry_color = c_entry; e1_exit_color = c_exit + e1_entry_vertex = entry; e1_exit_vertex = leaving + elif ei == e2: + c_entry = cyc_b if pos % 2 == 0 else cyc_a + pos += 1 + c_exit = cyc_b if pos % 2 == 0 else cyc_a + pos += 1 + e2_entry_color = c_entry; e2_exit_color = c_exit + e2_entry_vertex = entry; e2_exit_vertex = leaving + else: + nc = cyc_b if pos % 2 == 0 else cyc_a + other_new_colors[ei] = nc + pos += 1 + + # Identify which half of e1 is on f_n: it's the half adjacent to e_F. + # e_F has 2 endpoints; one is shared with e1, one with e2. + e_F_endpoints = set(edges[e_F]) + e1_endpoints = set(edges[e1]) + e2_endpoints = set(edges[e2]) + shared_e1_eF = (e_F_endpoints & e1_endpoints).pop() + shared_e2_eF = (e_F_endpoints & e2_endpoints).pop() + # The half of e1 on f_n connects X_1 to shared_e1_eF. So if shared_e1_eF + # equals e1_entry_vertex, the half on f_n is the "entry half"; else the + # "exit half". + if shared_e1_eF == e1_entry_vertex: + e1_h_color = e1_entry_color + else: + e1_h_color = e1_exit_color + if shared_e2_eF == e2_entry_vertex: + e2_h_color = e2_entry_color + else: + e2_h_color = e2_exit_color + + # Determine e_F's new colour: if e_F is on the Kempe cycle (other_new_colors) + # use that, else original. + if e_F in other_new_colors: + e_F_color = other_new_colors[e_F] + else: + e_F_color = col_list[e_F] + + # f_n's 4 edge colors: [new edge X_1-X_2 = c, e1_h, e_F, e2_h] + fn_colors = [c, e1_h_color, e_F_color, e2_h_color] + fn_distinct = set(fn_colors) + uses_3_colors = (len(fn_distinct) == 3) + + if uses_3_colors: + return True # clause 4(i) satisfied + + # Otherwise, check clause 4(ii): the {b,c}-Kempe cycle through the new + # edge has only X_1-X_2 in f_n's boundary. + # We need to actually build the modified graph and trace this cycle. + return check_clause_4_kempe_part(H, edges, col_list, named, witness, + a, b, c, e1_h_color, e2_h_color, + other_new_colors, + e1_entry_vertex, e1_exit_vertex, + e2_entry_vertex, e2_exit_vertex, + e1_entry_color, e1_exit_color, + e2_entry_color, e2_exit_color) + + +def check_clause_4_kempe_part(H, edges, col_list, named, witness, a, b, c, + e1_h_color, e2_h_color, other_new_colors, + e1_ev, e1_xv, e2_ev, e2_xv, + e1_ec, e1_xc, e2_ec, e2_xc): + """Build modified H' and check whether the {b,c}-Kempe cycle through X1-X2 + has only that one edge in partial(f_n).""" + e1 = witness['e1']; e2 = witness['e2']; e_F = witness['e_F'] + H2 = H.copy() + X1 = max(v for v in H.vertices(sort=False) if isinstance(v, int)) + 1 + X2 = X1 + 1 + H2.add_vertex(X1); H2.add_vertex(X2) + e1_uv = tuple(edges[e1]); e2_uv = tuple(edges[e2]) + H2.delete_edge(e1_uv); H2.delete_edge(e2_uv) + H2.add_edges([(e1_uv[0], X1), (X1, e1_uv[1]), + (e2_uv[0], X2), (X2, e2_uv[1]), + (X1, X2)]) + # Build coloring for H2 + new_coloring = {} + # Copy non-modified edges: original color (unless in other_new_colors) + for ei, c0 in enumerate(col_list): + if ei == e1 or ei == e2: continue + e_fs = frozenset(edges[ei]) + if ei in other_new_colors: + new_coloring[e_fs] = other_new_colors[ei] + else: + new_coloring[e_fs] = c0 + # Halves of e1, e2 + new_coloring[frozenset((e1_ev, X1))] = e1_ec + new_coloring[frozenset((X1, e1_xv))] = e1_xc + new_coloring[frozenset((e2_ev, X2))] = e2_ec + new_coloring[frozenset((X2, e2_xv))] = e2_xc + new_coloring[frozenset((X1, X2))] = c + + # Determine f_n's edges (in H2): new edge X1-X2, the half of e1 adjacent + # to e_F's shared vertex with e1, e_F itself, and the half of e2 adjacent + # to e_F's shared vertex with e2. + e_F_endpoints = set(edges[e_F]) + e1_endpoints = set(edges[e1]) + e2_endpoints = set(edges[e2]) + shared_e1_eF = (e_F_endpoints & e1_endpoints).pop() + shared_e2_eF = (e_F_endpoints & e2_endpoints).pop() + fn_edges = { + frozenset((X1, X2)), + frozenset((X1, shared_e1_eF)), + frozenset(edges[e_F]), + frozenset((X2, shared_e2_eF)), + } + + # Build edge list of H2 + coloring list + H2_edges = list(H2.edges(labels=False)) + H2_col_list = [new_coloring[frozenset(e)] for e in H2_edges] + # Sanity: verify phi' is a proper 3-edge-colouring of H2 (the conjecture + # asserts this; if the construction is wrong, abort). + for v in H2.vertex_iterator(): + seen = [] + for w in H2.neighbor_iterator(v): + seen.append(new_coloring[frozenset((v, w))]) + if len(set(seen)) != len(seen): + raise RuntimeError( + f"phi' is not proper at vertex {v}: colors {seen}") + # Trace the {b,c}-Kempe cycle through X1-X2 in H2 using new_coloring + H2_X1X2_idx = None + for i, e in enumerate(H2_edges): + if frozenset(e) == frozenset((X1, X2)): + H2_X1X2_idx = i; break + if H2_X1X2_idx is None: + return False + kc_bc = kempe_cycle_set(H2_edges, H2_col_list, H2_X1X2_idx, (b, c)) + # Count edges in kc_bc that are in fn_edges + count = 0 + for ei in kc_bc: + if frozenset(H2_edges[ei]) in fn_edges: + count += 1 + return count == 1 + + +def main(max_n=18, time_budget_per_n=3600): + rows = [] + for n in range(12, max_n + 1): + start = time.time() + try: + triangulations = list(graphs.triangulations(n, minimum_degree=5)) + except Exception as ex: + rows.append((n, 0, 0, 0, f"cannot enumerate")) + continue + n_tri = len(triangulations) + total_col = 0 + total_pass = 0 + timed_out = False + for tri_idx, G in enumerate(triangulations, start=1): + if time.time() - start > time_budget_per_n: + timed_out = True; break + G.is_planar(set_embedding=True) + D = dual_of(G); D.is_planar(set_embedding=True) + for face in D.faces(): + if len(face) != 5: continue + if time.time() - start > time_budget_per_n: + timed_out = True; break + for i_red in range(5): + res = apply_reduction(D, face, i_red, 9999) + if res is None: continue + H = res['H']; named = res['named'] + H.is_planar(set_embedding=True) + edges, colorings = proper_3_edge_colorings(H) + cand = [c for c in colorings + if matches_chord_apex_kempe(edges, c, named)] + for col in cand: + witnesses = find_all_36_witnesses(H, edges, list(col), + named) + if not witnesses: + continue + total_col += 1 + ok = False + for w in witnesses: + try: + if check_clause_4(H, edges, list(col), named, + w): + ok = True + break + except Exception: + pass + if ok: + total_pass += 1 + elapsed = time.time() - start + status = (f"TIMEOUT ({elapsed:.0f}s)" if timed_out + else f"complete ({elapsed:.0f}s)") + rows.append((n, n_tri, total_col, total_pass, status)) + print(f"n={n}: {n_tri} tri, {total_col} cand witnesses, " + f"{total_pass} pass clause 4, {status}") + sys.stdout.flush() + + print() + print("=" * 70) + print(f"{'n':>3} {'#tri':>5} {'#witness':>10} {'#pass_cl4':>10} {'status':>25}") + print("-" * 70) + for n, n_tri, n_col, n_pass, status in rows: + print(f"{n:>3} {n_tri:>5} {n_col:>10} {n_pass:>10} {status:>25}") + + +if __name__ == '__main__': + main() diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe.py b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe.py index c894ce7..47e563b 100644 --- a/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe.py +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe.py @@ -130,11 +130,12 @@ def matches_chord_apex_kempe(edges, col, named): def conjecture_holds_for(H, edges, col, named): - """Returns (F, e1, e2, kc) if some face F has two same-color edges (e1, e2) - and both, together with merged, lie on a Kempe cycle kc. Else None.""" + """Returns (F, e1, e2, kc) if some face F of H has two same-colour edges + (e1, e2), neither equal to merged, both on a Kempe cycle kc through + merged, AND exactly one edge of partial F lies between e1 and e2 along + one of the two arcs of partial F. Else None.""" merged_idx = edge_idx(edges, named['merged']) c_merged = col[merged_idx] - # All Kempe cycles through merged (one per color pair (c_merged, c')) kempe_cycles = [] for c_prime in range(3): if c_prime == c_merged: continue @@ -146,10 +147,17 @@ def conjecture_holds_for(H, edges, col, named): ei = edge_idx(edges, frozenset((u, v))) if ei is not None: face_edge_indices.append(ei) - for i in range(len(face_edge_indices)): - for j in range(i + 1, len(face_edge_indices)): + n_face = len(face_edge_indices) + for i in range(n_face): + for j in range(i + 1, n_face): e1, e2 = face_edge_indices[i], face_edge_indices[j] + if e1 == merged_idx or e2 == merged_idx: continue if col[e1] != col[e2]: continue + # exactly one edge between e1 and e2 in one arc + gap_a = (j - i - 1) + gap_b = (n_face - 2 - gap_a) + if gap_a != 1 and gap_b != 1: + continue for kc in kempe_cycles: if e1 in kc and e2 in kc: return face, e1, e2, kc diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe_scaled.py b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe_scaled.py new file mode 100644 index 0000000..c1e9412 --- /dev/null +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/check_conj_face_kempe_scaled.py @@ -0,0 +1,209 @@ +"""Test Conjecture 3.6 (with the 4-edge-face criterion) across all +min-degree-5 triangulations up to n = 18. Aggregates per-n totals. + +Run with: sage experiments/check_conj_face_kempe_scaled.py +""" +from sage.all import Graph +from sage.graphs.graph_generators import graphs +import sys +import time + + +def dual_of(G): + G.is_planar(set_embedding=True) + faces = G.faces() + edge_to_faces = {} + for fi, face in enumerate(faces): + for u, v in face: + edge_to_faces.setdefault(frozenset((u, v)), []).append(fi) + return Graph( + [(fs[0], fs[1]) for fs in edge_to_faces.values() if len(fs) == 2], + 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 or A[(i+3) % 5] == A[(i+4) % 5]: return None + H = G.copy() + for v in boundary: H.delete_vertex(v) + H.add_vertex(v_n_label) + side_0 = (v_n_label, A[i]) + spike = (v_n_label, A[(i+1) % 5]) + side_1 = (v_n_label, A[(i+2) % 5]) + merged = (A[(i+3) % 5], A[(i+4) % 5]) + H.add_edges([side_0, spike, side_1, merged]) + if H.has_multiple_edges() or H.has_loops(): return None + if not H.is_planar(set_embedding=True): return None + if not all(H.degree(v) == 3 for v in H.vertex_iterator()): return None + return { + 'H': H, + 'named': { + 'spike': frozenset(spike), + 'side_0': frozenset(side_0), + 'side_1': frozenset(side_1), + 'merged': frozenset(merged), + }, + } + + +def proper_3_edge_colorings(G): + edges = list(G.edges(labels=False)) + n = len(edges) + adj = [[] for _ in range(n)] + for i in range(n): + u, v = edges[i][0], edges[i][1] + for j in range(i): + x, y = edges[j][0], edges[j][1] + if u in (x, y) or v in (x, y): + adj[i].append(j); adj[j].append(i) + coloring = [-1] * n + results = [] + + def back(k): + if k == n: + results.append(tuple(coloring)); return + for c in range(3): + if all(coloring[j] != c for j in adj[k]): + coloring[k] = c + back(k + 1) + coloring[k] = -1 + back(0) + return edges, results + + +def kempe_cycle(edges, coloring, start_idx, color_pair): + a, b = color_pair + if coloring[start_idx] not in (a, b): + return set() + in_sub = set(i for i in range(len(edges)) if coloring[i] in (a, b)) + visited = {start_idx}; stack = [start_idx] + while stack: + cur = stack.pop() + u, v = edges[cur][0], edges[cur][1] + for j in in_sub: + if j in visited: + continue + x, y = edges[j][0], edges[j][1] + if u in (x, y) or v in (x, y): + visited.add(j); stack.append(j) + return visited + + +def edge_idx(edges, e_frozen): + for i, e in enumerate(edges): + if frozenset((e[0], e[1])) == e_frozen: + return i + return None + + +def matches_chord_apex_kempe(edges, col, named): + idx = {role: edge_idx(edges, ns) for role, ns in named.items()} + if any(v is None for v in idx.values()): return False + c_spike = col[idx['spike']] + c_merged = col[idx['merged']] + if c_spike != c_merged: return False + c_s0 = col[idx['side_0']]; c_s1 = col[idx['side_1']] + kc0 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s0)) + if idx['side_0'] not in kc0 or idx['merged'] not in kc0: return False + kc1 = kempe_cycle(edges, col, idx['spike'], (c_spike, c_s1)) + if idx['side_1'] not in kc1 or idx['merged'] not in kc1: return False + return True + + +def conjecture_holds_for(H, edges, col, named): + """Full Conjecture 3.6 check: face F with two same-colour edges e1, e2 + (neither equal to merged), both on a common Kempe cycle through merged, + and exactly one edge of partial F lies between them in one arc.""" + merged_idx = edge_idx(edges, named['merged']) + c_merged = col[merged_idx] + kempe_cycles = [] + for c_prime in range(3): + if c_prime == c_merged: continue + kc = kempe_cycle(edges, col, merged_idx, (c_merged, c_prime)) + kempe_cycles.append(kc) + for face in H.faces(): + face_edge_indices = [] + for u, v in face: + ei = edge_idx(edges, frozenset((u, v))) + if ei is not None: + face_edge_indices.append(ei) + n_face = len(face_edge_indices) + for i in range(n_face): + for j in range(i + 1, n_face): + e1, e2 = face_edge_indices[i], face_edge_indices[j] + if e1 == merged_idx or e2 == merged_idx: continue + if col[e1] != col[e2]: continue + gap_a = (j - i - 1) + gap_b = (n_face - 2 - gap_a) + if gap_a != 1 and gap_b != 1: + continue + for kc in kempe_cycles: + if e1 in kc and e2 in kc: + return True + return False + + +def main(max_n=22, time_budget_per_n=1800): + rows = [] + for n in range(12, max_n + 1): + start = time.time() + try: + triangulations = list(graphs.triangulations(n, minimum_degree=5)) + except Exception as ex: + print(f"n={n}: cannot enumerate: {ex}") + rows.append((n, None, None, None, 'cannot enumerate')) + continue + n_tri = len(triangulations) + total_col = 0 + total_pass = 0 + timed_out = False + for tri_idx, G in enumerate(triangulations, start=1): + if time.time() - start > time_budget_per_n: + timed_out = True + break + G.is_planar(set_embedding=True) + D = dual_of(G); D.is_planar(set_embedding=True) + for face in D.faces(): + if len(face) != 5: continue + if time.time() - start > time_budget_per_n: + timed_out = True + break + for i_red in range(5): + res = apply_reduction(D, face, i_red, 9999) + if res is None: continue + H = res['H']; named = res['named'] + H.is_planar(set_embedding=True) + edges, colorings = proper_3_edge_colorings(H) + cand = [c for c in colorings + if matches_chord_apex_kempe(edges, c, named)] + for col in cand: + total_col += 1 + if conjecture_holds_for(H, edges, col, named): + total_pass += 1 + elapsed = time.time() - start + status = (f"TIMEOUT after {elapsed:.0f}s" if timed_out + else f"complete ({elapsed:.0f}s)") + rows.append((n, n_tri, total_col, total_pass, status)) + print(f"n={n}: {n_tri} tri, {total_col} colorings, " + f"{total_pass} pass, {status}") + sys.stdout.flush() + + print() + print("=" * 60) + print(f"{'n':>3} {'#tri':>5} {'#col':>10} {'#pass':>10} {'status':>20}") + print("-" * 60) + for n, n_tri, n_col, n_pass, status in rows: + n_tri_s = str(n_tri) if n_tri is not None else '-' + n_col_s = str(n_col) if n_col is not None else '-' + n_pass_s = str(n_pass) if n_pass is not None else '-' + print(f"{n:>3} {n_tri_s:>5} {n_col_s:>10} {n_pass_s:>10} {status:>20}") + + +if __name__ == '__main__': + main() diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/draw_cubic_edge_contraction.py b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_cubic_edge_contraction.py new file mode 100644 index 0000000..25c0c5e --- /dev/null +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_cubic_edge_contraction.py @@ -0,0 +1,122 @@ +"""Draw a 3-panel illustration of cubic-graph edge contraction: + (1) the original cubic graph fragment with edge e = uv highlighted; + (2) after deleting e (u, v are degree-2); + (3) after smoothing u, v (gone, replaced by single edges). + +Produces fig_cubic_edge_contraction.png. +""" +import os +import matplotlib.pyplot as plt +from matplotlib.patches import FancyArrowPatch + +OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +DARK = '#374151' +GRAY = '#9ca3af' +HIGHLIGHT = '#dc2626' # the edge being contracted (panel 1) +GHOST = '#fca5a5' # removed edges (panel 2) +DEG2 = '#f59e0b' # degree-2 vertices (panel 2) +NEW = '#2563eb' # smoothed-in new edges (panel 3) + + +# Positions: u centered at (-1, 0), v at (1, 0); their outer neighbors angled. +pos = { + 'u': (-1.0, 0.0), + 'v': ( 1.0, 0.0), + 'a': (-2.2, 1.0), + 'b': (-2.2, -1.0), + 'c': ( 2.2, 1.0), + 'd': ( 2.2, -1.0), +} + + +def draw_vertex(ax, p, color, size=110, label=None, label_offset=(0, 0.22)): + ax.scatter([p[0]], [p[1]], s=size, color=color, zorder=4) + if label is not None: + ax.text(p[0] + label_offset[0], p[1] + label_offset[1], label, + ha='center', va='center', fontsize=12, zorder=5, + color=DARK) + + +def draw_edge(ax, p, q, color, lw=2.0, ls='-', zorder=2): + ax.plot([p[0], q[0]], [p[1], q[1]], color=color, lw=lw, ls=ls, + zorder=zorder, solid_capstyle='round') + + +def panel_before(ax): + # Outer edges (gray) + for (x, y) in [('a', 'u'), ('b', 'u'), ('c', 'v'), ('d', 'v')]: + draw_edge(ax, pos[x], pos[y], DARK, lw=2.0) + # The highlighted edge e = uv + draw_edge(ax, pos['u'], pos['v'], HIGHLIGHT, lw=3.2) + # Vertices + for v in ('a', 'b', 'c', 'd'): + draw_vertex(ax, pos[v], DARK, size=60) + draw_vertex(ax, pos['u'], DARK, size=120, label='$u$', + label_offset=(-0.05, 0.28)) + draw_vertex(ax, pos['v'], DARK, size=120, label='$v$', + label_offset=(0.05, 0.28)) + # Label on the edge + mid = ((pos['u'][0] + pos['v'][0]) / 2, + (pos['u'][1] + pos['v'][1]) / 2) + ax.text(mid[0], mid[1] + 0.25, '$e$', ha='center', va='center', + fontsize=13, color=HIGHLIGHT, zorder=5) + ax.set_title('(1) cubic plane graph with edge $e = uv$', + fontsize=11, color=DARK, pad=8) + + +def panel_after_delete(ax): + # Outer edges (gray) + for (x, y) in [('a', 'u'), ('b', 'u'), ('c', 'v'), ('d', 'v')]: + draw_edge(ax, pos[x], pos[y], DARK, lw=2.0) + # Ghost the deleted edge + draw_edge(ax, pos['u'], pos['v'], GHOST, lw=2.0, ls=':') + # Vertices: u, v are now degree-2 (highlighted color) + for v in ('a', 'b', 'c', 'd'): + draw_vertex(ax, pos[v], DARK, size=60) + draw_vertex(ax, pos['u'], DEG2, size=140, label='$u$', + label_offset=(-0.05, 0.32)) + draw_vertex(ax, pos['v'], DEG2, size=140, label='$v$', + label_offset=(0.05, 0.32)) + ax.set_title('(2) delete $e$: $u, v$ now have degree $2$', + fontsize=11, color=DARK, pad=8) + + +def panel_after_smooth(ax): + # The smoothed-in new edges + draw_edge(ax, pos['a'], pos['b'], NEW, lw=3.0) + draw_edge(ax, pos['c'], pos['d'], NEW, lw=3.0) + # Outer vertices remain + for v in ('a', 'b', 'c', 'd'): + draw_vertex(ax, pos[v], DARK, size=60) + # u, v are gone — show their former positions as faint markers + ax.scatter([pos['u'][0], pos['v'][0]], [pos['u'][1], pos['v'][1]], + s=140, facecolors='none', edgecolors=GRAY, lw=1.0, + linestyles='--', zorder=3) + ax.text(pos['u'][0], pos['u'][1] + 0.32, '$u$ gone', + ha='center', va='center', fontsize=9, color=GRAY) + ax.text(pos['v'][0], pos['v'][1] + 0.32, '$v$ gone', + ha='center', va='center', fontsize=9, color=GRAY) + ax.set_title('(3) smooth $u, v$: their incident edges merge', + fontsize=11, color=DARK, pad=8) + + +def main(): + fig, axes = plt.subplots(1, 3, figsize=(13.5, 4.2)) + for ax in axes: + ax.set_xlim(-3.0, 3.0) + ax.set_ylim(-1.7, 1.7) + ax.set_aspect('equal') + ax.axis('off') + panel_before(axes[0]) + panel_after_delete(axes[1]) + panel_after_smooth(axes[2]) + plt.subplots_adjust(left=0.02, right=0.98, top=0.92, bottom=0.04, + wspace=0.05) + out = os.path.join(OUT_DIR, 'fig_cubic_edge_contraction.png') + plt.savefig(out, dpi=180, bbox_inches='tight') + print(f"wrote {out}") + + +if __name__ == '__main__': + main() diff --git a/papers/dual_decomposition_minimal_counterexamples/experiments/draw_thm_cubic_contraction_4face.py b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_thm_cubic_contraction_4face.py new file mode 100644 index 0000000..69076fc --- /dev/null +++ b/papers/dual_decomposition_minimal_counterexamples/experiments/draw_thm_cubic_contraction_4face.py @@ -0,0 +1,205 @@ +"""Two-panel illustration of Theorem (cubic contraction across a 4-face). + +Left: H near the 4-face, with the forced 3-edge-colouring + e_0=a, e_1=b, e_2=e_3=c, w_0=w_1=b, w_2=w_3=a. +Right: H' after cubic-graph edge contraction on e_0, with the new colouring + (e_2', e_3' both b; e_1 recoloured to c; everything else unchanged). + +Produces fig_thm_cubic_contraction_4face.png. +""" +import os +import matplotlib.pyplot as plt + +OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +DARK = '#374151' +GRAY = '#9ca3af' +GHOST = '#fca5a5' +DEG2 = '#f59e0b' + +# Colour code: a=orange-ish, b=blue, c=green. Chosen colourblind-friendly. +COL_A = '#ea580c' # 'a' +COL_B = '#2563eb' # 'b' +COL_C = '#16a34a' # 'c' + +# Positions of the 4-face vertices and their external neighbours. +pos = { + 'v0': (0.0, 0.0), + 'v1': (2.4, 0.0), + 'v2': (2.4, 2.4), + 'v3': (0.0, 2.4), + 'u0': (-1.5, -0.8), + 'u1': ( 3.9, -0.8), + 'u2': ( 3.9, 3.2), + 'u3': (-1.5, 3.2), +} + + +def draw_edge(ax, p, q, color, lw=2.4, ls='-', zorder=2): + ax.plot([p[0], q[0]], [p[1], q[1]], color=color, lw=lw, ls=ls, + zorder=zorder, solid_capstyle='round') + + +def draw_vertex(ax, p, color=DARK, size=70, zorder=4): + ax.scatter([p[0]], [p[1]], s=size, color=color, zorder=zorder) + + +def label_vertex(ax, p, text, offset=(0.0, 0.28), fontsize=12, color=DARK): + ax.text(p[0] + offset[0], p[1] + offset[1], text, + ha='center', va='center', fontsize=fontsize, color=color, + zorder=5) + + +def label_edge(ax, p, q, text, offset=(0.0, 0.0), color=DARK, fontsize=11): + mid = ((p[0] + q[0]) / 2 + offset[0], (p[1] + q[1]) / 2 + offset[1]) + ax.text(mid[0], mid[1], text, ha='center', va='center', + fontsize=fontsize, color=color, zorder=5, + bbox=dict(boxstyle='round,pad=0.15', facecolor='white', + edgecolor='none', alpha=0.85)) + + +def shade_face(ax, vs, color='#fef9c3', alpha=0.7): + xs = [p[0] for p in vs] + [vs[0][0]] + ys = [p[1] for p in vs] + [vs[0][1]] + ax.fill(xs, ys, color=color, alpha=alpha, zorder=1) + + +def panel_before(ax): + # 4-face shading + shade_face(ax, [pos['v0'], pos['v1'], pos['v2'], pos['v3']]) + # Face label + ax.text(1.2, 1.2, '$f$', ha='center', va='center', fontsize=14, + color=GRAY, style='italic', zorder=2) + + # Face edges + draw_edge(ax, pos['v0'], pos['v1'], COL_A) # e_0 = a + draw_edge(ax, pos['v2'], pos['v3'], COL_B) # e_1 = b + draw_edge(ax, pos['v1'], pos['v2'], COL_C) # e_2 = c + draw_edge(ax, pos['v3'], pos['v0'], COL_C) # e_3 = c + # External edges + draw_edge(ax, pos['v0'], pos['u0'], COL_B) # w_0 = b + draw_edge(ax, pos['v1'], pos['u1'], COL_B) # w_1 = b + draw_edge(ax, pos['v2'], pos['u2'], COL_A) # w_2 = a + draw_edge(ax, pos['v3'], pos['u3'], COL_A) # w_3 = a + # Vertices + for v in ('v0', 'v1', 'v2', 'v3'): + draw_vertex(ax, pos[v], DARK, size=90) + for u in ('u0', 'u1', 'u2', 'u3'): + draw_vertex(ax, pos[u], DARK, size=60) + + # Labels + label_vertex(ax, pos['v0'], '$v_0$', offset=(-0.20, -0.25)) + label_vertex(ax, pos['v1'], '$v_1$', offset=( 0.20, -0.25)) + label_vertex(ax, pos['v2'], '$v_2$', offset=( 0.20, 0.25)) + label_vertex(ax, pos['v3'], '$v_3$', offset=(-0.20, 0.25)) + label_vertex(ax, pos['u0'], '$u_0$', offset=(-0.25, 0.00)) + label_vertex(ax, pos['u1'], '$u_1$', offset=( 0.25, 0.00)) + label_vertex(ax, pos['u2'], '$u_2$', offset=( 0.25, 0.00)) + label_vertex(ax, pos['u3'], '$u_3$', offset=(-0.25, 0.00)) + + # Edge labels with colour + label_edge(ax, pos['v0'], pos['v1'], r'$e_0\!=\!a$', offset=(0, -0.18), + color=COL_A) + label_edge(ax, pos['v2'], pos['v3'], r'$e_1\!=\!b$', offset=(0, 0.18), + color=COL_B) + label_edge(ax, pos['v1'], pos['v2'], r'$e_2\!=\!c$', offset=(0.30, 0.0), + color=COL_C) + label_edge(ax, pos['v3'], pos['v0'], r'$e_3\!=\!c$', offset=(-0.30, 0.0), + color=COL_C) + label_edge(ax, pos['v0'], pos['u0'], r'$w_0\!=\!b$', + offset=(-0.05, -0.05), color=COL_B, fontsize=10) + label_edge(ax, pos['v1'], pos['u1'], r'$w_1\!=\!b$', + offset=( 0.05, -0.05), color=COL_B, fontsize=10) + label_edge(ax, pos['v2'], pos['u2'], r'$w_2\!=\!a$', + offset=( 0.05, 0.05), color=COL_A, fontsize=10) + label_edge(ax, pos['v3'], pos['u3'], r'$w_3\!=\!a$', + offset=(-0.05, 0.05), color=COL_A, fontsize=10) + + ax.set_title('$H$ with proper $3$-edge-colouring $\\varphi$:\n' + '$\\varphi(e_0)=a$, $\\varphi(e_1)=b$ (opposite, different)', + fontsize=11, color=DARK, pad=10) + + +def panel_after(ax): + # New 'face' shading: now (v_2, v_3) connected via e_1, plus new edges + # form a hexagonal-ish region. Just shade the area lightly. + shade_face(ax, + [pos['u0'], pos['v3'], pos['v2'], pos['u1'], + (pos['u1'][0] + 0.7, pos['u1'][1] - 0.7), + (pos['u0'][0] - 0.7, pos['u0'][1] - 0.7)], + color='#fef9c3', alpha=0.0) # invisible, just spacing + + # Ghost the deleted edges (e_0, e_2, e_3, w_0, w_1) and former vertices + draw_edge(ax, pos['v0'], pos['v1'], GHOST, lw=1.5, ls=':') # e_0 + draw_edge(ax, pos['v1'], pos['v2'], GHOST, lw=1.5, ls=':') # e_2 + draw_edge(ax, pos['v3'], pos['v0'], GHOST, lw=1.5, ls=':') # e_3 + draw_edge(ax, pos['v0'], pos['u0'], GHOST, lw=1.5, ls=':') # w_0 + draw_edge(ax, pos['v1'], pos['u1'], GHOST, lw=1.5, ls=':') # w_1 + + # Surviving / recoloured edges + draw_edge(ax, pos['v2'], pos['v3'], COL_C, lw=3.0) # e_1 recoloured to c + draw_edge(ax, pos['v2'], pos['u2'], COL_A) # w_2 = a + draw_edge(ax, pos['v3'], pos['u3'], COL_A) # w_3 = a + + # Smoothed-in new edges: e_2' from v_2 to u_1, e_3' from v_3 to u_0 + draw_edge(ax, pos['v2'], pos['u1'], COL_B, lw=3.0) + draw_edge(ax, pos['v3'], pos['u0'], COL_B, lw=3.0) + + # Vertices: v_0, v_1 removed; show their former positions faintly + for ghost in ('v0', 'v1'): + ax.scatter([pos[ghost][0]], [pos[ghost][1]], s=120, + facecolors='none', edgecolors=GRAY, lw=1.0, + linestyles='--', zorder=3) + for v in ('v2', 'v3'): + draw_vertex(ax, pos[v], DARK, size=90) + for u in ('u0', 'u1', 'u2', 'u3'): + draw_vertex(ax, pos[u], DARK, size=60) + + # Labels + ax.text(pos['v0'][0], pos['v0'][1] - 0.30, '$v_0$ gone', + ha='center', va='center', fontsize=9, color=GRAY) + ax.text(pos['v1'][0], pos['v1'][1] - 0.30, '$v_1$ gone', + ha='center', va='center', fontsize=9, color=GRAY) + + label_vertex(ax, pos['v2'], '$v_2$', offset=( 0.20, 0.25)) + label_vertex(ax, pos['v3'], '$v_3$', offset=(-0.20, 0.25)) + label_vertex(ax, pos['u0'], '$u_0$', offset=(-0.25, 0.00)) + label_vertex(ax, pos['u1'], '$u_1$', offset=( 0.25, 0.00)) + label_vertex(ax, pos['u2'], '$u_2$', offset=( 0.25, 0.00)) + label_vertex(ax, pos['u3'], '$u_3$', offset=(-0.25, 0.00)) + + # Edge labels for the new/recoloured edges + label_edge(ax, pos['v2'], pos['v3'], r'$e_1\!=\!c$', offset=(0, 0.18), + color=COL_C) + label_edge(ax, pos['v2'], pos['u1'], r"$e_2'\!=\!b$", + offset=(0.20, 0.10), color=COL_B) + label_edge(ax, pos['v3'], pos['u0'], r"$e_3'\!=\!b$", + offset=(-0.20, 0.10), color=COL_B) + label_edge(ax, pos['v2'], pos['u2'], r'$w_2\!=\!a$', + offset=( 0.05, 0.05), color=COL_A, fontsize=10) + label_edge(ax, pos['v3'], pos['u3'], r'$w_3\!=\!a$', + offset=(-0.05, 0.05), color=COL_A, fontsize=10) + + ax.set_title("$H'$ after cubic contraction of $e_0$:\n" + r"$e_2', e_3'$ get colour $b$; $e_1$ recoloured to $c$", + fontsize=11, color=DARK, pad=10) + + +def main(): + fig, axes = plt.subplots(1, 2, figsize=(13, 5.8)) + for ax in axes: + ax.set_xlim(-2.6, 4.8) + ax.set_ylim(-2.0, 4.2) + ax.set_aspect('equal') + ax.axis('off') + panel_before(axes[0]) + panel_after(axes[1]) + plt.subplots_adjust(left=0.02, right=0.98, top=0.92, bottom=0.04, + wspace=0.05) + out = os.path.join(OUT_DIR, 'fig_thm_cubic_contraction_4face.png') + plt.savefig(out, dpi=180, bbox_inches='tight') + print(f"wrote {out}") + + +if __name__ == '__main__': + main() diff --git a/papers/dual_decomposition_minimal_counterexamples/fig_cubic_edge_contraction.png b/papers/dual_decomposition_minimal_counterexamples/fig_cubic_edge_contraction.png new file mode 100644 index 0000000..46572d0 Binary files /dev/null and b/papers/dual_decomposition_minimal_counterexamples/fig_cubic_edge_contraction.png differ diff --git a/papers/dual_decomposition_minimal_counterexamples/fig_thm_cubic_contraction_4face.png b/papers/dual_decomposition_minimal_counterexamples/fig_thm_cubic_contraction_4face.png new file mode 100644 index 0000000..29687dc Binary files /dev/null and b/papers/dual_decomposition_minimal_counterexamples/fig_thm_cubic_contraction_4face.png differ diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.aux b/papers/dual_decomposition_minimal_counterexamples/paper.aux index 95a65b0..b479cd5 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.aux +++ b/papers/dual_decomposition_minimal_counterexamples/paper.aux @@ -20,11 +20,20 @@ \@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{lem:exactly-one-match}{{3.4}{8}} +\newlabel{lem:all-distinct-exists}{{3.5}{9}} +\newlabel{conj:face-monochromatic-pair-on-merged-kempe-cycle}{{3.6}{9}} +\newlabel{rem:conj-3-6-empirical}{{3.7}{10}} +\newlabel{conj:face-monochromatic-pair-strengthened}{{3.8}{10}} +\newlabel{rem:conj-3-8-empirical}{{3.9}{11}} +\newlabel{def:cubic-edge-contraction}{{3.10}{11}} +\newlabel{thm:cubic-contraction-4face}{{3.11}{11}} \newlabel{tocindent-1}{0pt} \newlabel{tocindent0}{0pt} \newlabel{tocindent1}{17.77782pt} \newlabel{tocindent2}{0pt} \newlabel{tocindent3}{0pt} -\newlabel{lem:all-distinct-exists}{{3.5}{9}} -\newlabel{conj:face-monochromatic-pair-on-merged-kempe-cycle}{{3.6}{9}} -\gdef \@abspage@last{9} +\@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces Cubic-graph edge contraction (Definition\nonbreakingspace 3.10\hbox {}). Left: a fragment of a cubic plane graph with the contracted edge $e = uv$ highlighted in red. Middle: deleting $e$ leaves $u$ and $v$ of degree\nonbreakingspace $2$. Right: smoothing $u$ and $v$ replaces each pair of incident edges by a single new edge, removing $u, v$ and giving a cubic plane graph again.}}{12}{}\protected@file@percent } +\newlabel{fig:cubic-edge-contraction}{{4}{12}} +\@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces The recolouring used in the proof of Theorem\nonbreakingspace 3.11\hbox {}. Left: the $4$-face $f$ of $H$ under $\varphi $, with the forced colours $\varphi (e_0) = a$, $\varphi (e_1) = b$, $\varphi (e_2) = \varphi (e_3) = c$, $\varphi (w_0) = \varphi (w_1) = b$, and $\varphi (w_2) = \varphi (w_3) = a$. Right: the contracted graph $H'$ under $\varphi '$. The smoothed-in edges $e_2', e_3'$ inherit the colour $b$ from $w_0, w_1$, and $e_1$ is recoloured from $b$ to $c$; every edge outside the face neighbourhood keeps its $\varphi $-colour (dotted in red: the five edges of $H$ removed by the contraction).}}{13}{}\protected@file@percent } +\newlabel{fig:thm-cubic-contraction-4face}{{5}{13}} +\gdef \@abspage@last{13} diff --git a/papers/dual_decomposition_minimal_counterexamples/paper.log b/papers/dual_decomposition_minimal_counterexamples/paper.log index ebc5fd5..0f94032 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.log +++ b/papers/dual_decomposition_minimal_counterexamples/paper.log @@ -1,12 +1,12 @@ -This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 24 MAY 2026 11:25 +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 24 MAY 2026 13:27 entering extended mode restricted \write18 enabled. - file:line:error style messages enabled. %&-line parsing enabled. -**/Users/didericis/Code/math-research/papers/dual_decomposition_minimal_counterexamples/paper.tex -(/Users/didericis/Code/math-research/papers/dual_decomposition_minimal_counterexamples/paper.tex +**paper.tex +(./paper.tex LaTeX2e <2021-11-15> patch level 1 -L3 programming layer <2022-02-24> (/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls +L3 programming layer <2022-02-24> +(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls Document Class: amsart 2020/05/29 v2.20.6 \linespacing=\dimen138 \normalparindent=\dimen139 @@ -18,14 +18,17 @@ Package: amsmath 2021/10/15 v2.17l AMS math features For additional information on amsmath, use the `?' option. (/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty Package: amstext 2021/08/26 v2.01 AMS text - (/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty File: amsgen.sty 1999/11/30 v2.0 generic functions \@emptytoks=\toks16 \ex@=\dimen140 -)) (/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty +)) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty Package: amsbsy 1999/11/29 v1.2d Bold Symbols \pmbraise@=\dimen141 -) (/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty Package: amsopn 2021/08/26 v2.02 operator names ) \inf@bad=\count185 @@ -66,10 +69,13 @@ LaTeX Font Info: Redeclaring font encoding OMS on input line 744. LaTeX Info: Redefining \[ on input line 2938. LaTeX Info: Redefining \] on input line 2939. ) -LaTeX Font Info: Trying to load font information for U+msa on input line 397. - (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd +LaTeX Font Info: Trying to load font information for U+msa on input line 397 +. + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd File: umsa.fd 2013/01/14 v3.01 AMS symbols A -) (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support \symAMSa=\mathgroup4 \symAMSb=\mathgroup5 @@ -100,33 +106,42 @@ LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' \thm@postskip=\skip55 \thm@headsep=\skip56 \dth@everypar=\toks26 -) (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty Package: amssymb 2013/01/14 v3.01 AMS font symbols -) (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR) - (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty Package: keyval 2014/10/28 v1.15 key=value parser (DPC) \KV@toks@=\toks27 -) (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR) - (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty Package: trig 2021/08/11 v1.11 sin cos tan (DPC) -) (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration ) Package graphics Info: Driver file: pdftex.def on input line 107. - (/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex )) \Gin@req@height=\dimen150 \Gin@req@width=\dimen151 ) \c@theorem=\count272 - (/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def + +(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX) \l__color_backend_stack_int=\count273 \l__pdf_internal_box=\box53 -) (./paper.aux) +) +(./paper.aux) \openout1 = `paper.aux'. LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 27. @@ -144,13 +159,17 @@ 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 27. - (/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd + + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd File: umsb.fd 2013/01/14 v3.01 AMS symbols B -) (/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii +) +(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii [Loading MPS to PDF converter (version 2006.09.02).] \scratchcounter=\count274 \scratchdimen=\dimen152 @@ -165,12 +184,18 @@ File: umsb.fd 2013/01/14 v3.01 AMS symbols B \everyMPtoPDFconversion=\toks29 ) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf -Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 485. - (/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg -File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Live -)) [1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] +Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4 +85. + +(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +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 145--147 -[]\OT1/cmr/m/n/10 List the five degree-$2$ ver-tices in clock-wise or-der around $\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 )$. +[]\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 )$. [] @@ -196,7 +221,8 @@ Package pdftex.def Info: fig_reduced_dual_step4.png used on input line 168. LaTeX Warning: `h' float specifier changed to `ht'. -[2] [3 <./fig_reduced_dual_step1.png> <./fig_reduced_dual_step2.png> <./fig_reduced_dual_step3.png> <./fig_reduced_dual_step4.png>] +[2] [3 <./fig_reduced_dual_step1.png> <./fig_reduced_dual_step2.png> <./fig_red +uced_dual_step3.png> <./fig_reduced_dual_step4.png>] File: fig_chord_apex_step1.png Graphic file (type png) @@ -216,9 +242,15 @@ Package pdftex.def Info: fig_chord_apex_step3.png used on input line 291. LaTeX Warning: `h' float specifier changed to `ht'. -[4] [5 <./fig_chord_apex_step1.png> <./fig_chord_apex_step2.png> <./fig_chord_apex_step3.png>] [6] +[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 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[]\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 )\OML/cmm/m/it/10 ; '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\OT1/cmr/m/n/10 )$ +\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[ +]\OT1/cmr/m/n/10 )\OML/cmm/m/it/10 ; '[]\OT1/cmr/m/n/10 (\OML/cmm/m/it/10 f[]\O +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 )$ [] @@ -238,17 +270,21 @@ 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$ +\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$ ver- +\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[]$ +\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[]$ [] @@ -261,20 +297,73 @@ 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>] [9] (./paper.aux) ) -Here is how much of TeX's memory you used: - 3082 strings out of 478268 - 44173 string characters out of 5846347 - 344279 words of memory out of 5000000 - 21118 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,1392b,298s stack positions out of 10000i,1000n,20000p,200000b,200000s - -Output written on paper.pdf (9 pages, 977618 bytes). -PDF statistics: - 140 PDF objects out of 1000 (max. 8388607) - 74 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) +[7] [8 <./fig_alg_step0.png> <./fig_alg_step1.png> <./fig_alg_step2.png>] +[9] [10] +Underfull \hbox (badness 1648) in paragraph at lines 705--710 +\OT1/cmr/m/it/10 Remark \OT1/cmr/m/n/10 3.9\OT1/cmr/m/it/10 . \OT1/cmr/m/n/10 T +he strength-ened con-jec-ture was tested on the same chord- + [] + + +Underfull \hbox (badness 1014) in paragraph at lines 705--710 +\OT1/cmr/m/n/10 apex+Kempe colour-ings as Re-mark 3.7[]; for each colour-ing we + sought any + [] + + +File: fig_cubic_edge_contraction.png Graphic file (type png) + +Package pdftex.def Info: fig_cubic_edge_contraction.png used on input line 769 +. +(pdftex.def) Requested size: 341.9989pt x 73.08138pt. + +LaTeX Warning: `h' float specifier changed to `ht'. + +[11] + +File: fig_thm_cubic_contraction_4face.png Graphic file (type png) + +Package pdftex.def Info: fig_thm_cubic_contraction_4face.png used on input lin +e 844. +(pdftex.def) Requested size: 352.79846pt x 160.78339pt. + + +LaTeX Warning: `h' float specifier changed to `ht'. + +[12 <./fig_cubic_edge_contraction.png>] [13 <./fig_thm_cubic_contraction_4face. +png>] (./paper.aux) ) +Here is how much of TeX's memory you used: + 3112 strings out of 478268 + 44739 string characters out of 5846347 + 347356 words of memory out of 5000000 + 21143 multiletter control sequences out of 15000+600000 + 478077 words of font info for 62 fonts, out of 8000000 for 9000 + 1302 hyphenation exceptions out of 8191 + 69i,9n,76p,1306b,398s stack positions out of 10000i,1000n,20000p,200000b,200000s + +Output written on paper.pdf (13 pages, 1144897 bytes). +PDF statistics: + 172 PDF objects out of 1000 (max. 8388607) + 92 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 61 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 a4f46b4..d775bbe 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 936e5af..8fa70ee 100644 --- a/papers/dual_decomposition_minimal_counterexamples/paper.tex +++ b/papers/dual_decomposition_minimal_counterexamples/paper.tex @@ -610,10 +610,249 @@ a face $F$ of $\widehat{G}'_{v,i}$ and two distinct edges $e_1, e_2 \in \partial F$, with neither $e_1$ nor $e_2$ equal to the merged edge, such that \begin{enumerate} - \item $\varphi(e_1) = \varphi(e_2)$, and + \item $\varphi(e_1) = \varphi(e_2)$, \item $e_1$, $e_2$, and the merged edge all lie on a common - $\{a, b\}$-Kempe cycle of $\varphi$. + $\{a, b\}$-Kempe cycle of $\varphi$, and + \item exactly one edge of $\partial F$ lies between $e_1$ and $e_2$ along + one of the two arcs of $\partial F$; equivalently, subdividing $e_1$ + and $e_2$ by new vertices $X_1, X_2$ and joining them by a new edge + $X_1 X_2$ inside $F$ creates a new face bounded by exactly $4$ + edges (the new edge, the two subdivision halves adjacent to it, and + the single $\partial F$-edge between $e_1$ and $e_2$). \end{enumerate} \end{conjecture} +\begin{remark} +\label{rem:conj-3-6-empirical} +The conjecture cannot be tested on actual minimal counterexamples (none exist +by the Four Colour Theorem), but its conclusion is checkable on the +structural surrogates: proper $3$-edge-colourings of reduced duals that +satisfy both the chord-apex condition (Lemma~\ref{lem:chord-apex}) and the +Kempe-cycle conditions (Lemma~\ref{lem:kempe-spike}), since a counterexample's +reduced dual is forced to admit such colourings under any proper colouring. +For every min-degree-$5$ triangulation $G$ with $|V(G)| \leq 21$, every +pentagonal face $F$ of $G'$, and every reduction index +$i \in \{0,\dots,4\}$, we enumerated all such colourings and tested the +three clauses of Conjecture~\ref{conj:face-monochromatic-pair-on-merged-kempe-cycle} +(see \texttt{experiments/check\_conj\_face\_kempe\_scaled.py}); $n = 22$ +ran past a $1800$\,s budget after $641{,}700$ colourings (all pass), but +did not finish the full set of $651$ triangulations: +\begin{center} +\small +\renewcommand{\arraystretch}{1.15} +\begin{tabular}{r|r|r|r|l} +$n$ & \#tri & \#col.\ tested & \#sat.\ & status \\ +\hline +$12$ & $1$ & $0$ & --- & vacuous (icosahedron) \\ +$13$ & $0$ & --- & --- & no min-deg-$5$ tri \\ +$14$ & $1$ & $216$ & $216$ & all pass \\ +$15$ & $1$ & $0$ & --- & vacuous \\ +$16$ & $3$ & $864$ & $864$ & all pass \\ +$17$ & $4$ & $4{,}650$ & $4{,}650$ & all pass \\ +$18$ & $12$ & $8{,}070$ & $8{,}070$ & all pass \\ +$19$ & $23$ & $21{,}138$ & $21{,}138$ & all pass \\ +$20$ & $73$ & $107{,}874$ & $107{,}874$ & all pass \\ +$21$ & $192$ & $392{,}370$ & $392{,}370$ & all pass \\ +$22$ (part.) & $651$ & $641{,}700$ & $641{,}700$ & timeout \\ +\hline +total ($n \le 21$) & $311$ & $535{,}182$ & $535{,}182$ & \\ +\end{tabular} +\end{center} +\noindent The vacuous rows ($n = 12, 15$) are those where the relevant +reduced duals admit no proper $3$-edge-colouring satisfying chord-apex + +both Kempe-cycle conditions, so the conjecture has no content there. On +every $(G, F, i, \varphi)$ with content, all three clauses of the +conjecture hold simultaneously. +\end{remark} + +\begin{conjecture}[Strengthening of Conjecture~\ref{conj:face-monochromatic-pair-on-merged-kempe-cycle}] +\label{conj:face-monochromatic-pair-strengthened} +Let $G$, $\widehat{G}'_{v,i}$, $\varphi$ be as in +Conjecture~\ref{conj:face-monochromatic-pair-on-merged-kempe-cycle}. Then +there exist $F$, $e_1$, $e_2$ satisfying clauses (1)--(3) of that +conjecture, and the following additional clause holds. + +Let $X_1, X_2$ be the new vertices subdividing $e_1, e_2$, joined by a new +edge $X_1 X_2$ inside $F$; write $\widehat{G}'^{+}$ for the resulting +modified graph (which has $|V(\widehat{G}'_{v,i})|+2$ vertices and +$|E(\widehat{G}'_{v,i})|+3$ edges, is again cubic and plane, and admits a +proper $3$-edge-colouring). Let $\varphi'$ be the proper +$3$-edge-colouring of $\widehat{G}'^{+}$ obtained from $\varphi$ by +swapping the two colours along the (subdivided) $\{a, b\}$-Kempe cycle of +clause~(2) and assigning the new edge $X_1 X_2$ the remaining (third) +colour. In particular $\varphi'$ agrees with $\varphi$ on every edge of +$\widehat{G}'_{v,i}$ outside that Kempe cycle, and at $X_1$ and $X_2$ the +two subdivision halves take the colours $\{a, b\}$ in the order forced by +propriety. Write $a := \varphi(e_1) = \varphi(e_2)$, +$c := \varphi'(X_1 X_2)$, and let $b$ be the third colour. Let $f_n$ be +the new $4$-edge face of $\widehat{G}'^{+}$ incident to $X_1 X_2$. Then: +\begin{enumerate} + \setcounter{enumi}{3} + \item either + \begin{enumerate} + \item[(i)] $\partial f_n$ uses all three colours under + $\varphi'$, or + \item[(ii)] the $\{b, c\}$-Kempe cycle of $\varphi'$ through + $X_1 X_2$ is incident to exactly one edge of + $\partial f_n$ (namely $X_1 X_2$ itself). + \end{enumerate} +\end{enumerate} +\end{conjecture} + +\begin{remark} +\label{rem:conj-3-8-empirical} +\sloppy +The strengthened conjecture was tested on the same chord-apex+Kempe +colourings as Remark~\ref{rem:conj-3-6-empirical}; for each colouring we +sought any Conjecture-3.6-witness $(F, e_1, e_2)$ whose accompanying +$f_n$ satisfies clause~(4) (see +\texttt{experiments/check\_conj\_3\_8\_scaled.py}): +\begin{center} +\small +\renewcommand{\arraystretch}{1.15} +\begin{tabular}{r|r|r|r|l} +$n$ & \#tri & \#col.\ tested & \#sat.\ & status \\ +\hline +$12$ & $1$ & $0$ & --- & vacuous \\ +$13$ & $0$ & --- & --- & no min-deg-$5$ tri \\ +$14$ & $1$ & $216$ & $216$ & all pass \\ +$15$ & $1$ & $0$ & --- & vacuous \\ +$16$ & $3$ & $864$ & $864$ & all pass \\ +$17$ & $4$ & $4{,}650$ & $4{,}650$ & all pass \\ +$18$ & $12$ & $8{,}070$ & $8{,}070$ & all pass \\ +\hline +total & $23$ & $13{,}800$ & $13{,}800$ & \\ +\end{tabular} +\end{center} +\noindent A subtlety: only about half of the Conjecture-3.6-witnesses +individually satisfy clause (4) on each colouring, but in every case some +witness does. The conjecture is therefore an existential statement at the +witness level, not a property of every witness. +\end{remark} + +\medskip + +The next definition records a cubic-preserving analogue of edge contraction +which turns out --- under planar duality --- to coincide with simple-graph +contraction on the dual side. It will be useful when reasoning about the +modified graph $\widehat{G}'^{+}$ of Conjecture~\ref{conj:face-monochromatic-pair-strengthened} +and its further reductions. + +\begin{definition}[Cubic-graph edge contraction] +\label{def:cubic-edge-contraction} +Let $H$ be a cubic plane graph and $e = uv$ an edge of $H$ with $u \neq v$ and +no edge of $H$ parallel to $e$. The \emph{cubic-graph edge contraction} of $H$ +along $e$ is the graph $H'$ obtained in two steps: +\begin{enumerate} + \item \emph{Delete} the edge $e$; the endpoints $u$ and $v$ each drop to + degree $2$. + \item \emph{Smooth} each of $u$ and $v$: at $u$, replace $u$ and its two + remaining incident edges $ua, ub$ by a single new edge $ab$; do the + same at $v$. Both vertices $u$ and $v$ are removed, and two new edges + are added in their place. +\end{enumerate} +Provided the smoothings do not introduce a loop or parallel edge, $H'$ is +again a cubic plane graph, with $|V(H')| = |V(H)| - 2$ and +$|E(H')| = |E(H)| - 3$. + +Equivalently, $H'$ is the planar dual of $\mathrm{dual}(H) / e^{*}$, where +$e^{*}$ is the edge of $\mathrm{dual}(H)$ crossing $e$ and the contraction +on the right-hand side is simple-graph contraction (loops removed, parallel +edges absorbed). Under planar duality, contracting $e^{*}$ in +$\mathrm{dual}(H)$ merges the two triangular faces of $\mathrm{dual}(H)$ +incident to $e^{*}$, and the parallel-edge cleanup corresponds exactly to +the smoothing step on the primal side. +\end{definition} + +\begin{figure}[h] +\centering +\includegraphics[width=0.95\textwidth]{fig_cubic_edge_contraction.png} +\caption{Cubic-graph edge contraction +(Definition~\ref{def:cubic-edge-contraction}). Left: a fragment of a cubic +plane graph with the contracted edge $e = uv$ highlighted in red. Middle: +deleting $e$ leaves $u$ and $v$ of degree~$2$. Right: smoothing $u$ and $v$ +replaces each pair of incident edges by a single new edge, removing $u, v$ +and giving a cubic plane graph again.} +\label{fig:cubic-edge-contraction} +\end{figure} + +\begin{theorem}[Cubic contraction across a 4-face preserves 3-edge-colourability] +\label{thm:cubic-contraction-4face} +Let $H$ be a cubic plane graph with a proper $3$-edge-colouring $\varphi$, +let $f$ be a face of $H$ with $|\partial f| = 4$, and let $e_0, e_1$ be the +two edges of $\partial f$ sharing no endpoint (the opposite pair on the +$4$-cycle $\partial f$). If $\varphi(e_0) \neq \varphi(e_1)$ and the +cubic-graph edge contraction of $H$ along $e_0$ +(Definition~\ref{def:cubic-edge-contraction}) is well-defined (no loops or +parallel edges are created), then the contracted graph admits a proper +$3$-edge-colouring. +\end{theorem} + +\begin{proof} +Write $\partial f$ as the $4$-cycle $v_0 v_1 v_2 v_3$ with $e_0 = v_0 v_1$ +and $e_1 = v_2 v_3$ (so $e_0, e_1$ are opposite); the remaining two +boundary edges of $f$ are $e_2 := v_1 v_2$ and $e_3 := v_3 v_0$. Since $H$ +is cubic, each $v_i$ has exactly one edge not on $\partial f$: write $w_i$ +for that edge and $u_i$ for its other endpoint, so $w_i = v_i u_i$ with +$u_i \notin \{v_0, v_1, v_2, v_3\}$, for each $i \in \{0, 1, 2, 3\}$. Put +$a := \varphi(e_0)$, $b := \varphi(e_1)$, and let $c$ be the third colour. + +\emph{Forced colours on the face.} Propriety at $v_1$ and $v_2$ forces +$\varphi(e_2) \notin \{a, b\}$, so $\varphi(e_2) = c$; then +$\varphi(w_1) = b$ and $\varphi(w_2) = a$. Symmetrically $\varphi(e_3) = c$, +$\varphi(w_0) = b$, and $\varphi(w_3) = a$. In particular +$\varphi(w_0) = \varphi(w_1) = b$. + +\emph{Construction of $\varphi'$.} Let $H'$ denote the cubic-graph edge +contraction of $H$ along $e_0$; its new edges are $e_3' := v_3 u_0$ +(replacing $e_3$ and $w_0$ via the smoothing at $v_0$) and +$e_2' := v_2 u_1$ (replacing $e_2$ and $w_1$ via the smoothing at $v_1$). +Define $\varphi' \colon E(H') \to \{1, 2, 3\}$ by +\[ + \varphi'(e) := + \begin{cases} + c & \text{if } e = e_1, \\ + b & \text{if } e \in \{e_2', e_3'\}, \\ + \varphi(e) & \text{otherwise.} + \end{cases} +\] +That is: give each smoothed-in edge the colour $b$ (the colour of the two +$w_i$ it absorbs), recolour $e_1$ to $c$, and leave every other edge of +$H'$ with its $\varphi$-colour. + +\emph{Propriety.} Every vertex of $H'$ other than $v_2, v_3, u_0, u_1$ has +the same incident edges and the same $\varphi'$-colours as it did under +$\varphi$, so propriety is inherited there. At the four affected vertices, +\[ +\begin{array}{r|lll} +\text{vertex} & \text{edges in } H' & \text{colours under }\varphi' \\ +\hline +v_2 & e_1,\; w_2,\; e_2' & c,\; a,\; b \\ +v_3 & e_1,\; w_3,\; e_3' & c,\; a,\; b \\ +u_0 & e_3',\; \alpha_0,\; \beta_0 & b,\; a,\; c \\ +u_1 & e_2',\; \alpha_1,\; \beta_1 & b,\; a,\; c +\end{array} +\] +where $\alpha_i, \beta_i$ are the two edges of $H$ at $u_i$ other than +$w_i$, whose $\varphi$-colours are forced to $\{a, c\}$ by propriety at +$u_i$ (since $\varphi(w_i) = b$). Each row lists three distinct colours, so +$\varphi'$ is proper. +\end{proof} + +\begin{figure}[h] +\centering +\includegraphics[width=0.98\textwidth]{fig_thm_cubic_contraction_4face.png} +\caption{The recolouring used in the proof of +Theorem~\ref{thm:cubic-contraction-4face}. Left: the $4$-face $f$ of $H$ +under $\varphi$, with the forced colours $\varphi(e_0) = a$, +$\varphi(e_1) = b$, $\varphi(e_2) = \varphi(e_3) = c$, +$\varphi(w_0) = \varphi(w_1) = b$, and $\varphi(w_2) = \varphi(w_3) = a$. +Right: the contracted graph $H'$ under $\varphi'$. The smoothed-in edges +$e_2', e_3'$ inherit the colour $b$ from $w_0, w_1$, and $e_1$ is +recoloured from $b$ to $c$; every edge outside the face neighbourhood +keeps its $\varphi$-colour (dotted in red: the five edges of $H$ removed +by the contraction).} +\label{fig:thm-cubic-contraction-4face} +\end{figure} + \end{document}