papers: rename folders and retitle
- Main paper: dual_decomposition_minimal_counterexamples/ -> face_monochromatic_pairs/. Title is now "Face-Monochromatic Pairs and the Four Colour Theorem". - Companion paper: dual_decomposition_iterated_reduction/ -> iterated_reduction_in_reduced_dual/. Title is now "An Iterated Reduction in the Reduced Dual". Its prose and bibliography cite the parent under the new title. - Update one absolute sys.path reference inside check_conj_face_kempe_n15.py that pointed at the old folder. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
"""Probe: even when phi_t* is in a Kempe class with no all-distinct, does
|
||||
H_t* itself admit an all-distinct coloring in SOME (other) Kempe class?
|
||||
|
||||
Reuses the Conj 3.6 counterexample: n=14, tri#1, i_red=0, phi_1 from the
|
||||
chord-apex+Kempe colorings. Enumerates every proper 3-edge-coloring of
|
||||
H_t*, marks the Kempe classes, and for each class reports whether it
|
||||
contains an all-distinct coloring.
|
||||
|
||||
Run with: sage experiments/check_all_distinct_exists.py
|
||||
"""
|
||||
from sage.all import Graph
|
||||
from sage.graphs.graph_generators import graphs
|
||||
import sys
|
||||
|
||||
|
||||
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)
|
||||
dual_edges = [(fs[0], fs[1]) for fs in edge_to_faces.values() if len(fs) == 2]
|
||||
return Graph(dual_edges, multiedges=False, loops=False)
|
||||
|
||||
|
||||
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 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])
|
||||
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, 'A': A,
|
||||
'named': {
|
||||
'spike': frozenset(spike),
|
||||
'side_0': frozenset(side_0),
|
||||
'side_1': frozenset(side_1),
|
||||
'merged': frozenset(merged),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def find_safe_pentagonal_face(G, protected):
|
||||
for face in G.faces():
|
||||
if len(face) != 5:
|
||||
continue
|
||||
boundary = [u for (u, v) in face]
|
||||
boundary_edges = [frozenset([u, v]) for (u, v) in face]
|
||||
externals = []
|
||||
A = []
|
||||
ok = True
|
||||
for B_k in boundary:
|
||||
outer = [w for w in G.neighbor_iterator(B_k) if w not in boundary]
|
||||
if len(outer) != 1:
|
||||
ok = False
|
||||
break
|
||||
externals.append(frozenset([B_k, outer[0]]))
|
||||
A.append(outer[0])
|
||||
if not ok:
|
||||
continue
|
||||
if any(e in protected for e in boundary_edges + externals):
|
||||
continue
|
||||
return boundary, externals, A
|
||||
return None
|
||||
|
||||
|
||||
def valid_index(f_vec, A):
|
||||
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
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def run_algorithm_to_completion(H, phi_1_dict, named_step1, vn_start=10000):
|
||||
H = H.copy()
|
||||
coloring = dict(phi_1_dict)
|
||||
all_named = [named_step1]
|
||||
E = set(named_step1.values())
|
||||
next_vn = vn_start
|
||||
while True:
|
||||
H.is_planar(set_embedding=True)
|
||||
result = find_safe_pentagonal_face(H, E)
|
||||
if result is None:
|
||||
break
|
||||
boundary, externals, A = result
|
||||
f_vec = [coloring[e] for e in externals]
|
||||
i_t = valid_index(f_vec, A)
|
||||
if i_t is None:
|
||||
break
|
||||
v_n = next_vn
|
||||
next_vn += 1
|
||||
H_new = H.copy()
|
||||
for v in boundary:
|
||||
H_new.delete_vertex(v)
|
||||
H_new.add_vertex(v_n)
|
||||
side_0 = (v_n, A[i_t])
|
||||
spike = (v_n, A[(i_t + 1) % 5])
|
||||
side_1 = (v_n, 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])
|
||||
if H_new.has_multiple_edges() or H_new.has_loops():
|
||||
break
|
||||
if not H_new.is_planar(set_embedding=True):
|
||||
break
|
||||
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)] = 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())
|
||||
all_named.append(named)
|
||||
return H, coloring, all_named
|
||||
|
||||
|
||||
def is_all_distinct(edges, col, all_named):
|
||||
for named in all_named:
|
||||
i_s = edge_idx(edges, named['spike'])
|
||||
i_m = edge_idx(edges, named['merged'])
|
||||
if i_s is None or i_m is None:
|
||||
return False
|
||||
if col[i_s] == col[i_m]:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("Reconstructing Conj 3.6 counterexample: n=14, tri#1, i_red=0")
|
||||
for G in graphs.triangulations(14, minimum_degree=5):
|
||||
D = dual_of(G)
|
||||
D.is_planar(set_embedding=True)
|
||||
chosen = None
|
||||
for face in D.faces():
|
||||
if len(face) != 5:
|
||||
continue
|
||||
res = apply_reduction(D, face, 0, 9999)
|
||||
if res is None:
|
||||
continue
|
||||
H_1, named_1 = res['H'], res['named']
|
||||
edges_1, colorings_1 = proper_3_edge_colorings(H_1)
|
||||
cand = [c for c in colorings_1
|
||||
if matches_chord_apex_kempe(edges_1, c, named_1)]
|
||||
if cand:
|
||||
chosen = (face, res, cand)
|
||||
break
|
||||
if chosen is None:
|
||||
continue
|
||||
face, res, cand = chosen
|
||||
H_1, named_1 = res['H'], res['named']
|
||||
edges_1, _ = proper_3_edge_colorings(H_1)
|
||||
if not cand:
|
||||
continue
|
||||
phi_1 = cand[0]
|
||||
phi_1_dict = {frozenset((e[0], e[1])): c for e, c in zip(edges_1, phi_1)}
|
||||
H_final, phi_final_dict, all_named = run_algorithm_to_completion(
|
||||
H_1, phi_1_dict, named_1)
|
||||
edges_f, colorings_f = proper_3_edge_colorings(H_final)
|
||||
print(f" H_t*: |V|={H_final.order()}, |E|={H_final.size()}, "
|
||||
f"{len(colorings_f)} proper 3-edge-colorings, "
|
||||
f"{len(all_named)} named-edge steps.")
|
||||
|
||||
# Kempe equivalence classes via union-find
|
||||
col_idx = {c: i for i, c in enumerate(colorings_f)}
|
||||
parent = list(range(len(colorings_f)))
|
||||
|
||||
def find(x):
|
||||
while parent[x] != x:
|
||||
parent[x] = parent[parent[x]]
|
||||
x = parent[x]
|
||||
return x
|
||||
|
||||
def union(x, y):
|
||||
rx, ry = find(x), find(y)
|
||||
if rx != ry:
|
||||
parent[rx] = ry
|
||||
|
||||
for c_idx, col in enumerate(colorings_f):
|
||||
for pair in [(0, 1), (0, 2), (1, 2)]:
|
||||
visited = set()
|
||||
for start in range(len(edges_f)):
|
||||
if col[start] not in pair or start in visited:
|
||||
continue
|
||||
cyc = kempe_cycle(edges_f, col, start, pair)
|
||||
visited |= cyc
|
||||
a, b = pair
|
||||
new_col = list(col)
|
||||
for k in cyc:
|
||||
if new_col[k] == a:
|
||||
new_col[k] = b
|
||||
elif new_col[k] == b:
|
||||
new_col[k] = a
|
||||
new_col = tuple(new_col)
|
||||
if new_col != col and new_col in col_idx:
|
||||
union(c_idx, col_idx[new_col])
|
||||
|
||||
roots = {}
|
||||
for i in range(len(colorings_f)):
|
||||
r = find(i)
|
||||
roots.setdefault(r, []).append(i)
|
||||
|
||||
# phi_t* identification
|
||||
phi_final = tuple(phi_final_dict[frozenset((e[0], e[1]))]
|
||||
for e in edges_f)
|
||||
phi_idx = colorings_f.index(phi_final)
|
||||
phi_root = find(phi_idx)
|
||||
|
||||
print()
|
||||
print(f" Found {len(roots)} Kempe equivalence classes:")
|
||||
for r, members in sorted(roots.items(), key=lambda kv: -len(kv[1])):
|
||||
ad = [m for m in members
|
||||
if is_all_distinct(edges_f, colorings_f[m], all_named)]
|
||||
label = " (contains phi_t*)" if r == phi_root else ""
|
||||
print(f" class size {len(members)}: "
|
||||
f"{len(ad)} all-distinct colorings{label}")
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user