Draw the whole medial graph with all tire cuts
Add a --whole mode to draw_medial_tire_cut.py that renders the entire medial graph M(G) (the assembled cut graph), on a Kamada-Kawai layout, with the recognised tires highlighted (black annular vertices, blue/red teeth carrying walk depths, larger red bite apex) and the rest of M(G) in grey. Add the resulting figure (Figure 3) and a describing paragraph to the paper for the n=20 seed-72 example, via an \input-ed .tikz file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,23 +2,29 @@
|
||||
|
||||
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 a TikZ ``tikzpicture`` (walk-depth labels + cut slits) for each
|
||||
recognised full medial tire graph of the decomposition, using ``to_tikz`` from
|
||||
``medial_tire_cut_labelling``.
|
||||
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 the whole
|
||||
medial graph M(G) with every tire's cuts applied, on a Kamada--Kawai layout, the
|
||||
recognised tires highlighted and the rest of M(G) in grey.
|
||||
|
||||
This script only renders; the experiment itself draws nothing. Run with the
|
||||
repo venv (networkx): ``.venv/bin/python``.
|
||||
|
||||
Example:
|
||||
Examples:
|
||||
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 72 > panels.tex
|
||||
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 72 --whole > whole.tex
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
import networkx as nx
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _HERE)
|
||||
|
||||
@@ -38,14 +44,102 @@ def tikz_panels(n: int, seed: int, scale: float = 1.6) -> tuple[dict, list[str]]
|
||||
return result, panels
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# The whole medial graph: M(G) with all tire cuts applied.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def _is_split(node) -> bool:
|
||||
return isinstance(node, tuple) and len(node) == 3 and node[1] in ("A", "B")
|
||||
|
||||
|
||||
def _medial_layout(H: nx.Graph) -> dict:
|
||||
"""A Kamada--Kawai layout of the (planar) cut graph, normalised to the unit
|
||||
box. The two copies of a cut vertex have different neighbours, so the layout
|
||||
separates them automatically, showing the slit."""
|
||||
pos = nx.kamada_kawai_layout(H)
|
||||
xs = [p[0] for p in pos.values()]
|
||||
ys = [p[1] for p in pos.values()]
|
||||
cx, cy = 0.5 * (max(xs) + min(xs)), 0.5 * (max(ys) + min(ys))
|
||||
span = max(max(xs) - min(xs), max(ys) - min(ys)) or 1.0
|
||||
return {v: ((p[0] - cx) / span, (p[1] - cy) / span) for v, p in pos.items()}
|
||||
|
||||
|
||||
def medial_tikz(result: dict, scale: float = 9.0) -> str:
|
||||
"""A TikZ ``tikzpicture`` of the whole medial graph M(G) with every tire's
|
||||
cuts applied. Tire teeth are coloured and carry their walk depth; annular
|
||||
medial vertices are black; medial vertices outside any recognised tire are
|
||||
grey; cut (split) vertices are drawn as separated copies."""
|
||||
H = result["cut_graph"]
|
||||
pos = _medial_layout(H)
|
||||
|
||||
# role of each medial vertex: annular / up / down / bite, and walk depth.
|
||||
annular = set()
|
||||
for d in sorted(result["results"]):
|
||||
g, bij = result["results"][d]["g"], result["results"][d]["bij"]
|
||||
annular.update(bij[f"a{k}"] for k in range(g.n))
|
||||
apex = {r["apex"]: (r["role"], r["walk"]) for r in result["labels"]}
|
||||
|
||||
def edge_of(node):
|
||||
return node[0] if _is_split(node) else node
|
||||
|
||||
L = []
|
||||
A = L.append
|
||||
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||
A(" med/.style={black!30, line width=0.3pt},")
|
||||
A(" grey/.style={circle, draw=black!45, fill=black!8, inner sep=0.9pt},")
|
||||
A(" ann/.style={circle, fill=black, inner sep=1.0pt},")
|
||||
A(" cutv/.style={circle, draw=red!75!black, fill=red!12, inner sep=1.0pt},")
|
||||
A(" upv/.style={circle, draw=blue!70!black, fill=blue!15, inner sep=1.3pt},")
|
||||
A(" downv/.style={circle, draw=red!70!black, fill=red!15, inner sep=1.3pt},")
|
||||
A(" bitev/.style={circle, draw=red!70!black, fill=red!35, inner sep=1.6pt},")
|
||||
A(" dlbl/.style={font=\\tiny\\bfseries, text=black, inner sep=0.5pt}]")
|
||||
|
||||
def pt(node):
|
||||
x, y = pos[node]
|
||||
return f"({x:.3f},{y:.3f})"
|
||||
|
||||
for u, v in H.edges():
|
||||
A(f"\\draw[med] {pt(u)}--{pt(v)};")
|
||||
for node in H.nodes():
|
||||
mv = edge_of(node)
|
||||
if mv in apex:
|
||||
role, _ = apex[mv]
|
||||
style = {"up": "upv", "down": "downv", "bite": "bitev"}[role]
|
||||
elif mv in annular:
|
||||
style = "cutv" if _is_split(node) else "ann"
|
||||
else:
|
||||
style = "grey"
|
||||
A(f"\\node[{style}] at {pt(node)} {{}};")
|
||||
for node in H.nodes():
|
||||
mv = edge_of(node)
|
||||
if _is_split(node) or mv not in apex:
|
||||
continue
|
||||
x, y = pos[node]
|
||||
A(f"\\node[dlbl] at ({x:.3f},{y:.3f}) [yshift=4.5pt] {{{apex[mv][1]}}};")
|
||||
A("\\end{tikzpicture}")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
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("--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)
|
||||
treads = sorted(result["results"])
|
||||
print(f"% whole medial graph: n={args.n} seed={args.seed} "
|
||||
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 9.0))
|
||||
return
|
||||
|
||||
result, panels = tikz_panels(args.n, args.seed, scale=args.scale)
|
||||
treads = sorted(result["results"])
|
||||
print(f"% medial tire cut: n={args.n} seed={args.seed} "
|
||||
|
||||
Reference in New Issue
Block a user