Verify Remark 5.8 on genuine bite treads

Bites arise when the inner outerplanar graph O has a bridge: the bridge
edge is traversed twice by the outer-face walk, so its medial vertex is
adjacent to four annular vertices.

- check_remark58_bite.py: a minimal bite tread (outer 4-cycle + interior
  bridge u-w) restricts to Kempe-balanced on all colourings (outer face).
- check_remark58_bite_rich.py: O = triangle abc + pendant bridge a-d gives
  one bite plus three singleton down teeth in the bite's inner-gap face;
  every restriction is Kempe-balanced (the three gap singletons are a
  rainbow in every global colouring).

Update Remark 5.8's verification note: the bite case, including singletons
in the bite-gap face, is now confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 16:46:53 -04:00
parent 5bed8b4dfb
commit cf035243f6
7 changed files with 286 additions and 36 deletions
@@ -0,0 +1,116 @@
"""Directly test Remark 5.8 on a genuine tire piece that contains a BITE.
A bite arises when the inner outerplanar graph O has a bridge: the bridge edge
is traversed twice by the outer-face walk, so it borders two tread triangles and
its medial vertex is adjacent to four annular medial vertices.
Minimal construction. Outer 4-cycle o0,o1,o2,o3; two interior vertices u,w
joined by a bridge u-w (V_in = {u,w}). Triangulate the disk so that u-w lies in
two tread triangles:
(o0,o1,u) (o0,u,o3) (o1,w,u) (o1,o2,w) (o2,o3,w) (o3,u,w)
Cap the outer cycle with an apex N (the bridge bounds no inner hole, so no inner
cap is needed). The result G is a closed plane triangulation; M(G) is 4-regular.
Edge classification (by endpoints): annular = one endpoint outer & one inner;
up tooth = both endpoints outer (outer-cycle edge); down tooth = both endpoints
inner (here only the bridge u-w). The bridge's medial vertex is the bite apex.
Remark 5.8 predicts every proper 3-colouring of M(G) restricts to a
Kempe-balanced colouring. Here the only non-trivial condition is the outer face
(the four up apexes), since the single bite contributes no singleton down teeth.
"""
from __future__ import annotations
import networkx as nx
from check_remark58_bitefree import ekey, medial_graph, proper_3_colorings
PAIRS = ((0, 1), (0, 2), (1, 2))
OUTER = ["o0", "o1", "o2", "o3"]
INNER = ["u", "w"]
TREAD_TRIANGLES = [
("o0", "o1", "u"),
("o0", "u", "o3"),
("o1", "w", "u"),
("o1", "o2", "w"),
("o2", "o3", "w"),
("o3", "u", "w"),
]
def build():
g = nx.Graph()
for tri in TREAD_TRIANGLES:
a, b, c = tri
g.add_edges_from([(a, b), (b, c), (a, c)])
# outer cap
for i in range(4):
g.add_edges_from([("N", OUTER[i]), ("N", OUTER[(i + 1) % 4])])
return g
def classify_tread_edges(g):
out = set(OUTER)
inn = set(INNER)
tread_edges = set()
for tri in TREAD_TRIANGLES:
a, b, c = tri
tread_edges |= {ekey(a, b), ekey(b, c), ekey(a, c)}
annular, up, down = [], [], []
for e in tread_edges:
a, b = e
ao, bo = a in out, b in out
ai, bi = a in inn, b in inn
if (ao and bi) or (ai and bo):
annular.append(e)
elif ao and bo:
up.append(e)
elif ai and bi:
down.append(e)
return annular, up, down
def run():
g = build()
assert nx.check_planarity(g)[0]
M = medial_graph(g)
annular, up, down = classify_tread_edges(g)
annular_set = set(annular)
# confirm there is a bite: a down edge whose medial vertex has 4 annular nbrs
bites = [e for e in down if sum(1 for nb in M.neighbors(e) if nb in annular_set) == 4]
print(f"tread: annular={len(annular)} up={len(up)} down={len(down)} "
f"bite apexes={len(bites)} (bite edge: {bites})")
colorings = proper_3_colorings(M, limit=20000)
balanced = 0
bad = []
for col in colorings:
ok = all(
sum(1 for e in up if col[e] in pair) % 2 == 0
for pair in PAIRS
)
if ok:
balanced += 1
else:
bad.append(col)
print(f"|V(G)|={g.number_of_nodes()} |M(G)|={M.number_of_nodes()} "
f"colourings tested={len(colorings)}")
print(f" outer-face (up-apex) balanced={balanced} UNBALANCED={len(bad)}")
if bad:
print(f" first unbalanced up colours: {[bad[0][e] for e in up]}")
print()
print("Remark 5.8 holds on this bite tread"
if not bad else
"Remark 5.8 FAILS on this bite tread")
return len(bad)
if __name__ == "__main__":
run()
@@ -0,0 +1,132 @@
"""Test Remark 5.8 on a bite tread that also has singleton down teeth in the
bite's inner-gap face -- the subtle case of the condition.
Inner outerplanar graph O = triangle (a,b,c) plus a pendant bridge a-d. Its
outer-face walk is the cyclic sequence W = [d, a, b, c, a]: the bridge a-d is
traversed twice (-> a bite), the triangle edges a-b, b-c, c-a once each (-> three
singleton down teeth, all sitting in the bite's inner-gap face).
We triangulate the annulus between an outer m-cycle and W by the lattice-path
method, searching interleavings for one giving a simple closed triangulation
after capping the outer cycle with an apex N. Then we test Remark 5.8: every
proper 3-colouring of M(G) restricts to a Kempe-balanced colouring, i.e.
* the up apexes (outer edges) are even per colour pair, and
* the three singleton down apexes (a-b, b-c, c-a), which share the bite-gap
face, are even per colour pair (equivalently: a rainbow).
"""
from __future__ import annotations
import itertools
import networkx as nx
from check_remark58_bitefree import ekey, medial_graph, proper_3_colorings
PAIRS = ((0, 1), (0, 2), (1, 2))
INNER_WALK = ["d", "a", "b", "c", "a"] # bridge a-d traversed twice
SINGLETON_DOWN = [ekey("a", "b"), ekey("b", "c"), ekey("c", "a")]
BITE_EDGE = ekey("a", "d")
def build_tread(m: int, path: str):
"""Build the annular triangulation for a given lattice path (m 'O', L 'I')."""
outer = [f"o{t}" for t in range(m)]
W = INNER_WALK
L = len(W)
g = nx.Graph()
g.add_edge(outer[0], W[0]) # anchor
i = j = 0
tread_triangles = []
for mv in path:
if mv == "O":
tri = (outer[i % m], W[j % L], outer[(i + 1) % m])
i += 1
else:
tri = (outer[i % m], W[j % L], W[(j + 1) % L])
j += 1
a, b, c = tri
g.add_edges_from([(a, b), (b, c), (a, c)])
tread_triangles.append(tri)
if (i, j) != (m, L):
return None
return g, outer, tread_triangles
def cap_and_validate(g, outer):
"""Cap the outer cycle with apex N; require a simple closed triangulation."""
h = g.copy()
for t in range(len(outer)):
h.add_edges_from([("N", outer[t]), ("N", outer[(t + 1) % len(outer)])])
if not nx.check_planarity(h)[0]:
return None
V, E = h.number_of_nodes(), h.number_of_edges()
if E != 3 * V - 6: # maximal planar == triangulation
return None
return h
def find_construction(m: int):
L = len(INNER_WALK)
for combo in itertools.combinations(range(m + L), L):
path = "".join("I" if t in combo else "O" for t in range(m + L))
built = build_tread(m, path)
if built is None:
continue
g, outer, tris = built
h = cap_and_validate(g, outer)
if h is not None:
return h, outer, tris, path
return None
def run():
for m in (4, 5, 6, 7):
found = find_construction(m)
if found:
break
if not found:
print("no valid bite-with-singletons triangulation found")
return 1
h, outer, tris, path = found
M = medial_graph(h)
annular = set()
for tri in tris:
a, b, c = tri
for e in (ekey(a, b), ekey(b, c), ekey(a, c)):
x, y = e
xo, yo = x in outer, y in outer
if (xo and not yo) or (yo and not xo):
annular.add(e)
n_bite_nbrs = sum(1 for nb in M.neighbors(BITE_EDGE) if nb in annular)
up = [ekey(outer[t], outer[(t + 1) % len(outer)]) for t in range(len(outer))]
up = [e for e in up if e in M]
print(f"m={len(outer)} path={path} |V(G)|={h.number_of_nodes()} "
f"|M(G)|={M.number_of_nodes()}")
print(f"bite edge {BITE_EDGE}: annular neighbours={n_bite_nbrs} (4 => bite)")
print(f"up apexes={len(up)} singleton down apexes={SINGLETON_DOWN}")
colorings = proper_3_colorings(M, limit=50000)
bad_outer = bad_bitegap = 0
for col in colorings:
if any(sum(1 for e in up if col[e] in p) % 2 for p in PAIRS):
bad_outer += 1
if any(sum(1 for e in SINGLETON_DOWN if col[e] in p) % 2 for p in PAIRS):
bad_bitegap += 1
print(f"colourings tested={len(colorings)}")
print(f" outer face unbalanced: {bad_outer}")
print(f" bite-gap face (3 singletons) unbalanced: {bad_bitegap}")
print()
ok = (bad_outer == 0 and bad_bitegap == 0)
print("Remark 5.8 holds on this bite-with-singletons tread"
if ok else "Remark 5.8 FAILS on this bite-with-singletons tread")
return 0 if ok else 1
if __name__ == "__main__":
run()
@@ -46,5 +46,5 @@
\newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{29.38873pt}
\newlabel{tocindent3}{0pt}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{10}{}\protected@file@percent }
\gdef \@abspage@last{10}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{11}{}\protected@file@percent }
\gdef \@abspage@last{11}
@@ -1,5 +1,5 @@
# Fdb version 3
["pdflatex"] 1781209574 "paper.tex" "paper.pdf" "paper" 1781209575
["pdflatex"] 1781210675 "paper.tex" "paper.pdf" "paper" 1781210676
"/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" 1781209575 4206 a817291c83280f23be785ea9b9789717 "pdflatex"
"paper.tex" 1781209541 40737 e5d86b8964b20788119dc708cc9cd8ef ""
"paper.aux" 1781210676 4206 870862ca1c6762f39fd7ed9def109a09 "pdflatex"
"paper.tex" 1781210650 40922 403b0b9df57192dbf02362b0b06705c3 ""
(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 16:26
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 11 JUN 2026 16:44
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
@@ -506,7 +506,7 @@ LaTeX Warning: `h' float specifier changed to `ht'.
LaTeX Warning: `h' float specifier changed to `ht'.
[4] [5] [6] [7] [8] [9] [10] (./paper.aux) )
[4] [5] [6] [7] [8] [9] [10] [11] (./paper.aux) )
Here is how much of TeX's memory you used:
14419 strings out of 478268
283755 string characters out of 5846347
@@ -515,30 +515,30 @@ Here is how much of TeX's memory you used:
477048 words of font info for 58 fonts, out of 8000000 for 9000
1302 hyphenation exceptions out of 8191
84i,8n,89p,736b,838s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texlive/2022/texmf-dist
/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/
fonts/type1/public/amsfonts/cm/cmbx8.pfb></usr/local/texlive/2022/texmf-dist/fo
nts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fo
nts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fon
ts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts
/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
ype1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
e1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
blic/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
c/amsfonts/cm/cmss10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public
/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/
amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
sfonts/cm/cmsy6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
onts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
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, 277087 bytes).
</usr/local/texlive/2022/texmf
-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-
dist/fonts/type1/public/amsfonts/cm/cmbx8.pfb></usr/local/texlive/2022/texmf-di
st/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-di
st/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dis
t/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/
fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fo
nts/type1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/font
s/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/
type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
e1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/
public/amsfonts/cm/cmss10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
ublic/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
blic/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publ
ic/amsfonts/cm/cmsy6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public
/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
msfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
sfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
onts/cm/cmtt8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
ts/symbols/msam10.pfb>
Output written on paper.pdf (11 pages, 277538 bytes).
PDF statistics:
135 PDF objects out of 1000 (max. 8388607)
84 compressed objects within 1 object stream
138 PDF objects out of 1000 (max. 8388607)
86 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000)
13 words of extra memory for PDF output out of 10000 (max. 10000000)
@@ -841,12 +841,14 @@ 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.
This argument is verified computationally. For bite-free pieces---capped
triangulated annuli on annular cycles of length $6,8,10,12$---every proper
$3$-colouring of $M(G)$ restricts to a Kempe-balanced colouring. The same
holds for pieces carrying a bite, including the case where singleton down
teeth lie in the bite's inner-gap face: there the inner level cycle splits
into a child level cycle per gap, and conservation across each child cycle
supplies the parity (in the checked example the three singleton down apexes
of a bite gap are a rainbow in every restriction).
\end{remark}
More generally, let $T$ be a medial tire region with boundary