Add Kempe-balanced colouring definition and validity classifier
Define Kempe-balanced colourings of a full medial tire graph (Def 5.7):
for each valid face (outer face or interior non-tooth face of B(T)) and
each colour pair {a,b}, the number of tooth apexes incident to the face
coloured a or b must be even. Add Remark 5.8 (necessity: a colouring of
M(T) extends to M(G) only if it is Kempe-balanced) and rename Lemma 5.5
to "Kempe chains are cycles".
Add kempe_valid_colorings.py: enumerate all proper 3-colourings of a full
medial tire graph, label each Kempe-balanced/valid or invalid, and plot
them with the offending face's Kempe chains and odd apex set highlighted
on invalid panels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+361
@@ -0,0 +1,361 @@
|
||||
"""Enumerate proper 3-colourings of a full medial tire graph and label them
|
||||
valid / invalid by a Kempe-parity rule.
|
||||
|
||||
For a proper 3-colouring of M(T) and a colour pair P = {a, b}, the subgraph
|
||||
induced by the vertices coloured a or b splits into connected components, the
|
||||
P-Kempe chains. Every vertex coloured a or b lies on exactly one P-Kempe
|
||||
chain, so "lies on some P-Kempe chain" is the same as "coloured a or b".
|
||||
|
||||
A *valid face* is the single outer (unbounded) face, or an inner face that is
|
||||
not a tooth -- equivalently the faces of B(T) other than tooth triangles: the
|
||||
root face plus one inner-gap face per bite (Remark 3.8). A colouring is VALID
|
||||
when, for every valid face F and every colour pair P = {a, b}, the number of
|
||||
non-bite *apex* vertices incident to F that lie on a P-Kempe chain (i.e. are
|
||||
coloured a or b) is even. The count is over the whole pair P on the face, not
|
||||
per individual chain.
|
||||
|
||||
The non-bite apex vertices on the boundary of each valid face are:
|
||||
|
||||
* outer face: every up-tooth apex;
|
||||
* an inner non-tooth face F: the singleton down-tooth apexes whose edge lies
|
||||
on F's arc.
|
||||
|
||||
A singleton down apex on edge m lies on the inner face of the innermost bite
|
||||
(i, j) with i < m < j, else the root face. Annular vertices and bite apexes are
|
||||
never counted.
|
||||
|
||||
The rule is invariant under permuting the three colours, so colourings may be
|
||||
reduced modulo the six colour permutations without affecting validity.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
|
||||
from full_medial_tire_generator import (
|
||||
FullMedialTireGraph,
|
||||
face_singleton_counts,
|
||||
generate,
|
||||
innermost_bite,
|
||||
)
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
Coloring = dict[str, int]
|
||||
COLOR_PAIRS = ((0, 1), (0, 2), (1, 2))
|
||||
|
||||
|
||||
def adjacency(graph: FullMedialTireGraph) -> dict[str, set[str]]:
|
||||
adj: dict[str, set[str]] = {v: set() for v in graph.vertices()}
|
||||
for u, v in graph.edges():
|
||||
adj[u].add(v)
|
||||
adj[v].add(u)
|
||||
return adj
|
||||
|
||||
|
||||
def proper_3_colorings(graph: FullMedialTireGraph) -> Iterator[Coloring]:
|
||||
"""Every proper 3-colouring of M(T), by backtracking (annular cycle first)."""
|
||||
adj = adjacency(graph)
|
||||
# colour the heavily-constrained annular cycle first, then the apexes
|
||||
order = [f"a{k}" for k in range(graph.n)]
|
||||
order += [v for v in graph.vertices() if not v.startswith("a")]
|
||||
coloring: Coloring = {}
|
||||
|
||||
def rec(idx: int) -> Iterator[Coloring]:
|
||||
if idx == len(order):
|
||||
yield dict(coloring)
|
||||
return
|
||||
v = order[idx]
|
||||
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||
for c in (0, 1, 2):
|
||||
if c in used:
|
||||
continue
|
||||
coloring[v] = c
|
||||
yield from rec(idx + 1)
|
||||
coloring.pop(v, None)
|
||||
|
||||
yield from rec(0)
|
||||
|
||||
|
||||
def kempe_components(
|
||||
adj: dict[str, set[str]], coloring: Coloring, pair: tuple[int, int]
|
||||
) -> list[set[str]]:
|
||||
"""Connected components of the subgraph induced by the two colours in pair."""
|
||||
members = {v for v, c in coloring.items() if c in pair}
|
||||
seen: set[str] = set()
|
||||
components: list[set[str]] = []
|
||||
for start in members:
|
||||
if start in seen:
|
||||
continue
|
||||
stack = [start]
|
||||
comp: set[str] = set()
|
||||
seen.add(start)
|
||||
while stack:
|
||||
v = stack.pop()
|
||||
comp.add(v)
|
||||
for w in adj[v]:
|
||||
if w in members and w not in seen:
|
||||
seen.add(w)
|
||||
stack.append(w)
|
||||
components.append(comp)
|
||||
return components
|
||||
|
||||
|
||||
def valid_faces(graph: FullMedialTireGraph) -> dict[str, frozenset[str]]:
|
||||
"""Map each valid face to the set of non-bite *apex* vertices on it.
|
||||
|
||||
Keys: "outer", "root", or "bite(i,j)". The outer face carries the up-tooth
|
||||
apexes; each inner non-tooth face carries the singleton down-tooth apexes on
|
||||
its arc. Annular vertices and bite apexes are never included.
|
||||
"""
|
||||
faces: dict[str, set[str]] = {
|
||||
"outer": {f"u{i}" for i in graph.up_edges},
|
||||
"root": set(),
|
||||
}
|
||||
for i, j in graph.bites:
|
||||
faces[f"bite({i},{j})"] = set()
|
||||
|
||||
def inner_key(face) -> str:
|
||||
return "root" if face is None else f"bite({face[0]},{face[1]})"
|
||||
|
||||
for m in graph.singleton_down_edges:
|
||||
faces[inner_key(innermost_bite(m, graph.bites))].add(f"d{m}")
|
||||
|
||||
return {name: frozenset(verts) for name, verts in faces.items()}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Verdict:
|
||||
valid: bool
|
||||
reason: str = "" # "" when valid
|
||||
pair: tuple | None = None # the offending colour pair {a,b}
|
||||
apexes: frozenset = frozenset() # the non-bite apexes counted (odd)
|
||||
face: str = "" # name of the offending valid face
|
||||
|
||||
|
||||
def classify(graph: FullMedialTireGraph, coloring: Coloring) -> Verdict:
|
||||
"""Label a single proper 3-colouring valid / invalid by the Kempe rule.
|
||||
|
||||
For each valid face and each colour pair {a,b}, the non-bite apex vertices
|
||||
on that face coloured a or b must be even in number. On a violation the
|
||||
Verdict records the offending face, pair, and that odd apex set.
|
||||
"""
|
||||
faces = valid_faces(graph)
|
||||
|
||||
for face_name, face_apexes in faces.items():
|
||||
for pair in COLOR_PAIRS:
|
||||
on_pair = frozenset(v for v in face_apexes if coloring[v] in pair)
|
||||
if len(on_pair) % 2 != 0:
|
||||
return Verdict(
|
||||
False,
|
||||
f"{len(on_pair)} non-bite apex vertices on the {face_name} "
|
||||
f"lie on an {set(pair)}-Kempe chain (odd)",
|
||||
pair, on_pair, face_name,
|
||||
)
|
||||
return Verdict(True)
|
||||
|
||||
|
||||
def canonical_coloring(graph: FullMedialTireGraph, coloring: Coloring) -> tuple:
|
||||
"""Representative modulo the six colour permutations (first-seen relabel)."""
|
||||
order = graph.vertices()
|
||||
remap: dict[int, int] = {}
|
||||
out = []
|
||||
for v in order:
|
||||
c = coloring[v]
|
||||
if c not in remap:
|
||||
remap[c] = len(remap)
|
||||
out.append(remap[c])
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def classify_colorings(
|
||||
graph: FullMedialTireGraph, dedup_colors: bool = True
|
||||
) -> list[tuple[Coloring, Verdict]]:
|
||||
"""Enumerate all proper 3-colourings of ``graph`` and label each valid/invalid."""
|
||||
results: list[tuple[Coloring, Verdict]] = []
|
||||
seen: set[tuple] = set()
|
||||
for coloring in proper_3_colorings(graph):
|
||||
if dedup_colors:
|
||||
key = canonical_coloring(graph, coloring)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
results.append((coloring, classify(graph, coloring)))
|
||||
return results
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Plotting.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _draw_colored(ax, graph: FullMedialTireGraph, coloring: Coloring, verdict: Verdict):
|
||||
import matplotlib.pyplot as plt # noqa: F401 (backend already chosen in run)
|
||||
|
||||
n = graph.n
|
||||
palette = {0: "#e6550d", 1: "#3182bd", 2: "#31a354"} # three colour classes
|
||||
|
||||
def ann_xy(k):
|
||||
ang = math.pi / 2 - 2 * math.pi * k / n
|
||||
return math.cos(ang), math.sin(ang)
|
||||
|
||||
def mid_ang(i):
|
||||
return math.pi / 2 - 2 * math.pi * (i + 0.5) / n
|
||||
|
||||
pos: dict[str, tuple[float, float]] = {f"a{k}": ann_xy(k) for k in range(n)}
|
||||
matched = graph.bite_edges
|
||||
for i, tooth in enumerate(graph.tooth_word):
|
||||
if tooth == "U":
|
||||
r = 1.42
|
||||
pos[f"u{i}"] = (r * math.cos(mid_ang(i)), r * math.sin(mid_ang(i)))
|
||||
elif i not in matched:
|
||||
r = 0.58
|
||||
pos[f"d{i}"] = (r * math.cos(mid_ang(i)), r * math.sin(mid_ang(i)))
|
||||
for i, j in sorted(graph.bites):
|
||||
corners = [ann_xy(i), ann_xy((i + 1) % n), ann_xy(j), ann_xy((j + 1) % n)]
|
||||
cx = sum(p[0] for p in corners) / 4.0
|
||||
cy = sum(p[1] for p in corners) / 4.0
|
||||
pos[f"p{i}_{j}"] = (cx * 0.82, cy * 0.82)
|
||||
|
||||
for u, v in graph.edges():
|
||||
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]],
|
||||
color="#bbbbbb", lw=0.5, zorder=1)
|
||||
# annular cycle a little heavier
|
||||
for k in range(n):
|
||||
a, b = f"a{k}", f"a{(k + 1) % n}"
|
||||
ax.plot([pos[a][0], pos[b][0]], [pos[a][1], pos[b][1]],
|
||||
color="#666666", lw=1.0, zorder=2)
|
||||
|
||||
# highlight the offending pair's Kempe chains (those carrying the counted
|
||||
# apexes) on invalid colourings
|
||||
if not verdict.valid and verdict.pair is not None:
|
||||
pair = verdict.pair
|
||||
highlight: set[str] = set()
|
||||
for comp in kempe_components(adjacency(graph), coloring, pair):
|
||||
if comp & verdict.apexes:
|
||||
highlight |= comp
|
||||
for u, v in graph.edges():
|
||||
if u in highlight and v in highlight and coloring[u] in pair and coloring[v] in pair:
|
||||
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]],
|
||||
color="#f5b800", lw=2.4, zorder=2.5, solid_capstyle="round")
|
||||
|
||||
for v, (x, y) in pos.items():
|
||||
is_bite = v.startswith("p")
|
||||
ax.scatter([x], [y], s=34 if is_bite else 24, color=palette[coloring[v]],
|
||||
edgecolors="black", linewidths=0.5 if is_bite else 0.3, zorder=3)
|
||||
|
||||
# ring the apex vertices whose odd count caused the violation
|
||||
if not verdict.valid and verdict.apexes:
|
||||
xs = [pos[v][0] for v in verdict.apexes]
|
||||
ys = [pos[v][1] for v in verdict.apexes]
|
||||
ax.scatter(xs, ys, s=120, facecolors="none", edgecolors="#d62728",
|
||||
linewidths=1.8, zorder=4)
|
||||
|
||||
ax.set_xlim(-1.65, 1.65)
|
||||
ax.set_ylim(-1.85, 1.65)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
color = "#2ca02c" if verdict.valid else "#d62728"
|
||||
label = "VALID" if verdict.valid else "INVALID"
|
||||
ax.set_title(label, fontsize=7, color=color, pad=1.5)
|
||||
if not verdict.valid:
|
||||
ax.text(0.5, -0.04, verdict.reason, transform=ax.transAxes,
|
||||
ha="center", va="top", fontsize=4.6, color="#d62728")
|
||||
|
||||
|
||||
def plot_all(graph: FullMedialTireGraph, results, path_png: str, path_pdf: str):
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
cols = 10
|
||||
rows = math.ceil(len(results) / cols)
|
||||
fig, axes = plt.subplots(rows, cols, figsize=(cols * 1.5, rows * 1.65))
|
||||
axes = axes.reshape(rows, cols)
|
||||
for idx in range(rows * cols):
|
||||
ax = axes[idx // cols][idx % cols]
|
||||
if idx < len(results):
|
||||
coloring, verdict = results[idx]
|
||||
_draw_colored(ax, graph, coloring, verdict)
|
||||
else:
|
||||
ax.axis("off")
|
||||
|
||||
n_valid = sum(1 for _, v in results if v.valid)
|
||||
bites = ",".join(f"({i},{j})" for i, j in sorted(graph.bites)) or "-"
|
||||
fig.suptitle(
|
||||
f"Proper 3-colourings of M(T): word={graph.tooth_word}, bites={bites}\n"
|
||||
f"{len(results)} colourings (mod colour permutation) — "
|
||||
f"{n_valid} valid, {len(results) - n_valid} invalid\n"
|
||||
f"invalid panels: gold = offending pair's Kempe chains, "
|
||||
f"red ring = the odd set of non-bite apexes on the offending face",
|
||||
fontsize=11, y=0.998,
|
||||
)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.97))
|
||||
fig.savefig(path_png, dpi=200)
|
||||
fig.savefig(path_pdf)
|
||||
print(f"wrote {path_png}")
|
||||
print(f"wrote {path_pdf}")
|
||||
|
||||
|
||||
def pick_demo_graph() -> FullMedialTireGraph:
|
||||
"""A small n=9 class that has a bite, at least one VALID colouring, and
|
||||
(preferably) singleton down teeth sitting inside a bite's inner face, so the
|
||||
plot shows both valid and invalid colourings and exercises a bite face."""
|
||||
best = None
|
||||
fallback = None
|
||||
for g in generate(9, dedup=True):
|
||||
if not g.bites:
|
||||
continue
|
||||
fallback = fallback or g
|
||||
n_valid = sum(1 for _, v in classify_colorings(g, dedup_colors=True) if v.valid)
|
||||
if n_valid == 0:
|
||||
continue
|
||||
has_bite_face = any(
|
||||
face is not None for face in face_singleton_counts(g.tooth_word, g.bites)
|
||||
)
|
||||
key = (not has_bite_face, -n_valid, len(g.singleton_down_edges))
|
||||
if best is None or key < best[0]:
|
||||
best = (key, g)
|
||||
return best[1] if best else fallback
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> None:
|
||||
graph = pick_demo_graph()
|
||||
bites = ",".join(f"({i},{j})" for i, j in sorted(graph.bites)) or "-"
|
||||
print(f"demo graph: n={graph.n} word={graph.tooth_word} bites={bites}")
|
||||
print(f" up teeth: {graph.up_edges}")
|
||||
print(f" singleton down teeth: {graph.singleton_down_edges}")
|
||||
print(f" face singleton counts: {face_singleton_counts(graph.tooth_word, graph.bites)}")
|
||||
|
||||
all_results = classify_colorings(graph, dedup_colors=False)
|
||||
results = classify_colorings(graph, dedup_colors=True)
|
||||
n_valid = sum(1 for _, v in results if v.valid)
|
||||
print(f"proper 3-colourings: {len(all_results)} total, "
|
||||
f"{len(results)} modulo colour permutation")
|
||||
print(f" valid: {n_valid} invalid: {len(results) - n_valid}")
|
||||
|
||||
# show a couple of invalid reasons for transparency
|
||||
shown = 0
|
||||
for _, v in results:
|
||||
if not v.valid and shown < 3:
|
||||
print(f" invalid example: {v.reason}")
|
||||
shown += 1
|
||||
|
||||
# valid first, then invalid, for a readable grid
|
||||
results.sort(key=lambda cv: (not cv[1].valid,))
|
||||
png = os.path.join(HERE, "kempe_valid_colorings_demo.png")
|
||||
pdf = os.path.join(HERE, "kempe_valid_colorings_demo.pdf")
|
||||
plot_all(graph, results, png, pdf)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.parse_args()
|
||||
run(parser.parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 351 KiB |
@@ -35,6 +35,10 @@
|
||||
\newlabel{conj:medial-chain-pigeonhole}{{5.2}{7}}
|
||||
\newlabel{conj:medial-route-fct}{{5.3}{7}}
|
||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{5.1}{Kempe-cycle conservation across medial tires}}{7}{}\protected@file@percent }
|
||||
\newlabel{lem:kempe-cycles}{{5.5}{7}}
|
||||
\newlabel{lem:kempe-conservation}{{5.6}{8}}
|
||||
\newlabel{def:kempe-balanced}{{5.7}{8}}
|
||||
\newlabel{rem:kempe-balance-necessary}{{5.8}{8}}
|
||||
\bibcite{bauerfeld-nested-tire-decompositions}{1}
|
||||
\bibcite{tait-original}{2}
|
||||
\newlabel{tocindent-1}{0pt}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Fdb version 3
|
||||
["pdflatex"] 1781194762 "paper.tex" "paper.pdf" "paper" 1781194763
|
||||
["pdflatex"] 1781207945 "paper.tex" "paper.pdf" "paper" 1781207946
|
||||
"/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" 1781194763 4035 146533a306519cb8688d4e85db1d3f80 "pdflatex"
|
||||
"paper.tex" 1781194559 37363 b7b005cfaefc0e3a582f756b993a034e ""
|
||||
"paper.aux" 1781207946 4206 a817291c83280f23be785ea9b9789717 "pdflatex"
|
||||
"paper.tex" 1781207918 39941 f80d8bca5b99e67d65ad8e4bb2d30152 ""
|
||||
(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 12:19
|
||||
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
|
||||
entering extended mode
|
||||
restricted \write18 enabled.
|
||||
%&-line parsing enabled.
|
||||
@@ -508,10 +508,10 @@ LaTeX Warning: `h' float specifier changed to `ht'.
|
||||
|
||||
[4] [5] [6] [7] [8] [9] [10] (./paper.aux) )
|
||||
Here is how much of TeX's memory you used:
|
||||
14415 strings out of 478268
|
||||
283664 string characters out of 5846347
|
||||
609309 words of memory out of 5000000
|
||||
32244 multiletter control sequences out of 15000+600000
|
||||
14419 strings out of 478268
|
||||
283755 string characters out of 5846347
|
||||
609349 words of memory out of 5000000
|
||||
32248 multiletter control sequences out of 15000+600000
|
||||
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
|
||||
@@ -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, 272876 bytes).
|
||||
Output written on paper.pdf (10 pages, 276272 bytes).
|
||||
PDF statistics:
|
||||
135 PDF objects out of 1000 (max. 8388607)
|
||||
84 compressed objects within 1 object stream
|
||||
|
||||
Binary file not shown.
@@ -727,7 +727,8 @@ Since $M$ is $4$-regular and $\varphi$ is proper, every vertex of
|
||||
$M_P$ has degree $2$ in $M_P$. Hence every component of $M_P$ is a
|
||||
cycle. We call these components the $P$-Kempe cycles of $\varphi$.
|
||||
|
||||
\begin{lemma}[Kempe cycles are cycles]
|
||||
\begin{lemma}[Kempe chains are cycles]
|
||||
\label{lem:kempe-cycles}
|
||||
Let $G$ be a plane triangulation, let $M=M(G)$, and let
|
||||
$\varphi$ be a proper $3$-colouring of $M$. For each
|
||||
$P\in\{\{1,2\},\{2,3\},\{3,1\}\}$, every component of $M_P$ is a cycle.
|
||||
@@ -763,6 +764,7 @@ in the tire tree.
|
||||
The following lemma is the basic conservation principle.
|
||||
|
||||
\begin{lemma}[Kempe-cycle conservation across level cycles]
|
||||
\label{lem:kempe-conservation}
|
||||
Let $C$ be a level cycle of $M$ separating a parent side from a child
|
||||
side. Let $K$ be a $P$-Kempe cycle for some
|
||||
$P\in\{\{1,2\},\{2,3\},\{3,1\}\}$. Then $K$ cannot enter the child side
|
||||
@@ -784,6 +786,56 @@ $C$. Thus every entrance through $C$ is paired with an exit through
|
||||
$C$.
|
||||
\end{proof}
|
||||
|
||||
We now use these Kempe cycles to single out the colourings of a full
|
||||
medial tire graph that respect the annular tooth structure.
|
||||
|
||||
\begin{definition}[Kempe-balanced colouring]
|
||||
\label{def:kempe-balanced}
|
||||
Let $\varphi$ be a proper $3$-colouring of the full medial tire graph
|
||||
$\mathsf{M}(T)$. For a colour pair $P=\{a,b\}$, let $\mathsf{M}(T)_P$ be
|
||||
the subgraph induced by the vertices of colours $a$ and $b$. Since
|
||||
$\mathsf{M}(T)$ need not be $4$-regular, the components of
|
||||
$\mathsf{M}(T)_P$ are paths or cycles; we call them the $P$-\emph{Kempe
|
||||
chains} of $\varphi$. Every vertex of colour $a$ or $b$ lies on exactly
|
||||
one $P$-Kempe chain.
|
||||
|
||||
A \emph{valid face} is the outer face of $\mathsf{M}(T)$, or an interior
|
||||
face of $B(T)$ that is not a tooth---namely the root face or a bite
|
||||
inner-gap face of Remark~\ref{rem:bite-face-count}. The \emph{tooth
|
||||
apexes incident to} a valid face $F$ are:
|
||||
\begin{itemize}
|
||||
\item the up-tooth apexes (Definition~\ref{def:annular-teeth}), when
|
||||
$F$ is the outer face;
|
||||
\item the singleton down-tooth apexes whose annular edge lies on $F$,
|
||||
when $F$ is interior---the apex on annular edge $m$ being incident to
|
||||
the innermost bite $(i,j)$ with $i<m<j$, or to the root face if there
|
||||
is none.
|
||||
\end{itemize}
|
||||
Bite apexes are never incident to a valid face in this sense.
|
||||
|
||||
For a colour pair $P=\{a,b\}$ write $\nu_P(F)$ for the number of tooth
|
||||
apexes incident to $F$ that are coloured $a$ or $b$---equivalently, that
|
||||
lie on a $P$-Kempe chain. The colouring $\varphi$ is
|
||||
\emph{Kempe-balanced} if $\nu_P(F)$ is even for every valid face $F$ and
|
||||
every colour pair $P$.
|
||||
\end{definition}
|
||||
|
||||
\begin{remark}[Necessity of Kempe-balance]
|
||||
\label{rem:kempe-balance-necessary}
|
||||
A proper $3$-colouring of $\mathsf{M}(T)$ can be part of a proper
|
||||
$3$-colouring of the whole medial graph $M(G)$ only when it is
|
||||
Kempe-balanced: if $\varphi$ is the restriction to $\mathsf{M}(T)$ of a
|
||||
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.
|
||||
\end{remark}
|
||||
|
||||
More generally, let $T$ be a medial tire region with boundary
|
||||
\[
|
||||
\partial T = C_0\sqcup C_1\sqcup\cdots\sqcup C_m.
|
||||
|
||||
Reference in New Issue
Block a user