diff --git a/papers/medial_tire_cuts/experiments/medial_tire_cut_labelling.py b/papers/medial_tire_cuts/experiments/medial_tire_cut_labelling.py new file mode 100644 index 0000000..5a5441e --- /dev/null +++ b/papers/medial_tire_cuts/experiments/medial_tire_cut_labelling.py @@ -0,0 +1,386 @@ +"""Walk-depth labelling and cut of a full medial tire graph. + +Implements the procedure of Definition 2.1 ("Walk-depth labelling and cut") of +the *Medial Tire Cuts* paper: + + 1. Pick an arbitrary up tooth, the entry tooth; it has walk depth d. + 2. Traverse all teeth bounding the inner face incident to the entry tooth + clockwise until reaching the entry tooth, incrementing the walk depth by 1 + for each tooth traversed. + 3. On reaching the last tooth in the face, perform a cut by duplicating the + annular vertex at which the traversal closes (the annular vertex shared by + the last tooth and the closing tooth). + 4. Find the tooth t of highest walk depth that is a member of a bite. + 5. If t is incident to a face F with unlabelled teeth, traverse the teeth of F + starting from t in the direction of the unlabelled tooth incident to t + (sharing an annular vertex), incrementing the walk depth as you go. + 6. Repeat steps 3-5 until all teeth are labelled. + +The full medial tire graph model (annular cycle A(T), up/down teeth, bites, the +auxiliary plane graph B(T) and its inner faces) is the one from the companion +``full_medial_tire_generator.py`` of the medial tire decompositions paper, which +we import. + +Teeth are identified with the annular edges that carry them: edge i sits on the +annular vertices a_i and a_{(i+1) mod n} and carries exactly one tooth. A bite +(i, j) carries two teeth, one on edge i and one on edge j, that share the bite +apex p. The inner non-tooth faces of B(T) are the root face (written ``None``) +and one inner-gap face per bite. +""" + +from __future__ import annotations + +import argparse +import math +import os +import sys + +# Import the full medial tire model from the companion paper's experiments. +_GEN_DIR = os.path.normpath(os.path.join( + os.path.dirname(__file__), "..", "..", + "medial_tire_decompositions_of_plane_triangulations", "experiments", +)) +sys.path.insert(0, _GEN_DIR) + +from full_medial_tire_generator import ( # noqa: E402 + FullMedialTireGraph, + has_incident_bite, + innermost_bite, + satisfies_bite_face_condition, +) + +Face = "tuple[int, int] | None" # a bite (i, j), or None for the root face + + +# --------------------------------------------------------------------------- +# Face structure of B(T). +# --------------------------------------------------------------------------- + +def parent_face(graph: FullMedialTireGraph, bite: tuple[int, int]) -> Face: + """The face directly enclosing ``bite``: the minimal-span bite strictly + containing it, or the root face ``None``.""" + i, j = bite + enclosing = [b for b in graph.bites if b[0] < i and b[1] > j] + if not enclosing: + return None + return min(enclosing, key=lambda b: b[1] - b[0]) + + +def door_bite(graph: FullMedialTireGraph, edge: int) -> tuple[int, int] | None: + """The bite that ``edge`` is a door of (i.e. a bite edge), or None.""" + for b in graph.bites: + if edge in b: + return b + return None + + +def faces_bordered(graph: FullMedialTireGraph, edge: int) -> list[Face]: + """The inner non-tooth faces whose boundary the tooth on ``edge`` lies on. + + A bite door borders two faces (its bite's gap and that bite's parent); any + other tooth borders the single face directly containing its edge. + """ + bite = door_bite(graph, edge) + if bite is not None: + return [bite, parent_face(graph, bite)] + return [innermost_bite(edge, graph.bites)] + + +def face_boundary(graph: FullMedialTireGraph, face: Face) -> list[int]: + """The teeth (annular edges) bounding ``face``, in clockwise cyclic order. + + Clockwise is increasing edge index. For the root face the boundary is read + around the whole cycle; for a bite gap (i, j) it is read along the arc + i, i+1, ..., j and closes through the bite apex. Edges enclosed by a child + bite are skipped (they belong to the child's gap face). + """ + n = graph.n + arc = range(n) if face is None else range(face[0], face[1] + 1) + return [k for k in arc if face in faces_bordered(graph, k)] + + +def all_faces(graph: FullMedialTireGraph) -> list[Face]: + return [None] + sorted(graph.bites) + + +def shared_annular_vertex(graph: FullMedialTireGraph, e1: int, e2: int) -> int | None: + """The annular vertex a_k shared by edges ``e1`` and ``e2``, or None.""" + n = graph.n + common = {e1, (e1 + 1) % n} & {e2, (e2 + 1) % n} + return next(iter(common)) if common else None + + +# --------------------------------------------------------------------------- +# The walk-depth labelling and cut. +# --------------------------------------------------------------------------- + +class Cut: + """A cut performed when a face traversal closes: the duplicated annular + vertex, together with the last labelled tooth and the closing tooth that + share it, and the face being closed.""" + + __slots__ = ("vertex", "last_tooth", "closing_tooth", "face", "order") + + def __init__(self, vertex, last_tooth, closing_tooth, face, order): + self.vertex = vertex + self.last_tooth = last_tooth + self.closing_tooth = closing_tooth + self.face = face + self.order = order + + def __repr__(self): + f = "root" if self.face is None else f"bite{self.face}" + return (f"Cut(order={self.order}, a{self.vertex}, " + f"last=e{self.last_tooth}, closing=e{self.closing_tooth}, face={f})") + + +def label_and_cut(graph: FullMedialTireGraph, entry_edge: int, + start_depth: int = 0) -> tuple[dict[int, int], list[Cut]]: + """Run the procedure starting from up tooth ``entry_edge``. + + Returns ``(depth, cuts)`` where ``depth`` maps each annular edge (tooth) to + its walk depth, and ``cuts`` is the list of cuts in the order performed. + """ + if graph.tooth_word[entry_edge] != "U": + raise ValueError(f"entry edge {entry_edge} is not an up tooth") + + depth: dict[int, int] = {} + cuts: list[Cut] = [] + counter = start_depth + + def traverse(face: Face, start_edge: int, is_entry: bool) -> None: + nonlocal counter + boundary = face_boundary(graph, face) + m = len(boundary) + pos = boundary.index(start_edge) + if is_entry: + depth[start_edge] = counter + counter += 1 + direction = +1 + else: + # head toward the unlabelled tooth incident to the door t + direction = +1 if boundary[(pos + 1) % m] not in depth else -1 + last_new = start_edge + i = pos + while True: + i = (i + direction) % m + edge = boundary[i] + if edge in depth: # the closing tooth + cuts.append(Cut( + vertex=shared_annular_vertex(graph, last_new, edge), + last_tooth=last_new, closing_tooth=edge, + face=face, order=len(cuts), + )) + return + depth[edge] = counter + counter += 1 + last_new = edge + + # Steps 1-3: the entry face. + traverse(innermost_bite(entry_edge, graph.bites), entry_edge, is_entry=True) + + # Steps 4-6: descend through bites, deepest first. + while len(depth) < graph.n: + labelled_bite_teeth = sorted( + (e for e in depth if door_bite(graph, e) is not None), + key=lambda e: depth[e], reverse=True, + ) + for t in labelled_bite_teeth: + target = next((F for F in faces_bordered(graph, t) + if any(e not in depth for e in face_boundary(graph, F))), + None) + if target is not None: + traverse(target, t, is_entry=False) + break + else: + break # no progress possible + + return depth, cuts + + +# --------------------------------------------------------------------------- +# TikZ rendering. +# --------------------------------------------------------------------------- + +def _coords(graph: FullMedialTireGraph, + r_ann=1.0, r_up=1.46, r_down=0.60) -> dict[str, tuple[float, float]]: + n = graph.n + + def ang(k): # a_0 at the top, increasing k clockwise + return math.radians(90.0 - k * 360.0 / n) + + def edge_mid_dir(i): # angle of the bisector of edge i's two endpoints + a0, a1 = ang(i), ang((i + 1) % n) + return math.atan2(math.sin(a0) + math.sin(a1), math.cos(a0) + math.cos(a1)) + + pos = {f"a{k}": (r_ann * math.cos(ang(k)), r_ann * math.sin(ang(k))) + for k in range(n)} + for i in graph.up_edges: + a = edge_mid_dir(i) + pos[f"u{i}"] = (r_up * math.cos(a), r_up * math.sin(a)) + for i in graph.singleton_down_edges: + a = edge_mid_dir(i) + pos[f"d{i}"] = (r_down * math.cos(a), r_down * math.sin(a)) + for (i, j) in graph.bites: + pts = [pos[f"a{i}"], pos[f"a{(i + 1) % n}"], + pos[f"a{j}"], pos[f"a{(j + 1) % n}"]] + cx = sum(p[0] for p in pts) / 4.0 + cy = sum(p[1] for p in pts) / 4.0 + pos[f"p{i}_{j}"] = (0.9 * cx, 0.9 * cy) + return pos + + +def _edge_midpoint(pos, graph, edge): + n = graph.n + a, b = pos[f"a{edge}"], pos[f"a{(edge + 1) % n}"] + return (0.5 * (a[0] + b[0]), 0.5 * (a[1] + b[1])) + + +def to_tikz(graph: FullMedialTireGraph, + depth: dict[int, int] | None = None, + cuts: list[Cut] | None = None, + entry_edge: int | None = None, + scale: float = 2.2) -> str: + """A standalone ``tikzpicture`` for ``graph``; if ``depth`` is given, draw + the walk-depth labels and (with ``cuts``) the cut marks.""" + pos = _coords(graph) + n = graph.n + L = [] + A = L.append + A(f"\\begin{{tikzpicture}}[scale={scale},") + A(" ann/.style={circle, fill=black, inner sep=1.0pt},") + A(" upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt},") + A(" downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt},") + A(" bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt},") + A(" cyc/.style={black, line width=1.0pt},") + A(" tth/.style={black!55, line width=0.4pt},") + A(" lbl/.style={font=\\scriptsize},") + A(" dlbl/.style={font=\\scriptsize\\bfseries, text=black},") + A(" cut/.style={red!80!black, line width=1.3pt},") + A(" cutlbl/.style={font=\\tiny, text=red!75!black}]") + + def pt(name): + x, y = pos[name] + return f"({x:.3f},{y:.3f})" + + # annular cycle + cyc = "--".join(pt(f"a{k}") for k in range(n)) + "--cycle" + A(f"\\draw[cyc] {cyc};") + # spokes + for i in graph.up_edges: + A(f"\\draw[tth] {pt(f'u{i}')}--{pt(f'a{i}')} {pt(f'u{i}')}--{pt(f'a{(i+1)%n}')};") + for i in graph.singleton_down_edges: + A(f"\\draw[tth] {pt(f'd{i}')}--{pt(f'a{i}')} {pt(f'd{i}')}--{pt(f'a{(i+1)%n}')};") + for (i, j) in graph.bites: + apex = f"p{i}_{j}" + for e in (i, j): + A(f"\\draw[tth] {pt(apex)}--{pt(f'a{e}')} {pt(apex)}--{pt(f'a{(e+1)%n}')};") + # vertices + for k in range(n): + A(f"\\node[ann] at {pt(f'a{k}')} {{}};") + for i in graph.up_edges: + A(f"\\node[upv] at {pt(f'u{i}')} {{}};") + for i in graph.singleton_down_edges: + A(f"\\node[downv] at {pt(f'd{i}')} {{}};") + for (i, j) in sorted(graph.bites): + A(f"\\node[bitev] at {pt(f'p{i}_{j}')} {{}};") + + # walk-depth labels: placed along the spoke from apex toward the edge mid + if depth is not None: + for edge in range(n): + apex = graph.apex_of_edge(edge) + ax, ay = pos[apex] + mx, my = _edge_midpoint(pos, graph, edge) + f = 0.5 + lx, ly = ax + f * (mx - ax), ay + f * (my - ay) + A(f"\\node[dlbl] at ({lx:.3f},{ly:.3f}) {{{depth[edge]}}};") + + # cut marks: a short red slit across the duplicated annular vertex + if cuts: + for c in cuts: + if c.vertex is None: + continue + vx, vy = pos[f"a{c.vertex}"] + rad = math.atan2(vy, vx) + dx, dy = 0.16 * math.cos(rad), 0.16 * math.sin(rad) + A(f"\\draw[cut] ({vx-dx:.3f},{vy-dy:.3f})--({vx+dx:.3f},{vy+dy:.3f});") + lx, ly = vx + 0.30 * math.cos(rad), vy + 0.30 * math.sin(rad) + A(f"\\node[cutlbl] at ({lx:.3f},{ly:.3f}) {{cut {c.order+1}}};") + + if entry_edge is not None: + ex, ey = pos[graph.apex_of_edge(entry_edge)] + rad = math.atan2(ey, ex) + tx, ty = ex + 0.34 * math.cos(rad), ey + 0.34 * math.sin(rad) + A(f"\\node[lbl, text=blue!60!black] at ({tx:.3f},{ty:.3f}) {{entry}};") + + A("\\end{tikzpicture}") + return "\n".join(L) + + +# --------------------------------------------------------------------------- +# Worked example and CLI. +# --------------------------------------------------------------------------- + +def worked_example() -> FullMedialTireGraph: + """A clean 8-tooth piece: one bite (0,4), three down singletons 1,2,3 in its + gap, three up teeth 5,6,7 in the root face.""" + return FullMedialTireGraph(n=8, tooth_word="DDDDDUUU", bites=frozenset({(0, 4)})) + + +def _check(graph: FullMedialTireGraph) -> None: + assert not has_incident_bite(graph.bites, graph.n), "bite uses incident edges" + assert satisfies_bite_face_condition(graph.tooth_word, graph.bites), \ + "violates the bite-face condition" + assert graph.tooth_word.count("U") >= 3, "fewer than three up teeth" + + +def _describe(graph, depth, cuts) -> str: + lines = ["edge type walk-depth"] + for e in range(graph.n): + t = graph.tooth_word[e] + kind = {"U": "up"}.get(t, "down") + if door_bite(graph, e) is not None: + kind = "bite" + lines.append(f" e{e} {kind:<5} {depth[e]}") + lines.append("cuts (in order):") + for c in cuts: + f = "root" if c.face is None else f"bite{c.face}" + lines.append(f" cut {c.order+1}: duplicate a{c.vertex} " + f"(closing tooth e{c.closing_tooth} of {f})") + return "\n".join(lines) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--entry", default="u5", + help="entry up tooth, as an edge index or apex name like u5") + parser.add_argument("--start-depth", type=int, default=0) + parser.add_argument("--tikz", choices=["plain", "labelled", "both"], + help="emit TikZ for the worked example") + args = parser.parse_args() + + entry = args.entry + edge = int(entry[1:]) if isinstance(entry, str) and entry.startswith("u") else int(entry) + + graph = worked_example() + _check(graph) + depth, cuts = label_and_cut(graph, edge, start_depth=args.start_depth) + + if args.tikz == "plain": + print(to_tikz(graph)) + elif args.tikz == "labelled": + print(to_tikz(graph, depth=depth, cuts=cuts, entry_edge=edge)) + elif args.tikz == "both": + print("% --- plain ---") + print(to_tikz(graph)) + print("% --- labelled + cut ---") + print(to_tikz(graph, depth=depth, cuts=cuts, entry_edge=edge)) + else: + print(f"worked example: n={graph.n} word={graph.tooth_word} " + f"bites={sorted(graph.bites)} entry=e{edge}") + print(_describe(graph, depth, cuts)) + + +if __name__ == "__main__": + main() diff --git a/papers/medial_tire_cuts/paper.aux b/papers/medial_tire_cuts/paper.aux index 030da93..572f5be 100644 --- a/papers/medial_tire_cuts/paper.aux +++ b/papers/medial_tire_cuts/paper.aux @@ -5,11 +5,16 @@ \@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Introduction}}{1}{}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\tocsection {}{2}{Cutting a full medial tire graph}}{1}{}\protected@file@percent } \newlabel{def:walk-depth-cut}{{2.1}{1}} +\citation{bauerfeld-medial-tire} \bibcite{bauerfeld-medial-tire}{1} \newlabel{tocindent-1}{0pt} \newlabel{tocindent0}{12.7778pt} \newlabel{tocindent1}{17.77782pt} \newlabel{tocindent2}{0pt} \newlabel{tocindent3}{0pt} +\newlabel{rem:closing-tooth}{{2.2}{2}} +\newlabel{ex:worked-cut}{{2.3}{2}} \@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{2}{}\protected@file@percent } -\gdef \@abspage@last{2} +\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces A full medial tire graph (left) and its walk-depth labelling and cut (right), from Example\nonbreakingspace 2.3\hbox {}. Black vertices are the annular medial vertices of the cycle $A(T)$; blue vertices are up-tooth apexes, red vertices are down-tooth apexes, and the larger red vertex is the shared apex of the bite on annular edges $0$ and $4$. On the right, each tooth carries its walk depth, and the two red slits mark the cuts: \emph {cut\nonbreakingspace 1} duplicates $a_5$ as the root-face traversal closes, and \emph {cut\nonbreakingspace 2} duplicates $a_1$ as the bite's inner-gap face closes. After the cuts the only bounded faces are the eight teeth.}}{3}{}\protected@file@percent } +\newlabel{fig:worked-cut}{{1}{3}} +\gdef \@abspage@last{3} diff --git a/papers/medial_tire_cuts/paper.log b/papers/medial_tire_cuts/paper.log index 1c12057..f9e67be 100644 --- a/papers/medial_tire_cuts/paper.log +++ b/papers/medial_tire_cuts/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) 14 JUN 2026 21:38 +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 14 JUN 2026 21:56 entering extended mode restricted \write18 enabled. %&-line parsing enabled. @@ -495,30 +495,36 @@ File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv e )) [1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] -[2] (./paper.aux) ) + +LaTeX Warning: `h' float specifier changed to `ht'. + +[2] [3] (./paper.aux) ) Here is how much of TeX's memory you used: - 13526 strings out of 478268 - 270009 string characters out of 5846347 - 533560 words of memory out of 5000000 - 31357 multiletter control sequences out of 15000+600000 - 476571 words of font info for 56 fonts, out of 8000000 for 9000 + 13647 strings out of 478268 + 272595 string characters out of 5846347 + 554433 words of memory out of 5000000 + 31476 multiletter control sequences out of 15000+600000 + 477049 words of font info for 58 fonts, out of 8000000 for 9000 1302 hyphenation exceptions out of 8191 - 84i,6n,89p,414b,305s stack positions out of 10000i,1000n,20000p,200000b,200000s - -Output written on paper.pdf (2 pages, 130843 bytes). + 84i,9n,89p,936b,704s stack positions out of 10000i,1000n,20000p,200000b,200000s + +Output written on paper.pdf (3 pages, 172798 bytes). PDF statistics: - 64 PDF objects out of 1000 (max. 8388607) - 39 compressed objects within 1 object stream + 82 PDF objects out of 1000 (max. 8388607) + 50 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) diff --git a/papers/medial_tire_cuts/paper.pdf b/papers/medial_tire_cuts/paper.pdf index d28ac40..a4de8e9 100644 Binary files a/papers/medial_tire_cuts/paper.pdf and b/papers/medial_tire_cuts/paper.pdf differ diff --git a/papers/medial_tire_cuts/paper.tex b/papers/medial_tire_cuts/paper.tex index a782683..5c18ac6 100644 --- a/papers/medial_tire_cuts/paper.tex +++ b/papers/medial_tire_cuts/paper.tex @@ -115,6 +115,152 @@ cuts as follows. \end{enumerate} \end{definition} +\begin{remark}[Closing tooth of a descended face] +\label{rem:closing-tooth} +For the entry face the traversal of step (2) closes by returning to the +entry tooth, so the cut of step (3) duplicates the annular vertex shared +by the last tooth and the entry tooth. For a face $F$ entered in step +(5), the traversal instead closes upon reaching an already-labelled +tooth: the other tooth of the bite through which $F$ was entered. In +both cases the cut of step (3) duplicates the annular vertex shared by +the last newly labelled tooth and this \emph{closing tooth}. Since both +teeth of a bite are labelled while traversing its parent face, every +descended face closes on such a tooth. +\end{remark} + +\begin{example}[A worked walk-depth labelling and cut] +\label{ex:worked-cut} +Figure~\ref{fig:worked-cut} shows a full medial tire graph with annular +cycle of length $8$, generated by the full medial tire generator +of~\cite{bauerfeld-medial-tire}. Its eight teeth are: three up teeth on +the annular edges $5,6,7$ in the root face; one bite pairing the annular +edges $0$ and $4$; and three singleton down teeth on the annular edges +$1,2,3$ lying in that bite's inner-gap face. + +Take the up tooth on edge $5$ as the entry tooth, with walk depth $0$. +Its inner face is the root face, bounded by the teeth on edges +$5,6,7,0,4$ in clockwise order. Step (2) labels them +\[ + 5\mapsto 0,\quad 6\mapsto 1,\quad 7\mapsto 2,\quad + 0\mapsto 3,\quad 4\mapsto 4, +\] +and step (3) cuts by duplicating the annular vertex $a_5$ shared by the +last tooth (edge $4$) and the entry tooth (edge $5$). The highest-depth +bite tooth is now the one on edge $4$ (walk depth $4$); it is incident to +the still-unlabelled inner-gap face of the bite $(0,4)$. Entering that +face from edge $4$ toward its unlabelled neighbour, step (5) labels the +three down teeth +\[ + 3\mapsto 5,\quad 2\mapsto 6,\quad 1\mapsto 7, +\] +and closes on the already-labelled bite tooth of edge $0$, so step (3) +cuts by duplicating the annular vertex $a_1$ +(Remark~\ref{rem:closing-tooth}). All eight teeth are now labelled, and +the two cuts leave only the outer face and the eight teeth as +$3$-faces. The labelling and cuts are produced by the script +\texttt{experiments/medial\_tire\_cut\_labelling.py}. +\end{example} + +\begin{figure}[h] +\centering +\begin{tikzpicture}[scale=1.6, + ann/.style={circle, fill=black, inner sep=1.0pt}, + upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt}, + downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt}, + bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt}, + cyc/.style={black, line width=1.0pt}, + tth/.style={black!55, line width=0.4pt}, + lbl/.style={font=\scriptsize}, + dlbl/.style={font=\scriptsize\bfseries, text=black}, + cut/.style={red!80!black, line width=1.3pt}, + cutlbl/.style={font=\tiny, text=red!75!black}] +\draw[cyc] (0.000,1.000)--(0.707,0.707)--(1.000,0.000)--(0.707,-0.707)--(0.000,-1.000)--(-0.707,-0.707)--(-1.000,-0.000)--(-0.707,0.707)--cycle; +\draw[tth] (-1.349,-0.559)--(-0.707,-0.707) (-1.349,-0.559)--(-1.000,-0.000); +\draw[tth] (-1.349,0.559)--(-1.000,-0.000) (-1.349,0.559)--(-0.707,0.707); +\draw[tth] (-0.559,1.349)--(-0.707,0.707) (-0.559,1.349)--(0.000,1.000); +\draw[tth] (0.554,0.230)--(0.707,0.707) (0.554,0.230)--(1.000,0.000); +\draw[tth] (0.554,-0.230)--(1.000,0.000) (0.554,-0.230)--(0.707,-0.707); +\draw[tth] (0.230,-0.554)--(0.707,-0.707) (0.230,-0.554)--(0.000,-1.000); +\draw[tth] (0.000,-0.000)--(0.000,1.000) (0.000,-0.000)--(0.707,0.707); +\draw[tth] (0.000,-0.000)--(0.000,-1.000) (0.000,-0.000)--(-0.707,-0.707); +\node[ann] at (0.000,1.000) {}; +\node[ann] at (0.707,0.707) {}; +\node[ann] at (1.000,0.000) {}; +\node[ann] at (0.707,-0.707) {}; +\node[ann] at (0.000,-1.000) {}; +\node[ann] at (-0.707,-0.707) {}; +\node[ann] at (-1.000,-0.000) {}; +\node[ann] at (-0.707,0.707) {}; +\node[upv] at (-1.349,-0.559) {}; +\node[upv] at (-1.349,0.559) {}; +\node[upv] at (-0.559,1.349) {}; +\node[downv] at (0.554,0.230) {}; +\node[downv] at (0.554,-0.230) {}; +\node[downv] at (0.230,-0.554) {}; +\node[bitev] at (0.000,-0.000) {}; +\end{tikzpicture} +\qquad +\begin{tikzpicture}[scale=1.6, + ann/.style={circle, fill=black, inner sep=1.0pt}, + upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt}, + downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt}, + bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt}, + cyc/.style={black, line width=1.0pt}, + tth/.style={black!55, line width=0.4pt}, + lbl/.style={font=\scriptsize}, + dlbl/.style={font=\scriptsize\bfseries, text=black}, + cut/.style={red!80!black, line width=1.3pt}, + cutlbl/.style={font=\tiny, text=red!75!black}] +\draw[cyc] (0.000,1.000)--(0.707,0.707)--(1.000,0.000)--(0.707,-0.707)--(0.000,-1.000)--(-0.707,-0.707)--(-1.000,-0.000)--(-0.707,0.707)--cycle; +\draw[tth] (-1.349,-0.559)--(-0.707,-0.707) (-1.349,-0.559)--(-1.000,-0.000); +\draw[tth] (-1.349,0.559)--(-1.000,-0.000) (-1.349,0.559)--(-0.707,0.707); +\draw[tth] (-0.559,1.349)--(-0.707,0.707) (-0.559,1.349)--(0.000,1.000); +\draw[tth] (0.554,0.230)--(0.707,0.707) (0.554,0.230)--(1.000,0.000); +\draw[tth] (0.554,-0.230)--(1.000,0.000) (0.554,-0.230)--(0.707,-0.707); +\draw[tth] (0.230,-0.554)--(0.707,-0.707) (0.230,-0.554)--(0.000,-1.000); +\draw[tth] (0.000,-0.000)--(0.000,1.000) (0.000,-0.000)--(0.707,0.707); +\draw[tth] (0.000,-0.000)--(0.000,-1.000) (0.000,-0.000)--(-0.707,-0.707); +\node[ann] at (0.000,1.000) {}; +\node[ann] at (0.707,0.707) {}; +\node[ann] at (1.000,0.000) {}; +\node[ann] at (0.707,-0.707) {}; +\node[ann] at (0.000,-1.000) {}; +\node[ann] at (-0.707,-0.707) {}; +\node[ann] at (-1.000,-0.000) {}; +\node[ann] at (-0.707,0.707) {}; +\node[upv] at (-1.349,-0.559) {}; +\node[upv] at (-1.349,0.559) {}; +\node[upv] at (-0.559,1.349) {}; +\node[downv] at (0.554,0.230) {}; +\node[downv] at (0.554,-0.230) {}; +\node[downv] at (0.230,-0.554) {}; +\node[bitev] at (0.000,-0.000) {}; +\node[dlbl] at (0.177,0.427) {3}; +\node[dlbl] at (0.704,0.292) {7}; +\node[dlbl] at (0.704,-0.292) {6}; +\node[dlbl] at (0.292,-0.704) {5}; +\node[dlbl] at (-0.177,-0.427) {4}; +\node[dlbl] at (-1.101,-0.456) {0}; +\node[dlbl] at (-1.101,0.456) {1}; +\node[dlbl] at (-0.456,1.101) {2}; +\draw[cut] (-0.594,-0.594)--(-0.820,-0.820); +\node[cutlbl] at (-0.919,-0.919) {cut 1}; +\draw[cut] (0.594,0.594)--(0.820,0.820); +\node[cutlbl] at (0.919,0.919) {cut 2}; +\node[lbl, text=blue!60!black] at (-1.663,-0.689) {entry}; +\end{tikzpicture} +\caption{A full medial tire graph (left) and its walk-depth labelling and +cut (right), from Example~\ref{ex:worked-cut}. Black vertices are the +annular medial vertices of the cycle $A(T)$; blue vertices are up-tooth +apexes, red vertices are down-tooth apexes, and the larger red vertex is +the shared apex of the bite on annular edges $0$ and $4$. On the right, +each tooth carries its walk depth, and the two red slits mark the cuts: +\emph{cut~1} duplicates $a_5$ as the root-face traversal closes, and +\emph{cut~2} duplicates $a_1$ as the bite's inner-gap face closes. After +the cuts the only bounded faces are the eight teeth.} +\label{fig:worked-cut} +\end{figure} + \begin{thebibliography}{9} \bibitem{bauerfeld-medial-tire}