coloring_nested_tire_graphs: add figure of partial tire dual

Adds fig_partial_tire_dual.png next to Definition 1.7 (Partial tire
dual), illustrating the corona-graph structure C_{n+m} ∘ K_1 from
Proposition 1.8 on a concrete m=6, k=4 spoke-only tire.

Drawing details:
- d_f interior vertices (purple squares) at centroids of annular
  triangles; the 10 of them form a 10-cycle (solid purple edges).
- B_out leaves (orange diamonds) placed outside B_out; the leaf edge
  is dashed orange.
- B_in leaves placed on the central-hole side of each red edge, at
  a fixed small offset past the midpoint of the edge in the
  d_f -> midpoint direction.  This positions each leaf edge to
  cross its red edge exactly at the midpoint while keeping the leaf
  vertex itself inside the central hole and off the border.

Generator: experiments/draw_partial_tire_dual.py (also committed).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 18:37:19 -04:00
parent e4216ec7a2
commit e212ada649
7 changed files with 275 additions and 31 deletions
@@ -0,0 +1,221 @@
"""Draw a partial tire dual on top of a small tire graph.
Uses a spoke-only tire with m=6 outer cycle, k=4 inner cycle, no
chords. Overlays the partial tire dual:
- d_f vertices at centroids of annular triangles (purple squares).
- Leaf vertices for B_out edges (orange diamonds, outside the tire).
- Leaf vertices for B_in edges (orange diamonds, inside the inner hole).
- Interior dual edges (dashed purple).
- Leaf edges (dashed orange).
"""
import math
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from tire_graph import random_tire, planar_positions
def draw_partial_tire_dual(tire, filename):
m, k = tire['m'], tire['k']
outer, inner = tire['outer'], tire['inner']
triangles = tire['triangles']
lattice_path = tire['lattice_path']
R_out, R_in = 1.0, 0.45
pos = planar_positions(tire, R_out=R_out, R_in=R_in)
fig, ax = plt.subplots(figsize=(9, 9))
# === Draw underlying tire graph faintly ===
outer_set = set(outer)
inner_set = set(inner)
for u, v in tire['edges']:
x1, y1 = pos[u]; x2, y2 = pos[v]
if u in outer_set and v in outer_set:
color = '#a8c9e8'; lw = 2.0
elif u in inner_set and v in inner_set:
color = '#e8a8a8'; lw = 2.0
else:
color = '#cccccc'; lw = 1.0
ax.plot([x1, x2], [y1, y2], color=color, linewidth=lw, zorder=1)
for v in outer:
x, y = pos[v]
ax.plot(x, y, 'o', color='#a8c9e8', markersize=14, zorder=2)
ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
fontsize=8, fontweight='bold', zorder=3)
for v in inner:
x, y = pos[v]
ax.plot(x, y, 'o', color='#e8a8a8', markersize=12, zorder=2)
ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
fontsize=7, fontweight='bold', zorder=3)
# === Reconstruct annular triangles in cyclic order ===
# The triangles list from random_tire gives them in lattice-path order.
print(f"Lattice path: {lattice_path}")
print(f"Triangles: {triangles}")
n_tri = len(triangles)
assert n_tri == m + k, f"expected {m+k} triangles, got {n_tri}"
# Compute centroid of each triangle
centroids = []
for tri in triangles:
v1, v2, v3 = tri
cx = (pos[v1][0] + pos[v2][0] + pos[v3][0]) / 3
cy = (pos[v1][1] + pos[v2][1] + pos[v3][1]) / 3
centroids.append((cx, cy))
# === Compute leaves and which annular face each is incident to ===
# For each B_out edge: find which annular triangle contains it.
# For each B_in edge: similarly.
from collections import defaultdict
bout_edges = [frozenset({outer[i], outer[(i+1) % m]}) for i in range(m)]
bin_edges = [frozenset({inner[j], inner[(j+1) % k]}) for j in range(k)]
# Map: edge -> list of triangle indices containing it
edge_to_tris = defaultdict(list)
for t_idx, tri in enumerate(triangles):
e1 = frozenset({tri[0], tri[1]})
e2 = frozenset({tri[1], tri[2]})
e3 = frozenset({tri[0], tri[2]})
for e in (e1, e2, e3):
edge_to_tris[e].append(t_idx)
bout_leaves = [] # list of (edge, tri_idx, leaf_position)
for e in bout_edges:
tris_with_e = edge_to_tris[e]
assert len(tris_with_e) == 1, \
f"B_out edge {e} should be in exactly 1 annular triangle"
t_idx = tris_with_e[0]
u, v = list(e)
midx = (pos[u][0] + pos[v][0]) / 2
midy = (pos[u][1] + pos[v][1]) / 2
# Push outward
d = math.sqrt(midx**2 + midy**2)
push = 1.18
leaf_pos = (midx * push / d, midy * push / d)
bout_leaves.append((e, t_idx, leaf_pos))
bin_leaves = []
for e in bin_edges:
tris_with_e = edge_to_tris[e]
assert len(tris_with_e) == 1, \
f"B_in edge {e} should be in exactly 1 annular triangle"
t_idx = tris_with_e[0]
u, v = list(e)
midx = (pos[u][0] + pos[v][0]) / 2
midy = (pos[u][1] + pos[v][1]) / 2
# Place the leaf aligned with the midpoint of the red edge it crosses,
# on the central-hole side, at a fixed small distance past the
# midpoint along the (d_f -> midpoint) direction. This keeps the
# leaf inside the central hole (not on the border) while making the
# leaf edge cross the red edge exactly at its midpoint.
cx, cy = centroids[t_idx]
vx, vy = midx - cx, midy - cy
norm = math.sqrt(vx * vx + vy * vy)
if norm < 1e-9:
nx, ny = 0.0, -1.0
else:
nx, ny = vx / norm, vy / norm
offset = 0.13 # absolute distance past the midpoint
leaf_pos = (midx + nx * offset, midy + ny * offset)
bin_leaves.append((e, t_idx, leaf_pos))
# === Determine interior dual cycle order ===
# Two annular triangles are adjacent in the dual cycle iff they share an
# interior annular edge (= an edge with both endpoints in V(B_out) V(B_in)
# whose two incident faces are both annular triangles, i.e., shared by 2
# of our triangles).
# In a spoke-only tire each interior edge is shared by exactly 2 triangles.
tri_adj = defaultdict(set)
for e, ts in edge_to_tris.items():
if len(ts) == 2:
t1, t2 = ts
tri_adj[t1].add(t2)
tri_adj[t2].add(t1)
# === Draw partial tire dual ===
DUAL_COLOR = '#7d3c98' # purple
LEAF_COLOR = '#e67e22' # orange
# Interior dual edges
for t1 in range(n_tri):
for t2 in tri_adj[t1]:
if t2 <= t1:
continue
x1, y1 = centroids[t1]
x2, y2 = centroids[t2]
ax.plot([x1, x2], [y1, y2], color=DUAL_COLOR, linewidth=2.0,
linestyle='-', zorder=4)
# Leaf edges
for e, t_idx, lpos in bout_leaves + bin_leaves:
cx, cy = centroids[t_idx]
lx, ly = lpos
ax.plot([cx, lx], [lx and cy or cy, ly], visible=False)
# use a proper plot:
ax.plot([cx, lx], [cy, ly], color=LEAF_COLOR, linewidth=1.8,
linestyle='--', zorder=4)
# Interior dual vertices
for t_idx, (cx, cy) in enumerate(centroids):
ax.plot(cx, cy, 's', color=DUAL_COLOR, markersize=14, zorder=5)
ax.annotate(f"$d_{{{t_idx}}}$", (cx, cy), color='white',
ha='center', va='center', fontsize=7, fontweight='bold',
zorder=6)
# Leaf vertices
for label_prefix, leaves in (('out', bout_leaves), ('in', bin_leaves)):
for i, (e, t_idx, lpos) in enumerate(leaves):
ax.plot(lpos[0], lpos[1], 'D', color=LEAF_COLOR,
markersize=11, zorder=5)
ax.annotate(f"$\\ell^{{{label_prefix}}}_{{{i}}}$",
(lpos[0], lpos[1]), color='white', ha='center',
va='center', fontsize=6, fontweight='bold', zorder=6)
# Legend
legend_items = [
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#a8c9e8',
markersize=12, label='$B_{\\mathrm{out}}$ vertex (tire)'),
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#e8a8a8',
markersize=11, label='$B_{\\mathrm{in}}$ vertex (tire)'),
plt.Line2D([], [], marker='s', color='w', markerfacecolor=DUAL_COLOR,
markersize=12, label='$d_f$ (interior dual vertex)'),
plt.Line2D([], [], marker='D', color='w', markerfacecolor=LEAF_COLOR,
markersize=10, label='leaf vertex'),
plt.Line2D([], [], color=DUAL_COLOR, linewidth=2.0,
label='interior dual edge'),
plt.Line2D([], [], color=LEAF_COLOR, linewidth=1.8, linestyle='--',
label='leaf edge'),
]
ax.legend(handles=legend_items, loc='upper left',
bbox_to_anchor=(1.0, 1.0), fontsize=9, frameon=False)
ax.set_xlim(-1.35, 1.35)
ax.set_ylim(-1.35, 1.35)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title(
f'Partial tire dual $D(T)$ (m={m}, k={k}, spoke-only)\n'
f'underlying tire faint; $D(T) \\cong C_{{{m+k}}} \\circ K_1$',
fontsize=12)
plt.savefig(filename, dpi=160, bbox_inches='tight')
plt.close()
print(f"wrote {filename}")
def main():
# m=6 outer, k=4 inner, no chords (spoke-only), reproducible seed
tire = random_tire(m=6, k=4, n_chords=0, seed=3)
out = os.path.join(HERE, 'partial_tire_dual_example.png')
draw_partial_tire_dual(tire, out)
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

+6 -4
View File
@@ -8,18 +8,20 @@
\newlabel{fig:tire-example}{{2}{3}}
\newlabel{rem:tire-counts}{{1.6}{3}}
\newlabel{def:partial-tire-dual}{{1.7}{3}}
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces The partial tire dual $D(T)$ (purple squares + orange diamonds) drawn on top of a small tire graph $T$ (faint) with $m = 6$ and $k = 4$. The ten interior vertices $d_f$ at the centroids of the annular triangles form a single $10$-cycle (solid purple); each boundary edge of the annular region (either of $B_{\mathrm {out}}$ or of $B_{\mathrm {in}}$) contributes a degree-$1$ leaf (orange diamond) attached to the unique annular face incident to it (dashed orange), giving the structure $C_{10} \circ K_1$ of Proposition\nonbreakingspace 1.8\hbox {}.}}{4}{}\protected@file@percent }
\newlabel{fig:partial-tire-dual-example}{{3}{4}}
\newlabel{prop:partial-tire-dual-structure}{{1.8}{4}}
\newlabel{prop:no-level-d-pinch}{{1.9}{4}}
\citation{bauerfeld-pds}
\newlabel{prop:no-level-d-pinch}{{1.9}{5}}
\newlabel{lem:tire-component}{{1.10}{5}}
\citation{bauerfeld-pds}
\newlabel{rem:tire-component-degenerate}{{1.11}{6}}
\newlabel{rem:tire-no-extra-hypotheses}{{1.12}{6}}
\citation{bauerfeld-pds}
\bibcite{bauerfeld-pds}{1}
\newlabel{tocindent-1}{0pt}
\newlabel{tocindent0}{12.7778pt}
\newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt}
\newlabel{rem:tire-component-degenerate}{{1.11}{7}}
\newlabel{rem:tire-no-extra-hypotheses}{{1.12}{7}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{7}{}\protected@file@percent }
\gdef \@abspage@last{7}
+33 -27
View File
@@ -1,4 +1,4 @@
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 25 MAY 2026 17:59
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 25 MAY 2026 18:37
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
@@ -207,36 +207,42 @@ File: fig_tire_example.png Graphic file (type png)
<use fig_tire_example.png>
Package pdftex.def Info: fig_tire_example.png used on input line 160.
(pdftex.def) Requested size: 280.79956pt x 188.56097pt.
[3 <./fig_tire_example.png>] [4] [5] [6] [7]
(./paper.aux) )
[3 <./fig_tire_example.png>]
<fig_partial_tire_dual.png, id=30, 657.657pt x 546.54187pt>
File: fig_partial_tire_dual.png Graphic file (type png)
<use fig_partial_tire_dual.png>
Package pdftex.def Info: fig_partial_tire_dual.png used on input line 225.
(pdftex.def) Requested size: 280.79956pt x 233.36552pt.
[4 <./fig_partial_tire_dual.png>] [5] [6] [7] (./paper.aux) )
Here is how much of TeX's memory you used:
3010 strings out of 478268
42095 string characters out of 5846347
346199 words of memory out of 5000000
21057 multiletter control sequences out of 15000+600000
3018 strings out of 478268
42332 string characters out of 5846347
345207 words of memory out of 5000000
21064 multiletter control sequences out of 15000+600000
475666 words of font info for 53 fonts, out of 8000000 for 9000
1302 hyphenation exceptions out of 8191
69i,8n,76p,625b,316s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/
cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
m/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
m/cmex10.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/
cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cm
mi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr1
0.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.p
fb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb>
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></u
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></us
r/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy5.pfb></usr/
local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/lo
cal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/loc
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local
/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (7 pages, 499770 bytes).
69i,8n,76p,687b,316s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texli
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texliv
e/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texliv
e/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.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/cmmi5.pfb></usr/local/texlive/20
22/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022
/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/t
exmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/texm
f-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-d
ist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist
/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/
fonts/type1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fo
nts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/font
s/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts
/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
ype1/public/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (7 pages, 616266 bytes).
PDF statistics:
113 PDF objects out of 1000 (max. 8388607)
115 PDF objects out of 1000 (max. 8388607)
67 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000)
11 words of extra memory for PDF output out of 10000 (max. 10000000)
16 words of extra memory for PDF output out of 10000 (max. 10000000)
Binary file not shown.
@@ -220,6 +220,21 @@ tire dual} of $T$, written $D(T)$, is the graph defined as follows.
\end{enumerate}
\end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=0.78\textwidth]{fig_partial_tire_dual.png}
\caption{The partial tire dual $D(T)$ (purple squares + orange
diamonds) drawn on top of a small tire graph $T$ (faint) with $m = 6$
and $k = 4$. The ten interior vertices $d_f$ at the centroids of the
annular triangles form a single $10$-cycle (solid purple); each
boundary edge of the annular region (either of $B_{\mathrm{out}}$ or
of $B_{\mathrm{in}}$) contributes a degree-$1$ leaf (orange diamond)
attached to the unique annular face incident to it (dashed orange),
giving the structure $C_{10} \circ K_1$ of
Proposition~\ref{prop:partial-tire-dual-structure}.}
\label{fig:partial-tire-dual-example}
\end{figure}
\begin{proposition}[Structure of $D(T)$ when the annular triangulation
is spoke-only]
\label{prop:partial-tire-dual-structure}