Verify Remark 5.8 mechanism; correct it to level-cycle conservation
Computational checks of the necessity of Kempe-balance (Remark 5.8): - check_medial_face_parity.py shows the naive "even P-coloured vertices per medial face" claim is false (odd vertex-faces on the octahedron and stacked triangulations), so the original face-parity justification was wrong. - check_remark58_bitefree.py builds genuine bite-free tire pieces (capped triangulated annuli) and confirms every proper 3-colouring of M(G) restricts to a Kempe-balanced colouring (|A(T)|=6,8,10,12, all colourings, zero failures). Rewrite Remark 5.8 to cite the correct mechanism: the up/down apexes lie on level cycles, and a P-Kempe cycle meets each level cycle in an even number of P-coloured incidences (Lemma 5.6). Note the bite case is not yet checked end to end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+181
@@ -0,0 +1,181 @@
|
||||
"""Probe the parity mechanism behind Remark 5.8.
|
||||
|
||||
Claim X: in a proper 3-colouring of the 4-regular medial graph M(G) of a plane
|
||||
triangulation G, for every face f of M(G) and every colour pair P = {a,b}, the
|
||||
number of vertices on the boundary of f coloured a or b is even.
|
||||
|
||||
M(G) has two kinds of faces: a "vertex-face" per vertex v of G (the cyclic
|
||||
sequence of edges around v) and a "face-face" per triangular face of G (its
|
||||
three edges). The face-faces are triangles, trivially even (count 2); the
|
||||
vertex-faces are the non-obvious case.
|
||||
|
||||
We build M(G) from a planar embedding's rotation system, enumerate proper
|
||||
3-colourings of M(G), and check Claim X on every face / pair.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
# --- a handful of small plane triangulations (maximal planar graphs) ---------
|
||||
|
||||
def tetrahedron() -> nx.Graph:
|
||||
return nx.complete_graph(4)
|
||||
|
||||
|
||||
def octahedron() -> nx.Graph:
|
||||
# K_{2,2,2}: antipodal pairs non-adjacent
|
||||
g = nx.Graph()
|
||||
pairs = [(0, 1), (2, 3), (4, 5)]
|
||||
nonadj = set(map(frozenset, pairs))
|
||||
for u in range(6):
|
||||
for v in range(u + 1, 6):
|
||||
if frozenset((u, v)) not in nonadj:
|
||||
g.add_edge(u, v)
|
||||
return g
|
||||
|
||||
|
||||
def stacked(levels: int) -> nx.Graph:
|
||||
"""Apollonian-style: repeatedly insert a vertex in a triangular face."""
|
||||
g = nx.Graph()
|
||||
g.add_edges_from([(0, 1), (1, 2), (0, 2)])
|
||||
faces = [(0, 1, 2)]
|
||||
nxt = 3
|
||||
for _ in range(levels):
|
||||
a, b, c = faces.pop(0)
|
||||
v = nxt
|
||||
nxt += 1
|
||||
g.add_edges_from([(v, a), (v, b), (v, c)])
|
||||
faces += [(a, b, v), (b, c, v), (a, c, v)]
|
||||
return g
|
||||
|
||||
|
||||
def icosahedron() -> nx.Graph:
|
||||
return nx.icosahedral_graph()
|
||||
|
||||
|
||||
def double_wheel(rim: int) -> nx.Graph:
|
||||
"""Two apexes over a rim cycle: a simple triangulated 'tire' with caps."""
|
||||
g = nx.Graph()
|
||||
g.add_cycle = None
|
||||
for i in range(rim):
|
||||
g.add_edge(i, (i + 1) % rim)
|
||||
g.add_edge(i, "N")
|
||||
g.add_edge(i, "S")
|
||||
return g
|
||||
|
||||
|
||||
# --- medial graph from a rotation system -------------------------------------
|
||||
|
||||
def rotation_system(g: nx.Graph) -> dict:
|
||||
ok, emb = nx.check_planarity(g)
|
||||
if not ok:
|
||||
raise ValueError("graph is not planar")
|
||||
return {v: list(emb.neighbors_cw_order(v)) for v in g.nodes()}, emb
|
||||
|
||||
|
||||
def medial_graph(g: nx.Graph):
|
||||
"""Return (M, vertex_faces, face_faces) built from the rotation system.
|
||||
|
||||
Medial vertices are edges of g (as sorted tuples). Around each vertex the
|
||||
incident edges form a face cycle (vertex-face); around each triangular face
|
||||
of g its three edges form a face cycle (face-face).
|
||||
"""
|
||||
rot, emb = rotation_system(g)
|
||||
|
||||
def ekey(u, v):
|
||||
return (u, v) if u <= v else (v, u)
|
||||
|
||||
M = nx.Graph()
|
||||
M.add_nodes_from(ekey(u, v) for u, v in g.edges())
|
||||
|
||||
vertex_faces = []
|
||||
for v, order in rot.items():
|
||||
edges = [ekey(v, w) for w in order]
|
||||
vertex_faces.append(edges)
|
||||
for i in range(len(edges)):
|
||||
M.add_edge(edges[i], edges[(i + 1) % len(edges)])
|
||||
|
||||
# face-faces: traverse each face of the embedding once
|
||||
seen = set()
|
||||
face_faces = []
|
||||
for u, v in list(emb.edges()):
|
||||
if (u, v) in seen:
|
||||
continue
|
||||
face = emb.traverse_face(u, v, mark_half_edges=seen)
|
||||
edges = [ekey(face[i], face[(i + 1) % len(face)]) for i in range(len(face))]
|
||||
face_faces.append(edges)
|
||||
return M, vertex_faces, face_faces
|
||||
|
||||
|
||||
# --- proper 3-colourings of M(G) ---------------------------------------------
|
||||
|
||||
def proper_3_colorings(M: nx.Graph, limit: int | None = None):
|
||||
nodes = list(M.nodes())
|
||||
adj = {v: set(M.neighbors(v)) for v in nodes}
|
||||
coloring: dict = {}
|
||||
out = []
|
||||
|
||||
def rec(i):
|
||||
if limit is not None and len(out) >= limit:
|
||||
return
|
||||
if i == len(nodes):
|
||||
out.append(dict(coloring))
|
||||
return
|
||||
v = nodes[i]
|
||||
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||
for c in (0, 1, 2):
|
||||
if c not in used:
|
||||
coloring[v] = c
|
||||
rec(i + 1)
|
||||
del coloring[v]
|
||||
|
||||
rec(0)
|
||||
return out
|
||||
|
||||
|
||||
def check_claim_x(name: str, g: nx.Graph, color_limit: int = 200):
|
||||
M, vfaces, ffaces = medial_graph(g)
|
||||
colorings = proper_3_colorings(M, limit=color_limit)
|
||||
if not colorings:
|
||||
print(f"{name}: M(G) has no proper 3-colouring (skip)")
|
||||
return
|
||||
faces = [("vertex", f) for f in vfaces] + [("face", f) for f in ffaces]
|
||||
violations = 0
|
||||
odd_vertex_faces = 0
|
||||
for col in colorings:
|
||||
for kind, face in faces:
|
||||
for pair in ((0, 1), (0, 2), (1, 2)):
|
||||
cnt = sum(1 for v in face if col[v] in pair)
|
||||
if cnt % 2 != 0:
|
||||
violations += 1
|
||||
if kind == "vertex":
|
||||
odd_vertex_faces += 1
|
||||
deg = sorted({len(f) for f in vfaces})
|
||||
print(f"{name}: |V(G)|={g.number_of_nodes()} |M|={M.number_of_nodes()} "
|
||||
f"colourings tested={len(colorings)} vertex-face sizes={deg}")
|
||||
print(f" Claim X violations: {violations} "
|
||||
f"(vertex-face violations: {odd_vertex_faces})")
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
("tetrahedron", tetrahedron()),
|
||||
("octahedron", octahedron()),
|
||||
("stacked-3", stacked(3)),
|
||||
("stacked-6", stacked(6)),
|
||||
("double_wheel-5", double_wheel(5)),
|
||||
("double_wheel-6", double_wheel(6)),
|
||||
("double_wheel-7", double_wheel(7)),
|
||||
("icosahedron", icosahedron()),
|
||||
]
|
||||
for name, g in cases:
|
||||
# ensure it is a triangulation (every face a triangle)
|
||||
check_claim_x(name, g)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
"""Directly test Remark 5.8 on genuine (bite-free) tire pieces.
|
||||
|
||||
Construction. Build a triangulated annulus (an antiprism band) between an outer
|
||||
p-cycle O = o_0..o_{p-1} and an inner p-cycle I = i_0..i_{p-1}, with the 2p
|
||||
triangles
|
||||
|
||||
(o_k, o_{k+1}, i_k) and (o_{k+1}, i_k, i_{k+1}).
|
||||
|
||||
Cap the outer disk with an apex N joined to all o_k and the inner disk with an
|
||||
apex S joined to all i_k. The result G is a closed plane triangulation, so its
|
||||
medial graph M(G) is 4-regular.
|
||||
|
||||
The tread T is the annulus; its full medial tire graph M(T) is the subgraph of
|
||||
M(G) on the medial vertices of the tread edges (outer, inner and annular edges).
|
||||
This tread has simple boundaries, hence no bites: the up teeth are the outer
|
||||
edges, the down teeth the inner edges, and the only valid faces are the outer
|
||||
face (up apexes) and the root face (down apexes).
|
||||
|
||||
Remark 5.8 predicts: every proper 3-colouring of M(G), restricted to M(T), is
|
||||
Kempe-balanced, i.e. for each colour pair P the up apexes coloured in P are even
|
||||
in number, and likewise the down apexes. We enumerate colourings of M(G) and
|
||||
check this.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
|
||||
PAIRS = ((0, 1), (0, 2), (1, 2))
|
||||
|
||||
|
||||
def ekey(u, v):
|
||||
return (u, v) if (str(u), u) <= (str(v), v) else (v, u)
|
||||
|
||||
|
||||
def build_capped_annulus(p: int):
|
||||
g = nx.Graph()
|
||||
O = [("o", k) for k in range(p)]
|
||||
I = [("i", k) for k in range(p)]
|
||||
outer_edges, inner_edges, annular_edges = [], [], []
|
||||
for k in range(p):
|
||||
o, on = O[k], O[(k + 1) % p]
|
||||
i, ino = I[k], I[(k + 1) % p]
|
||||
outer_edges.append(ekey(o, on))
|
||||
inner_edges.append(ekey(i, ino))
|
||||
annular_edges += [ekey(o, i), ekey(on, i)]
|
||||
# tread triangles
|
||||
g.add_edges_from([(o, on), (on, i), (o, i)]) # (o_k,o_{k+1},i_k)
|
||||
g.add_edges_from([(on, i), (i, ino), (on, ino)]) # (o_{k+1},i_k,i_{k+1})
|
||||
# caps
|
||||
for k in range(p):
|
||||
g.add_edges_from([("N", O[k]), ("N", O[(k + 1) % p])])
|
||||
g.add_edges_from([("S", I[k]), ("S", I[(k + 1) % p])])
|
||||
meta = {
|
||||
"outer_edges": [ekey(*e) for e in outer_edges],
|
||||
"inner_edges": [ekey(*e) for e in inner_edges],
|
||||
"annular_edges": [ekey(*e) for e in annular_edges],
|
||||
}
|
||||
return g, meta
|
||||
|
||||
|
||||
def medial_graph(g: nx.Graph) -> nx.Graph:
|
||||
ok, emb = nx.check_planarity(g)
|
||||
if not ok:
|
||||
raise ValueError("not planar")
|
||||
M = nx.Graph()
|
||||
M.add_nodes_from(ekey(u, v) for u, v in g.edges())
|
||||
for v in g.nodes():
|
||||
order = list(emb.neighbors_cw_order(v))
|
||||
edges = [ekey(v, w) for w in order]
|
||||
for a in range(len(edges)):
|
||||
M.add_edge(edges[a], edges[(a + 1) % len(edges)])
|
||||
return M
|
||||
|
||||
|
||||
def proper_3_colorings(M: nx.Graph, limit: int):
|
||||
nodes = list(M.nodes())
|
||||
adj = {v: set(M.neighbors(v)) for v in nodes}
|
||||
coloring: dict = {}
|
||||
out = []
|
||||
|
||||
def rec(i):
|
||||
if len(out) >= limit:
|
||||
return
|
||||
if i == len(nodes):
|
||||
out.append(dict(coloring))
|
||||
return
|
||||
v = nodes[i]
|
||||
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||
for c in (0, 1, 2):
|
||||
if c not in used:
|
||||
coloring[v] = c
|
||||
rec(i + 1)
|
||||
del coloring[v]
|
||||
|
||||
rec(0)
|
||||
return out
|
||||
|
||||
|
||||
def is_kempe_balanced(coloring, up_apexes, down_apexes):
|
||||
for face in (up_apexes, down_apexes):
|
||||
for pair in PAIRS:
|
||||
if sum(1 for e in face if coloring[e] in pair) % 2 != 0:
|
||||
return False, face is down_apexes
|
||||
return True, None
|
||||
|
||||
|
||||
def run(p: int, limit: int = 4000):
|
||||
g, meta = build_capped_annulus(p)
|
||||
M = medial_graph(g)
|
||||
up = meta["outer_edges"]
|
||||
down = meta["inner_edges"]
|
||||
colorings = proper_3_colorings(M, limit)
|
||||
|
||||
balanced = 0
|
||||
unbalanced = []
|
||||
for col in colorings:
|
||||
ok, _ = is_kempe_balanced(col, up, down)
|
||||
if ok:
|
||||
balanced += 1
|
||||
else:
|
||||
unbalanced.append(col)
|
||||
|
||||
n_ann = len(meta["annular_edges"])
|
||||
print(f"p={p}: |V(G)|={g.number_of_nodes()} |M(G)|={M.number_of_nodes()} "
|
||||
f"|A(T)|={n_ann} up={len(up)} down={len(down)}")
|
||||
print(f" colourings tested={len(colorings)} (cap {limit}) "
|
||||
f"balanced={balanced} UNBALANCED={len(unbalanced)}")
|
||||
if unbalanced:
|
||||
col = unbalanced[0]
|
||||
upc = [col[e] for e in up]
|
||||
dnc = [col[e] for e in down]
|
||||
print(f" first unbalanced restriction: up colours={upc} down colours={dnc}")
|
||||
return len(unbalanced)
|
||||
|
||||
|
||||
def main():
|
||||
total_bad = 0
|
||||
for p in (3, 4, 5, 6):
|
||||
total_bad += run(p)
|
||||
print()
|
||||
print("Remark 5.8 (bite-free) holds on all tested colourings"
|
||||
if total_bad == 0 else
|
||||
f"Remark 5.8 (bite-free) FAILS: {total_bad} unbalanced restrictions found")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,5 +1,5 @@
|
||||
# Fdb version 3
|
||||
["pdflatex"] 1781207945 "paper.tex" "paper.pdf" "paper" 1781207946
|
||||
["pdflatex"] 1781209574 "paper.tex" "paper.pdf" "paper" 1781209575
|
||||
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
||||
@@ -132,8 +132,8 @@
|
||||
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
||||
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
||||
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
||||
"paper.aux" 1781207946 4206 a817291c83280f23be785ea9b9789717 "pdflatex"
|
||||
"paper.tex" 1781207918 39941 f80d8bca5b99e67d65ad8e4bb2d30152 ""
|
||||
"paper.aux" 1781209575 4206 a817291c83280f23be785ea9b9789717 "pdflatex"
|
||||
"paper.tex" 1781209541 40737 e5d86b8964b20788119dc708cc9cd8ef ""
|
||||
(generated)
|
||||
"paper.aux"
|
||||
"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) 11 JUN 2026 15:59
|
||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 11 JUN 2026 16:26
|
||||
entering extended mode
|
||||
restricted \write18 enabled.
|
||||
%&-line parsing enabled.
|
||||
@@ -535,7 +535,7 @@ ts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfont
|
||||
s/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/
|
||||
cm/cmtt8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/sy
|
||||
mbols/msam10.pfb>
|
||||
Output written on paper.pdf (10 pages, 276272 bytes).
|
||||
Output written on paper.pdf (10 pages, 277087 bytes).
|
||||
PDF statistics:
|
||||
135 PDF objects out of 1000 (max. 8388607)
|
||||
84 compressed objects within 1 object stream
|
||||
|
||||
Binary file not shown.
@@ -829,11 +829,24 @@ proper $3$-colouring of $M(G)$, then $\varphi$ is Kempe-balanced.
|
||||
Equivalently, a colouring of $\mathsf{M}(T)$ that fails the parity
|
||||
condition at some valid face and colour pair cannot extend to a proper
|
||||
$3$-colouring of $M(G)$. This is an instance of Kempe-cycle
|
||||
conservation (Lemma~\ref{lem:kempe-conservation}): in the $4$-regular
|
||||
graph $M(G)$ each $P$-Kempe chain of $\mathsf{M}(T)$ closes up into a
|
||||
$P$-Kempe cycle, and such a cycle crosses the boundary separating a
|
||||
valid face from the rest of the sphere an even number of times, so the
|
||||
$P$-coloured apexes incident to that face occur in even number.
|
||||
conservation (Lemma~\ref{lem:kempe-conservation}). The tooth apexes
|
||||
incident to a valid face are boundary medial vertices
|
||||
(Definition~\ref{def:boundary-medial-vertices}) lying on a single level
|
||||
cycle of the tire decomposition: the up-tooth apexes lie on the outer
|
||||
level cycle, and the singleton down-tooth apexes incident to an interior
|
||||
non-tooth face lie on the inner level cycle bounding that face. In the
|
||||
$4$-regular graph $M(G)$ each $P$-Kempe chain of $\mathsf{M}(T)$ closes
|
||||
up into a $P$-Kempe cycle, which by Lemma~\ref{lem:kempe-conservation}
|
||||
meets each level cycle in an even number of $P$-coloured incidences; for
|
||||
a given valid face these incidences are exactly its incident tooth
|
||||
apexes coloured $a$ or $b$, whence $\nu_P(F)$ is even.
|
||||
|
||||
This argument is verified computationally for bite-free pieces: across
|
||||
all proper $3$-colourings of the capped triangulated annuli on annular
|
||||
cycles of length $6,8,10,12$, every restriction to the tire piece is
|
||||
Kempe-balanced. The case with bites, where the inner level cycle splits
|
||||
into several child level cycles, is consistent with the same mechanism
|
||||
but is not yet checked end to end.
|
||||
\end{remark}
|
||||
|
||||
More generally, let $T$ be a medial tire region with boundary
|
||||
|
||||
Reference in New Issue
Block a user