diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_medial_face_parity.py b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_medial_face_parity.py new file mode 100644 index 0000000..1593b71 --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_medial_face_parity.py @@ -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() diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_remark58_bitefree.py b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_remark58_bitefree.py new file mode 100644 index 0000000..9ef3984 --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_remark58_bitefree.py @@ -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() diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/paper.fdb_latexmk b/papers/medial_tire_decompositions_of_plane_triangulations/paper.fdb_latexmk index 8230c63..03043cf 100644 --- a/papers/medial_tire_decompositions_of_plane_triangulations/paper.fdb_latexmk +++ b/papers/medial_tire_decompositions_of_plane_triangulations/paper.fdb_latexmk @@ -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" diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/paper.log b/papers/medial_tire_decompositions_of_plane_triangulations/paper.log index bdc8dc2..d5a7d5c 100644 --- a/papers/medial_tire_decompositions_of_plane_triangulations/paper.log +++ b/papers/medial_tire_decompositions_of_plane_triangulations/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> -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 diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/paper.pdf b/papers/medial_tire_decompositions_of_plane_triangulations/paper.pdf index 058fc07..772bc32 100644 Binary files a/papers/medial_tire_decompositions_of_plane_triangulations/paper.pdf and b/papers/medial_tire_decompositions_of_plane_triangulations/paper.pdf differ diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/paper.tex b/papers/medial_tire_decompositions_of_plane_triangulations/paper.tex index 42a9ca8..b23f78b 100644 --- a/papers/medial_tire_decompositions_of_plane_triangulations/paper.tex +++ b/papers/medial_tire_decompositions_of_plane_triangulations/paper.tex @@ -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