coloring_nested_tire_graphs: add figure of the complete tire dual D*(T)

Adds fig_complete_tire_dual.png next to Definition 1.13 of the
complete tire dual.  The figure uses the same m=6, k=4 spoke-only
tire as the partial-tire-dual figure (so the two figures can be
directly compared), with the n=6 outer leaves merged into a single
outer-face vertex v_out (blue hexagon, drawn outside the tire) of
degree 6, and the m=4 inner leaves merged into a single inner-face
vertex v_in (red hexagon, drawn at the centre of the inner cycle)
of degree 4.

Generator: experiments/draw_complete_tire_dual.py.

Paper grows from 8 to 9 pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 19:28:19 -04:00
parent 93ae55bd42
commit 2b84513f83
7 changed files with 257 additions and 34 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

@@ -0,0 +1,199 @@
"""Draw the complete tire dual D*(T) on top of a small tire graph.
For a spoke-only tire with 2-connected O = C_k (no chords), D*(T) is
obtained from D(T) by merging:
- all B_out-leaves into a single outer-face vertex v_out (degree n);
- the B_in-leaves into a single inner-face vertex v_in (degree k),
because O has exactly one bounded interior face.
Equivalently, D*(T) is the planar dual of T.
"""
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_complete_tire_dual(tire, filename):
m, k = tire['m'], tire['k']
outer, inner = tire['outer'], tire['inner']
triangles = tire['triangles']
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)
n_tri = len(triangles)
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))
# Identify which triangles are incident to a B_out edge (O-move type)
# vs incident to a B_in edge (I-move type).
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)}
bout_incident = [] # triangles incident to a B_out edge
bin_incident = [] # triangles incident to a B_in edge
for t_idx, tri in enumerate(triangles):
tri_edges = {frozenset({tri[0], tri[1]}),
frozenset({tri[1], tri[2]}),
frozenset({tri[0], tri[2]})}
if tri_edges & bout_edges:
bout_incident.append(t_idx)
if tri_edges & bin_edges:
bin_incident.append(t_idx)
print(f"O-move triangles (B_out incident): {bout_incident}")
print(f"I-move triangles (B_in incident): {bin_incident}")
# Place v_out outside the outer cycle (far to the upper right)
# and v_in at the origin (centroid of inner cycle).
v_out_pos = (1.55, 0.0) # well outside the outer cycle
v_in_pos = (0.0, 0.0) # center of inner cycle
# Identify dual cycle (interior dual edges)
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)
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 complete tire dual ===
DUAL_COLOR = '#7d3c98' # purple
OUT_COLOR = '#1f77b4' # blue (for v_out edges)
IN_COLOR = '#d62728' # red (for v_in edges)
# Interior dual edges (d_f to d_f)
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,
zorder=4)
# v_out edges (to all O-move d_f's)
for t_idx in bout_incident:
cx, cy = centroids[t_idx]
ax.plot([cx, v_out_pos[0]], [cy, v_out_pos[1]],
color=OUT_COLOR, linewidth=1.6, linestyle='--', zorder=4)
# v_in edges (to all I-move d_f's)
for t_idx in bin_incident:
cx, cy = centroids[t_idx]
ax.plot([cx, v_in_pos[0]], [cy, v_in_pos[1]],
color=IN_COLOR, linewidth=1.6, linestyle='--', zorder=4)
# Interior dual vertices d_f
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)
# v_out (outer-face vertex)
ax.plot(v_out_pos[0], v_out_pos[1], 'h', color=OUT_COLOR,
markersize=20, zorder=5)
ax.annotate("$v_{\\mathrm{out}}$", v_out_pos, color='white',
ha='center', va='center', fontsize=9, fontweight='bold',
zorder=6)
# v_in (inner-face vertex)
ax.plot(v_in_pos[0], v_in_pos[1], 'h', color=IN_COLOR,
markersize=18, zorder=5)
ax.annotate("$v_{\\mathrm{in}}$", v_in_pos, color='white',
ha='center', va='center', fontsize=8, 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$ (annular face dual vertex)'),
plt.Line2D([], [], marker='h', color='w', markerfacecolor=OUT_COLOR,
markersize=14,
label='$v_{\\mathrm{out}}$ (outer-face vertex, deg ${%d}$)' % m),
plt.Line2D([], [], marker='h', color='w', markerfacecolor=IN_COLOR,
markersize=13,
label='$v_{\\mathrm{in}}$ (inner-face vertex, deg ${%d}$)' % k),
plt.Line2D([], [], color=DUAL_COLOR, linewidth=2.0,
label='interior dual edge ($d_f$$d_{f^\\prime}$)'),
plt.Line2D([], [], color=OUT_COLOR, linewidth=1.6, linestyle='--',
label='dual edge to $v_{\\mathrm{out}}$'),
plt.Line2D([], [], color=IN_COLOR, linewidth=1.6, linestyle='--',
label='dual edge to $v_{\\mathrm{in}}$'),
]
ax.legend(handles=legend_items, loc='upper left',
bbox_to_anchor=(1.02, 1.0), fontsize=9, frameon=False)
ax.set_xlim(-1.35, 2.05)
ax.set_ylim(-1.35, 1.35)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title(
f'Complete tire dual $D^{{*}}(T)$ (m={m}, k={k}, spoke-only)\n'
f'underlying tire faint; $|V| = {n_tri + 2}$, $|E| = {2*n_tri}$',
fontsize=12)
plt.savefig(filename, dpi=160, bbox_inches='tight')
plt.close()
print(f"wrote {filename}")
def main():
tire = random_tire(m=6, k=4, n_chords=0, seed=3)
out = os.path.join(HERE, 'complete_tire_dual_example.png')
draw_complete_tire_dual(tire, out)
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

+7 -5
View File
@@ -19,6 +19,11 @@
\newlabel{rem:tire-no-extra-hypotheses}{{1.12}{7}} \newlabel{rem:tire-no-extra-hypotheses}{{1.12}{7}}
\newlabel{def:complete-tire-dual}{{1.13}{7}} \newlabel{def:complete-tire-dual}{{1.13}{7}}
\citation{Tait1880} \citation{Tait1880}
\@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces The complete tire dual $D^{\ast }(T)$ (purple squares + face hexagons) drawn on top of the same $m = 6$, $k = 4$ spoke-only tire graph as Figure\nonbreakingspace 3\hbox {}. The ten annular-face vertices $d_f$ form a $10$-cycle (solid purple); the $n$ outer leaves of $D(T)$ have been merged into a single outer-face vertex $v_{\mathrm {out}}$ (blue hexagon, drawn outside the tire) of degree $n = 6$, and the $m$ inner leaves into a single inner-face vertex $v_{\mathrm {in}}$ (red hexagon, at the centre of the inner cycle) of degree $m = 4$. Total $|V(D^{\ast }(T))| = 12$ and $|E(D^{\ast }(T))| = 20$.}}{8}{}\protected@file@percent }
\newlabel{fig:complete-tire-dual-example}{{4}{8}}
\newlabel{prop:tait-tire-complete}{{1.14}{8}}
\newlabel{rem:tait-construction}{{1.15}{8}}
\newlabel{rem:tait-octahedron}{{1.16}{8}}
\bibcite{Tait1880}{1} \bibcite{Tait1880}{1}
\bibcite{bauerfeld-pds}{2} \bibcite{bauerfeld-pds}{2}
\newlabel{tocindent-1}{0pt} \newlabel{tocindent-1}{0pt}
@@ -26,8 +31,5 @@
\newlabel{tocindent1}{17.77782pt} \newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt} \newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt} \newlabel{tocindent3}{0pt}
\newlabel{prop:tait-tire-complete}{{1.14}{8}} \@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{9}{}\protected@file@percent }
\newlabel{rem:tait-construction}{{1.15}{8}} \gdef \@abspage@last{9}
\newlabel{rem:tait-octahedron}{{1.16}{8}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{8}{}\protected@file@percent }
\gdef \@abspage@last{8}
+35 -29
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 18:59 This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 25 MAY 2026 19:28
entering extended mode entering extended mode
restricted \write18 enabled. restricted \write18 enabled.
%&-line parsing enabled. %&-line parsing enabled.
@@ -213,37 +213,43 @@ File: fig_partial_tire_dual.png Graphic file (type png)
<use fig_partial_tire_dual.png> <use fig_partial_tire_dual.png>
Package pdftex.def Info: fig_partial_tire_dual.png used on input line 225. Package pdftex.def Info: fig_partial_tire_dual.png used on input line 225.
(pdftex.def) Requested size: 280.79956pt x 233.36552pt. (pdftex.def) Requested size: 280.79956pt x 233.36552pt.
[4 <./fig_partial_tire_dual.png>] [5] [6] [7] [8] (./paper.aux) ) [4 <./fig_partial_tire_dual.png>] [5] [6] [7]
<fig_complete_tire_dual.png, id=48, 701.47069pt x 448.074pt>
File: fig_complete_tire_dual.png Graphic file (type png)
<use fig_complete_tire_dual.png>
Package pdftex.def Info: fig_complete_tire_dual.png used on input line 513.
(pdftex.def) Requested size: 295.20264pt x 188.55899pt.
[8 <./fig_complete_tire_dual.png>] [9] (./paper.aux) )
Here is how much of TeX's memory you used: Here is how much of TeX's memory you used:
3023 strings out of 478268 3031 strings out of 478268
42435 string characters out of 5846347 42680 string characters out of 5846347
345254 words of memory out of 5000000 345262 words of memory out of 5000000
21069 multiletter control sequences out of 15000+600000 21076 multiletter control sequences out of 15000+600000
475666 words of font info for 53 fonts, out of 8000000 for 9000 475666 words of font info for 53 fonts, out of 8000000 for 9000
1302 hyphenation exceptions out of 8191 1302 hyphenation exceptions out of 8191
69i,8n,76p,687b,316s stack positions out of 10000i,1000n,20000p,200000b,200000s 69i,8n,76p,742b,316s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/t </usr/local/texlive/2022
exlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/te /texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/
xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/te texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/
xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></usr/local/tex texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/t
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texl exmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/te
ive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texliv xmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texm
e/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/ f-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-
2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/20 dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-di
22/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/ st/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/texmf-dist/
texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/tex fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fon
mf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf- ts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/
dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-d type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
ist/fonts/type1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dis ype1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
t/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/ e1/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/f /public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/
onts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fon public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
ts/type1/public/amsfonts/symbols/msam10.pfb></usr/local/texlive/2022/texmf-dist blic/amsfonts/symbols/msam10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type
/fonts/type1/public/amsfonts/symbols/msbm10.pfb> 1/public/amsfonts/symbols/msbm10.pfb>
Output written on paper.pdf (8 pages, 628165 bytes). Output written on paper.pdf (9 pages, 748601 bytes).
PDF statistics: PDF statistics:
123 PDF objects out of 1000 (max. 8388607) 128 PDF objects out of 1000 (max. 8388607)
72 compressed objects within 1 object stream 74 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000) 0 named destinations out of 1000 (max. 500000)
16 words of extra memory for PDF output out of 10000 (max. 10000000) 21 words of extra memory for PDF output out of 10000 (max. 10000000)
Binary file not shown.
@@ -508,6 +508,22 @@ vertex per face of $T$ (annular triangle, outer face, or bounded
interior face of $O$), one dual edge per edge of $T$. interior face of $O$), one dual edge per edge of $T$.
\end{definition} \end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=0.82\textwidth]{fig_complete_tire_dual.png}
\caption{The complete tire dual $D^{\ast}(T)$ (purple squares + face
hexagons) drawn on top of the same $m = 6$, $k = 4$ spoke-only tire
graph as Figure~\ref{fig:partial-tire-dual-example}. The ten
annular-face vertices $d_f$ form a $10$-cycle (solid purple); the
$n$ outer leaves of $D(T)$ have been merged into a single outer-face
vertex $v_{\mathrm{out}}$ (blue hexagon, drawn outside the tire) of
degree $n = 6$, and the $m$ inner leaves into a single inner-face
vertex $v_{\mathrm{in}}$ (red hexagon, at the centre of the inner
cycle) of degree $m = 4$. Total $|V(D^{\ast}(T))| = 12$ and
$|E(D^{\ast}(T))| = 20$.}
\label{fig:complete-tire-dual-example}
\end{figure}
\begin{proposition}[Tait correspondence on the complete tire dual] \begin{proposition}[Tait correspondence on the complete tire dual]
\label{prop:tait-tire-complete} \label{prop:tait-tire-complete}
Let $T$ be a tire graph. Then the number of non-equivalent proper Let $T$ be a tire graph. Then the number of non-equivalent proper