Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b605931678 | |||
| 7554582056 | |||
| 192d97a31d | |||
| 4e92dde36e | |||
| 0a3d7b2615 | |||
| 367b5adc71 | |||
| 94d59ceaed | |||
| 24af5485d2 | |||
| ea1ab0b986 | |||
| c64c720e5a | |||
| 9d7cb7644e | |||
| a22ca4b888 | |||
| b4ddc7da8b | |||
| 291f7e98c7 |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 297 KiB |
@@ -0,0 +1,286 @@
|
|||||||
|
"""Draw the walk-depth labelling and cut of a medial tire decomposition.
|
||||||
|
|
||||||
|
Paper-graphics companion to ``run_medial_tire_cut_experiment.py``: it imports
|
||||||
|
``run_experiment`` from there, runs the pipeline on a random maximal planar
|
||||||
|
graph, and emits TikZ. By default it draws one ``tikzpicture`` (walk-depth
|
||||||
|
labels + cut slits) per recognised full medial tire graph, using ``to_tikz``
|
||||||
|
from ``medial_tire_cut_labelling``. With ``--whole`` it instead draws a
|
||||||
|
two-panel Figure 3 graphic: the source graph with its source highlighted and
|
||||||
|
the whole medial graph M(G) drawn with every medial vertex at the midpoint of
|
||||||
|
its source edge and labelled by that source edge, with the full BFS-level chain
|
||||||
|
shown and the currently computed walk-depth labels and cuts marked.
|
||||||
|
|
||||||
|
This script only renders; the experiment itself draws nothing. Run with the
|
||||||
|
repo venv (networkx): ``.venv/bin/python``.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 59 > panels.tex
|
||||||
|
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 59 --whole > whole.tex
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, _HERE)
|
||||||
|
|
||||||
|
from run_medial_tire_cut_experiment import run_experiment # noqa: E402
|
||||||
|
from medial_tire_cut_labelling import to_tikz # noqa: E402
|
||||||
|
from tire_realization_analysis import triangular_faces # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def tikz_panels(n: int, seed: int, scale: float = 1.6,
|
||||||
|
min_degree: int = 5, attempts: int = 1000) -> tuple[dict, list[str]]:
|
||||||
|
"""Run the experiment and return ``(result, panels)``, one TikZ panel per
|
||||||
|
recognised tread, each showing that tread's walk-depth labelling and cut."""
|
||||||
|
result = run_experiment(n=n, seed=seed, min_degree=min_degree, attempts=attempts)
|
||||||
|
panels = []
|
||||||
|
for d in sorted(result["results"]):
|
||||||
|
rec = result["results"][d]
|
||||||
|
panels.append(to_tikz(rec["g"], depth=rec["depth"], cuts=rec["cuts"],
|
||||||
|
entry_edge=rec["entry_edge"], scale=scale))
|
||||||
|
return result, panels
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Figure 3: the source graph and midpoint drawing of the whole medial graph.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _source_layout(G: nx.Graph) -> dict[int, tuple[float, float]]:
|
||||||
|
"""Straight-line planar layout for the source graph, normalised to the unit
|
||||||
|
box and reused by the medial drawing."""
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
outer = list(faces[0])
|
||||||
|
outer_set = set(outer)
|
||||||
|
raw = {}
|
||||||
|
for i, v in enumerate(outer):
|
||||||
|
angle = math.radians(90.0 - i * 360.0 / len(outer))
|
||||||
|
raw[v] = np.array([math.cos(angle), math.sin(angle)], dtype=float)
|
||||||
|
|
||||||
|
inner = [v for v in sorted(G.nodes()) if v not in outer_set]
|
||||||
|
if inner:
|
||||||
|
idx = {v: i for i, v in enumerate(inner)}
|
||||||
|
n = len(inner)
|
||||||
|
A = np.zeros((n, n))
|
||||||
|
bx = np.zeros(n)
|
||||||
|
by = np.zeros(n)
|
||||||
|
for i, v in enumerate(inner):
|
||||||
|
nbrs = list(G.neighbors(v))
|
||||||
|
A[i, i] = 1.0
|
||||||
|
for w in nbrs:
|
||||||
|
if w in idx:
|
||||||
|
A[i, idx[w]] -= 1.0 / len(nbrs)
|
||||||
|
else:
|
||||||
|
bx[i] += raw[w][0] / len(nbrs)
|
||||||
|
by[i] += raw[w][1] / len(nbrs)
|
||||||
|
xs = np.linalg.solve(A, bx)
|
||||||
|
ys = np.linalg.solve(A, by)
|
||||||
|
for v in inner:
|
||||||
|
raw[v] = np.array([xs[idx[v]], ys[idx[v]]], dtype=float)
|
||||||
|
|
||||||
|
pts = np.array([raw[v] for v in G.nodes()], dtype=float)
|
||||||
|
center = 0.5 * (pts.max(axis=0) + pts.min(axis=0))
|
||||||
|
span = float(max(*(pts.max(axis=0) - pts.min(axis=0)), 1.0))
|
||||||
|
return {
|
||||||
|
v: tuple((raw[v] - center) / span)
|
||||||
|
for v in G.nodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _edge_midpoint(pos: dict, edge) -> tuple[float, float]:
|
||||||
|
u, v = edge
|
||||||
|
ux, uy = pos[u]
|
||||||
|
vx, vy = pos[v]
|
||||||
|
return (0.5 * (ux + vx), 0.5 * (uy + vy))
|
||||||
|
|
||||||
|
|
||||||
|
def _edge_label(edge) -> str:
|
||||||
|
u, v = edge
|
||||||
|
return f"${u}\\!{{-}}\\!{v}$"
|
||||||
|
|
||||||
|
|
||||||
|
def _source_graph_tikz(result: dict, pos: dict, scale: float) -> str:
|
||||||
|
G, source = result["G"], result["source"]
|
||||||
|
L = []
|
||||||
|
A = L.append
|
||||||
|
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||||
|
A(" sedge/.style={black!50, line width=0.35pt},")
|
||||||
|
A(" sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},")
|
||||||
|
A(" srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]")
|
||||||
|
|
||||||
|
def pt(v):
|
||||||
|
x, y = pos[v]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
for u, v in sorted(G.edges()):
|
||||||
|
A(f"\\draw[sedge] {pt(u)}--{pt(v)};")
|
||||||
|
for v in sorted(G.nodes()):
|
||||||
|
style = "srcv" if v == source else "sv"
|
||||||
|
A(f"\\node[{style}] at {pt(v)} {{}};")
|
||||||
|
sx, sy = pos[source]
|
||||||
|
A(f"\\node[font=\\scriptsize, text=blue!70!black] at ({sx:.3f},{sy - 0.085:.3f}) {{source {source}}};")
|
||||||
|
A("\\end{tikzpicture}")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
def _medial_midpoint_tikz(result: dict, pos: dict, scale: float) -> str:
|
||||||
|
"""Draw M(G) with each medial vertex at the midpoint of its source edge.
|
||||||
|
Every medial vertex is labelled by its source edge; same-level source edges
|
||||||
|
show the BFS level-chain tooth layers, and interlevel source edges show the
|
||||||
|
annular layers. Currently computed tire walk-depth labels and cut labels
|
||||||
|
are overlaid without moving the medial vertices away from their source
|
||||||
|
edges."""
|
||||||
|
G, M = result["G"], result["M"]
|
||||||
|
levels = nx.single_source_shortest_path_length(G, result["source"])
|
||||||
|
medial_pos = {edge: _edge_midpoint(pos, edge) for edge in M.nodes()}
|
||||||
|
|
||||||
|
apex_roles = {}
|
||||||
|
apex_walks = {}
|
||||||
|
for r in result["labels"]:
|
||||||
|
apex_roles[r["apex"]] = r["role"]
|
||||||
|
apex_walks.setdefault(r["apex"], []).append(r["walk"])
|
||||||
|
|
||||||
|
cut_records = []
|
||||||
|
cut_number = 1
|
||||||
|
for c in result.get("cap_cuts", []):
|
||||||
|
cut_records.append((cut_number, c["medial_vertex"], "cap", c))
|
||||||
|
cut_number += 1
|
||||||
|
for d in sorted(result["results"]):
|
||||||
|
rec = result["results"][d]
|
||||||
|
g, bij = rec["g"], rec["bij"]
|
||||||
|
for c in rec["cuts"]:
|
||||||
|
if c.vertex is None:
|
||||||
|
continue
|
||||||
|
cut_records.append((cut_number, bij[f"a{c.vertex}"], d, c))
|
||||||
|
cut_number += 1
|
||||||
|
|
||||||
|
L = []
|
||||||
|
A = L.append
|
||||||
|
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||||
|
A(" base/.style={black!12, line width=0.25pt},")
|
||||||
|
A(" med/.style={black!38, line width=0.32pt},")
|
||||||
|
A(" annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},")
|
||||||
|
A(" levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},")
|
||||||
|
A(" levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},")
|
||||||
|
A(" levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},")
|
||||||
|
A(" knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},")
|
||||||
|
A(" elbl/.style={font=\\tiny, text=black!70, inner sep=0.2pt},")
|
||||||
|
A(" dlbl/.style={font=\\tiny\\bfseries, text=black, inner sep=0.5pt},")
|
||||||
|
A(" cut/.style={red!80!black, line width=1.0pt},")
|
||||||
|
A(" cutlbl/.style={font=\\tiny, text=red!75!black}]")
|
||||||
|
|
||||||
|
def pt_med(edge):
|
||||||
|
x, y = medial_pos[edge]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
def pt_src(v):
|
||||||
|
x, y = pos[v]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
for u, v in sorted(result["G"].edges()):
|
||||||
|
A(f"\\draw[base] {pt_src(u)}--{pt_src(v)};")
|
||||||
|
for u, v in M.edges():
|
||||||
|
A(f"\\draw[med] {pt_med(u)}--{pt_med(v)};")
|
||||||
|
|
||||||
|
def chain_style(edge):
|
||||||
|
u, v = edge
|
||||||
|
lu, lv = levels[u], levels[v]
|
||||||
|
if lu != lv:
|
||||||
|
return "annv"
|
||||||
|
if edge in apex_roles:
|
||||||
|
return "knownv"
|
||||||
|
return {1: "levone", 2: "levtwo", 3: "levthree"}.get(lu, "annv")
|
||||||
|
|
||||||
|
for mv in sorted(M.nodes()):
|
||||||
|
A(f"\\node[{chain_style(mv)}] at {pt_med(mv)} {{}};")
|
||||||
|
for mv in sorted(M.nodes()):
|
||||||
|
x, y = medial_pos[mv]
|
||||||
|
A(f"\\node[elbl] at ({x:.3f},{y:.3f}) [yshift=-4.8pt] {{{_edge_label(mv)}}};")
|
||||||
|
for mv in sorted(apex_walks):
|
||||||
|
x, y = medial_pos[mv]
|
||||||
|
label = ",".join(str(w) for w in sorted(apex_walks[mv]))
|
||||||
|
A(f"\\node[dlbl] at ({x:.3f},{y:.3f}) [yshift=5.0pt] {{{label}}};")
|
||||||
|
for number, mv, _d, _cut in cut_records:
|
||||||
|
u, v = mv
|
||||||
|
ux, uy = pos[u]
|
||||||
|
vx, vy = pos[v]
|
||||||
|
mx, my = medial_pos[mv]
|
||||||
|
ex, ey = vx - ux, vy - uy
|
||||||
|
length = math.hypot(ex, ey) or 1.0
|
||||||
|
dx, dy = -0.035 * ey / length, 0.035 * ex / length
|
||||||
|
A(f"\\draw[cut] ({mx - dx:.3f},{my - dy:.3f})--({mx + dx:.3f},{my + dy:.3f});")
|
||||||
|
A(f"\\node[cutlbl] at ({mx + 2.4 * dx:.3f},{my + 2.4 * dy:.3f}) {{cut {number}}};")
|
||||||
|
A("\\end{tikzpicture}")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
def medial_tikz(result: dict, scale: float = 7.0) -> str:
|
||||||
|
"""Two-panel TikZ for Figure 3: the source graph and the midpoint drawing of
|
||||||
|
its medial graph with all medial vertices labelled, plus the tire
|
||||||
|
walk-depth labels and cuts."""
|
||||||
|
pos = _source_layout(result["G"])
|
||||||
|
source = _source_graph_tikz(result, pos, scale=0.58 * scale)
|
||||||
|
medial = _medial_midpoint_tikz(result, pos, scale=scale)
|
||||||
|
return "\n".join([
|
||||||
|
"\\begin{tabular}{c}",
|
||||||
|
source,
|
||||||
|
"\\\\[-0.25ex]",
|
||||||
|
"{\\scriptsize source graph $G$}",
|
||||||
|
"\\\\[1.0ex]",
|
||||||
|
medial,
|
||||||
|
"\\\\[-0.25ex]",
|
||||||
|
"{\\scriptsize medial graph $M(G)$ at edge midpoints}",
|
||||||
|
"\\end{tabular}",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-n", type=int, default=20)
|
||||||
|
parser.add_argument("--seed", type=int, default=72)
|
||||||
|
parser.add_argument("--scale", type=float, default=1.6)
|
||||||
|
parser.add_argument("--min-degree", type=int, default=5,
|
||||||
|
help="reject random graphs below this minimum degree")
|
||||||
|
parser.add_argument("--attempts", type=int, default=1000,
|
||||||
|
help="number of consecutive seeds to try for --min-degree")
|
||||||
|
parser.add_argument("--whole", action="store_true",
|
||||||
|
help="draw the whole medial graph M(G) with all cuts, "
|
||||||
|
"instead of one panel per tread")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.whole:
|
||||||
|
result = run_experiment(n=args.n, seed=args.seed,
|
||||||
|
min_degree=args.min_degree, attempts=args.attempts)
|
||||||
|
treads = sorted(result["results"])
|
||||||
|
print(f"% whole medial graph: n={args.n} seed={args.seed} "
|
||||||
|
f"graph_seed={result['graph_seed']} min_degree={result['min_degree']} "
|
||||||
|
f"source={result['source']} recognised treads={treads} "
|
||||||
|
f"|M(G)|={result['M'].number_of_nodes()}")
|
||||||
|
print(medial_tikz(result, scale=args.scale if args.scale != 1.6 else 7.0))
|
||||||
|
return
|
||||||
|
|
||||||
|
result, panels = tikz_panels(args.n, args.seed, scale=args.scale,
|
||||||
|
min_degree=args.min_degree, attempts=args.attempts)
|
||||||
|
treads = sorted(result["results"])
|
||||||
|
print(f"% medial tire cut: n={args.n} seed={args.seed} "
|
||||||
|
f"graph_seed={result['graph_seed']} min_degree={result['min_degree']} "
|
||||||
|
f"source={result['source']} recognised treads={treads}")
|
||||||
|
if not panels:
|
||||||
|
print("% (no recognised full medial tire graphs for this graph)")
|
||||||
|
for d, panel in zip(treads, panels):
|
||||||
|
g = result["results"][d]["g"]
|
||||||
|
print(f"% --- tread {d}: |A(T)|={g.n} word={g.tooth_word} "
|
||||||
|
f"bites={sorted(g.bites)} ---")
|
||||||
|
print(panel)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,388 @@
|
|||||||
|
"""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 (or ascend) through bites, deepest first. The root
|
||||||
|
# face is ``None``, so we use a distinct sentinel for "no unlabelled face".
|
||||||
|
_MISSING = object()
|
||||||
|
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))),
|
||||||
|
_MISSING)
|
||||||
|
if target is not _MISSING:
|
||||||
|
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()
|
||||||
@@ -0,0 +1,765 @@
|
|||||||
|
"""Source-dual cut from a chained medial tire cut.
|
||||||
|
|
||||||
|
Companion to ``run_medial_tire_cut_experiment.py``. Where that script reports
|
||||||
|
the cut graph of M(G), this one takes the same chained walk-depth labelling and
|
||||||
|
cut and reads it off as a *source-dual* cut: the planar dual of the source
|
||||||
|
triangulation G with the cut edges removed, as drawn for
|
||||||
|
``seed59_min5_dual_cut_1.png``.
|
||||||
|
|
||||||
|
The dual of a plane triangulation G has one node per triangular face and one
|
||||||
|
edge per primal edge (joining the two faces that share it). Its faces are the
|
||||||
|
*vertices* of G, each bounded by ``deg(v)`` dual edges. A medial tire cut at an
|
||||||
|
annular medial vertex removes the dual edge of the corresponding primal edge;
|
||||||
|
the interesting quantity is how many of those removed (``missing``) dual edges
|
||||||
|
surround each dual face (vertex of G). For ``seed59`` at source 5 the maximum
|
||||||
|
is 3, around the degree-9 vertex 3.
|
||||||
|
|
||||||
|
The level source is chosen by deep embedding: pick a random face of G, take the
|
||||||
|
deep embedding G' relative to that face (subdividing every neutral face,
|
||||||
|
including the chosen one), and use the outer-cap vertex x* placed inside the
|
||||||
|
chosen face as the source. The whole dual cut is then read off G'.
|
||||||
|
|
||||||
|
Four chained entry points (broad to narrow control):
|
||||||
|
|
||||||
|
* ``random_dual_cut(n, ...)`` -- find a random maximal planar graph of a given
|
||||||
|
minimum degree, then defer to ``dual_cut_random_face``.
|
||||||
|
* ``dual_cut_random_face(G, ...)`` -- choose a random face, deep-embed
|
||||||
|
relative to it, and use the cap vertex as the source, then defer to
|
||||||
|
``dual_cut_random_entry``.
|
||||||
|
* ``dual_cut_random_entry(G', cap, ...)`` -- choose a random root entry
|
||||||
|
tooth, then defer to ``medial_tire_dual_cut``.
|
||||||
|
* ``medial_tire_dual_cut(G', source, entry_edge)`` -- the worker: chain the
|
||||||
|
walk-depth labelling/cut from the given root entry tooth and assemble the
|
||||||
|
source-dual cut.
|
||||||
|
|
||||||
|
Run with the repo venv (networkx; matplotlib only for ``--png``):
|
||||||
|
``.venv/bin/python``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_MTD = os.path.normpath(os.path.join(
|
||||||
|
_HERE, "..", "..",
|
||||||
|
"medial_tire_decompositions_of_plane_triangulations", "experiments"))
|
||||||
|
sys.path.insert(0, _MTD)
|
||||||
|
sys.path.insert(0, _HERE)
|
||||||
|
|
||||||
|
from tire_realization_analysis import ( # noqa: E402
|
||||||
|
ekey, extract_tread, medial_graph, medial_tire_facemodel,
|
||||||
|
recognise, triangular_faces,
|
||||||
|
)
|
||||||
|
from run_medial_tire_cut_experiment import ( # noqa: E402
|
||||||
|
_assemble_cut_graph, _cap_cut, _label_treads,
|
||||||
|
random_maximal_planar_min_degree,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Tread recognition and the source-dual graph.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _build_treads(faces, levels):
|
||||||
|
"""Recognise the full medial tire graph(s) of every BFS-level tread.
|
||||||
|
|
||||||
|
A tread depth whose annular frontier splits into several disjoint cycles
|
||||||
|
yields one tire per cycle. Returns ``(treads, skipped)`` where ``treads``
|
||||||
|
maps ``(depth, component)`` to the recognised ``(g, bij)`` and ``skipped``
|
||||||
|
lists ``(d, reason)`` for the depths that produced no tire.
|
||||||
|
"""
|
||||||
|
treads, skipped = {}, []
|
||||||
|
for d in range(max(levels.values())):
|
||||||
|
tread = extract_tread(faces, levels, d)
|
||||||
|
if tread is None:
|
||||||
|
skipped.append((d, "no tread faces"))
|
||||||
|
continue
|
||||||
|
if len(tread["up"]) < 3:
|
||||||
|
skipped.append((d, f"only {len(tread['up'])} up teeth"))
|
||||||
|
continue
|
||||||
|
tires = recognise(medial_tire_facemodel(tread["tread_faces"]), tread)
|
||||||
|
if not tires:
|
||||||
|
skipped.append((d, "no annular cycle recognised as a tire"))
|
||||||
|
continue
|
||||||
|
for c, gb in enumerate(tires):
|
||||||
|
treads[(d, c)] = gb
|
||||||
|
return treads, skipped
|
||||||
|
|
||||||
|
|
||||||
|
def root_entry_choices(G, source):
|
||||||
|
"""Edge indices of the root tread's up teeth -- the eligible entry teeth.
|
||||||
|
|
||||||
|
Empty when ``source`` induces no recognised root tread.
|
||||||
|
"""
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
levels = nx.single_source_shortest_path_length(G, source)
|
||||||
|
treads, _ = _build_treads(faces, levels)
|
||||||
|
if not treads:
|
||||||
|
return []
|
||||||
|
g, _bij = treads[min(treads)]
|
||||||
|
return sorted(g.up_edges)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Deep embedding (relative to a chosen face) and its outer-cap vertex.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _plane_depth(G, outer_cycle):
|
||||||
|
"""Plane depth of every vertex of ``G`` relative to ``outer_cycle``: the
|
||||||
|
graph distance to the nearest outer-cycle vertex (outer cycle = depth 0).
|
||||||
|
|
||||||
|
Mirrors ``plane_depth_sequencing.get_plane_depth_labelling`` -- attach a
|
||||||
|
temporary super-source to the outer cycle, BFS, and subtract one."""
|
||||||
|
tmp = G.copy()
|
||||||
|
s = max(G.nodes()) + 1
|
||||||
|
tmp.add_node(s)
|
||||||
|
tmp.add_edges_from((s, v) for v in outer_cycle)
|
||||||
|
dist = nx.single_source_shortest_path_length(tmp, s)
|
||||||
|
return {v: dist[v] - 1 for v in G.nodes()}
|
||||||
|
|
||||||
|
|
||||||
|
def deep_embedding(G, face):
|
||||||
|
"""Deep embedding of maximal planar ``G`` relative to triangular ``face``.
|
||||||
|
|
||||||
|
Networkx port of ``plane_depth_sequencing.extended_deep_embedding``: with
|
||||||
|
``face`` taken as the outer face, subdivide every *neutral* triangular face
|
||||||
|
(all three vertices at equal plane depth) -- including ``face`` itself -- by
|
||||||
|
inserting a new vertex adjacent to its three corners. The vertex inserted
|
||||||
|
inside ``face`` is the outer-cap vertex x* (depth -1); the rest sit one
|
||||||
|
level deeper than the face they cap.
|
||||||
|
|
||||||
|
Returns ``(G_prime, cap_vertex, depth)``.
|
||||||
|
"""
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
outer = frozenset(face)
|
||||||
|
depth = _plane_depth(G, face)
|
||||||
|
G_prime = G.copy()
|
||||||
|
nxt = max(G.nodes()) + 1
|
||||||
|
cap_vertex = None
|
||||||
|
for f in faces:
|
||||||
|
assert len(f) == 3, f"non-triangular face {f} (graph not maximal planar?)"
|
||||||
|
a, b, c = f
|
||||||
|
if depth[a] == depth[b] == depth[c]:
|
||||||
|
x = nxt
|
||||||
|
nxt += 1
|
||||||
|
G_prime.add_node(x)
|
||||||
|
G_prime.add_edges_from([(x, a), (x, b), (x, c)])
|
||||||
|
if frozenset(f) == outer:
|
||||||
|
cap_vertex = x
|
||||||
|
depth[x] = -1
|
||||||
|
else:
|
||||||
|
depth[x] = depth[a] + 1
|
||||||
|
if cap_vertex is None:
|
||||||
|
raise ValueError(f"face {face} is not a face of G")
|
||||||
|
return G_prime, cap_vertex, depth
|
||||||
|
|
||||||
|
|
||||||
|
def deep_embed_random_face(G, rng=None):
|
||||||
|
"""Pick a random triangular face of ``G`` and deep-embed relative to it.
|
||||||
|
|
||||||
|
Returns ``(G_prime, cap_vertex, face)``; ``cap_vertex`` is the outer-cap
|
||||||
|
vertex used as the level source."""
|
||||||
|
rng = rng or random.Random()
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
face = rng.choice(faces)
|
||||||
|
G_prime, cap, _depth = deep_embedding(G, face)
|
||||||
|
return G_prime, cap, face
|
||||||
|
|
||||||
|
|
||||||
|
def source_dual(G, faces):
|
||||||
|
"""The planar dual of triangulation ``G``: one node per face, one edge per
|
||||||
|
primal edge (tagged ``primal``). Faces are indexed as in ``faces``."""
|
||||||
|
edge_faces = defaultdict(list)
|
||||||
|
for fi, f in enumerate(faces):
|
||||||
|
for a, b in ((f[0], f[1]), (f[1], f[2]), (f[2], f[0])):
|
||||||
|
edge_faces[ekey(a, b)].append(fi)
|
||||||
|
D = nx.Graph()
|
||||||
|
D.add_nodes_from(range(len(faces)))
|
||||||
|
for e, fs in edge_faces.items():
|
||||||
|
if len(fs) == 2:
|
||||||
|
D.add_edge(fs[0], fs[1], primal=e)
|
||||||
|
return D
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cut_edges(results, cap_cuts):
|
||||||
|
"""Primal edges whose dual edge a *closing* cut removes: the cap cut plus
|
||||||
|
each tread's annular-vertex duplications."""
|
||||||
|
removed = set()
|
||||||
|
for c in cap_cuts or []:
|
||||||
|
removed.add(c["medial_vertex"])
|
||||||
|
for key in sorted(results):
|
||||||
|
bij = results[key]["bij"]
|
||||||
|
for c in results[key]["cuts"]:
|
||||||
|
if c.vertex is not None:
|
||||||
|
removed.add(bij[f"a{c.vertex}"])
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
def up_apex_cut_edges(results):
|
||||||
|
"""Primal edges whose dual edge the apex duplications remove: the apex
|
||||||
|
medial vertex of every (singleton) up tooth across all treads, except the
|
||||||
|
entry tooth of each tread (its apex is not duplicated)."""
|
||||||
|
removed = set()
|
||||||
|
for key in sorted(results):
|
||||||
|
g, bij = results[key]["g"], results[key]["bij"]
|
||||||
|
entry = results[key]["entry_edge"]
|
||||||
|
for i in g.up_edges:
|
||||||
|
if i == entry:
|
||||||
|
continue
|
||||||
|
removed.add(bij[f"u{i}"])
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
def removed_dual_edges(results, cap_cuts):
|
||||||
|
"""All primal edges whose dual edge the cut removes: the closing annular
|
||||||
|
cuts together with the up-tooth apex duplications."""
|
||||||
|
return annular_cut_edges(results, cap_cuts) | up_apex_cut_edges(results)
|
||||||
|
|
||||||
|
|
||||||
|
def dual_face_missing(G, removed):
|
||||||
|
"""For each dual face (vertex ``v`` of ``G``), the number of bounding dual
|
||||||
|
edges removed by the cut."""
|
||||||
|
return {v: sum(1 for w in G.neighbors(v) if ekey(v, w) in removed)
|
||||||
|
for v in G.nodes()}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# The four chained entry points.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def medial_tire_dual_cut(G, source, entry_edge):
|
||||||
|
"""Chain the walk-depth labelling/cut from root entry tooth ``entry_edge``
|
||||||
|
and assemble the source-dual cut of ``G`` at level ``source``.
|
||||||
|
|
||||||
|
``entry_edge`` must be an up tooth of the root (lowest recognised) tread;
|
||||||
|
see ``root_entry_choices``. Returns a structured result dict.
|
||||||
|
"""
|
||||||
|
faces, emb = triangular_faces(G)
|
||||||
|
M = medial_graph(G)
|
||||||
|
levels = nx.single_source_shortest_path_length(G, source)
|
||||||
|
treads, skipped = _build_treads(faces, levels)
|
||||||
|
if not treads:
|
||||||
|
raise ValueError(f"level source {source} induces no recognised tread")
|
||||||
|
|
||||||
|
g_root = treads[min(treads)][0]
|
||||||
|
if entry_edge not in g_root.up_edges:
|
||||||
|
raise ValueError(
|
||||||
|
f"entry edge {entry_edge} is not an up tooth of the root tread "
|
||||||
|
f"(choices: {sorted(g_root.up_edges)})")
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
_label_treads(treads, results, root_entry_edge=entry_edge)
|
||||||
|
cap_cuts = _cap_cut(G, emb, source, levels, results)
|
||||||
|
cut_graph, labels, warnings = _assemble_cut_graph(M, results, cap_cuts=cap_cuts)
|
||||||
|
|
||||||
|
dual = source_dual(G, faces)
|
||||||
|
annular = annular_cut_edges(results, cap_cuts)
|
||||||
|
apex = up_apex_cut_edges(results)
|
||||||
|
removed = annular | apex
|
||||||
|
missing = dual_face_missing(G, removed)
|
||||||
|
|
||||||
|
# The first entry: the medial vertex (primal edge) of the root tread's
|
||||||
|
# entry up-tooth apex. This apex is *not* duplicated, so it is the seam the
|
||||||
|
# chained walk starts from rather than a removed edge.
|
||||||
|
root = min(results)
|
||||||
|
entry_medial = results[root]["bij"][f"u{entry_edge}"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"G": G, "M": M, "source": source, "entry_edge": entry_edge,
|
||||||
|
"entry_medial_vertex": entry_medial,
|
||||||
|
"faces": faces, "outer_face": 0,
|
||||||
|
"levels": levels, "treads": treads, "skipped": skipped,
|
||||||
|
"results": results, "cap_cuts": cap_cuts, "cut_graph": cut_graph,
|
||||||
|
"labels": labels, "warnings": warnings,
|
||||||
|
"dual": dual, "removed_dual_edges": removed,
|
||||||
|
"annular_cut_edges": annular, "apex_cut_edges": apex,
|
||||||
|
"dual_face_missing": missing,
|
||||||
|
"max_missing": max(missing.values()) if missing else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def dual_cut_random_entry(G, source, rng=None):
|
||||||
|
"""Pick a random root entry tooth at ``source``, then ``medial_tire_dual_cut``."""
|
||||||
|
rng = rng or random.Random()
|
||||||
|
choices = root_entry_choices(G, source)
|
||||||
|
if not choices:
|
||||||
|
raise ValueError(f"level source {source} induces no recognised root tread")
|
||||||
|
return medial_tire_dual_cut(G, source, rng.choice(choices))
|
||||||
|
|
||||||
|
|
||||||
|
def dual_cut_random_face(G, rng=None):
|
||||||
|
"""Pick a random face of ``G``, deep-embed relative to it, and cut from the
|
||||||
|
resulting outer-cap vertex, then ``dual_cut_random_entry``.
|
||||||
|
|
||||||
|
Faces are tried in random order; the first whose cap vertex induces a
|
||||||
|
recognised root tread is used. The dual cut is then read off the deep
|
||||||
|
embedding ``G'`` (stored as ``result["G"]``); the original triangulation is
|
||||||
|
kept as ``result["base_graph"]``."""
|
||||||
|
rng = rng or random.Random()
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
order = list(faces)
|
||||||
|
rng.shuffle(order)
|
||||||
|
for face in order:
|
||||||
|
G_prime, cap, depth = deep_embedding(G, face)
|
||||||
|
if root_entry_choices(G_prime, cap):
|
||||||
|
result = dual_cut_random_entry(G_prime, cap, rng=rng)
|
||||||
|
result["base_graph"] = G
|
||||||
|
result["chosen_face"] = tuple(face)
|
||||||
|
result["cap_vertex"] = cap
|
||||||
|
result["deep_depth"] = depth
|
||||||
|
return result
|
||||||
|
raise ValueError("no face's cap vertex induces a recognised root tread")
|
||||||
|
|
||||||
|
|
||||||
|
def random_dual_cut(n=20, seed=0, rng=None, min_degree=5, flips=400, attempts=1000):
|
||||||
|
"""Find a random maximal planar graph of minimum degree ``min_degree``, then
|
||||||
|
``dual_cut_random_face``.
|
||||||
|
|
||||||
|
``seed`` drives the graph sample; ``rng`` (defaulting to ``Random(seed)``)
|
||||||
|
drives the random face, deep embedding, and entry choices, so the whole
|
||||||
|
pipeline is reproducible from ``(n, seed)``.
|
||||||
|
"""
|
||||||
|
rng = rng or random.Random(seed)
|
||||||
|
G, graph_seed = random_maximal_planar_min_degree(
|
||||||
|
n, seed, flips=flips, min_degree=min_degree, attempts=attempts)
|
||||||
|
result = dual_cut_random_face(G, rng=rng)
|
||||||
|
result["graph_seed"] = graph_seed
|
||||||
|
result["base_min_degree"] = min(dict(G.degree()).values())
|
||||||
|
result["min_degree"] = min(dict(result["G"].degree()).values())
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Reporting and (optional) rendering.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def summary(result):
|
||||||
|
G, missing = result["G"], result["dual_face_missing"]
|
||||||
|
removed = result["removed_dual_edges"]
|
||||||
|
hist = defaultdict(int)
|
||||||
|
for k in missing.values():
|
||||||
|
hist[k] += 1
|
||||||
|
base = result.get("base_graph")
|
||||||
|
lines = [
|
||||||
|
f"source-dual cut: n={G.number_of_nodes()} "
|
||||||
|
f"(deep embedding of base n="
|
||||||
|
f"{base.number_of_nodes() if base is not None else '?'}) "
|
||||||
|
f"graph_seed={result.get('graph_seed', '?')} "
|
||||||
|
f"min_degree={result.get('min_degree', min(dict(G.degree()).values()))}",
|
||||||
|
f"chosen face: {result.get('chosen_face', '?')} "
|
||||||
|
f"-> cap vertex x*={result.get('cap_vertex', result['source'])}",
|
||||||
|
f"level source: cap vertex {result['source']} "
|
||||||
|
f"root entry tooth: e{result['entry_edge']}",
|
||||||
|
f"recognised tires (depth.component): "
|
||||||
|
f"{[f'{d}.{c}' for d, c in sorted(result['treads'])]} "
|
||||||
|
f"skipped: {result['skipped']}",
|
||||||
|
f"removed source-dual edges ({len(removed)}): "
|
||||||
|
f"{len(result['annular_cut_edges'])} annular/cap + "
|
||||||
|
f"{len(result['apex_cut_edges'])} up-tooth apex",
|
||||||
|
f" annular/cap: {sorted(result['annular_cut_edges'])}",
|
||||||
|
f" up apexes: {sorted(result['apex_cut_edges'])}",
|
||||||
|
f"dual-face missing-edge histogram (count by #removed around the dual "
|
||||||
|
f"face): {dict(sorted(hist.items()))} max={result['max_missing']}",
|
||||||
|
]
|
||||||
|
for v in sorted(missing, key=lambda v: (-missing[v], v)):
|
||||||
|
if missing[v]:
|
||||||
|
inc = [ekey(v, w) for w in G.neighbors(v) if ekey(v, w) in removed]
|
||||||
|
lines.append(f" dual face v{v} (deg {G.degree(v)}): "
|
||||||
|
f"{missing[v]} missing -> {inc}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def _radial_source_layout(G, source, levels):
|
||||||
|
"""Concentric ('onion') layout rooted at the cap ``source``: radius grows
|
||||||
|
with BFS level so the depth rings are actual circles, and each ring's
|
||||||
|
angular order is inherited from its lower-level neighbours to keep the
|
||||||
|
nesting legible. This matches the cap-source construction, where the BFS
|
||||||
|
rings are exactly the plane-depth rings."""
|
||||||
|
import math
|
||||||
|
max_level = max(levels.values()) or 1
|
||||||
|
ring = defaultdict(list)
|
||||||
|
for v, d in levels.items():
|
||||||
|
ring[d].append(v)
|
||||||
|
angle = {source: 0.0}
|
||||||
|
pos = {source: (0.0, 0.0)}
|
||||||
|
for d in range(1, max_level + 1):
|
||||||
|
verts = ring[d]
|
||||||
|
prov = {}
|
||||||
|
for v in verts:
|
||||||
|
pa = [angle[w] for w in G.neighbors(v)
|
||||||
|
if levels.get(w) == d - 1 and w in angle]
|
||||||
|
if pa:
|
||||||
|
sx = sum(math.cos(a) for a in pa)
|
||||||
|
sy = sum(math.sin(a) for a in pa)
|
||||||
|
prov[v] = math.atan2(sy, sx)
|
||||||
|
else:
|
||||||
|
prov[v] = 0.0
|
||||||
|
verts.sort(key=lambda v: prov[v] % (2 * math.pi))
|
||||||
|
k = len(verts)
|
||||||
|
base = prov[verts[0]] if verts else 0.0
|
||||||
|
r = d / max_level
|
||||||
|
for i, v in enumerate(verts):
|
||||||
|
a = base + 2 * math.pi * i / k
|
||||||
|
angle[v] = a
|
||||||
|
pos[v] = (r * math.cos(a), r * math.sin(a))
|
||||||
|
return pos
|
||||||
|
|
||||||
|
|
||||||
|
def draw_png(result, path, scale=6.0):
|
||||||
|
"""Render the source-dual cut: dual nodes at face centroids, dual edges
|
||||||
|
drawn light gray where the cut removed them, labelled by missing count.
|
||||||
|
|
||||||
|
The source graph is laid out concentrically around the cap source so the
|
||||||
|
BFS/plane-depth rings read as nested circles."""
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
G, faces, dual = result["G"], result["faces"], result["dual"]
|
||||||
|
removed = result["removed_dual_edges"]
|
||||||
|
missing = result["dual_face_missing"]
|
||||||
|
source = result["source"]
|
||||||
|
entry_medial = result.get("entry_medial_vertex")
|
||||||
|
pos_v = _radial_source_layout(G, source, result["levels"])
|
||||||
|
|
||||||
|
def centroid(fi):
|
||||||
|
xs = [pos_v[u][0] for u in faces[fi]]
|
||||||
|
ys = [pos_v[u][1] for u in faces[fi]]
|
||||||
|
return (sum(xs) / 3.0, sum(ys) / 3.0)
|
||||||
|
|
||||||
|
pos = {fi: centroid(fi) for fi in dual.nodes()}
|
||||||
|
fig, ax = plt.subplots(figsize=(7.6, 7.6))
|
||||||
|
# primal (source) graph, faint, for orientation
|
||||||
|
for u, v in G.edges():
|
||||||
|
ax.plot([pos_v[u][0], pos_v[v][0]], [pos_v[u][1], pos_v[v][1]],
|
||||||
|
color="0.85", lw=0.5, zorder=0)
|
||||||
|
# the entry medial vertex = a primal edge; highlight that primal edge and
|
||||||
|
# the dual edge crossing it.
|
||||||
|
if entry_medial is not None:
|
||||||
|
eu, ev = entry_medial
|
||||||
|
ax.plot([pos_v[eu][0], pos_v[ev][0]], [pos_v[eu][1], pos_v[ev][1]],
|
||||||
|
color="#1b9e44", lw=2.6, zorder=2, solid_capstyle="round")
|
||||||
|
for u, v, data in dual.edges(data=True):
|
||||||
|
cut = data["primal"] in removed
|
||||||
|
is_entry = entry_medial is not None and data["primal"] == entry_medial
|
||||||
|
if is_entry:
|
||||||
|
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]],
|
||||||
|
color="#1b9e44", lw=2.6, zorder=4, solid_capstyle="round")
|
||||||
|
mx, my = (pos[u][0] + pos[v][0]) / 2, (pos[u][1] + pos[v][1]) / 2
|
||||||
|
ax.plot(mx, my, "*", ms=15, mfc="#1b9e44", mec="white",
|
||||||
|
mew=0.7, zorder=5)
|
||||||
|
ax.text(mx, my - 0.06, "entry", color="#1b9e44", fontsize=8,
|
||||||
|
fontweight="bold", ha="center", va="top", zorder=5)
|
||||||
|
else:
|
||||||
|
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]],
|
||||||
|
color="0.80" if cut else "0.25",
|
||||||
|
lw=1.0 if cut else 1.3,
|
||||||
|
linestyle=(0, (2, 2)) if cut else "solid", zorder=1)
|
||||||
|
for fi in dual.nodes():
|
||||||
|
x, y = pos[fi]
|
||||||
|
ax.plot(x, y, "o", ms=4, color="#3a6ea5", zorder=3)
|
||||||
|
# label each source-graph vertex by its id; the cap source is flagged.
|
||||||
|
for v in G.nodes():
|
||||||
|
x, y = pos_v[v]
|
||||||
|
is_src = v == source
|
||||||
|
ax.text(x, y, str(v),
|
||||||
|
color="#0b6", fontsize=8 if not is_src else 9,
|
||||||
|
fontweight="bold" if is_src else "normal",
|
||||||
|
ha="center", va="center", zorder=6,
|
||||||
|
bbox=dict(boxstyle="round,pad=0.12",
|
||||||
|
fc="#eafff2" if is_src else "white",
|
||||||
|
ec="#1b9e44" if is_src else "0.6", lw=0.7))
|
||||||
|
# missing-edge count, offset above-right of the vertex label.
|
||||||
|
m = missing[v]
|
||||||
|
if m:
|
||||||
|
ax.text(x + 0.045, y + 0.045, str(m), color="#b03030", fontsize=7,
|
||||||
|
ha="left", va="bottom", zorder=7,
|
||||||
|
bbox=dict(boxstyle="circle,pad=0.05", fc="white",
|
||||||
|
ec="#b03030", lw=0.6))
|
||||||
|
ax.set_title(f"source-dual cut (cap source {source}, entry "
|
||||||
|
f"e{result['entry_edge']} = medial vtx {entry_medial}); "
|
||||||
|
f"gray = edges missing after cuts\n"
|
||||||
|
f"green star = first entry medial vertex; red numbers = "
|
||||||
|
f"#missing dual edges around each dual face; "
|
||||||
|
f"max {result['max_missing']}", fontsize=9)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(path, dpi=150)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def _tire_coords(g, r_ann=1.0, r_up=1.46, r_down=0.60):
|
||||||
|
"""Annular/teeth coordinates for one tread, matching
|
||||||
|
``medial_tire_cut_labelling.to_tikz``: a_0 at the top, k increasing CW."""
|
||||||
|
import math
|
||||||
|
n = g.n
|
||||||
|
|
||||||
|
def ang(k):
|
||||||
|
return math.radians(90.0 - k * 360.0 / n)
|
||||||
|
|
||||||
|
def mid(i):
|
||||||
|
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 g.up_edges:
|
||||||
|
a = mid(i)
|
||||||
|
pos[f"u{i}"] = (r_up * math.cos(a), r_up * math.sin(a))
|
||||||
|
for i in g.singleton_down_edges:
|
||||||
|
a = mid(i)
|
||||||
|
pos[f"d{i}"] = (r_down * math.cos(a), r_down * math.sin(a))
|
||||||
|
for (i, j) in g.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 _draw_tread(ax, g, depth, cuts, entry_edge, title):
|
||||||
|
"""Draw one full medial tire cut on ``ax`` (annular cycle, teeth, walk-depth
|
||||||
|
labels, cut slits), mirroring ``medial_tire_cut_labelling.to_tikz``."""
|
||||||
|
import math
|
||||||
|
n = g.n
|
||||||
|
pos = _tire_coords(g)
|
||||||
|
|
||||||
|
def seg(a, b, **kw):
|
||||||
|
ax.plot([pos[a][0], pos[b][0]], [pos[a][1], pos[b][1]], **kw)
|
||||||
|
|
||||||
|
# annular cycle
|
||||||
|
xs = [pos[f"a{k}"][0] for k in range(n)] + [pos["a0"][0]]
|
||||||
|
ys = [pos[f"a{k}"][1] for k in range(n)] + [pos["a0"][1]]
|
||||||
|
ax.plot(xs, ys, color="black", lw=1.4, zorder=1)
|
||||||
|
# spokes (teeth)
|
||||||
|
for i in g.up_edges:
|
||||||
|
seg(f"u{i}", f"a{i}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
seg(f"u{i}", f"a{(i + 1) % n}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
for i in g.singleton_down_edges:
|
||||||
|
seg(f"d{i}", f"a{i}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
seg(f"d{i}", f"a{(i + 1) % n}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
for (i, j) in g.bites:
|
||||||
|
apex = f"p{i}_{j}"
|
||||||
|
for e in (i, j):
|
||||||
|
seg(apex, f"a{e}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
seg(apex, f"a{(e + 1) % n}", color="0.55", lw=0.6, zorder=1)
|
||||||
|
# vertices
|
||||||
|
for k in range(n):
|
||||||
|
ax.plot(*pos[f"a{k}"], "o", ms=3, color="black", zorder=3)
|
||||||
|
for i in g.up_edges:
|
||||||
|
ax.plot(*pos[f"u{i}"], "o", ms=5, mfc="#cfe0f3", mec="#3a6ea5", zorder=3)
|
||||||
|
for i in g.singleton_down_edges:
|
||||||
|
ax.plot(*pos[f"d{i}"], "o", ms=5, mfc="#f3cfcf", mec="#a53a3a", zorder=3)
|
||||||
|
for (i, j) in g.bites:
|
||||||
|
ax.plot(*pos[f"p{i}_{j}"], "o", ms=6, mfc="#e8a0a0", mec="#a53a3a", zorder=3)
|
||||||
|
# walk-depth labels along each spoke
|
||||||
|
if depth is not None:
|
||||||
|
for edge in range(n):
|
||||||
|
ax_, ay = pos[g.apex_of_edge(edge)]
|
||||||
|
a, b = pos[f"a{edge}"], pos[f"a{(edge + 1) % n}"]
|
||||||
|
mx, my = 0.5 * (a[0] + b[0]), 0.5 * (a[1] + b[1])
|
||||||
|
lx, ly = ax_ + 0.5 * (mx - ax_), ay + 0.5 * (my - ay)
|
||||||
|
ax.text(lx, ly, str(depth[edge]), fontsize=7, fontweight="bold",
|
||||||
|
ha="center", va="center", zorder=4)
|
||||||
|
# annular-vertex cut slits (radial)
|
||||||
|
for c in cuts or []:
|
||||||
|
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)
|
||||||
|
ax.plot([vx - dx, vx + dx], [vy - dy, vy + dy],
|
||||||
|
color="#cc2020", lw=2.0, zorder=5)
|
||||||
|
ax.text(vx + 0.34 * math.cos(rad), vy + 0.34 * math.sin(rad),
|
||||||
|
f"cut {c.order + 1}", fontsize=6, color="#cc2020",
|
||||||
|
ha="center", va="center", zorder=5)
|
||||||
|
# up-tooth apex duplications (slit tangential, across the apex marker);
|
||||||
|
# the entry tooth's apex is not duplicated
|
||||||
|
for i in g.up_edges:
|
||||||
|
if i == entry_edge:
|
||||||
|
continue
|
||||||
|
vx, vy = pos[f"u{i}"]
|
||||||
|
rad = math.atan2(vy, vx)
|
||||||
|
tx, ty = -math.sin(rad), math.cos(rad) # tangential
|
||||||
|
ax.plot([vx - 0.12 * tx, vx + 0.12 * tx],
|
||||||
|
[vy - 0.12 * ty, vy + 0.12 * ty],
|
||||||
|
color="#cc2020", lw=2.0, zorder=6)
|
||||||
|
# entry marker
|
||||||
|
if entry_edge is not None:
|
||||||
|
ex, ey = pos[g.apex_of_edge(entry_edge)]
|
||||||
|
rad = math.atan2(ey, ex)
|
||||||
|
ax.text(ex + 0.34 * math.cos(rad), ey + 0.34 * math.sin(rad),
|
||||||
|
"entry", fontsize=6, color="#3a6ea5", ha="center", va="center")
|
||||||
|
ax.set_title(title, fontsize=8)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_tire_cuts_png(result, path):
|
||||||
|
"""Render every recognised tire's full medial tire cut, one panel each.
|
||||||
|
|
||||||
|
A tread depth with several disjoint annular cycles contributes one panel
|
||||||
|
per cycle, labelled ``tread d.c``."""
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
res = result["results"]
|
||||||
|
keys = sorted(res)
|
||||||
|
if not keys:
|
||||||
|
raise ValueError("no recognised tires to draw")
|
||||||
|
fig, axes = plt.subplots(1, len(keys), figsize=(5.2 * len(keys), 5.4))
|
||||||
|
if len(keys) == 1:
|
||||||
|
axes = [axes]
|
||||||
|
for ax, key in zip(axes, keys):
|
||||||
|
d, comp = key
|
||||||
|
rec = res[key]
|
||||||
|
g = rec["g"]
|
||||||
|
title = (f"tread {d}.{comp}: |A(T)|={g.n} word={g.tooth_word}\n"
|
||||||
|
f"bites={sorted(g.bites)} entry=e{rec['entry_edge']} "
|
||||||
|
f"start_depth={rec['start_depth']} cuts={len(rec['cuts'])}")
|
||||||
|
_draw_tread(ax, g, rec["depth"], rec["cuts"], rec["entry_edge"], title)
|
||||||
|
fig.suptitle(f"full medial tire cuts -- source {result['source']}, "
|
||||||
|
f"root entry e{result['entry_edge']}", fontsize=10)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(path, dpi=150)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_cap_png(result, path):
|
||||||
|
"""Render tread 0, the source cap: a wheel with the source at the hub, its
|
||||||
|
link cycle as the rim, the cap triangles (down teeth) filled, and the cap
|
||||||
|
cut marked. Tread 0 is skipped by tire recognition (a wheel has no up
|
||||||
|
teeth), so this draws the ``extract_tread`` roles directly."""
|
||||||
|
import math
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
G, source = result["G"], result["source"]
|
||||||
|
faces, emb = triangular_faces(G)
|
||||||
|
levels = nx.single_source_shortest_path_length(G, source)
|
||||||
|
tr = extract_tread(faces, levels, 0)
|
||||||
|
if tr is None:
|
||||||
|
raise ValueError("no tread-0 (cap) faces")
|
||||||
|
link = list(emb.neighbors_cw_order(source))
|
||||||
|
cap_cuts = {c["medial_vertex"] for c in result.get("cap_cuts", [])}
|
||||||
|
|
||||||
|
pos = {source: (0.0, 0.0)}
|
||||||
|
k = len(link)
|
||||||
|
for i, v in enumerate(link):
|
||||||
|
a = math.radians(90 - i * 360.0 / k)
|
||||||
|
pos[v] = (math.cos(a), math.sin(a))
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6.5, 6.8))
|
||||||
|
for f in tr["tread_faces"]:
|
||||||
|
if all(v in pos for v in f):
|
||||||
|
xy = [pos[v] for v in f]
|
||||||
|
ax.fill([p[0] for p in xy], [p[1] for p in xy],
|
||||||
|
color="#eef3fa", zorder=0)
|
||||||
|
|
||||||
|
def edge(u, v, **kw):
|
||||||
|
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]], **kw)
|
||||||
|
|
||||||
|
for u, v in tr["annular"]: # spokes (source -> link)
|
||||||
|
edge(u, v, color="0.45", lw=1.0, zorder=1)
|
||||||
|
for u, v in tr["down"]: # link cycle (down-tooth bases)
|
||||||
|
edge(u, v, color="black", lw=1.6, zorder=1)
|
||||||
|
|
||||||
|
ax.plot(*pos[source], "o", ms=11, mfc="#cfe0f3", mec="#3a6ea5", zorder=4)
|
||||||
|
ax.text(*pos[source], str(source), ha="center", va="center", fontsize=9,
|
||||||
|
fontweight="bold", color="#234", zorder=5)
|
||||||
|
for v in link:
|
||||||
|
ax.plot(*pos[v], "o", ms=9, mfc="white", mec="black", zorder=4)
|
||||||
|
x, y = pos[v]
|
||||||
|
ax.text(x * 1.13, y * 1.13, str(v), ha="center", va="center", fontsize=9)
|
||||||
|
|
||||||
|
for u, v in list(tr["annular"]) + list(tr["down"]):
|
||||||
|
mx, my = (pos[u][0] + pos[v][0]) / 2, (pos[u][1] + pos[v][1]) / 2
|
||||||
|
cut = ekey(u, v) in cap_cuts
|
||||||
|
ax.plot(mx, my, "s", ms=5, mfc=("#cc2020" if cut else "#888"),
|
||||||
|
mec="none", zorder=3)
|
||||||
|
if cut:
|
||||||
|
dx, dy = pos[v][0] - pos[u][0], pos[v][1] - pos[u][1]
|
||||||
|
L = math.hypot(dx, dy) or 1.0
|
||||||
|
px, py = -dy / L * 0.13, dx / L * 0.13
|
||||||
|
ax.plot([mx - px, mx + px], [my - py, my + py],
|
||||||
|
color="#cc2020", lw=2.2, zorder=5)
|
||||||
|
ax.text(mx + 0.12, my, "cap cut", color="#cc2020", fontsize=7,
|
||||||
|
va="center")
|
||||||
|
|
||||||
|
ax.set_title(f"tread 0 (source cap) -- source {source}, link {link}\n"
|
||||||
|
f"{len(tr['tread_faces'])} cap triangles; no up teeth (skipped); "
|
||||||
|
f"down teeth = link cycle", fontsize=8)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
ax.set_xlim(-1.4, 1.4)
|
||||||
|
ax.set_ylim(-1.4, 1.4)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(path, dpi=150)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-n", type=int, default=20, help="number of vertices")
|
||||||
|
parser.add_argument("--seed", type=int, default=0, help="graph sample seed")
|
||||||
|
parser.add_argument("--min-degree", type=int, default=5)
|
||||||
|
parser.add_argument("--face", type=str, default=None,
|
||||||
|
help="fix the chosen face as 'a,b,c' (default: random "
|
||||||
|
"via rng); the deep embedding's cap vertex is the "
|
||||||
|
"source")
|
||||||
|
parser.add_argument("--entry", type=int, default=None,
|
||||||
|
help="fix the root entry tooth (requires --face)")
|
||||||
|
parser.add_argument("--png", metavar="PATH", help="render the dual cut to PNG")
|
||||||
|
parser.add_argument("--tire-png", metavar="PATH",
|
||||||
|
help="render each full medial tire cut to PNG")
|
||||||
|
parser.add_argument("--cap-png", metavar="PATH",
|
||||||
|
help="render tread 0 (the source cap) to PNG")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
rng = random.Random(args.seed)
|
||||||
|
if args.face is not None:
|
||||||
|
G, graph_seed = random_maximal_planar_min_degree(
|
||||||
|
args.n, args.seed, min_degree=args.min_degree)
|
||||||
|
face = tuple(int(x) for x in args.face.split(","))
|
||||||
|
G_prime, cap, depth = deep_embedding(G, face)
|
||||||
|
if args.entry is not None:
|
||||||
|
result = medial_tire_dual_cut(G_prime, cap, args.entry)
|
||||||
|
else:
|
||||||
|
result = dual_cut_random_entry(G_prime, cap, rng=rng)
|
||||||
|
result["base_graph"] = G
|
||||||
|
result["chosen_face"] = face
|
||||||
|
result["cap_vertex"] = cap
|
||||||
|
result["deep_depth"] = depth
|
||||||
|
result["graph_seed"] = graph_seed
|
||||||
|
result["base_min_degree"] = min(dict(G.degree()).values())
|
||||||
|
result["min_degree"] = min(dict(G_prime.degree()).values())
|
||||||
|
else:
|
||||||
|
result = random_dual_cut(n=args.n, seed=args.seed,
|
||||||
|
rng=rng, min_degree=args.min_degree)
|
||||||
|
|
||||||
|
print(summary(result))
|
||||||
|
if args.png:
|
||||||
|
draw_png(result, args.png)
|
||||||
|
print(f"wrote {args.png}")
|
||||||
|
if args.tire_png:
|
||||||
|
draw_tire_cuts_png(result, args.tire_png)
|
||||||
|
print(f"wrote {args.tire_png}")
|
||||||
|
if args.cap_png:
|
||||||
|
draw_cap_png(result, args.cap_png)
|
||||||
|
print(f"wrote {args.cap_png}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
"""Medial tire cut experiment.
|
||||||
|
|
||||||
|
End-to-end experiment for the *Medial Tire Cuts* paper:
|
||||||
|
|
||||||
|
1. Generate a random maximal planar graph G on n vertices (stacked seed plus
|
||||||
|
random diagonal flips; ``random_maximal_planar`` from the medial tire
|
||||||
|
decompositions experiments), optionally rejecting samples below a requested
|
||||||
|
minimum degree.
|
||||||
|
2. Build its medial graph M(G).
|
||||||
|
3. Take the nested tire decomposition at one random vertex level source: the
|
||||||
|
BFS-level treads, each realized as a FullMedialTireGraph.
|
||||||
|
4. Walk-depth label and cut each full medial tire graph, chaining the labels
|
||||||
|
down the tire tree, and assemble one final cut graph of M(G) with a global
|
||||||
|
label map.
|
||||||
|
|
||||||
|
This script produces *data* (the final cut graph and its labels); it draws
|
||||||
|
nothing. Anything for the paper (figures) lives in a separate script that
|
||||||
|
imports ``run_experiment`` from here.
|
||||||
|
|
||||||
|
Chaining rule (walk depths across the tire tree).
|
||||||
|
* The root tread (no recognised parent) is entered at an arbitrary up tooth
|
||||||
|
with walk depth 0.
|
||||||
|
* A child tread is entered at the up tooth whose apex is the *same medial
|
||||||
|
vertex* as the parent's down tooth of lowest walk depth -- a parent down
|
||||||
|
tooth and the child up tooth glued to it across the shared level cycle are
|
||||||
|
the same medial vertex of M(G). The entry up tooth's walk depth is that
|
||||||
|
parent down tooth's depth + 1, and the walk increments locally from there.
|
||||||
|
* The source cap contributes one additional cut. It is placed at the
|
||||||
|
counter-clockwise source edge incident to the cap down tooth whose apex is
|
||||||
|
the root tread's entry up tooth.
|
||||||
|
|
||||||
|
Run with the repo venv (networkx + scipy): ``.venv/bin/python``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
# Reuse the realization pipeline from the medial tire decompositions paper, and
|
||||||
|
# the walk-depth labelling/cut from this paper's companion script.
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_MTD = os.path.normpath(os.path.join(
|
||||||
|
_HERE, "..", "..",
|
||||||
|
"medial_tire_decompositions_of_plane_triangulations", "experiments"))
|
||||||
|
sys.path.insert(0, _MTD)
|
||||||
|
sys.path.insert(0, _HERE)
|
||||||
|
|
||||||
|
from tire_realization_analysis import ( # noqa: E402
|
||||||
|
ekey, extract_tread, medial_graph, medial_tire_facemodel,
|
||||||
|
random_maximal_planar, recognise, triangular_faces,
|
||||||
|
)
|
||||||
|
from medial_tire_cut_labelling import door_bite, label_and_cut # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 4. Walk-depth labelling and cut, chained down the tire tree.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _apex_vertex(g, bij, edge):
|
||||||
|
"""The medial vertex that is the apex of the tooth on annular ``edge``."""
|
||||||
|
return bij[g.apex_of_edge(edge)]
|
||||||
|
|
||||||
|
|
||||||
|
def _label_treads(treads, results, root_entry_edge=None):
|
||||||
|
"""Fill ``results[(d, c)]`` with the walk-depth labelling and cuts for every
|
||||||
|
recognised tire ``c`` of every tread depth ``d``, chaining child entries to
|
||||||
|
parent down teeth.
|
||||||
|
|
||||||
|
``treads`` maps ``(depth, component)`` -> ``(g, bij)``; a tread depth may
|
||||||
|
carry several tires (one per disjoint annular cycle). The root tire
|
||||||
|
``(root_d, 0)`` is entered at ``root_entry_edge`` when given -- it must be
|
||||||
|
one of that tire's up teeth -- otherwise at an arbitrary up tooth. Each
|
||||||
|
other tire chains to whichever parent-depth down tooth (across all parent
|
||||||
|
tires) shares its apex, at the lowest parent walk depth.
|
||||||
|
"""
|
||||||
|
if not treads:
|
||||||
|
return
|
||||||
|
depths = sorted({k[0] for k in treads})
|
||||||
|
root_d = depths[0]
|
||||||
|
for d in depths:
|
||||||
|
# apex medial vertex -> child start depth, over all parent-depth tires
|
||||||
|
parent_down = {}
|
||||||
|
for pk in (k for k in treads if k[0] == d - 1):
|
||||||
|
pg, pbij = treads[pk]
|
||||||
|
pdepth = results[pk]["depth"]
|
||||||
|
for e in pg.down_edges:
|
||||||
|
apex = _apex_vertex(pg, pbij, e)
|
||||||
|
value = pdepth[e] + 1
|
||||||
|
if apex not in parent_down or value < parent_down[apex]:
|
||||||
|
parent_down[apex] = value
|
||||||
|
has_parent = any(k[0] == d - 1 for k in treads)
|
||||||
|
for key in sorted(k for k in treads if k[0] == d):
|
||||||
|
g, bij = treads[key]
|
||||||
|
if not has_parent:
|
||||||
|
if (key == (root_d, 0) and root_entry_edge is not None
|
||||||
|
and root_entry_edge in g.up_edges):
|
||||||
|
entry_edge, start_depth = root_entry_edge, 0
|
||||||
|
else:
|
||||||
|
entry_edge, start_depth = g.up_edges[0], 0 # arbitrary entry
|
||||||
|
else:
|
||||||
|
child_up_apex = {bij[f"u{m}"]: m for m in g.up_edges}
|
||||||
|
best = None
|
||||||
|
for apex, value in parent_down.items():
|
||||||
|
if apex in child_up_apex and (best is None or value < best[1]):
|
||||||
|
best = (child_up_apex[apex], value)
|
||||||
|
if best is not None: # chains to a parent down tooth
|
||||||
|
entry_edge, start_depth = best
|
||||||
|
else: # no shared apex (degenerate); root-style
|
||||||
|
entry_edge, start_depth = g.up_edges[0], 0
|
||||||
|
depth, cuts = label_and_cut(g, entry_edge, start_depth=start_depth)
|
||||||
|
results[key] = {"g": g, "bij": bij, "entry_edge": entry_edge,
|
||||||
|
"start_depth": start_depth, "depth": depth,
|
||||||
|
"cuts": cuts}
|
||||||
|
|
||||||
|
|
||||||
|
def _cap_cut(G, emb, source, levels, results):
|
||||||
|
"""The source-cap cut determined by the first recognised tread's entry.
|
||||||
|
|
||||||
|
If the root tread enters at an up tooth whose apex is the level-1 edge
|
||||||
|
``xy``, then ``xy`` is a down tooth of the source cap. Cut the
|
||||||
|
counter-clockwise source edge incident to that down tooth. The returned
|
||||||
|
record also stores the local neighbour split used to unzip the medial
|
||||||
|
vertex in the whole medial graph.
|
||||||
|
"""
|
||||||
|
if not results:
|
||||||
|
return []
|
||||||
|
|
||||||
|
root_depth = min(results)
|
||||||
|
rec = results[root_depth]
|
||||||
|
g, bij = rec["g"], rec["bij"]
|
||||||
|
x, y = _apex_vertex(g, bij, rec["entry_edge"])
|
||||||
|
if levels.get(x) != 1 or levels.get(y) != 1:
|
||||||
|
return []
|
||||||
|
|
||||||
|
order = list(emb.neighbors_cw_order(source))
|
||||||
|
if x not in order or y not in order:
|
||||||
|
return []
|
||||||
|
|
||||||
|
next_cw = {v: order[(i + 1) % len(order)] for i, v in enumerate(order)}
|
||||||
|
prev_cw = {v: order[(i - 1) % len(order)] for i, v in enumerate(order)}
|
||||||
|
if next_cw[x] == y:
|
||||||
|
cut_endpoint, other_endpoint = x, y
|
||||||
|
elif next_cw[y] == x:
|
||||||
|
cut_endpoint, other_endpoint = y, x
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
other_cap_endpoint = prev_cw[cut_endpoint]
|
||||||
|
mv = ekey(source, cut_endpoint)
|
||||||
|
return [{
|
||||||
|
"medial_vertex": mv,
|
||||||
|
"down_tooth": ekey(cut_endpoint, other_endpoint),
|
||||||
|
"neighbours_a": [
|
||||||
|
ekey(source, other_endpoint),
|
||||||
|
ekey(cut_endpoint, other_endpoint),
|
||||||
|
],
|
||||||
|
"neighbours_b": [
|
||||||
|
ekey(source, other_cap_endpoint),
|
||||||
|
ekey(cut_endpoint, other_cap_endpoint),
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Assemble one final cut graph of M(G) with a global label map.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _assemble_cut_graph(M, results, cap_cuts=None):
|
||||||
|
"""Apply every tread's cuts to M(G).
|
||||||
|
|
||||||
|
Each cut duplicates an annular medial vertex, splitting its four incident
|
||||||
|
medial edges along the slit between the two teeth meeting there: the tooth
|
||||||
|
on the previous annular edge (with that edge's far annular vertex) goes to
|
||||||
|
one copy, the tooth on the next annular edge to the other.
|
||||||
|
|
||||||
|
Returns ``(H, label_records, warnings)`` where ``H`` is the cut graph (a
|
||||||
|
networkx graph whose split vertices are keyed ``(medial_vertex, "A"/"B",
|
||||||
|
tread)``) and ``label_records`` lists every tooth's walk depth.
|
||||||
|
"""
|
||||||
|
# Per cut annular vertex: map each original neighbour -> which copy keeps it.
|
||||||
|
split = {} # medial_vertex -> {neighbour_medial_vertex: copy_node}
|
||||||
|
warnings = []
|
||||||
|
for i, c in enumerate(cap_cuts or []):
|
||||||
|
mv = c["medial_vertex"]
|
||||||
|
if mv in split:
|
||||||
|
warnings.append(f"cap cut at {mv} was already cut; skipped")
|
||||||
|
continue
|
||||||
|
copy_a = (mv, "A", f"cap{i}")
|
||||||
|
copy_b = (mv, "B", f"cap{i}")
|
||||||
|
split[mv] = {
|
||||||
|
**{v: copy_a for v in c["neighbours_a"]},
|
||||||
|
**{v: copy_b for v in c["neighbours_b"]},
|
||||||
|
}
|
||||||
|
for key in sorted(results):
|
||||||
|
td = key[0]
|
||||||
|
g, bij = results[key]["g"], results[key]["bij"]
|
||||||
|
n = g.n
|
||||||
|
for c in results[key]["cuts"]:
|
||||||
|
kk = c.vertex
|
||||||
|
if kk is None:
|
||||||
|
continue
|
||||||
|
mv = bij[f"a{kk}"]
|
||||||
|
if mv in split:
|
||||||
|
warnings.append(f"annular vertex a{kk} of tread {key} cut twice; "
|
||||||
|
f"second cut not applied")
|
||||||
|
continue
|
||||||
|
e_prev, e_next = (kk - 1) % n, kk
|
||||||
|
copy_a = (mv, "A", td)
|
||||||
|
copy_b = (mv, "B", td)
|
||||||
|
split[mv] = {
|
||||||
|
bij[f"a{(kk - 1) % n}"]: copy_a,
|
||||||
|
_apex_vertex(g, bij, e_prev): copy_a,
|
||||||
|
bij[f"a{(kk + 1) % n}"]: copy_b,
|
||||||
|
_apex_vertex(g, bij, e_next): copy_b,
|
||||||
|
}
|
||||||
|
|
||||||
|
def resolve(node, other):
|
||||||
|
return split[node][other] if node in split else node
|
||||||
|
|
||||||
|
H = nx.Graph()
|
||||||
|
H.add_nodes_from(v for v in M.nodes() if v not in split)
|
||||||
|
for v, copies in split.items():
|
||||||
|
H.add_nodes_from(set(copies.values()))
|
||||||
|
for u, v in M.edges():
|
||||||
|
H.add_edge(resolve(u, v), resolve(v, u))
|
||||||
|
|
||||||
|
label_records = []
|
||||||
|
for key in sorted(results):
|
||||||
|
td = key[0]
|
||||||
|
g, bij, depth = results[key]["g"], results[key]["bij"], results[key]["depth"]
|
||||||
|
for k in range(g.n):
|
||||||
|
role = ("up" if g.tooth_word[k] == "U"
|
||||||
|
else "bite" if door_bite(g, k) is not None else "down")
|
||||||
|
label_records.append({
|
||||||
|
"tread": td, "comp": key[1], "edge": k, "role": role,
|
||||||
|
"apex": _apex_vertex(g, bij, k), "walk": depth[k],
|
||||||
|
})
|
||||||
|
return H, label_records, warnings
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Driver.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def random_maximal_planar_min_degree(n: int, seed: int, flips: int = 400,
|
||||||
|
min_degree: int = 0,
|
||||||
|
attempts: int = 1000) -> tuple[nx.Graph, int]:
|
||||||
|
"""Generate a random maximal planar graph with minimum degree at least
|
||||||
|
``min_degree``. The returned seed is the actual sample seed used."""
|
||||||
|
if min_degree <= 0:
|
||||||
|
return random_maximal_planar(n, seed, flips=flips), seed
|
||||||
|
|
||||||
|
if min_degree >= 5:
|
||||||
|
plantri = os.path.normpath(os.path.join(_HERE, "..", "..", "..",
|
||||||
|
"plantri", "plantri"))
|
||||||
|
if os.path.exists(plantri):
|
||||||
|
data = subprocess.check_output(
|
||||||
|
[plantri, f"-m{min_degree}", "-g", str(n)],
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
graphs = [line for line in data.splitlines() if line]
|
||||||
|
if graphs:
|
||||||
|
G = nx.from_graph6_bytes(graphs[seed % len(graphs)])
|
||||||
|
return nx.convert_node_labels_to_integers(G), seed
|
||||||
|
|
||||||
|
for offset in range(attempts):
|
||||||
|
sample_seed = seed + offset
|
||||||
|
G = random_maximal_planar(n, sample_seed, flips=flips)
|
||||||
|
if min(dict(G.degree()).values()) >= min_degree:
|
||||||
|
return G, sample_seed
|
||||||
|
raise RuntimeError(
|
||||||
|
f"no random maximal planar graph on {n} vertices with "
|
||||||
|
f"minimum degree >= {min_degree} found in {attempts} attempts "
|
||||||
|
f"starting at seed {seed}")
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiment(n: int = 12, seed: int = 0, flips: int = 400,
|
||||||
|
min_degree: int = 5, attempts: int = 1000) -> dict:
|
||||||
|
"""Run the full pipeline and return a structured result.
|
||||||
|
|
||||||
|
Result keys: ``n, seed, G, M, source, treads`` (dict depth -> (g, bij)),
|
||||||
|
``results`` (dict depth -> labelling/cut record), ``skipped`` (list of
|
||||||
|
(depth, reason)), ``cut_graph`` (networkx graph), ``labels`` (list of tooth
|
||||||
|
records), ``warnings``.
|
||||||
|
"""
|
||||||
|
G, graph_seed = random_maximal_planar_min_degree(
|
||||||
|
n, seed, flips=flips, min_degree=min_degree, attempts=attempts)
|
||||||
|
faces, emb = triangular_faces(G)
|
||||||
|
M = medial_graph(G)
|
||||||
|
source = random.Random(f"source-{graph_seed}").choice(sorted(G.nodes()))
|
||||||
|
levels = nx.single_source_shortest_path_length(G, source)
|
||||||
|
|
||||||
|
treads, skipped = {}, []
|
||||||
|
for d in range(max(levels.values())):
|
||||||
|
tread = extract_tread(faces, levels, d)
|
||||||
|
if tread is None:
|
||||||
|
skipped.append((d, "no tread faces"))
|
||||||
|
continue
|
||||||
|
if len(tread["up"]) < 3:
|
||||||
|
skipped.append((d, f"only {len(tread['up'])} up teeth"))
|
||||||
|
continue
|
||||||
|
tires = recognise(medial_tire_facemodel(tread["tread_faces"]), tread)
|
||||||
|
if not tires:
|
||||||
|
skipped.append((d, "no annular cycle recognised as a tire"))
|
||||||
|
continue
|
||||||
|
for c, gb in enumerate(tires):
|
||||||
|
treads[(d, c)] = gb
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
_label_treads(treads, results)
|
||||||
|
cap_cuts = _cap_cut(G, emb, source, levels, results)
|
||||||
|
cut_graph, labels, warnings = _assemble_cut_graph(M, results, cap_cuts=cap_cuts)
|
||||||
|
return {
|
||||||
|
"n": n, "seed": seed, "graph_seed": graph_seed,
|
||||||
|
"min_degree": min(dict(G.degree()).values()),
|
||||||
|
"G": G, "M": M, "source": source,
|
||||||
|
"treads": treads, "results": results, "cap_cuts": cap_cuts,
|
||||||
|
"skipped": skipped,
|
||||||
|
"cut_graph": cut_graph, "labels": labels, "warnings": warnings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Serialization / reporting.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _vname(v) -> str:
|
||||||
|
"""Stable string name for a medial vertex (an edge key) or a split node."""
|
||||||
|
if isinstance(v, tuple) and len(v) == 3 and v[1] in ("A", "B"):
|
||||||
|
mv, side, d = v
|
||||||
|
return f"{mv[0]}-{mv[1]}#{side}@T{d}"
|
||||||
|
return f"{v[0]}-{v[1]}"
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(result: dict) -> dict:
|
||||||
|
res = result["results"]
|
||||||
|
treads_out = []
|
||||||
|
for key in sorted(res):
|
||||||
|
d, comp = key
|
||||||
|
g, bij = res[key]["g"], res[key]["bij"]
|
||||||
|
depth, cuts = res[key]["depth"], res[key]["cuts"]
|
||||||
|
teeth = [{
|
||||||
|
"edge": k,
|
||||||
|
"role": ("up" if g.tooth_word[k] == "U"
|
||||||
|
else "bite" if door_bite(g, k) is not None else "down"),
|
||||||
|
"apex": _vname(_apex_vertex(g, bij, k)),
|
||||||
|
"walk": depth[k],
|
||||||
|
} for k in range(g.n)]
|
||||||
|
treads_out.append({
|
||||||
|
"depth": d, "comp": comp, "n": g.n, "tooth_word": g.tooth_word,
|
||||||
|
"bites": sorted(list(b) for b in g.bites),
|
||||||
|
"entry_edge": res[key]["entry_edge"], "start_depth": res[key]["start_depth"],
|
||||||
|
"teeth": teeth,
|
||||||
|
"cuts": [{
|
||||||
|
"annular_index": c.vertex,
|
||||||
|
"annular_vertex": _vname(bij[f"a{c.vertex}"]),
|
||||||
|
"last_edge": c.last_tooth, "closing_edge": c.closing_tooth,
|
||||||
|
} for c in cuts],
|
||||||
|
})
|
||||||
|
H = result["cut_graph"]
|
||||||
|
return {
|
||||||
|
"n": result["n"], "seed": result["seed"],
|
||||||
|
"graph_seed": result["graph_seed"], "min_degree": result["min_degree"],
|
||||||
|
"source": result["source"],
|
||||||
|
"graph_edges": sorted([int(u), int(v)] for u, v in result["G"].edges()),
|
||||||
|
"medial_vertices": result["M"].number_of_nodes(),
|
||||||
|
"skipped": [[d, why] for d, why in result["skipped"]],
|
||||||
|
"cap_cuts": [{
|
||||||
|
"medial_vertex": _vname(c["medial_vertex"]),
|
||||||
|
"down_tooth": _vname(c["down_tooth"]),
|
||||||
|
} for c in result["cap_cuts"]],
|
||||||
|
"treads": treads_out,
|
||||||
|
"cut_graph": {
|
||||||
|
"nodes": sorted(_vname(v) for v in H.nodes()),
|
||||||
|
"edges": sorted([_vname(u), _vname(v)] for u, v in H.edges()),
|
||||||
|
},
|
||||||
|
"labels": [{
|
||||||
|
"tread": r["tread"], "comp": r.get("comp", 0),
|
||||||
|
"edge": r["edge"], "role": r["role"],
|
||||||
|
"apex": _vname(r["apex"]), "walk": r["walk"],
|
||||||
|
} for r in result["labels"]],
|
||||||
|
"warnings": result["warnings"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def summary(result: dict) -> str:
|
||||||
|
H, res = result["cut_graph"], result["results"]
|
||||||
|
lines = [
|
||||||
|
f"random maximal planar graph: n={result['n']} requested seed={result['seed']} "
|
||||||
|
f"graph seed={result['graph_seed']} "
|
||||||
|
f"({result['G'].number_of_edges()} edges, min degree {result['min_degree']})",
|
||||||
|
f"medial graph M(G): {result['M'].number_of_nodes()} vertices",
|
||||||
|
f"level source: vertex {result['source']}",
|
||||||
|
f"recognised tires (depth, component): {sorted(res)}",
|
||||||
|
f"skipped treads: {result['skipped']}",
|
||||||
|
]
|
||||||
|
for key in sorted(res):
|
||||||
|
d, comp = key
|
||||||
|
g = res[key]["g"]
|
||||||
|
ncuts = len(res[key]["cuts"])
|
||||||
|
lines.append(
|
||||||
|
f" tread {d}.{comp}: |A(T)|={g.n} word={g.tooth_word} "
|
||||||
|
f"bites={sorted(g.bites)} entry=e{res[key]['entry_edge']} "
|
||||||
|
f"start_depth={res[key]['start_depth']} cuts={ncuts}")
|
||||||
|
lines.append(
|
||||||
|
f"final cut graph: {H.number_of_nodes()} vertices, "
|
||||||
|
f"{H.number_of_edges()} edges, "
|
||||||
|
f"{len(result['cap_cuts']) + sum(len(r['cuts']) for r in res.values())} cuts total")
|
||||||
|
if result["warnings"]:
|
||||||
|
lines.append("warnings: " + "; ".join(result["warnings"]))
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-n", type=int, default=12, help="number of vertices")
|
||||||
|
parser.add_argument("--seed", type=int, default=0)
|
||||||
|
parser.add_argument("--flips", type=int, default=400,
|
||||||
|
help="number of random diagonal flips when building G")
|
||||||
|
parser.add_argument("--min-degree", type=int, default=5,
|
||||||
|
help="reject random graphs below this minimum degree")
|
||||||
|
parser.add_argument("--attempts", type=int, default=1000,
|
||||||
|
help="number of consecutive seeds to try for --min-degree")
|
||||||
|
parser.add_argument("--json", metavar="PATH",
|
||||||
|
help="write the full result as JSON to PATH")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = run_experiment(n=args.n, seed=args.seed, flips=args.flips,
|
||||||
|
min_degree=args.min_degree, attempts=args.attempts)
|
||||||
|
print(summary(result))
|
||||||
|
if args.json:
|
||||||
|
with open(args.json, "w") as fh:
|
||||||
|
json.dump(to_json(result), fh, indent=2)
|
||||||
|
print(f"wrote {args.json}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
\relax
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\@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}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\newlabel{rem:closing-tooth}{{2.2}{2}}
|
||||||
|
\newlabel{ex:worked-cut}{{2.3}{2}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{3}{Chaining across the tire tree}}{2}{}\protected@file@percent }
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\@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}}
|
||||||
|
\newlabel{rem:chaining-candidates}{{3.1}{3}}
|
||||||
|
\newlabel{ex:real-cut}{{3.2}{4}}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces The recognised tread $T_2$ of the medial tire decomposition of a random maximal planar graph on $20$ vertices (Example\nonbreakingspace 3.2\hbox {}), with its walk-depth labelling and cut. Black vertices are the annular medial vertices of $A(T)$; blue vertices are up-tooth apexes and red vertices down-tooth apexes, the larger red vertex being the shared apex of the bite on annular edges $2$ and $5$. Each tooth carries its walk depth; the red slits are the two cuts.}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:real-cut}{{2}{4}}
|
||||||
|
\bibcite{bauerfeld-medial-tire}{1}
|
||||||
|
\newlabel{tocindent-1}{0pt}
|
||||||
|
\newlabel{tocindent0}{12.7778pt}
|
||||||
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
|
\newlabel{tocindent2}{0pt}
|
||||||
|
\newlabel{tocindent3}{0pt}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces The source graph $G$ and the whole medial graph $M(G)$ of the minimum-degree-$5$ maximal planar graph on $20$ vertices generated by \texttt {plantri -m5} at seed $59$. The source vertex $5$ is highlighted in the top panel. In the bottom panel, each medial vertex is placed at the midpoint of its corresponding source edge and labelled by that edge. Black vertices come from source edges between consecutive levels; coloured vertices come from source edges within a single level of the chain. The red-highlighted vertices, walk-depth labels, and seven red slits are the computed source-cap cut and full-medial-tire labelling cuts for the recognised treads $T_1$ and $T_2$. Drawn by \texttt {experiments/draw\_medial\_tire\_cut.py} with \texttt {--whole --min-degree 5}.}}{5}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:whole-medial}{{3}{5}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{5}{}\protected@file@percent }
|
||||||
|
\gdef \@abspage@last{5}
|
||||||
@@ -0,0 +1,533 @@
|
|||||||
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 15 JUN 2026 01:07
|
||||||
|
entering extended mode
|
||||||
|
restricted \write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**paper.tex
|
||||||
|
(./paper.tex
|
||||||
|
LaTeX2e <2021-11-15> patch level 1
|
||||||
|
L3 programming layer <2022-02-24>
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
||||||
|
Document Class: amsart 2020/05/29 v2.20.6
|
||||||
|
\linespacing=\dimen138
|
||||||
|
\normalparindent=\dimen139
|
||||||
|
\normaltopskip=\skip47
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
|
||||||
|
Package: amsmath 2021/10/15 v2.17l AMS math features
|
||||||
|
\@mathmargin=\skip48
|
||||||
|
|
||||||
|
For additional information on amsmath, use the `?' option.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
|
||||||
|
Package: amstext 2021/08/26 v2.01 AMS text
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
|
||||||
|
File: amsgen.sty 1999/11/30 v2.0 generic functions
|
||||||
|
\@emptytoks=\toks16
|
||||||
|
\ex@=\dimen140
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
|
||||||
|
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
|
||||||
|
\pmbraise@=\dimen141
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
|
||||||
|
Package: amsopn 2021/08/26 v2.02 operator names
|
||||||
|
)
|
||||||
|
\inf@bad=\count185
|
||||||
|
LaTeX Info: Redefining \frac on input line 234.
|
||||||
|
\uproot@=\count186
|
||||||
|
\leftroot@=\count187
|
||||||
|
LaTeX Info: Redefining \overline on input line 399.
|
||||||
|
\classnum@=\count188
|
||||||
|
\DOTSCASE@=\count189
|
||||||
|
LaTeX Info: Redefining \ldots on input line 496.
|
||||||
|
LaTeX Info: Redefining \dots on input line 499.
|
||||||
|
LaTeX Info: Redefining \cdots on input line 620.
|
||||||
|
\Mathstrutbox@=\box50
|
||||||
|
\strutbox@=\box51
|
||||||
|
\big@size=\dimen142
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
|
||||||
|
\macc@depth=\count190
|
||||||
|
\c@MaxMatrixCols=\count191
|
||||||
|
\dotsspace@=\muskip16
|
||||||
|
\c@parentequation=\count192
|
||||||
|
\dspbrk@lvl=\count193
|
||||||
|
\tag@help=\toks17
|
||||||
|
\row@=\count194
|
||||||
|
\column@=\count195
|
||||||
|
\maxfields@=\count196
|
||||||
|
\andhelp@=\toks18
|
||||||
|
\eqnshift@=\dimen143
|
||||||
|
\alignsep@=\dimen144
|
||||||
|
\tagshift@=\dimen145
|
||||||
|
\tagwidth@=\dimen146
|
||||||
|
\totwidth@=\dimen147
|
||||||
|
\lineht@=\dimen148
|
||||||
|
\@envbody=\toks19
|
||||||
|
\multlinegap=\skip49
|
||||||
|
\multlinetaggap=\skip50
|
||||||
|
\mathdisplay@stack=\toks20
|
||||||
|
LaTeX Info: Redefining \[ on input line 2938.
|
||||||
|
LaTeX Info: Redefining \] on input line 2939.
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 397
|
||||||
|
.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
|
||||||
|
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
|
||||||
|
\symAMSa=\mathgroup4
|
||||||
|
\symAMSb=\mathgroup5
|
||||||
|
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
|
||||||
|
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
|
||||||
|
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
|
||||||
|
)
|
||||||
|
\copyins=\insert199
|
||||||
|
\abstractbox=\box52
|
||||||
|
\listisep=\skip51
|
||||||
|
\c@part=\count197
|
||||||
|
\c@section=\count198
|
||||||
|
\c@subsection=\count266
|
||||||
|
\c@subsubsection=\count267
|
||||||
|
\c@paragraph=\count268
|
||||||
|
\c@subparagraph=\count269
|
||||||
|
\c@figure=\count270
|
||||||
|
\c@table=\count271
|
||||||
|
\abovecaptionskip=\skip52
|
||||||
|
\belowcaptionskip=\skip53
|
||||||
|
\captionindent=\dimen149
|
||||||
|
\thm@style=\toks21
|
||||||
|
\thm@bodyfont=\toks22
|
||||||
|
\thm@headfont=\toks23
|
||||||
|
\thm@notefont=\toks24
|
||||||
|
\thm@headpunct=\toks25
|
||||||
|
\thm@preskip=\skip54
|
||||||
|
\thm@postskip=\skip55
|
||||||
|
\thm@headsep=\skip56
|
||||||
|
\dth@everypar=\toks26
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
|
||||||
|
Package: amssymb 2013/01/14 v3.01 AMS font symbols
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
|
||||||
|
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
|
||||||
|
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
|
||||||
|
\KV@toks@=\toks27
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
|
||||||
|
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
|
||||||
|
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
|
||||||
|
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||||
|
)
|
||||||
|
Package graphics Info: Driver file: pdftex.def on input line 107.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
|
||||||
|
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
|
||||||
|
))
|
||||||
|
\Gin@req@height=\dimen150
|
||||||
|
\Gin@req@width=\dimen151
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.te
|
||||||
|
x
|
||||||
|
\pgfutil@everybye=\toks28
|
||||||
|
\pgfutil@tempdima=\dimen152
|
||||||
|
\pgfutil@tempdimb=\dimen153
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-li
|
||||||
|
sts.tex))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def
|
||||||
|
\pgfutil@abb=\box53
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/pgf.revision.tex)
|
||||||
|
Package: pgfrcs 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
))
|
||||||
|
Package: pgf 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
|
||||||
|
Package: pgfsys 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
|
||||||
|
\pgfkeys@pathtoks=\toks29
|
||||||
|
\pgfkeys@temptoks=\toks30
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.c
|
||||||
|
ode.tex
|
||||||
|
\pgfkeys@tmptoks=\toks31
|
||||||
|
))
|
||||||
|
\pgf@x=\dimen154
|
||||||
|
\pgf@y=\dimen155
|
||||||
|
\pgf@xa=\dimen156
|
||||||
|
\pgf@ya=\dimen157
|
||||||
|
\pgf@xb=\dimen158
|
||||||
|
\pgf@yb=\dimen159
|
||||||
|
\pgf@xc=\dimen160
|
||||||
|
\pgf@yc=\dimen161
|
||||||
|
\pgf@xd=\dimen162
|
||||||
|
\pgf@yd=\dimen163
|
||||||
|
\w@pgf@writea=\write3
|
||||||
|
\r@pgf@reada=\read2
|
||||||
|
\c@pgf@counta=\count272
|
||||||
|
\c@pgf@countb=\count273
|
||||||
|
\c@pgf@countc=\count274
|
||||||
|
\c@pgf@countd=\count275
|
||||||
|
\t@pgf@toka=\toks32
|
||||||
|
\t@pgf@tokb=\toks33
|
||||||
|
\t@pgf@tokc=\toks34
|
||||||
|
\pgf@sys@id@count=\count276
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg
|
||||||
|
File: pgf.cfg 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
Driver file for pgf: pgfsys-pdftex.def
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.d
|
||||||
|
ef
|
||||||
|
File: pgfsys-pdftex.def 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-p
|
||||||
|
df.def
|
||||||
|
File: pgfsys-common-pdf.def 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.
|
||||||
|
code.tex
|
||||||
|
File: pgfsyssoftpath.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfsyssoftpath@smallbuffer@items=\count277
|
||||||
|
\pgfsyssoftpath@bigbuffer@items=\count278
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.
|
||||||
|
code.tex
|
||||||
|
File: pgfsysprotocol.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)) (/usr/local/texlive/2022/texmf-dist/tex/latex/xcolor/xcolor.sty
|
||||||
|
Package: xcolor 2021/10/31 v2.13 LaTeX color extensions (UK)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/color.cfg
|
||||||
|
File: color.cfg 2016/01/02 v1.6 sample color configuration
|
||||||
|
)
|
||||||
|
Package xcolor Info: Driver file: pdftex.def on input line 227.
|
||||||
|
Package xcolor Info: Model `cmy' substituted by `cmy0' on input line 1352.
|
||||||
|
Package xcolor Info: Model `hsb' substituted by `rgb' on input line 1356.
|
||||||
|
Package xcolor Info: Model `RGB' extended on input line 1368.
|
||||||
|
Package xcolor Info: Model `HTML' substituted by `rgb' on input line 1370.
|
||||||
|
Package xcolor Info: Model `Hsb' substituted by `hsb' on input line 1371.
|
||||||
|
Package xcolor Info: Model `tHsb' substituted by `hsb' on input line 1372.
|
||||||
|
Package xcolor Info: Model `HSB' substituted by `hsb' on input line 1373.
|
||||||
|
Package xcolor Info: Model `Gray' substituted by `gray' on input line 1374.
|
||||||
|
Package xcolor Info: Model `wave' substituted by `hsb' on input line 1375.
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
|
||||||
|
Package: pgfcore 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex
|
||||||
|
\pgfmath@dimen=\dimen164
|
||||||
|
\pgfmath@count=\count279
|
||||||
|
\pgfmath@box=\box54
|
||||||
|
\pgfmath@toks=\toks35
|
||||||
|
\pgfmath@stack@operand=\toks36
|
||||||
|
\pgfmath@stack@operation=\toks37
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.
|
||||||
|
tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic
|
||||||
|
.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigo
|
||||||
|
nometric.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.rando
|
||||||
|
m.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.compa
|
||||||
|
rison.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.
|
||||||
|
code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round
|
||||||
|
.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.
|
||||||
|
code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integ
|
||||||
|
erarithmetics.code.tex)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex
|
||||||
|
\c@pgfmathroundto@lastzeros=\count280
|
||||||
|
)) (/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfint.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorepoints.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@picminx=\dimen165
|
||||||
|
\pgf@picmaxx=\dimen166
|
||||||
|
\pgf@picminy=\dimen167
|
||||||
|
\pgf@picmaxy=\dimen168
|
||||||
|
\pgf@pathminx=\dimen169
|
||||||
|
\pgf@pathmaxx=\dimen170
|
||||||
|
\pgf@pathminy=\dimen171
|
||||||
|
\pgf@pathmaxy=\dimen172
|
||||||
|
\pgf@xx=\dimen173
|
||||||
|
\pgf@xy=\dimen174
|
||||||
|
\pgf@yx=\dimen175
|
||||||
|
\pgf@yy=\dimen176
|
||||||
|
\pgf@zx=\dimen177
|
||||||
|
\pgf@zy=\dimen178
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconst
|
||||||
|
ruct.code.tex
|
||||||
|
File: pgfcorepathconstruct.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@path@lastx=\dimen179
|
||||||
|
\pgf@path@lasty=\dimen180
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage
|
||||||
|
.code.tex
|
||||||
|
File: pgfcorepathusage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@shorten@end@additional=\dimen181
|
||||||
|
\pgf@shorten@start@additional=\dimen182
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorescopes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfpic=\box55
|
||||||
|
\pgf@hbox=\box56
|
||||||
|
\pgf@layerbox@main=\box57
|
||||||
|
\pgf@picture@serial@count=\count281
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicst
|
||||||
|
ate.code.tex
|
||||||
|
File: pgfcoregraphicstate.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgflinewidth=\dimen183
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransform
|
||||||
|
ations.code.tex
|
||||||
|
File: pgfcoretransformations.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@pt@x=\dimen184
|
||||||
|
\pgf@pt@y=\dimen185
|
||||||
|
\pgf@pt@temp=\dimen186
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcorequick.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.c
|
||||||
|
ode.tex
|
||||||
|
File: pgfcoreobjects.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathproce
|
||||||
|
ssing.code.tex
|
||||||
|
File: pgfcorepathprocessing.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorearrows.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfarrowsep=\dimen187
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcoreshade.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@max=\dimen188
|
||||||
|
\pgf@sys@shading@range@num=\count282
|
||||||
|
\pgf@shadingcount=\count283
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcoreimage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.
|
||||||
|
code.tex
|
||||||
|
File: pgfcoreexternal.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfexternal@startupbox=\box58
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorelayers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretranspare
|
||||||
|
ncy.code.tex
|
||||||
|
File: pgfcoretransparency.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.
|
||||||
|
code.tex
|
||||||
|
File: pgfcorepatterns.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorerdf.code.
|
||||||
|
tex
|
||||||
|
File: pgfcorerdf.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfmoduleshapes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfnodeparttextbox=\box59
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.
|
||||||
|
tex
|
||||||
|
File: pgfmoduleplot.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version
|
||||||
|
-0-65.sty
|
||||||
|
Package: pgfcomp-version-0-65 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@nodesepstart=\dimen189
|
||||||
|
\pgf@nodesepend=\dimen190
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version
|
||||||
|
-1-18.sty
|
||||||
|
Package: pgfcomp-version-1-18 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgffor.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex)
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/math/pgfmath.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
|
||||||
|
Package: pgffor 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex)
|
||||||
|
\pgffor@iter=\dimen191
|
||||||
|
\pgffor@skip=\dimen192
|
||||||
|
\pgffor@stack=\toks38
|
||||||
|
\pgffor@toks=\toks39
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.cod
|
||||||
|
e.tex
|
||||||
|
Package: tikz 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothan
|
||||||
|
dlers.code.tex
|
||||||
|
File: pgflibraryplothandlers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@plot@mark@count=\count284
|
||||||
|
\pgfplotmarksize=\dimen193
|
||||||
|
)
|
||||||
|
\tikz@lastx=\dimen194
|
||||||
|
\tikz@lasty=\dimen195
|
||||||
|
\tikz@lastxsaved=\dimen196
|
||||||
|
\tikz@lastysaved=\dimen197
|
||||||
|
\tikz@lastmovetox=\dimen198
|
||||||
|
\tikz@lastmovetoy=\dimen256
|
||||||
|
\tikzleveldistance=\dimen257
|
||||||
|
\tikzsiblingdistance=\dimen258
|
||||||
|
\tikz@figbox=\box60
|
||||||
|
\tikz@figbox@bg=\box61
|
||||||
|
\tikz@tempbox=\box62
|
||||||
|
\tikz@tempbox@bg=\box63
|
||||||
|
\tikztreelevel=\count285
|
||||||
|
\tikznumberofchildren=\count286
|
||||||
|
\tikznumberofcurrentchild=\count287
|
||||||
|
\tikz@fig@count=\count288
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfmodulematrix.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfmatrixcurrentrow=\count289
|
||||||
|
\pgfmatrixcurrentcolumn=\count290
|
||||||
|
\pgf@matrix@numberofcolumns=\count291
|
||||||
|
)
|
||||||
|
\tikz@expandcount=\count292
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/librarie
|
||||||
|
s/tikzlibrarytopaths.code.tex
|
||||||
|
File: tikzlibrarytopaths.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/librarie
|
||||||
|
s/tikzlibrarybackgrounds.code.tex
|
||||||
|
File: tikzlibrarybackgrounds.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@layerbox@background=\box64
|
||||||
|
\pgf@layerboxsaved@background=\box65
|
||||||
|
)
|
||||||
|
\c@theorem=\count293
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
|
||||||
|
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
|
||||||
|
\l__color_backend_stack_int=\count294
|
||||||
|
\l__pdf_internal_box=\box66
|
||||||
|
)
|
||||||
|
(./paper.aux)
|
||||||
|
\openout1 = `paper.aux'.
|
||||||
|
|
||||||
|
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 29.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msb on input line 29.
|
||||||
|
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
|
||||||
|
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
|
||||||
|
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||||
|
\scratchcounter=\count295
|
||||||
|
\scratchdimen=\dimen259
|
||||||
|
\scratchbox=\box67
|
||||||
|
\nofMPsegments=\count296
|
||||||
|
\nofMParguments=\count297
|
||||||
|
\everyMPshowfont=\toks40
|
||||||
|
\MPscratchCnt=\count298
|
||||||
|
\MPscratchDim=\dimen260
|
||||||
|
\MPnumerator=\count299
|
||||||
|
\makeMPintoPDFobject=\count300
|
||||||
|
\everyMPtoPDFconversion=\toks41
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
|
||||||
|
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||||
|
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||||
|
85.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
|
||||||
|
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}]
|
||||||
|
|
||||||
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
|
[2] [3] (./whole_medial_seed59_min5.tikz) [4] [5] (./paper.aux) )
|
||||||
|
Here is how much of TeX's memory you used:
|
||||||
|
13803 strings out of 478268
|
||||||
|
275582 string characters out of 5846347
|
||||||
|
655790 words of memory out of 5000000
|
||||||
|
31627 multiletter control sequences out of 15000+600000
|
||||||
|
477661 words of font info for 60 fonts, out of 8000000 for 9000
|
||||||
|
1302 hyphenation exceptions out of 8191
|
||||||
|
84i,15n,89p,907b,804s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
|
</usr/local/te
|
||||||
|
xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/tex
|
||||||
|
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx6.pfb></usr/local/texli
|
||||||
|
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx7.pfb></usr/local/texlive
|
||||||
|
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive
|
||||||
|
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/
|
||||||
|
2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/20
|
||||||
|
22/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022
|
||||||
|
/texmf-dist/fonts/type1/public/amsfonts/cm/cmr6.pfb></usr/local/texlive/2022/te
|
||||||
|
xmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf
|
||||||
|
-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-di
|
||||||
|
st/fonts/type1/public/amsfonts/cm/cmss10.pfb></usr/local/texlive/2022/texmf-dis
|
||||||
|
t/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist
|
||||||
|
/fonts/type1/public/amsfonts/cm/cmsy6.pfb></usr/local/texlive/2022/texmf-dist/f
|
||||||
|
onts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fo
|
||||||
|
nts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/font
|
||||||
|
s/type1/public/amsfonts/cm/cmtt10.pfb>
|
||||||
|
Output written on paper.pdf (5 pages, 226751 bytes).
|
||||||
|
PDF statistics:
|
||||||
|
103 PDF objects out of 1000 (max. 8388607)
|
||||||
|
63 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)
|
||||||
|
|
||||||
@@ -0,0 +1,438 @@
|
|||||||
|
%% filename: amsart-template.tex
|
||||||
|
%% American Mathematical Society
|
||||||
|
%% AMS-LaTeX v.2 template for use with amsart
|
||||||
|
%% ====================================================================
|
||||||
|
|
||||||
|
\documentclass{amsart}
|
||||||
|
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usetikzlibrary{backgrounds}
|
||||||
|
|
||||||
|
\newtheorem{theorem}{Theorem}[section]
|
||||||
|
\newtheorem{lemma}[theorem]{Lemma}
|
||||||
|
\newtheorem{corollary}[theorem]{Corollary}
|
||||||
|
\newtheorem{proposition}[theorem]{Proposition}
|
||||||
|
\newtheorem{conjecture}[theorem]{Conjecture}
|
||||||
|
|
||||||
|
\theoremstyle{definition}
|
||||||
|
\newtheorem{definition}[theorem]{Definition}
|
||||||
|
\newtheorem{example}[theorem]{Example}
|
||||||
|
\newtheorem{xca}[theorem]{Exercise}
|
||||||
|
|
||||||
|
\theoremstyle{remark}
|
||||||
|
\newtheorem{remark}[theorem]{Remark}
|
||||||
|
|
||||||
|
\numberwithin{equation}{section}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\title{Medial Tire Cuts}
|
||||||
|
|
||||||
|
% author one information
|
||||||
|
\author{Eric Bauerfeld}
|
||||||
|
\address{}
|
||||||
|
\curraddr{}
|
||||||
|
\email{}
|
||||||
|
\thanks{}
|
||||||
|
|
||||||
|
\subjclass[2010]{Primary }
|
||||||
|
|
||||||
|
\keywords{plane graph, triangulation, medial graph, tire graph, Tait coloring, Four Colour Theorem}
|
||||||
|
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\dedicatory{}
|
||||||
|
|
||||||
|
\begin{abstract}
|
||||||
|
Starting from the medial tire decomposition of a plane triangulation, we
|
||||||
|
study the cuts that medial tires make in the full medial graph. We will
|
||||||
|
show how to use medial tires to decompose the medial graph into a tree of
|
||||||
|
three faces.
|
||||||
|
\end{abstract}
|
||||||
|
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
|
||||||
|
This paper builds on the medial tire decomposition
|
||||||
|
of~\cite{bauerfeld-medial-tire}. For a plane triangulation $G$ with
|
||||||
|
fixed embedding we use freely the terminology and notation introduced
|
||||||
|
there: the full medial graph $M(G)$, its decomposition into full medial
|
||||||
|
tire graphs $\mathsf{M}(T)$ indexed by the treads $T$ of the tire tree
|
||||||
|
$\mathcal{T}(G,S)$ at a level source $S$, the annular medial cycle
|
||||||
|
$A(T)$, and the boundary medial vertex sets.
|
||||||
|
|
||||||
|
We will show how to use medial tires to decompose the medial graph into
|
||||||
|
a tree of three faces.
|
||||||
|
|
||||||
|
\section{Cutting a full medial tire graph}
|
||||||
|
|
||||||
|
We first describe a procedure that simultaneously \emph{labels} and
|
||||||
|
\emph{cuts} a single full medial tire graph $\mathsf{M}(T)$ so that,
|
||||||
|
after the cuts, the only faces are the outer face and $3$-faces
|
||||||
|
(triangles)---the teeth of~\cite{bauerfeld-medial-tire}. The labelling
|
||||||
|
assigns to each tooth an integer \emph{walk depth}; the cuts break the
|
||||||
|
cyclic adjacencies of the teeth so that what remains is a tree of
|
||||||
|
$3$-faces.
|
||||||
|
|
||||||
|
By a \emph{cut} we mean the duplication of a single vertex of
|
||||||
|
$\mathsf{M}(T)$: the vertex is split into two copies and the embedding is
|
||||||
|
slit open along it (a planar unzip), separating the faces that meet only
|
||||||
|
at that vertex. A cut therefore reduces the number of bounded faces that
|
||||||
|
are not teeth.
|
||||||
|
|
||||||
|
Throughout we use the teeth, up and down teeth, apexes, bites, the
|
||||||
|
annular medial cycle $A(T)$, and the auxiliary plane graph $B(T)$
|
||||||
|
of~\cite{bauerfeld-medial-tire}. Each tooth is a $3$-face of
|
||||||
|
$\mathsf{M}(T)$, and the inner faces of $B(T)$ (the root face and the
|
||||||
|
bite inner-gap faces) are the larger faces to be cut into teeth.
|
||||||
|
|
||||||
|
\begin{definition}[Walk-depth labelling and cut]
|
||||||
|
\label{def:walk-depth-cut}
|
||||||
|
Let $\mathsf{M}(T)$ be a full medial tire graph. Assign walk depths and
|
||||||
|
cuts as follows.
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Pick an arbitrary up tooth, the \emph{entry tooth}. It has walk
|
||||||
|
depth $d$.
|
||||||
|
\item Traverse all the teeth that bound the inner face incident to the
|
||||||
|
entry tooth clockwise until we reach the entry tooth, incrementing the
|
||||||
|
walk depth by $1$ for each tooth traversed. (The \emph{inner face
|
||||||
|
incident to the entry tooth} is the inner face of $B(T)$ whose boundary
|
||||||
|
contains the annular edge of $A(T)$ carrying the entry tooth.)
|
||||||
|
\item When you reach the last tooth in the face, perform a \emph{cut}
|
||||||
|
by duplicating the annular vertex at which the traversal closes---the
|
||||||
|
annular vertex of $A(T)$ shared by the last tooth and the entry tooth.
|
||||||
|
\item Find the tooth $t$ with the highest walk depth which is a member
|
||||||
|
of a bite.
|
||||||
|
\item If $t$ is incident to a face $F$ with unlabelled teeth, traverse
|
||||||
|
the teeth in $F$ starting from $t$ in the direction of the tooth
|
||||||
|
incident to $t$ which is unlabelled, and increment the walk depth by
|
||||||
|
$1$ as you travel. (Here a tooth is \emph{incident to $t$} when it
|
||||||
|
shares an annular vertex of $A(T)$ with $t$.)
|
||||||
|
\item Repeat steps (3)--(5) until all teeth have been labelled.
|
||||||
|
\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}
|
||||||
|
|
||||||
|
\section{Chaining across the tire tree}
|
||||||
|
|
||||||
|
Definition~\ref{def:walk-depth-cut} labels and cuts a single full medial
|
||||||
|
tire graph. We extend it to the whole medial graph $M(G)$ through the
|
||||||
|
medial tire decomposition of~\cite{bauerfeld-medial-tire}: the tire tree
|
||||||
|
decomposes $M(G)$ into full medial tire graphs $\mathsf{M}(T)$, one per
|
||||||
|
tread $T$, glued along their boundary medial vertices. A parent tread's
|
||||||
|
inner level cycle is a child tread's outer level cycle, and the boundary
|
||||||
|
medial vertices on that shared cycle belong to both treads.
|
||||||
|
|
||||||
|
The key incidence is this. A \emph{boundary} (singleton) down tooth of a
|
||||||
|
parent tread and the up tooth of the child tread glued to it across the
|
||||||
|
shared level cycle have the \emph{same apex}: both apexes are the same
|
||||||
|
medial vertex of $M(G)$, namely the medial vertex of an edge with both
|
||||||
|
endpoints on the shared level cycle. We use this to carry the walk depth
|
||||||
|
from a parent into its children.
|
||||||
|
|
||||||
|
We label tread by tread, outward from the root:
|
||||||
|
\begin{itemize}
|
||||||
|
\item a tread with no parent in the decomposition---in particular the
|
||||||
|
innermost recognised tread---is treated as a \emph{root} and entered at
|
||||||
|
an arbitrary up tooth with walk depth $0$;
|
||||||
|
\item a child tread is entered at the up tooth whose apex is the parent's
|
||||||
|
boundary down tooth of lowest walk depth; that entry up tooth's walk depth
|
||||||
|
is one more than that down tooth's, and the walk then increments locally
|
||||||
|
within the child as in Definition~\ref{def:walk-depth-cut}.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
The source cap contributes one additional cut before the recognised
|
||||||
|
treads are assembled. If the root tread enters at an up tooth whose apex
|
||||||
|
is the cap down tooth $xy$, we cut the cap annular vertex corresponding
|
||||||
|
to the counter-clockwise source edge incident to $xy$. In the example of
|
||||||
|
Figure~\ref{fig:whole-medial}, the root entry apex is the cap down tooth
|
||||||
|
$14\!-\!4$, so the cap cut is placed at the medial vertex $14\!-\!5$.
|
||||||
|
|
||||||
|
\begin{remark}[Candidate down teeth for chaining]
|
||||||
|
\label{rem:chaining-candidates}
|
||||||
|
The down teeth eligible to fix a child's entry are exactly the
|
||||||
|
\emph{boundary} (singleton) down teeth of the parent: those lying in a
|
||||||
|
single tread face, whose apex is the shared boundary medial vertex glued to
|
||||||
|
a child up tooth. A bite's two down teeth are \emph{not} eligible. By the
|
||||||
|
definition of a bite in~\cite{bauerfeld-medial-tire} its annular edge borders
|
||||||
|
two tread faces, so a bite tooth is interior to the parent tread and its
|
||||||
|
apex is a boundary medial vertex of no child. Hence ``the down tooth of
|
||||||
|
lowest walk depth'' is read among the boundary down teeth only; a bite of
|
||||||
|
even lower walk depth is skipped.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
Applying every tread's cuts to $M(G)$ assembles the per-tread labellings
|
||||||
|
and cuts into a single cut graph of $M(G)$ together with a global
|
||||||
|
walk-depth label map. This pipeline---random maximal planar graph, medial
|
||||||
|
graph, tire decomposition at a vertex level source, and chained walk-depth
|
||||||
|
labelling and cut---is carried out by the experiment script
|
||||||
|
\texttt{experiments/run\_medial\_tire\_cut\_experiment.py}.
|
||||||
|
|
||||||
|
\begin{example}[A medial tire cut from a random graph]
|
||||||
|
\label{ex:real-cut}
|
||||||
|
Run on a random maximal planar graph on $20$ vertices (seed $72$, level
|
||||||
|
source vertex $9$), the experiment yields a single recognised tread
|
||||||
|
$T_2$, drawn in Figure~\ref{fig:real-cut} with the walk-depth labelling
|
||||||
|
and cut emitted by the graphics companion
|
||||||
|
\texttt{experiments/draw\_medial\_tire\_cut.py}. Its annular cycle has
|
||||||
|
length $8$, with up teeth on annular edges $0,3,4$, singleton down teeth
|
||||||
|
on $1,6,7$, and a bite on the non-incident annular edges $2$ and $5$ (the
|
||||||
|
central shared apex). Entering at the up tooth on edge $0$ with walk
|
||||||
|
depth $0$, the root face is labelled in order ($0,1,2$ then $3,4,5$) and
|
||||||
|
\emph{cut~1} duplicates $a_0$ as it closes; the walk then descends through
|
||||||
|
the bite into its inner-gap face, labelling the two teeth there ($6,7$),
|
||||||
|
and \emph{cut~2} duplicates $a_3$ as that face closes. The two cuts leave
|
||||||
|
only the outer face and the eight teeth as $3$-faces.
|
||||||
|
\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] (0.559,1.349)--(0.000,1.000) (0.559,1.349)--(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.559,-1.349)--(0.000,-1.000) (-0.559,-1.349)--(-0.707,-0.707);
|
||||||
|
\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.318)--(1.000,0.000) (0.000,-0.318)--(0.707,-0.707);
|
||||||
|
\draw[tth] (0.000,-0.318)--(-0.707,-0.707) (0.000,-0.318)--(-1.000,-0.000);
|
||||||
|
\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 (0.559,1.349) {};
|
||||||
|
\node[upv] at (0.559,-1.349) {};
|
||||||
|
\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.318) {};
|
||||||
|
\node[dlbl] at (0.456,1.101) {0};
|
||||||
|
\node[dlbl] at (0.704,0.292) {1};
|
||||||
|
\node[dlbl] at (0.427,-0.336) {2};
|
||||||
|
\node[dlbl] at (0.456,-1.101) {7};
|
||||||
|
\node[dlbl] at (-0.456,-1.101) {6};
|
||||||
|
\node[dlbl] at (-0.427,-0.336) {3};
|
||||||
|
\node[dlbl] at (-0.704,0.292) {4};
|
||||||
|
\node[dlbl] at (-0.292,0.704) {5};
|
||||||
|
\draw[cut] (0.000,0.840)--(0.000,1.160);
|
||||||
|
\node[cutlbl] at (0.000,1.300) {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 (0.689,1.663) {entry};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\caption{The recognised tread $T_2$ of the medial tire decomposition of a
|
||||||
|
random maximal planar graph on $20$ vertices
|
||||||
|
(Example~\ref{ex:real-cut}), with its walk-depth labelling and cut. Black
|
||||||
|
vertices are the annular medial vertices of $A(T)$; blue vertices are
|
||||||
|
up-tooth apexes and red vertices down-tooth apexes, the larger red vertex
|
||||||
|
being the shared apex of the bite on annular edges $2$ and $5$. Each
|
||||||
|
tooth carries its walk depth; the red slits are the two cuts.}
|
||||||
|
\label{fig:real-cut}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Figure~\ref{fig:whole-medial} repeats the whole-medial-graph drawing on a
|
||||||
|
random maximal planar graph on $20$ vertices with minimum degree $5$
|
||||||
|
(plantri seed $59$, level source vertex $5$). The experiment recognises
|
||||||
|
two full medial tire treads, $T_1$ and $T_2$, and produces seven cuts:
|
||||||
|
one source-cap cut and six full-tread cuts. The
|
||||||
|
top panel shows the source triangulation with its level source
|
||||||
|
highlighted; the bottom panel draws $M(G)$ on the same straight-line
|
||||||
|
embedding by placing each medial vertex at the midpoint of its
|
||||||
|
corresponding source edge. Every medial vertex is labelled by that source
|
||||||
|
edge. Black vertices correspond to source edges joining consecutive
|
||||||
|
levels, and coloured vertices correspond to source edges within one level.
|
||||||
|
The red-highlighted vertices, walk-depth labels, and red slits are the
|
||||||
|
computed full-medial-tire labelling and cuts.
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\input{whole_medial_seed59_min5.tikz}
|
||||||
|
\caption{The source graph $G$ and the whole medial graph $M(G)$ of the
|
||||||
|
minimum-degree-$5$ maximal planar graph on $20$ vertices generated by
|
||||||
|
\texttt{plantri -m5} at seed $59$. The source vertex $5$ is highlighted
|
||||||
|
in the top panel. In the bottom panel, each medial vertex is placed at
|
||||||
|
the midpoint of its corresponding source edge and labelled by that edge.
|
||||||
|
Black vertices come from source edges between consecutive levels; coloured
|
||||||
|
vertices come from source edges within a single level of the chain. The
|
||||||
|
red-highlighted vertices, walk-depth labels, and seven red slits are the
|
||||||
|
computed source-cap cut and full-medial-tire labelling cuts for the
|
||||||
|
recognised treads $T_1$ and $T_2$. Drawn by
|
||||||
|
\texttt{experiments/draw\_medial\_tire\_cut.py} with
|
||||||
|
\texttt{--whole --min-degree 5}.}
|
||||||
|
\label{fig:whole-medial}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{thebibliography}{9}
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-medial-tire}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Medial Tire Decompositions of Plane Triangulations},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\end{thebibliography}
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 283 KiB |
@@ -0,0 +1,403 @@
|
|||||||
|
% whole medial graph: n=20 seed=59 graph_seed=59 min_degree=5 source=5 recognised treads=[1, 2] |M(G)|=54
|
||||||
|
\begin{tabular}{c}
|
||||||
|
\begin{tikzpicture}[scale=4.06,
|
||||||
|
sedge/.style={black!50, line width=0.35pt},
|
||||||
|
sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},
|
||||||
|
srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.084,-0.096);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.018,0.026);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.073,-0.186);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.084,-0.096);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(-0.018,0.026);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.060,-0.177);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.035,-0.147);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.024,-0.131);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.015,-0.113);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.128,-0.048)--(-0.073,-0.186);
|
||||||
|
\draw[sedge] (-0.128,-0.048)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.019,-0.303)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.019,-0.303)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.139,-0.245)--(0.060,-0.177);
|
||||||
|
\draw[sedge] (0.139,-0.245)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.035,-0.147);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(0.024,-0.131);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(0.015,-0.113);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (0.002,-0.076)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (0.002,-0.076)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.048,-0.081)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.023,-0.119)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (-0.012,-0.141)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (-0.003,-0.156)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.010,-0.177)--(0.031,-0.218);
|
||||||
|
\node[sv] at (0.000,0.433) {};
|
||||||
|
\node[sv] at (-0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.084,-0.096) {};
|
||||||
|
\node[sv] at (-0.018,0.026) {};
|
||||||
|
\node[srcv] at (-0.128,-0.048) {};
|
||||||
|
\node[sv] at (-0.073,-0.186) {};
|
||||||
|
\node[sv] at (0.019,-0.303) {};
|
||||||
|
\node[sv] at (0.139,-0.245) {};
|
||||||
|
\node[sv] at (0.060,-0.177) {};
|
||||||
|
\node[sv] at (0.035,-0.147) {};
|
||||||
|
\node[sv] at (0.024,-0.131) {};
|
||||||
|
\node[sv] at (0.015,-0.113) {};
|
||||||
|
\node[sv] at (0.002,-0.076) {};
|
||||||
|
\node[sv] at (-0.048,-0.081) {};
|
||||||
|
\node[sv] at (-0.023,-0.119) {};
|
||||||
|
\node[sv] at (-0.012,-0.141) {};
|
||||||
|
\node[sv] at (-0.003,-0.156) {};
|
||||||
|
\node[sv] at (0.010,-0.177) {};
|
||||||
|
\node[sv] at (0.031,-0.218) {};
|
||||||
|
\node[font=\scriptsize, text=blue!70!black] at (-0.128,-0.133) {source 5};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize source graph $G$}
|
||||||
|
\\[1.0ex]
|
||||||
|
\begin{tikzpicture}[scale=7.0,
|
||||||
|
base/.style={black!12, line width=0.25pt},
|
||||||
|
med/.style={black!38, line width=0.32pt},
|
||||||
|
annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},
|
||||||
|
levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},
|
||||||
|
levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},
|
||||||
|
levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},
|
||||||
|
knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},
|
||||||
|
elbl/.style={font=\tiny, text=black!70, inner sep=0.2pt},
|
||||||
|
dlbl/.style={font=\tiny\bfseries, text=black, inner sep=0.5pt},
|
||||||
|
cut/.style={red!80!black, line width=1.0pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[base] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.084,-0.096);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.018,0.026);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.073,-0.186);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.084,-0.096);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.084,-0.096)--(-0.018,0.026);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.060,-0.177);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.035,-0.147);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.024,-0.131);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.015,-0.113);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.002,-0.076);
|
||||||
|
\draw[base] (-0.018,0.026)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.018,0.026)--(0.002,-0.076);
|
||||||
|
\draw[base] (-0.018,0.026)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.128,-0.048)--(-0.073,-0.186);
|
||||||
|
\draw[base] (-0.128,-0.048)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.019,-0.303);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.012,-0.141);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.003,-0.156);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.010,-0.177);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.019,-0.303)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.019,-0.303)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.139,-0.245)--(0.060,-0.177);
|
||||||
|
\draw[base] (0.139,-0.245)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.035,-0.147);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.035,-0.147)--(0.024,-0.131);
|
||||||
|
\draw[base] (0.035,-0.147)--(-0.003,-0.156);
|
||||||
|
\draw[base] (0.035,-0.147)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.024,-0.131)--(0.015,-0.113);
|
||||||
|
\draw[base] (0.024,-0.131)--(-0.012,-0.141);
|
||||||
|
\draw[base] (0.024,-0.131)--(-0.003,-0.156);
|
||||||
|
\draw[base] (0.015,-0.113)--(0.002,-0.076);
|
||||||
|
\draw[base] (0.015,-0.113)--(-0.023,-0.119);
|
||||||
|
\draw[base] (0.015,-0.113)--(-0.012,-0.141);
|
||||||
|
\draw[base] (0.002,-0.076)--(-0.048,-0.081);
|
||||||
|
\draw[base] (0.002,-0.076)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.048,-0.081)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.023,-0.119)--(-0.012,-0.141);
|
||||||
|
\draw[base] (-0.012,-0.141)--(-0.003,-0.156);
|
||||||
|
\draw[base] (-0.003,-0.156)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.010,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.064,0.192);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.250,0.000);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.314,-0.241);
|
||||||
|
\draw[med] (0.250,0.000)--(0.042,0.169);
|
||||||
|
\draw[med] (0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (0.250,0.000)--(0.292,-0.264);
|
||||||
|
\draw[med] (0.042,0.169)--(-0.009,0.230);
|
||||||
|
\draw[med] (0.042,0.169)--(0.292,-0.264);
|
||||||
|
\draw[med] (0.042,0.169)--(0.033,-0.035);
|
||||||
|
\draw[med] (-0.009,0.230)--(-0.064,0.192);
|
||||||
|
\draw[med] (-0.009,0.230)--(0.033,-0.035);
|
||||||
|
\draw[med] (-0.009,0.230)--(-0.073,-0.011);
|
||||||
|
\draw[med] (-0.064,0.192)--(-0.073,-0.011);
|
||||||
|
\draw[med] (-0.064,0.192)--(-0.314,-0.241);
|
||||||
|
\draw[med] (0.000,-0.433)--(-0.240,-0.368);
|
||||||
|
\draw[med] (0.000,-0.433)--(0.260,-0.368);
|
||||||
|
\draw[med] (-0.314,-0.241)--(-0.286,-0.310);
|
||||||
|
\draw[med] (-0.314,-0.241)--(-0.100,-0.117);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.240,-0.368);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.100,-0.117);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.027,-0.245);
|
||||||
|
\draw[med] (-0.240,-0.368)--(-0.027,-0.245);
|
||||||
|
\draw[med] (-0.240,-0.368)--(0.260,-0.368);
|
||||||
|
\draw[med] (0.292,-0.264)--(0.319,-0.339);
|
||||||
|
\draw[med] (0.292,-0.264)--(0.111,-0.171);
|
||||||
|
\draw[med] (0.260,-0.368)--(0.319,-0.339);
|
||||||
|
\draw[med] (0.260,-0.368)--(0.079,-0.274);
|
||||||
|
\draw[med] (0.319,-0.339)--(0.079,-0.274);
|
||||||
|
\draw[med] (0.319,-0.339)--(0.111,-0.171);
|
||||||
|
\draw[med] (0.033,-0.035)--(0.043,-0.086);
|
||||||
|
\draw[med] (0.033,-0.035)--(-0.008,-0.025);
|
||||||
|
\draw[med] (0.111,-0.171)--(0.072,-0.136);
|
||||||
|
\draw[med] (0.111,-0.171)--(0.099,-0.211);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.059,-0.122);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.099,-0.211);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.047,-0.162);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.054,-0.113);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.047,-0.162);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.029,-0.139);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.049,-0.104);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.029,-0.139);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.019,-0.122);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.043,-0.086);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.019,-0.122);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.008,-0.095);
|
||||||
|
\draw[med] (0.043,-0.086)--(0.008,-0.095);
|
||||||
|
\draw[med] (0.043,-0.086)--(-0.008,-0.025);
|
||||||
|
\draw[med] (-0.073,-0.011)--(-0.033,-0.027);
|
||||||
|
\draw[med] (-0.073,-0.011)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.008,-0.025)--(-0.033,-0.027);
|
||||||
|
\draw[med] (-0.008,-0.025)--(-0.023,-0.079);
|
||||||
|
\draw[med] (-0.033,-0.027)--(-0.023,-0.079);
|
||||||
|
\draw[med] (-0.033,-0.027)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.100,-0.117)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.100,-0.117)--(-0.060,-0.134);
|
||||||
|
\draw[med] (-0.088,-0.064)--(-0.060,-0.134);
|
||||||
|
\draw[med] (-0.027,-0.245)--(-0.021,-0.202);
|
||||||
|
\draw[med] (-0.027,-0.245)--(0.025,-0.260);
|
||||||
|
\draw[med] (-0.060,-0.134)--(-0.048,-0.153);
|
||||||
|
\draw[med] (-0.060,-0.134)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.042,-0.164);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.038,-0.171);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.008,-0.149);
|
||||||
|
\draw[med] (-0.038,-0.171)--(-0.031,-0.182);
|
||||||
|
\draw[med] (-0.038,-0.171)--(-0.008,-0.149);
|
||||||
|
\draw[med] (-0.038,-0.171)--(0.003,-0.167);
|
||||||
|
\draw[med] (-0.031,-0.182)--(-0.021,-0.202);
|
||||||
|
\draw[med] (-0.031,-0.182)--(0.003,-0.167);
|
||||||
|
\draw[med] (-0.031,-0.182)--(0.021,-0.197);
|
||||||
|
\draw[med] (-0.021,-0.202)--(0.021,-0.197);
|
||||||
|
\draw[med] (-0.021,-0.202)--(0.025,-0.260);
|
||||||
|
\draw[med] (0.079,-0.274)--(0.025,-0.260);
|
||||||
|
\draw[med] (0.079,-0.274)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.025,-0.260)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.099,-0.211)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.099,-0.211)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.085,-0.231)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.047,-0.162)--(0.035,-0.177);
|
||||||
|
\draw[med] (0.047,-0.162)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.021,-0.197);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.045,-0.197)--(0.021,-0.197);
|
||||||
|
\draw[med] (0.029,-0.139)--(0.016,-0.152);
|
||||||
|
\draw[med] (0.029,-0.139)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.003,-0.167);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.022,-0.162)--(0.003,-0.167);
|
||||||
|
\draw[med] (0.019,-0.122)--(0.006,-0.136);
|
||||||
|
\draw[med] (0.019,-0.122)--(0.001,-0.127);
|
||||||
|
\draw[med] (0.006,-0.136)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.006,-0.136)--(-0.008,-0.149);
|
||||||
|
\draw[med] (0.006,-0.136)--(0.001,-0.127);
|
||||||
|
\draw[med] (0.010,-0.144)--(-0.008,-0.149);
|
||||||
|
\draw[med] (0.008,-0.095)--(-0.004,-0.116);
|
||||||
|
\draw[med] (0.008,-0.095)--(-0.011,-0.098);
|
||||||
|
\draw[med] (-0.004,-0.116)--(0.001,-0.127);
|
||||||
|
\draw[med] (-0.004,-0.116)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.004,-0.116)--(-0.011,-0.098);
|
||||||
|
\draw[med] (0.001,-0.127)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.023,-0.079)--(-0.011,-0.098);
|
||||||
|
\draw[med] (-0.023,-0.079)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.011,-0.098)--(-0.035,-0.100);
|
||||||
|
\node[knownv] at (-0.250,0.000) {};
|
||||||
|
\node[annv] at (0.250,0.000) {};
|
||||||
|
\node[annv] at (0.042,0.169) {};
|
||||||
|
\node[knownv] at (-0.009,0.230) {};
|
||||||
|
\node[annv] at (-0.064,0.192) {};
|
||||||
|
\node[annv] at (0.000,-0.433) {};
|
||||||
|
\node[annv] at (-0.314,-0.241) {};
|
||||||
|
\node[knownv] at (-0.286,-0.310) {};
|
||||||
|
\node[annv] at (-0.240,-0.368) {};
|
||||||
|
\node[knownv] at (0.292,-0.264) {};
|
||||||
|
\node[knownv] at (0.260,-0.368) {};
|
||||||
|
\node[annv] at (0.319,-0.339) {};
|
||||||
|
\node[annv] at (0.033,-0.035) {};
|
||||||
|
\node[annv] at (0.111,-0.171) {};
|
||||||
|
\node[annv] at (0.072,-0.136) {};
|
||||||
|
\node[annv] at (-0.073,-0.011) {};
|
||||||
|
\node[annv] at (-0.100,-0.117) {};
|
||||||
|
\node[annv] at (-0.027,-0.245) {};
|
||||||
|
\node[annv] at (0.079,-0.274) {};
|
||||||
|
\node[knownv] at (0.099,-0.211) {};
|
||||||
|
\node[annv] at (0.059,-0.122) {};
|
||||||
|
\node[knownv] at (0.047,-0.162) {};
|
||||||
|
\node[knownv] at (0.029,-0.139) {};
|
||||||
|
\node[annv] at (0.016,-0.152) {};
|
||||||
|
\node[annv] at (0.022,-0.162) {};
|
||||||
|
\node[annv] at (0.054,-0.113) {};
|
||||||
|
\node[knownv] at (0.019,-0.122) {};
|
||||||
|
\node[annv] at (0.006,-0.136) {};
|
||||||
|
\node[annv] at (0.010,-0.144) {};
|
||||||
|
\node[annv] at (0.049,-0.104) {};
|
||||||
|
\node[annv] at (0.008,-0.095) {};
|
||||||
|
\node[annv] at (-0.004,-0.116) {};
|
||||||
|
\node[annv] at (0.001,-0.127) {};
|
||||||
|
\node[knownv] at (0.043,-0.086) {};
|
||||||
|
\node[annv] at (-0.008,-0.025) {};
|
||||||
|
\node[annv] at (-0.023,-0.079) {};
|
||||||
|
\node[knownv] at (-0.011,-0.098) {};
|
||||||
|
\node[knownv] at (-0.033,-0.027) {};
|
||||||
|
\node[annv] at (-0.088,-0.064) {};
|
||||||
|
\node[knownv] at (-0.060,-0.134) {};
|
||||||
|
\node[annv] at (-0.035,-0.100) {};
|
||||||
|
\node[annv] at (-0.048,-0.153) {};
|
||||||
|
\node[knownv] at (-0.018,-0.130) {};
|
||||||
|
\node[annv] at (-0.042,-0.164) {};
|
||||||
|
\node[knownv] at (-0.008,-0.149) {};
|
||||||
|
\node[annv] at (-0.038,-0.171) {};
|
||||||
|
\node[knownv] at (0.003,-0.167) {};
|
||||||
|
\node[annv] at (-0.031,-0.182) {};
|
||||||
|
\node[annv] at (0.035,-0.177) {};
|
||||||
|
\node[knownv] at (0.021,-0.197) {};
|
||||||
|
\node[annv] at (-0.021,-0.202) {};
|
||||||
|
\node[knownv] at (0.025,-0.260) {};
|
||||||
|
\node[annv] at (0.085,-0.231) {};
|
||||||
|
\node[annv] at (0.045,-0.197) {};
|
||||||
|
\node[elbl] at (-0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!1$};
|
||||||
|
\node[elbl] at (0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.042,0.169) [yshift=-4.8pt] {$0\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.009,0.230) [yshift=-4.8pt] {$0\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.064,0.192) [yshift=-4.8pt] {$0\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.000,-0.433) [yshift=-4.8pt] {$1\!{-}\!2$};
|
||||||
|
\node[elbl] at (-0.314,-0.241) [yshift=-4.8pt] {$1\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.286,-0.310) [yshift=-4.8pt] {$1\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.240,-0.368) [yshift=-4.8pt] {$1\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.292,-0.264) [yshift=-4.8pt] {$2\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.260,-0.368) [yshift=-4.8pt] {$2\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.319,-0.339) [yshift=-4.8pt] {$2\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.033,-0.035) [yshift=-4.8pt] {$3\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.111,-0.171) [yshift=-4.8pt] {$3\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.072,-0.136) [yshift=-4.8pt] {$3\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.073,-0.011) [yshift=-4.8pt] {$4\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.100,-0.117) [yshift=-4.8pt] {$5\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.027,-0.245) [yshift=-4.8pt] {$6\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.079,-0.274) [yshift=-4.8pt] {$7\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.099,-0.211) [yshift=-4.8pt] {$8\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.059,-0.122) [yshift=-4.8pt] {$10\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.047,-0.162) [yshift=-4.8pt] {$10\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.029,-0.139) [yshift=-4.8pt] {$10\!{-}\!11$};
|
||||||
|
\node[elbl] at (0.016,-0.152) [yshift=-4.8pt] {$10\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.022,-0.162) [yshift=-4.8pt] {$10\!{-}\!18$};
|
||||||
|
\node[elbl] at (0.054,-0.113) [yshift=-4.8pt] {$11\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.019,-0.122) [yshift=-4.8pt] {$11\!{-}\!12$};
|
||||||
|
\node[elbl] at (0.006,-0.136) [yshift=-4.8pt] {$11\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.010,-0.144) [yshift=-4.8pt] {$11\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.049,-0.104) [yshift=-4.8pt] {$12\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.008,-0.095) [yshift=-4.8pt] {$12\!{-}\!13$};
|
||||||
|
\node[elbl] at (-0.004,-0.116) [yshift=-4.8pt] {$12\!{-}\!15$};
|
||||||
|
\node[elbl] at (0.001,-0.127) [yshift=-4.8pt] {$12\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.043,-0.086) [yshift=-4.8pt] {$13\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.008,-0.025) [yshift=-4.8pt] {$13\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.023,-0.079) [yshift=-4.8pt] {$13\!{-}\!14$};
|
||||||
|
\node[elbl] at (-0.011,-0.098) [yshift=-4.8pt] {$13\!{-}\!15$};
|
||||||
|
\node[elbl] at (-0.033,-0.027) [yshift=-4.8pt] {$14\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.088,-0.064) [yshift=-4.8pt] {$14\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.060,-0.134) [yshift=-4.8pt] {$14\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.035,-0.100) [yshift=-4.8pt] {$14\!{-}\!15$};
|
||||||
|
\node[elbl] at (-0.048,-0.153) [yshift=-4.8pt] {$15\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.018,-0.130) [yshift=-4.8pt] {$15\!{-}\!16$};
|
||||||
|
\node[elbl] at (-0.042,-0.164) [yshift=-4.8pt] {$16\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.008,-0.149) [yshift=-4.8pt] {$16\!{-}\!17$};
|
||||||
|
\node[elbl] at (-0.038,-0.171) [yshift=-4.8pt] {$17\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.003,-0.167) [yshift=-4.8pt] {$17\!{-}\!18$};
|
||||||
|
\node[elbl] at (-0.031,-0.182) [yshift=-4.8pt] {$18\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.035,-0.177) [yshift=-4.8pt] {$18\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.021,-0.197) [yshift=-4.8pt] {$18\!{-}\!19$};
|
||||||
|
\node[elbl] at (-0.021,-0.202) [yshift=-4.8pt] {$19\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.025,-0.260) [yshift=-4.8pt] {$19\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.085,-0.231) [yshift=-4.8pt] {$19\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.045,-0.197) [yshift=-4.8pt] {$19\!{-}\!9$};
|
||||||
|
\node[dlbl] at (-0.250,0.000) [yshift=5.0pt] {4};
|
||||||
|
\node[dlbl] at (-0.009,0.230) [yshift=5.0pt] {2};
|
||||||
|
\node[dlbl] at (-0.286,-0.310) [yshift=5.0pt] {6};
|
||||||
|
\node[dlbl] at (0.292,-0.264) [yshift=5.0pt] {3,18};
|
||||||
|
\node[dlbl] at (0.260,-0.368) [yshift=5.0pt] {5,17};
|
||||||
|
\node[dlbl] at (0.099,-0.211) [yshift=5.0pt] {13,14};
|
||||||
|
\node[dlbl] at (0.047,-0.162) [yshift=5.0pt] {11,12};
|
||||||
|
\node[dlbl] at (0.029,-0.139) [yshift=5.0pt] {7,8};
|
||||||
|
\node[dlbl] at (0.019,-0.122) [yshift=5.0pt] {5,6};
|
||||||
|
\node[dlbl] at (0.043,-0.086) [yshift=5.0pt] {1,2};
|
||||||
|
\node[dlbl] at (-0.011,-0.098) [yshift=5.0pt] {3,13};
|
||||||
|
\node[dlbl] at (-0.033,-0.027) [yshift=5.0pt] {0};
|
||||||
|
\node[dlbl] at (-0.060,-0.134) [yshift=5.0pt] {12};
|
||||||
|
\node[dlbl] at (-0.018,-0.130) [yshift=5.0pt] {4,11};
|
||||||
|
\node[dlbl] at (-0.008,-0.149) [yshift=5.0pt] {9,10};
|
||||||
|
\node[dlbl] at (0.003,-0.167) [yshift=5.0pt] {9,10};
|
||||||
|
\node[dlbl] at (0.021,-0.197) [yshift=5.0pt] {8,15};
|
||||||
|
\node[dlbl] at (0.025,-0.260) [yshift=5.0pt] {7,16};
|
||||||
|
\draw[cut] (-0.075,-0.032)--(-0.101,-0.097);
|
||||||
|
\node[cutlbl] at (-0.120,-0.142) {cut 1};
|
||||||
|
\draw[cut] (-0.026,-0.044)--(-0.020,-0.114);
|
||||||
|
\node[cutlbl] at (-0.016,-0.162) {cut 2};
|
||||||
|
\draw[cut] (0.058,-0.138)--(0.041,-0.070);
|
||||||
|
\node[cutlbl] at (0.030,-0.023) {cut 3};
|
||||||
|
\draw[cut] (-0.004,-0.102)--(0.016,-0.169);
|
||||||
|
\node[cutlbl] at (0.029,-0.217) {cut 4};
|
||||||
|
\draw[cut] (0.085,-0.146)--(0.034,-0.097);
|
||||||
|
\node[cutlbl] at (-0.001,-0.063) {cut 5};
|
||||||
|
\draw[cut] (0.035,-0.212)--(0.035,-0.142);
|
||||||
|
\node[cutlbl] at (0.034,-0.093) {cut 6};
|
||||||
|
\draw[cut] (0.079,-0.183)--(0.144,-0.158);
|
||||||
|
\node[cutlbl] at (0.190,-0.142) {cut 7};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize medial graph $M(G)$ at edge midpoints}
|
||||||
|
\end{tabular}
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
% whole medial graph: n=20 seed=72 source=9 recognised treads=[2] |M(G)|=54
|
||||||
|
\begin{tabular}{c}
|
||||||
|
\begin{tikzpicture}[scale=4.06,
|
||||||
|
sedge/.style={black!50, line width=0.35pt},
|
||||||
|
sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},
|
||||||
|
srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.124,-0.234);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.124,-0.234);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.292,-0.291)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.233,-0.249)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.233,-0.249)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (-0.170,-0.348)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (0.151,-0.341)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (-0.218,-0.346)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.221,-0.241)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (0.146,-0.243)--(0.124,-0.234);
|
||||||
|
\node[sv] at (0.000,0.433) {};
|
||||||
|
\node[sv] at (-0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.027,-0.257) {};
|
||||||
|
\node[sv] at (0.199,-0.203) {};
|
||||||
|
\node[sv] at (0.012,-0.355) {};
|
||||||
|
\node[sv] at (0.234,-0.280) {};
|
||||||
|
\node[sv] at (0.292,-0.291) {};
|
||||||
|
\node[sv] at (0.233,-0.249) {};
|
||||||
|
\node[srcv] at (-0.170,-0.348) {};
|
||||||
|
\node[sv] at (0.151,-0.341) {};
|
||||||
|
\node[sv] at (0.254,-0.323) {};
|
||||||
|
\node[sv] at (-0.218,-0.346) {};
|
||||||
|
\node[sv] at (0.221,-0.241) {};
|
||||||
|
\node[sv] at (0.063,-0.317) {};
|
||||||
|
\node[sv] at (0.146,-0.243) {};
|
||||||
|
\node[sv] at (0.218,-0.231) {};
|
||||||
|
\node[sv] at (-0.158,-0.086) {};
|
||||||
|
\node[sv] at (0.124,-0.234) {};
|
||||||
|
\node[sv] at (-0.230,-0.345) {};
|
||||||
|
\node[font=\scriptsize, text=blue!70!black] at (-0.170,-0.433) {source 9};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize source graph $G$}
|
||||||
|
\\[1.0ex]
|
||||||
|
\begin{tikzpicture}[scale=7.0,
|
||||||
|
base/.style={black!12, line width=0.25pt},
|
||||||
|
med/.style={black!38, line width=0.32pt},
|
||||||
|
annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},
|
||||||
|
levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},
|
||||||
|
levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},
|
||||||
|
levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},
|
||||||
|
knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},
|
||||||
|
elbl/.style={font=\tiny, text=black!70, inner sep=0.2pt},
|
||||||
|
dlbl/.style={font=\tiny\bfseries, text=black, inner sep=0.5pt},
|
||||||
|
cut/.style={red!80!black, line width=1.0pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[base] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (0.000,0.433)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.158,-0.086);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.170,-0.348);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.218,-0.346);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.158,-0.086);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.012,-0.355);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.170,-0.348);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.218,-0.346);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.063,-0.317);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.158,-0.086);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.124,-0.234);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.218,-0.231);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.124,-0.234);
|
||||||
|
\draw[base] (0.012,-0.355)--(-0.170,-0.348);
|
||||||
|
\draw[base] (0.012,-0.355)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.012,-0.355)--(0.063,-0.317);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.292,-0.291)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.233,-0.249)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.233,-0.249)--(0.218,-0.231);
|
||||||
|
\draw[base] (-0.170,-0.348)--(-0.218,-0.346);
|
||||||
|
\draw[base] (0.151,-0.341)--(0.063,-0.317);
|
||||||
|
\draw[base] (-0.218,-0.346)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.221,-0.241)--(0.218,-0.231);
|
||||||
|
\draw[base] (0.146,-0.243)--(0.124,-0.234);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.079,0.174);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.250,0.000);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.329,-0.259);
|
||||||
|
\draw[med] (0.250,0.000)--(0.100,0.115);
|
||||||
|
\draw[med] (0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (0.250,0.000)--(0.350,-0.318);
|
||||||
|
\draw[med] (0.014,0.088)--(-0.079,0.174);
|
||||||
|
\draw[med] (0.014,0.088)--(0.100,0.115);
|
||||||
|
\draw[med] (0.014,0.088)--(0.113,-0.230);
|
||||||
|
\draw[med] (0.014,0.088)--(-0.065,-0.171);
|
||||||
|
\draw[med] (0.100,0.115)--(0.350,-0.318);
|
||||||
|
\draw[med] (0.100,0.115)--(0.113,-0.230);
|
||||||
|
\draw[med] (-0.079,0.174)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.079,0.174)--(-0.329,-0.259);
|
||||||
|
\draw[med] (0.000,-0.433)--(-0.244,-0.394);
|
||||||
|
\draw[med] (0.000,-0.433)--(0.256,-0.394);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.365,-0.389);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.329,-0.259);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.244,-0.394)--(-0.335,-0.390);
|
||||||
|
\draw[med] (-0.244,-0.394)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.244,-0.394)--(0.256,-0.394);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.359,-0.389);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.365,-0.389);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.224,-0.345);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.329,-0.259)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.365,-0.389)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.365,-0.389)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.377,-0.378);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.325,-0.387);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.089,-0.299);
|
||||||
|
\draw[med] (0.350,-0.318)--(0.396,-0.362);
|
||||||
|
\draw[med] (0.350,-0.318)--(0.245,-0.247);
|
||||||
|
\draw[med] (0.256,-0.394)--(0.325,-0.387);
|
||||||
|
\draw[med] (0.256,-0.394)--(0.081,-0.348);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.396,-0.362);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.377,-0.378);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.244,-0.302);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.263,-0.286);
|
||||||
|
\draw[med] (0.396,-0.362)--(0.263,-0.286);
|
||||||
|
\draw[med] (0.396,-0.362)--(0.245,-0.247);
|
||||||
|
\draw[med] (0.325,-0.387)--(0.081,-0.348);
|
||||||
|
\draw[med] (0.325,-0.387)--(0.089,-0.299);
|
||||||
|
\draw[med] (0.377,-0.378)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.377,-0.378)--(0.244,-0.302);
|
||||||
|
\draw[med] (0.113,-0.230)--(0.076,-0.246);
|
||||||
|
\draw[med] (0.113,-0.230)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.019,-0.306)--(-0.071,-0.302);
|
||||||
|
\draw[med] (0.019,-0.306)--(0.045,-0.287);
|
||||||
|
\draw[med] (0.019,-0.306)--(-0.079,-0.351);
|
||||||
|
\draw[med] (0.019,-0.306)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.087,-0.250);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.244,-0.302);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.096,-0.301);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.194,-0.347);
|
||||||
|
\draw[med] (0.089,-0.299)--(0.045,-0.287);
|
||||||
|
\draw[med] (0.089,-0.299)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.140,-0.290)--(0.244,-0.302);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.045,-0.287)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.045,-0.287)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.076,-0.246);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.076,-0.246)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.076,-0.246)--(0.135,-0.239);
|
||||||
|
\draw[med] (-0.102,-0.301)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.173,-0.223);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.210,-0.222);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.245,-0.247)--(0.216,-0.226);
|
||||||
|
\draw[med] (0.245,-0.247)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.209,-0.217);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.209,-0.217);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.209,-0.217)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.209,-0.217)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.162,-0.218)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.081,-0.348)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.081,-0.348)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.038,-0.336)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.263,-0.286)--(0.233,-0.265);
|
||||||
|
\draw[med] (0.263,-0.286)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.227,-0.245);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.227,-0.260)--(0.227,-0.245);
|
||||||
|
\draw[med] (0.227,-0.245)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.227,-0.245)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.225,-0.240)--(0.219,-0.236);
|
||||||
|
\node[annv] at (-0.250,0.000) {};
|
||||||
|
\node[levtwo] at (0.250,0.000) {};
|
||||||
|
\node[annv] at (0.014,0.088) {};
|
||||||
|
\node[levtwo] at (0.100,0.115) {};
|
||||||
|
\node[levtwo] at (-0.079,0.174) {};
|
||||||
|
\node[annv] at (0.000,-0.433) {};
|
||||||
|
\node[levone] at (-0.236,-0.345) {};
|
||||||
|
\node[levone] at (-0.244,-0.394) {};
|
||||||
|
\node[annv] at (-0.335,-0.390) {};
|
||||||
|
\node[levone] at (-0.359,-0.389) {};
|
||||||
|
\node[annv] at (-0.329,-0.259) {};
|
||||||
|
\node[annv] at (-0.365,-0.389) {};
|
||||||
|
\node[annv] at (0.264,-0.345) {};
|
||||||
|
\node[knownv] at (0.350,-0.318) {};
|
||||||
|
\node[annv] at (0.256,-0.394) {};
|
||||||
|
\node[knownv] at (0.367,-0.357) {};
|
||||||
|
\node[annv] at (0.396,-0.362) {};
|
||||||
|
\node[annv] at (0.113,-0.230) {};
|
||||||
|
\node[levone] at (0.019,-0.306) {};
|
||||||
|
\node[annv] at (0.131,-0.268) {};
|
||||||
|
\node[annv] at (-0.071,-0.302) {};
|
||||||
|
\node[knownv] at (0.217,-0.241) {};
|
||||||
|
\node[annv] at (0.245,-0.247) {};
|
||||||
|
\node[annv] at (0.216,-0.226) {};
|
||||||
|
\node[annv] at (-0.079,-0.351) {};
|
||||||
|
\node[annv] at (0.263,-0.286) {};
|
||||||
|
\node[annv] at (0.233,-0.265) {};
|
||||||
|
\node[knownv] at (0.262,-0.270) {};
|
||||||
|
\node[levtwo] at (0.325,-0.387) {};
|
||||||
|
\node[annv] at (0.089,-0.299) {};
|
||||||
|
\node[annv] at (0.081,-0.348) {};
|
||||||
|
\node[levtwo] at (0.107,-0.329) {};
|
||||||
|
\node[levtwo] at (0.377,-0.378) {};
|
||||||
|
\node[annv] at (0.140,-0.290) {};
|
||||||
|
\node[levtwo] at (0.244,-0.302) {};
|
||||||
|
\node[levone] at (-0.096,-0.301) {};
|
||||||
|
\node[annv] at (-0.194,-0.347) {};
|
||||||
|
\node[annv] at (-0.224,-0.345) {};
|
||||||
|
\node[annv] at (0.210,-0.222) {};
|
||||||
|
\node[annv] at (0.227,-0.260) {};
|
||||||
|
\node[knownv] at (0.227,-0.245) {};
|
||||||
|
\node[knownv] at (0.219,-0.236) {};
|
||||||
|
\node[annv] at (0.045,-0.287) {};
|
||||||
|
\node[annv] at (0.038,-0.336) {};
|
||||||
|
\node[annv] at (0.087,-0.250) {};
|
||||||
|
\node[levtwo] at (0.173,-0.223) {};
|
||||||
|
\node[levtwo] at (0.190,-0.262) {};
|
||||||
|
\node[levtwo] at (0.135,-0.239) {};
|
||||||
|
\node[annv] at (0.209,-0.217) {};
|
||||||
|
\node[knownv] at (0.225,-0.240) {};
|
||||||
|
\node[annv] at (-0.065,-0.171) {};
|
||||||
|
\node[annv] at (0.076,-0.246) {};
|
||||||
|
\node[levtwo] at (0.162,-0.218) {};
|
||||||
|
\node[annv] at (-0.102,-0.301) {};
|
||||||
|
\node[elbl] at (-0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!1$};
|
||||||
|
\node[elbl] at (0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.014,0.088) [yshift=-4.8pt] {$0\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.100,0.115) [yshift=-4.8pt] {$0\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.079,0.174) [yshift=-4.8pt] {$0\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.000,-0.433) [yshift=-4.8pt] {$1\!{-}\!2$};
|
||||||
|
\node[elbl] at (-0.236,-0.345) [yshift=-4.8pt] {$1\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.244,-0.394) [yshift=-4.8pt] {$1\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.335,-0.390) [yshift=-4.8pt] {$1\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.359,-0.389) [yshift=-4.8pt] {$1\!{-}\!12$};
|
||||||
|
\node[elbl] at (-0.329,-0.259) [yshift=-4.8pt] {$1\!{-}\!17$};
|
||||||
|
\node[elbl] at (-0.365,-0.389) [yshift=-4.8pt] {$1\!{-}\!19$};
|
||||||
|
\node[elbl] at (0.264,-0.345) [yshift=-4.8pt] {$2\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.350,-0.318) [yshift=-4.8pt] {$2\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.256,-0.394) [yshift=-4.8pt] {$2\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.367,-0.357) [yshift=-4.8pt] {$2\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.396,-0.362) [yshift=-4.8pt] {$2\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.113,-0.230) [yshift=-4.8pt] {$3\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.019,-0.306) [yshift=-4.8pt] {$3\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.131,-0.268) [yshift=-4.8pt] {$3\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.071,-0.302) [yshift=-4.8pt] {$3\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.217,-0.241) [yshift=-4.8pt] {$4\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.245,-0.247) [yshift=-4.8pt] {$4\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.216,-0.226) [yshift=-4.8pt] {$4\!{-}\!8$};
|
||||||
|
\node[elbl] at (-0.079,-0.351) [yshift=-4.8pt] {$5\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.263,-0.286) [yshift=-4.8pt] {$6\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.233,-0.265) [yshift=-4.8pt] {$6\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.262,-0.270) [yshift=-4.8pt] {$7\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.325,-0.387) [yshift=-4.8pt] {$10\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.089,-0.299) [yshift=-4.8pt] {$10\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.081,-0.348) [yshift=-4.8pt] {$10\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.107,-0.329) [yshift=-4.8pt] {$10\!{-}\!14$};
|
||||||
|
\node[elbl] at (0.377,-0.378) [yshift=-4.8pt] {$11\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.140,-0.290) [yshift=-4.8pt] {$11\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.244,-0.302) [yshift=-4.8pt] {$11\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.096,-0.301) [yshift=-4.8pt] {$12\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.194,-0.347) [yshift=-4.8pt] {$12\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.224,-0.345) [yshift=-4.8pt] {$12\!{-}\!19$};
|
||||||
|
\node[elbl] at (0.210,-0.222) [yshift=-4.8pt] {$13\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.227,-0.260) [yshift=-4.8pt] {$13\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.227,-0.245) [yshift=-4.8pt] {$13\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.219,-0.236) [yshift=-4.8pt] {$13\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.045,-0.287) [yshift=-4.8pt] {$14\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.038,-0.336) [yshift=-4.8pt] {$14\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.087,-0.250) [yshift=-4.8pt] {$15\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.173,-0.223) [yshift=-4.8pt] {$15\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.190,-0.262) [yshift=-4.8pt] {$15\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.135,-0.239) [yshift=-4.8pt] {$15\!{-}\!18$};
|
||||||
|
\node[elbl] at (0.209,-0.217) [yshift=-4.8pt] {$16\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.225,-0.240) [yshift=-4.8pt] {$16\!{-}\!8$};
|
||||||
|
\node[elbl] at (-0.065,-0.171) [yshift=-4.8pt] {$17\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.076,-0.246) [yshift=-4.8pt] {$18\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.162,-0.218) [yshift=-4.8pt] {$18\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.102,-0.301) [yshift=-4.8pt] {$19\!{-}\!3$};
|
||||||
|
\node[dlbl] at (0.350,-0.318) [yshift=5.0pt] {6};
|
||||||
|
\node[dlbl] at (0.367,-0.357) [yshift=5.0pt] {7};
|
||||||
|
\node[dlbl] at (0.217,-0.241) [yshift=5.0pt] {0};
|
||||||
|
\node[dlbl] at (0.262,-0.270) [yshift=5.0pt] {2,3};
|
||||||
|
\node[dlbl] at (0.227,-0.245) [yshift=5.0pt] {1};
|
||||||
|
\node[dlbl] at (0.219,-0.236) [yshift=5.0pt] {5};
|
||||||
|
\node[dlbl] at (0.225,-0.240) [yshift=5.0pt] {4};
|
||||||
|
\draw[cut] (0.241,-0.204)--(0.180,-0.239);
|
||||||
|
\node[cutlbl] at (0.137,-0.263) {cut 1};
|
||||||
|
\draw[cut] (0.256,-0.320)--(0.269,-0.251);
|
||||||
|
\node[cutlbl] at (0.279,-0.203) {cut 2};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize medial graph $M(G)$ at edge midpoints}
|
||||||
|
\end{tabular}
|
||||||
@@ -178,19 +178,17 @@ def extract_tread(faces, levels, d):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def annular_cycle_order(M: nx.Graph, annular: set):
|
def _cycle_order(sub: nx.Graph, comp):
|
||||||
"""Cyclic order of the annular medial vertices (they induce a cycle)."""
|
"""Cyclic order of a 2-regular connected component ``comp`` of ``sub``;
|
||||||
sub = M.subgraph(annular)
|
None if it is not a single simple cycle of length >= 3."""
|
||||||
if sub.number_of_nodes() == 0 or any(sub.degree(v) != 2 for v in sub):
|
csub = sub.subgraph(comp)
|
||||||
|
if csub.number_of_nodes() < 3 or any(csub.degree(v) != 2 for v in csub):
|
||||||
return None
|
return None
|
||||||
if not nx.is_connected(sub):
|
start = next(iter(comp))
|
||||||
return None
|
|
||||||
start = next(iter(annular))
|
|
||||||
order = [start]
|
order = [start]
|
||||||
prev = None
|
prev, cur = None, start
|
||||||
cur = start
|
|
||||||
while True:
|
while True:
|
||||||
nbrs = [w for w in sub.neighbors(cur) if w != prev]
|
nbrs = [w for w in csub.neighbors(cur) if w != prev]
|
||||||
if not nbrs:
|
if not nbrs:
|
||||||
break
|
break
|
||||||
nxt = nbrs[0]
|
nxt = nbrs[0]
|
||||||
@@ -198,7 +196,33 @@ def annular_cycle_order(M: nx.Graph, annular: set):
|
|||||||
break
|
break
|
||||||
order.append(nxt)
|
order.append(nxt)
|
||||||
prev, cur = cur, nxt
|
prev, cur = cur, nxt
|
||||||
return order if len(order) == len(annular) else None
|
return order if len(order) == csub.number_of_nodes() else None
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cycle_order(M: nx.Graph, annular: set):
|
||||||
|
"""Cyclic order of the annular medial vertices when they induce a *single*
|
||||||
|
cycle; None otherwise. See ``annular_cycle_components`` for the
|
||||||
|
multi-component case."""
|
||||||
|
sub = M.subgraph(annular)
|
||||||
|
if not annular or not nx.is_connected(sub):
|
||||||
|
return None
|
||||||
|
return _cycle_order(sub, set(annular))
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cycle_components(M: nx.Graph, annular: set):
|
||||||
|
"""Cyclic orders of the annular medial vertices, one per connected
|
||||||
|
component of the annular subgraph.
|
||||||
|
|
||||||
|
A tread's annular frontier may split into several disjoint cycles (one per
|
||||||
|
boundary component); each is its own full medial tire graph. Components
|
||||||
|
that are not a single simple cycle of length >= 3 are skipped."""
|
||||||
|
sub = M.subgraph(annular)
|
||||||
|
orders = []
|
||||||
|
for comp in nx.connected_components(sub):
|
||||||
|
order = _cycle_order(sub, comp)
|
||||||
|
if order is not None:
|
||||||
|
orders.append(order)
|
||||||
|
return orders
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
@@ -226,38 +250,35 @@ def _linear_cut(n, bite_pairs):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def recognise(M, tread):
|
def _recognise_one(M, order, up, ann_global):
|
||||||
"""Return (FullMedialTireGraph, bijection fmt-name -> medial vertex) or None.
|
"""Recognise a single annular cycle (given as the cyclic order of its
|
||||||
|
medial vertices) as a ``FullMedialTireGraph``.
|
||||||
|
|
||||||
``M`` here is the tread-face model M(T) (cycle + teeth + bites)."""
|
``up`` is the tread's up-edge medial-vertex set; ``ann_global`` is the full
|
||||||
annular = tread["annular"]
|
annular set of the tread (used to exclude annular vertices, including those
|
||||||
order = annular_cycle_order(M, annular)
|
of *other* components, when picking each cycle edge's apex). Returns
|
||||||
if order is None or len(order) < 3:
|
``(g, bij)`` or None."""
|
||||||
return None
|
|
||||||
n = len(order)
|
n = len(order)
|
||||||
ann_set = set(annular)
|
if n < 3:
|
||||||
|
return None
|
||||||
|
ann_set = set(order)
|
||||||
|
|
||||||
apex_of_edge = []
|
apex_of_edge = []
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
a, b = order[i], order[(i + 1) % 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]
|
common = [w for w in set(M.neighbors(a)) & set(M.neighbors(b))
|
||||||
|
if w not in ann_global]
|
||||||
if len(common) != 1:
|
if len(common) != 1:
|
||||||
return None
|
return None
|
||||||
apex_of_edge.append(common[0])
|
apex_of_edge.append(common[0])
|
||||||
|
|
||||||
up = set(tread["up"])
|
|
||||||
# bite apex: serves two cycle edges (== adjacent to four annular vertices)
|
# bite apex: serves two cycle edges (== adjacent to four annular vertices)
|
||||||
apex_positions = defaultdict(list)
|
apex_positions = defaultdict(list)
|
||||||
for i, ap in enumerate(apex_of_edge):
|
for i, ap in enumerate(apex_of_edge):
|
||||||
apex_positions[ap].append(i)
|
apex_positions[ap].append(i)
|
||||||
|
bite_pairs = [tuple(sorted(positions))
|
||||||
tooth = []
|
for positions in apex_positions.values() if len(positions) == 2]
|
||||||
bite_pairs = []
|
tooth = ["U" if ap in up else "D" for ap in apex_of_edge]
|
||||||
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)
|
cut = _linear_cut(n, bite_pairs)
|
||||||
if cut is None:
|
if cut is None:
|
||||||
@@ -279,14 +300,35 @@ def recognise(M, tread):
|
|||||||
for (i, j) in sorted(g.bites):
|
for (i, j) in sorted(g.bites):
|
||||||
bij[f"p{i}_{j}"] = apex_of_edge[(i + r) % n]
|
bij[f"p{i}_{j}"] = apex_of_edge[(i + r) % n]
|
||||||
|
|
||||||
# verify the reconstructed graph is edge-faithful to the tread-face M(T)
|
# verify the reconstructed graph is edge-faithful to this cycle's sub-model
|
||||||
mt_edges = {ekey(*e) for e in M.edges()}
|
# (its annular vertices together with their tooth apexes).
|
||||||
|
sub_nodes = ann_set | set(apex_of_edge)
|
||||||
|
sub_edges = {ekey(*e) for e in M.subgraph(sub_nodes).edges()}
|
||||||
rec_edges = {ekey(bij[u], bij[v]) for u, v in g.edges()}
|
rec_edges = {ekey(bij[u], bij[v]) for u, v in g.edges()}
|
||||||
if rec_edges != mt_edges:
|
if rec_edges != sub_edges:
|
||||||
return None
|
return None
|
||||||
return g, bij
|
return g, bij
|
||||||
|
|
||||||
|
|
||||||
|
def recognise(M, tread):
|
||||||
|
"""Recognise the tread's medial-tire structure.
|
||||||
|
|
||||||
|
A tread's annular frontier may be several disjoint cycles, each its own
|
||||||
|
full medial tire graph. Returns a list of ``(FullMedialTireGraph,
|
||||||
|
bijection fmt-name -> medial vertex)`` -- one per annular cycle component
|
||||||
|
that recognises -- or ``[]`` if none do.
|
||||||
|
|
||||||
|
``M`` here is the tread-face model M(T) (cycle(s) + teeth + bites)."""
|
||||||
|
up = set(tread["up"])
|
||||||
|
ann_global = set(tread["annular"])
|
||||||
|
tires = []
|
||||||
|
for order in annular_cycle_components(M, tread["annular"]):
|
||||||
|
rec = _recognise_one(M, order, up, ann_global)
|
||||||
|
if rec is not None:
|
||||||
|
tires.append(rec)
|
||||||
|
return tires
|
||||||
|
|
||||||
|
|
||||||
def canonical(coloring, ordered):
|
def canonical(coloring, ordered):
|
||||||
remap, out = {}, []
|
remap, out = {}, []
|
||||||
for v in ordered:
|
for v in ordered:
|
||||||
@@ -341,32 +383,29 @@ def iter_pieces(seed: int, color_limit: int = 400000):
|
|||||||
if tread is None or len(tread["up"]) < 3:
|
if tread is None or len(tread["up"]) < 3:
|
||||||
continue
|
continue
|
||||||
mt = medial_tire_facemodel(tread["tread_faces"])
|
mt = medial_tire_facemodel(tread["tread_faces"])
|
||||||
rec = recognise(mt, tread)
|
for comp, (g, bij) in enumerate(recognise(mt, tread)):
|
||||||
if rec is None:
|
mt_nodes = list(bij.values())
|
||||||
continue
|
name_of = {v: k for k, v in bij.items()}
|
||||||
g, bij = rec
|
|
||||||
mt_nodes = list(bij.values())
|
|
||||||
name_of = {v: k for k, v in bij.items()}
|
|
||||||
|
|
||||||
realized = set()
|
realized = set()
|
||||||
for col in global_colorings:
|
for col in global_colorings:
|
||||||
realized.add(canonical({v: col[v] for v in mt_nodes}, mt_nodes))
|
realized.add(canonical({v: col[v] for v in mt_nodes}, mt_nodes))
|
||||||
|
|
||||||
colorings = []
|
colorings = []
|
||||||
seen = set()
|
seen = set()
|
||||||
for col in proper_3_colorings_subgraph(mt, mt_nodes):
|
for col in proper_3_colorings_subgraph(mt, mt_nodes):
|
||||||
key = canonical(col, mt_nodes)
|
key = canonical(col, mt_nodes)
|
||||||
if key in seen:
|
if key in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
fmt_col = {name_of[v]: c for v, c in col.items()}
|
fmt_col = {name_of[v]: c for v, c in col.items()}
|
||||||
balanced = kempe_classify(g, fmt_col).valid
|
balanced = kempe_classify(g, fmt_col).valid
|
||||||
is_real = key in realized
|
is_real = key in realized
|
||||||
cat = ("Invalid" if not balanced
|
cat = ("Invalid" if not balanced
|
||||||
else "Realized" if is_real else "Unrealized")
|
else "Realized" if is_real else "Unrealized")
|
||||||
colorings.append((fmt_col, cat))
|
colorings.append((fmt_col, cat))
|
||||||
meta = {"source": s, "tread": d}
|
meta = {"source": s, "tread": d, "comp": comp}
|
||||||
yield (meta, g, colorings)
|
yield (meta, g, colorings)
|
||||||
|
|
||||||
|
|
||||||
def analyse(seed: int, color_limit: int = 400000):
|
def analyse(seed: int, color_limit: int = 400000):
|
||||||
|
|||||||