Add Realized/Unrealized/Invalid tire-colouring analysis
For a random 12-vertex maximal planar graph (sphere convex hull), enumerate all proper 3-colourings of M(G), take the BFS-level (tire-tree) decomposition from every source vertex, and build each full medial tire graph M(T) in the ambient tread-face model (cycle + teeth + bites). Recognise each M(T) as a FullMedialTireGraph and label every proper 3-colouring Realized (Kempe-balanced and a restriction of a global colouring), Unrealized (balanced but not a restriction), or Invalid (not balanced). Findings on seed 1 (17 pieces, M(G) with 90 colourings): zero realized-but- invalid colourings (confirms Remark 5.8 on a real triangulation), and 12 of 17 pieces carry Unrealized colourings -- Kempe-balance is necessary but not sufficient for realization; it is sufficient only on cap-like all-up/shallow treads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+451
@@ -0,0 +1,451 @@
|
||||
"""Realized / Unrealized / Invalid analysis of full medial tire graphs.
|
||||
|
||||
Pipeline.
|
||||
1. Take a maximal planar graph G on 12 vertices.
|
||||
2. Build its medial graph M(G) and enumerate every proper 3-colouring.
|
||||
3. For every source vertex S, take the BFS-level (tire-tree) decomposition;
|
||||
each tread T_d sits between level cycle d and level cycle d+1, and yields a
|
||||
full medial tire graph M(T_d) (the medial vertices of its tread edges).
|
||||
4. For each M(T): enumerate every proper 3-colouring and label it
|
||||
Realized -- Kempe-balanced AND a restriction of some colouring of M(G);
|
||||
Unrealized -- Kempe-balanced but NOT such a restriction;
|
||||
Invalid -- not Kempe-balanced.
|
||||
|
||||
Tread edge types are read off BFS levels: an edge with endpoints on levels d and
|
||||
d+1 is annular; both endpoints on level d is an up (outer) tooth edge; both on
|
||||
level d+1 is a down (inner) tooth edge. A down edge incident to two tread faces
|
||||
is a bite.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import random
|
||||
from collections import defaultdict
|
||||
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
import scipy.spatial
|
||||
|
||||
|
||||
def random_sphere_triangulation(n: int, seed: int) -> nx.Graph:
|
||||
"""A random maximal planar graph: convex hull of random points on S^2."""
|
||||
rng = np.random.default_rng(seed)
|
||||
pts = rng.normal(size=(n, 3))
|
||||
pts /= np.linalg.norm(pts, axis=1, keepdims=True)
|
||||
hull = scipy.spatial.ConvexHull(pts)
|
||||
g = nx.Graph()
|
||||
for a, b, c in hull.simplices:
|
||||
g.add_edges_from([(int(a), int(b)), (int(b), int(c)), (int(a), int(c))])
|
||||
return g
|
||||
|
||||
|
||||
def medial_tire_facemodel(tread_faces) -> nx.Graph:
|
||||
"""Full medial tire graph M(T): the ambient tread-face model. Each tread
|
||||
face contributes a triangle on its three edge-medial-vertices; no medial
|
||||
edges between two same-boundary edges (those come from non-tread corners)."""
|
||||
Mt = nx.Graph()
|
||||
for f in tread_faces:
|
||||
es = [ekey(f[0], f[1]), ekey(f[1], f[2]), ekey(f[2], f[0])]
|
||||
Mt.add_nodes_from(es)
|
||||
for a in range(3):
|
||||
Mt.add_edge(es[a], es[(a + 1) % 3])
|
||||
return Mt
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Maximal planar graph on n vertices: stacked seed + random diagonal flips.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def random_maximal_planar(n: int, seed: int, flips: int = 400) -> nx.Graph:
|
||||
rng = random.Random(seed)
|
||||
g = nx.Graph()
|
||||
g.add_edges_from([(0, 1), (1, 2), (0, 2)])
|
||||
faces = [(0, 1, 2)]
|
||||
nxt = 3
|
||||
while nxt < n:
|
||||
a, b, c = faces.pop(rng.randrange(len(faces)))
|
||||
v = nxt
|
||||
nxt += 1
|
||||
g.add_edges_from([(v, a), (v, b), (v, c)])
|
||||
faces += [(a, b, v), (b, c, v), (a, c, v)]
|
||||
# randomise with diagonal flips that keep it simple and planar
|
||||
edges = list(g.edges())
|
||||
for _ in range(flips):
|
||||
u, v = rng.choice(edges)
|
||||
common = list(set(g.neighbors(u)) & set(g.neighbors(v)))
|
||||
if len(common) != 2:
|
||||
continue
|
||||
a, b = common
|
||||
if g.has_edge(a, b):
|
||||
continue
|
||||
g.remove_edge(u, v)
|
||||
g.add_edge(a, b)
|
||||
if not nx.check_planarity(g)[0] or g.number_of_edges() != 3 * n - 6:
|
||||
g.add_edge(u, v)
|
||||
g.remove_edge(a, b)
|
||||
else:
|
||||
edges = list(g.edges())
|
||||
return g
|
||||
|
||||
|
||||
def ekey(u, v):
|
||||
return (u, v) if (str(u), u) <= (str(v), v) else (v, u)
|
||||
|
||||
|
||||
def triangular_faces(g: nx.Graph):
|
||||
ok, emb = nx.check_planarity(g)
|
||||
if not ok:
|
||||
raise ValueError("not planar")
|
||||
seen = set()
|
||||
faces = []
|
||||
for u, v in list(emb.edges()):
|
||||
if (u, v) in seen:
|
||||
continue
|
||||
f = emb.traverse_face(u, v, mark_half_edges=seen)
|
||||
faces.append(tuple(f))
|
||||
return faces, emb
|
||||
|
||||
|
||||
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 = sorted(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
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Tread extraction from a BFS-level decomposition.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def extract_tread(faces, levels, d):
|
||||
"""Tread T_d: faces spanning levels {d, d+1}. Returns its edge classes."""
|
||||
tread_faces = []
|
||||
for f in faces:
|
||||
lv = [levels[x] for x in f]
|
||||
if min(lv) == d and max(lv) == d + 1:
|
||||
tread_faces.append(f)
|
||||
if not tread_faces:
|
||||
return None
|
||||
annular, up, down = set(), set(), set()
|
||||
face_of_down = defaultdict(int)
|
||||
for f in tread_faces:
|
||||
for x, y in ((f[0], f[1]), (f[1], f[2]), (f[2], f[0])):
|
||||
e = ekey(x, y)
|
||||
lx, ly = levels[x], levels[y]
|
||||
if {lx, ly} == {d, d + 1}:
|
||||
annular.add(e)
|
||||
elif lx == ly == d:
|
||||
up.add(e)
|
||||
elif lx == ly == d + 1:
|
||||
down.add(e)
|
||||
face_of_down[e] += 1
|
||||
bites = {e for e in down if face_of_down[e] == 2}
|
||||
return {
|
||||
"tread_faces": tread_faces,
|
||||
"annular": annular, "up": up, "down": down, "bites": bites,
|
||||
}
|
||||
|
||||
|
||||
def annular_cycle_order(M: nx.Graph, annular: set):
|
||||
"""Cyclic order of the annular medial vertices (they induce a cycle)."""
|
||||
sub = M.subgraph(annular)
|
||||
if sub.number_of_nodes() == 0 or any(sub.degree(v) != 2 for v in sub):
|
||||
return None
|
||||
if not nx.is_connected(sub):
|
||||
return None
|
||||
start = next(iter(annular))
|
||||
order = [start]
|
||||
prev = None
|
||||
cur = start
|
||||
while True:
|
||||
nbrs = [w for w in sub.neighbors(cur) if w != prev]
|
||||
if not nbrs:
|
||||
break
|
||||
nxt = nbrs[0]
|
||||
if nxt == start:
|
||||
break
|
||||
order.append(nxt)
|
||||
prev, cur = cur, nxt
|
||||
return order if len(order) == len(annular) else None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Recognise a genuine tread as a FullMedialTireGraph and classify colourings.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
from full_medial_tire_generator import FullMedialTireGraph
|
||||
from kempe_valid_colorings import classify as kempe_classify
|
||||
|
||||
|
||||
def _linear_cut(n, bite_pairs):
|
||||
"""Rotate the cycle so the bite pairs become linear non-crossing intervals."""
|
||||
for r in range(n):
|
||||
rel = [tuple(sorted(((i - r) % n, (j - r) % n))) for i, j in bite_pairs]
|
||||
ok = True
|
||||
for a, b in rel:
|
||||
for c, d in rel:
|
||||
if (a, b) != (c, d) and (a < c < b < d or c < a < d < b):
|
||||
ok = False
|
||||
break
|
||||
if not ok:
|
||||
break
|
||||
if ok:
|
||||
return r, rel
|
||||
return None
|
||||
|
||||
|
||||
def recognise(M, tread):
|
||||
"""Return (FullMedialTireGraph, bijection fmt-name -> medial vertex) or None.
|
||||
|
||||
``M`` here is the tread-face model M(T) (cycle + teeth + bites)."""
|
||||
annular = tread["annular"]
|
||||
order = annular_cycle_order(M, annular)
|
||||
if order is None or len(order) < 3:
|
||||
return None
|
||||
n = len(order)
|
||||
ann_set = set(annular)
|
||||
|
||||
apex_of_edge = []
|
||||
for i in range(n):
|
||||
a, b = order[i], order[(i + 1) % n]
|
||||
common = [w for w in set(M.neighbors(a)) & set(M.neighbors(b)) if w not in ann_set]
|
||||
if len(common) != 1:
|
||||
return None
|
||||
apex_of_edge.append(common[0])
|
||||
|
||||
up = set(tread["up"])
|
||||
# bite apex: serves two cycle edges (== adjacent to four annular vertices)
|
||||
apex_positions = defaultdict(list)
|
||||
for i, ap in enumerate(apex_of_edge):
|
||||
apex_positions[ap].append(i)
|
||||
|
||||
tooth = []
|
||||
bite_pairs = []
|
||||
for ap, positions in apex_positions.items():
|
||||
if len(positions) == 2:
|
||||
bite_pairs.append(tuple(sorted(positions)))
|
||||
for i, ap in enumerate(apex_of_edge):
|
||||
tooth.append("U" if ap in up else "D")
|
||||
|
||||
cut = _linear_cut(n, bite_pairs)
|
||||
if cut is None:
|
||||
return None
|
||||
r, rel_bites = cut
|
||||
word = [""] * n
|
||||
for i in range(n):
|
||||
word[(i - r) % n] = tooth[i]
|
||||
g = FullMedialTireGraph(n=n, tooth_word="".join(word), bites=frozenset(rel_bites))
|
||||
|
||||
# bijection fmt-name -> medial vertex
|
||||
bij = {}
|
||||
for k in range(n):
|
||||
bij[f"a{k}"] = order[(k + r) % n]
|
||||
for i in g.up_edges:
|
||||
bij[f"u{i}"] = apex_of_edge[(i + r) % n]
|
||||
for i in g.singleton_down_edges:
|
||||
bij[f"d{i}"] = apex_of_edge[(i + r) % n]
|
||||
for (i, j) in sorted(g.bites):
|
||||
bij[f"p{i}_{j}"] = apex_of_edge[(i + r) % n]
|
||||
|
||||
# verify the reconstructed graph is edge-faithful to the tread-face M(T)
|
||||
mt_edges = {ekey(*e) for e in M.edges()}
|
||||
rec_edges = {ekey(bij[u], bij[v]) for u, v in g.edges()}
|
||||
if rec_edges != mt_edges:
|
||||
return None
|
||||
return g, bij
|
||||
|
||||
|
||||
def canonical(coloring, ordered):
|
||||
remap, out = {}, []
|
||||
for v in ordered:
|
||||
c = coloring[v]
|
||||
if c not in remap:
|
||||
remap[c] = len(remap)
|
||||
out.append(remap[c])
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def proper_3_colorings_subgraph(M, nodes, limit=200000):
|
||||
nodes = sorted(nodes)
|
||||
adj = {v: set(M.neighbors(v)) & set(nodes) for v in nodes}
|
||||
coloring, 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 analyse(seed: int, color_limit: int = 400000):
|
||||
G = random_sphere_triangulation(12, seed)
|
||||
faces, _ = triangular_faces(G)
|
||||
M = medial_graph(G)
|
||||
global_colorings = proper_3_colorings(M, color_limit)
|
||||
assert len(global_colorings) < color_limit, "global colouring cap hit"
|
||||
|
||||
records = []
|
||||
for s in sorted(G.nodes()):
|
||||
levels = nx.single_source_shortest_path_length(G, s)
|
||||
for d in range(max(levels.values())):
|
||||
tread = extract_tread(faces, levels, d)
|
||||
if tread is None or len(tread["up"]) < 3:
|
||||
continue
|
||||
mt = medial_tire_facemodel(tread["tread_faces"])
|
||||
rec = recognise(mt, tread)
|
||||
if rec is None:
|
||||
continue
|
||||
g, bij = rec
|
||||
mt_nodes = list(bij.values())
|
||||
name_of = {v: k for k, v in bij.items()}
|
||||
|
||||
# restrictions of global colourings to this M(T)
|
||||
realized = set()
|
||||
for col in global_colorings:
|
||||
realized.add(canonical({v: col[v] for v in mt_nodes}, mt_nodes))
|
||||
|
||||
r_cnt = u_cnt = i_cnt = 0
|
||||
viol58 = 0
|
||||
seen = set()
|
||||
for col in proper_3_colorings_subgraph(mt, mt_nodes):
|
||||
key = canonical(col, mt_nodes)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
fmt_col = {name_of[v]: c for v, c in col.items()}
|
||||
balanced = kempe_classify(g, fmt_col).valid
|
||||
is_real = key in realized
|
||||
if is_real and not balanced:
|
||||
viol58 += 1
|
||||
if not balanced:
|
||||
i_cnt += 1
|
||||
elif is_real:
|
||||
r_cnt += 1
|
||||
else:
|
||||
u_cnt += 1
|
||||
records.append({
|
||||
"source": s, "tread": d, "n": g.n, "word": g.tooth_word,
|
||||
"bites": sorted(g.bites), "up": len(g.up_edges),
|
||||
"down": len(g.down_edges),
|
||||
"realized": r_cnt, "unrealized": u_cnt, "invalid": i_cnt,
|
||||
"total": r_cnt + u_cnt + i_cnt, "viol58": viol58,
|
||||
})
|
||||
return G, M, len(global_colorings), records
|
||||
|
||||
|
||||
def write_note(seed, G, M, n_global, records, path):
|
||||
lines = []
|
||||
lines.append(f"# Realized / Unrealized / Invalid analysis (seed {seed})\n")
|
||||
lines.append(
|
||||
f"Random maximal planar graph on **{G.number_of_nodes()} vertices** "
|
||||
f"({G.number_of_edges()} edges, {2*G.number_of_nodes()-4} faces). "
|
||||
f"Medial graph $M(G)$ has **{M.number_of_nodes()} vertices** and "
|
||||
f"**{n_global} proper 3-colourings**.\n")
|
||||
lines.append(
|
||||
"For each full medial tire graph $M(T)$ from a source/tread, every proper "
|
||||
"3-colouring (mod colour permutation) is labelled **Realized** "
|
||||
"(Kempe-balanced and a restriction of a colouring of $M(G)$), "
|
||||
"**Unrealized** (balanced but not a restriction), or **Invalid** "
|
||||
"(not Kempe-balanced).\n")
|
||||
tot58 = sum(r["viol58"] for r in records)
|
||||
lines.append(
|
||||
f"Remark 5.8 cross-check: **{tot58}** realized-but-invalid colourings "
|
||||
f"across all pieces (must be 0; a positive count would refute 5.8).\n")
|
||||
lines.append("| # | source | tread | n=\\|A(T)\\| | word | bites | "
|
||||
"Realized | Unrealized | Invalid | total |")
|
||||
lines.append("|--:|--:|--:|--:|:--|:--|--:|--:|--:|--:|")
|
||||
for idx, r in enumerate(records):
|
||||
b = ",".join(f"({i},{j})" for i, j in r["bites"]) or "-"
|
||||
lines.append(
|
||||
f"| {idx} | {r['source']} | T{r['tread']} | {r['n']} | `{r['word']}` | "
|
||||
f"{b} | {r['realized']} | {r['unrealized']} | {r['invalid']} | "
|
||||
f"{r['total']} |")
|
||||
lines.append("")
|
||||
tot_r = sum(r["realized"] for r in records)
|
||||
tot_u = sum(r["unrealized"] for r in records)
|
||||
tot_i = sum(r["invalid"] for r in records)
|
||||
n_unreal = sum(1 for r in records if r["unrealized"] > 0)
|
||||
suff = [idx for idx, r in enumerate(records) if r["unrealized"] == 0]
|
||||
lines.append("## Findings\n")
|
||||
lines.append(
|
||||
f"- **Remark 5.8 holds here.** No colouring is realized-but-invalid "
|
||||
f"({tot58} across all pieces): every restriction of a global colouring is "
|
||||
f"Kempe-balanced, as Remark 5.8 predicts.")
|
||||
lines.append(
|
||||
f"- **Balance is necessary but not sufficient.** {n_unreal} of "
|
||||
f"{len(records)} pieces have a *Kempe-balanced* colouring that is **not** "
|
||||
f"the restriction of any global colouring (Unrealized). Totals over all "
|
||||
f"pieces: Realized {tot_r}, Unrealized {tot_u}, Invalid {tot_i}.")
|
||||
lines.append(
|
||||
f"- **Where balance *is* sufficient:** pieces {suff} have zero Unrealized "
|
||||
f"colourings (every balanced colouring is realized). These are the small "
|
||||
f"all-up treads (`UUUU`, `UUUUU`) and the shallow bite tread "
|
||||
f"`DUUUDUU`, i.e. the cap-like pieces with few down teeth.")
|
||||
lines.append("")
|
||||
lines.append(
|
||||
"## Method\n\n"
|
||||
"1. `random_sphere_triangulation` -- convex hull of 12 random points on "
|
||||
"the sphere. 2. `medial_graph` and all proper 3-colourings of $M(G)$. "
|
||||
"3. For each source $S$ the BFS-level decomposition; tread $T_d$ spans "
|
||||
"levels $d,d+1$, and `medial_tire_facemodel` builds $M(T_d)$ in the "
|
||||
"ambient tread-face model (cycle + teeth + bites). Degenerate treads "
|
||||
"(no annular cycle, or fewer than three up teeth) are skipped. 4. Each "
|
||||
"$M(T)$ is recognised as a `FullMedialTireGraph`; its colourings are "
|
||||
"classified with the Kempe-balance rule and matched (mod colour "
|
||||
"permutation) against restrictions of the global colourings.")
|
||||
with open(path, "w") as fh:
|
||||
fh.write("\n".join(lines) + "\n")
|
||||
print(f"wrote {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
SEED = 1
|
||||
G, M, n_global, records = analyse(SEED)
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
for r in records:
|
||||
print(r)
|
||||
write_note(SEED, G, M, n_global, records,
|
||||
os.path.join(here, f"tire_realization_seed{SEED}.md"))
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Realized / Unrealized / Invalid analysis (seed 1)
|
||||
|
||||
Random maximal planar graph on **12 vertices** (30 edges, 20 faces). Medial graph $M(G)$ has **30 vertices** and **90 proper 3-colourings**.
|
||||
|
||||
For each full medial tire graph $M(T)$ from a source/tread, every proper 3-colouring (mod colour permutation) is labelled **Realized** (Kempe-balanced and a restriction of a colouring of $M(G)$), **Unrealized** (balanced but not a restriction), or **Invalid** (not Kempe-balanced).
|
||||
|
||||
Remark 5.8 cross-check: **0** realized-but-invalid colourings across all pieces (must be 0; a positive count would refute 5.8).
|
||||
|
||||
| # | source | tread | n=\|A(T)\| | word | bites | Realized | Unrealized | Invalid | total |
|
||||
|--:|--:|--:|--:|:--|:--|--:|--:|--:|--:|
|
||||
| 0 | 0 | T1 | 13 | `DUUUDDUDUUUDD` | (0,4),(5,12),(7,11) | 15 | 30 | 0 | 45 |
|
||||
| 1 | 1 | T1 | 12 | `DDDUDDUDUDUU` | (0,9) | 15 | 39 | 203 | 257 |
|
||||
| 2 | 2 | T1 | 9 | `UDUDUUDDD` | - | 11 | 13 | 61 | 85 |
|
||||
| 3 | 2 | T2 | 7 | `DUUUDUU` | (0,4) | 7 | 0 | 0 | 7 |
|
||||
| 4 | 3 | T1 | 13 | `DUUDUDUUDUUDD` | (0,3),(5,12),(8,11) | 15 | 40 | 0 | 55 |
|
||||
| 5 | 4 | T1 | 12 | `DDUUDUUDUDUD` | (1,4) | 14 | 58 | 185 | 257 |
|
||||
| 6 | 5 | T1 | 10 | `DDDUUDUDDU` | - | 14 | 40 | 117 | 171 |
|
||||
| 7 | 5 | T2 | 5 | `UUUUU` | - | 5 | 0 | 0 | 5 |
|
||||
| 8 | 6 | T1 | 12 | `DUDUDDDUUDDU` | - | 15 | 174 | 494 | 683 |
|
||||
| 9 | 7 | T1 | 11 | `UUDUDDDUDDD` | - | 13 | 89 | 239 | 341 |
|
||||
| 10 | 8 | T1 | 11 | `DUDDDUDUDDU` | - | 11 | 78 | 252 | 341 |
|
||||
| 11 | 8 | T2 | 4 | `UUUU` | - | 3 | 0 | 0 | 3 |
|
||||
| 12 | 9 | T1 | 10 | `UUUDDDUDUD` | - | 14 | 38 | 119 | 171 |
|
||||
| 13 | 9 | T2 | 4 | `UUUU` | - | 3 | 0 | 0 | 3 |
|
||||
| 14 | 10 | T1 | 10 | `DDUUDDDUUU` | - | 13 | 28 | 130 | 171 |
|
||||
| 15 | 10 | T2 | 4 | `UUUU` | - | 3 | 0 | 0 | 3 |
|
||||
| 16 | 11 | T1 | 11 | `DDUDDDUDUUD` | - | 14 | 88 | 239 | 341 |
|
||||
|
||||
## Findings
|
||||
|
||||
- **Remark 5.8 holds here.** No colouring is realized-but-invalid (0 across all pieces): every restriction of a global colouring is Kempe-balanced, as Remark 5.8 predicts.
|
||||
- **Balance is necessary but not sufficient.** 12 of 17 pieces have a *Kempe-balanced* colouring that is **not** the restriction of any global colouring (Unrealized). Totals over all pieces: Realized 185, Unrealized 715, Invalid 2039.
|
||||
- **Where balance *is* sufficient:** pieces [3, 7, 11, 13, 15] have zero Unrealized colourings (every balanced colouring is realized). These are the small all-up treads (`UUUU`, `UUUUU`) and the shallow bite tread `DUUUDUU`, i.e. the cap-like pieces with few down teeth.
|
||||
|
||||
## Method
|
||||
|
||||
1. `random_sphere_triangulation` -- convex hull of 12 random points on the sphere. 2. `medial_graph` and all proper 3-colourings of $M(G)$. 3. For each source $S$ the BFS-level decomposition; tread $T_d$ spans levels $d,d+1$, and `medial_tire_facemodel` builds $M(T_d)$ in the ambient tread-face model (cycle + teeth + bites). Degenerate treads (no annular cycle, or fewer than three up teeth) are skipped. 4. Each $M(T)$ is recognised as a `FullMedialTireGraph`; its colourings are classified with the Kempe-balance rule and matched (mod colour permutation) against restrictions of the global colourings.
|
||||
Reference in New Issue
Block a user