Add walk-depth labelling/cut script and worked example
New experiments/medial_tire_cut_labelling.py: takes a full medial tire graph and an entry up tooth and runs the walk-depth labelling-and-cut procedure, reusing the full medial tire generator's model and emitting TikZ. Add a generator-produced 8-tooth example to the paper (Figure 1, Example 2.3) showing the labelling and the two cuts, plus a remark fixing the cut's closing tooth for descended faces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||||
@@ -5,11 +5,16 @@
|
|||||||
\@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Introduction}}{1}{}\protected@file@percent }
|
\@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 }
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{2}{Cutting a full medial tire graph}}{1}{}\protected@file@percent }
|
||||||
\newlabel{def:walk-depth-cut}{{2.1}{1}}
|
\newlabel{def:walk-depth-cut}{{2.1}{1}}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
\bibcite{bauerfeld-medial-tire}{1}
|
\bibcite{bauerfeld-medial-tire}{1}
|
||||||
\newlabel{tocindent-1}{0pt}
|
\newlabel{tocindent-1}{0pt}
|
||||||
\newlabel{tocindent0}{12.7778pt}
|
\newlabel{tocindent0}{12.7778pt}
|
||||||
\newlabel{tocindent1}{17.77782pt}
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
\newlabel{tocindent2}{0pt}
|
\newlabel{tocindent2}{0pt}
|
||||||
\newlabel{tocindent3}{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 }
|
\@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}
|
||||||
|
|||||||
@@ -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
|
entering extended mode
|
||||||
restricted \write18 enabled.
|
restricted \write18 enabled.
|
||||||
%&-line parsing 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
|
e
|
||||||
))
|
))
|
||||||
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
[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:
|
Here is how much of TeX's memory you used:
|
||||||
13526 strings out of 478268
|
13647 strings out of 478268
|
||||||
270009 string characters out of 5846347
|
272595 string characters out of 5846347
|
||||||
533560 words of memory out of 5000000
|
554433 words of memory out of 5000000
|
||||||
31357 multiletter control sequences out of 15000+600000
|
31476 multiletter control sequences out of 15000+600000
|
||||||
476571 words of font info for 56 fonts, out of 8000000 for 9000
|
477049 words of font info for 58 fonts, out of 8000000 for 9000
|
||||||
1302 hyphenation exceptions out of 8191
|
1302 hyphenation exceptions out of 8191
|
||||||
84i,6n,89p,414b,305s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
84i,9n,89p,936b,704s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfo
|
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
|
||||||
nts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
msfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||||
ts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
sfonts/cm/cmbx7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
ts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfont
|
onts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
s/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/
|
onts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfo
|
||||||
cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/
|
nts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfont
|
||||||
cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cms
|
s/cm/cmr6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
|
||||||
s10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy
|
m/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/c
|
||||||
10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti1
|
mr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmss
|
||||||
0.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.
|
10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy1
|
||||||
pfb>
|
0.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10
|
||||||
Output written on paper.pdf (2 pages, 130843 bytes).
|
.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.p
|
||||||
|
fb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pf
|
||||||
|
b>
|
||||||
|
Output written on paper.pdf (3 pages, 172798 bytes).
|
||||||
PDF statistics:
|
PDF statistics:
|
||||||
64 PDF objects out of 1000 (max. 8388607)
|
82 PDF objects out of 1000 (max. 8388607)
|
||||||
39 compressed objects within 1 object stream
|
50 compressed objects within 1 object stream
|
||||||
0 named destinations out of 1000 (max. 500000)
|
0 named destinations out of 1000 (max. 500000)
|
||||||
13 words of extra memory for PDF output out of 10000 (max. 10000000)
|
13 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -115,6 +115,152 @@ cuts as follows.
|
|||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
\end{definition}
|
\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}
|
\begin{thebibliography}{9}
|
||||||
|
|
||||||
\bibitem{bauerfeld-medial-tire}
|
\bibitem{bauerfeld-medial-tire}
|
||||||
|
|||||||
Reference in New Issue
Block a user