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()