coloring_nested_tire_graphs: relax Def 1.5 to allow non-simple inner-boundary walks; drop (R2)

The previous reading of Def 1.5 had B_in be a simple cycle on V(O).
This forced an artificial (R2) hypothesis on Lemma 1.7 ruling out
multi-hole topology of R_{C'}.  But the multi-hole structure is
already captured naturally by the inner outerplanar graph O itself:
when O is not 2-connected (has a bridge, cut-vertex, or multiple
components), its outer-face boundary is a closed walk rather than a
simple cycle, and that walk traverses bridges twice and visits cut-
vertices multiple times.

Changes:

- Definition 1.5: B_in is now defined as the closed walk in O that
  traces O's outer-face boundary in the inherited embedding.  It is
  a simple cycle when O is 2-connected and a non-simple closed walk
  in general.  B_out remains a simple cycle (or degenerate single
  vertex).

- Lemma 1.7: (R2) hypothesis removed.  Only (R1) (manifold) remains.
  The proof of the boundary structure is rewritten: we identify
  B_out as the source-side boundary cycle, O as G[V_{C'} ∩ L_{d+1}]
  (outerplanar by Lemma 2.6 of [bauerfeld-pds]), and B_in as O's
  outer-face boundary walk.  Multi-hole topology of R_{C'} is now
  captured by O being non-2-connected or disconnected, without
  needing an extra constraint.

- Remark 1.10 (was R1+R2): now Remark 1.10 (R1-when), explaining the
  pinch obstruction for (R1) and explicitly noting that multi-hole
  topology does NOT require an additional hypothesis under the new
  Definition 1.5.

Empirical motivation: in the brute-force search over maximal planar
graphs at n in [7, 11] (30,587 components from all single-vertex
sources), 171 components at depth 1 had R_{C'} with 3 boundary
cycles in the surface-classification sense (n_bdry > 2).  Each is a
valid tire graph under the relaxed definition: B_out is the level-d
cycle, O is the level-(d+1) outerplanar subgraph (non-2-connected
when this occurs), and B_in is its outer-face boundary walk.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 16:26:21 -04:00
parent 1ac80aa5cf
commit 717c8cf5ea
11 changed files with 903 additions and 121 deletions
@@ -0,0 +1,253 @@
"""Brute-force search for (R1)/(R2) violations in small maximal planar
graphs.
For each maximal planar graph G on n vertices (with delta(G) >= 3),
for each single-vertex source v_0 in V(G), compute:
- BFS depths from v_0.
- For each face, its dual depth (= min BFS level of vertices).
- For each depth d, connected components of the dual subgraph G'_d.
- For each component, check (R1) manifold property and count (R2)
boundary components.
Report:
- Per depth d, count of components and count of (R1)-violators,
(R2)-violators, both.
Run: sage -python experiments/check_r2.py [n_min] [n_max]
"""
import sys
import json
from collections import defaultdict
from sage.all import Graph
from sage.graphs.graph_generators import graphs
def face_verts(face):
"""face = list of edges [(u, v), (v, w), ...]. Return ordered vertex
cycle [u, v, w, ...]."""
if not face:
return []
verts = [face[0][0]]
for e in face:
verts.append(e[1])
return verts[:-1] # last = first
def face_edges_set(face):
return {frozenset(e) for e in face}
def find_components(face_depths, target_d):
"""face_depths: list of (face, depth, vertex_set).
Returns list of components; each component = list of (face_idx, face_depth_entry)."""
cands = [(i, fd) for i, fd in enumerate(face_depths) if fd[1] == target_d]
n = len(cands)
if n == 0:
return []
edge_to_idxs = defaultdict(list)
for k, (i, fd) in enumerate(cands):
for e in face_edges_set(fd[0]):
edge_to_idxs[e].append(k)
adj = [[] for _ in range(n)]
for idxs in edge_to_idxs.values():
if len(idxs) >= 2:
for a in idxs:
for b in idxs:
if a != b:
adj[a].append(b)
visited = [False] * n
comps = []
for s in range(n):
if visited[s]:
continue
stack = [s]
comp = []
while stack:
x = stack.pop()
if visited[x]:
continue
visited[x] = True
comp.append(cands[x])
for y in adj[x]:
if not visited[y]:
stack.append(y)
comps.append(comp)
return comps
def check_component(comp, G, emb):
"""For a connected component of same-depth faces, check R1 (manifold)
and count boundary components (R2)."""
# boundary edges: edges of G appearing in exactly one face of comp
edge_count = defaultdict(int)
for (_, fd) in comp:
for e in face_edges_set(fd[0]):
edge_count[e] += 1
bdry_edges = {e for e, c in edge_count.items() if c == 1}
if not bdry_edges:
# No boundary (closed surface). In a planar graph this shouldn't happen.
return {'is_manifold': False, 'n_boundary': 0, 'reason': 'no boundary'}
# R1 check: at each vertex v in V(comp), count how many faces of comp
# are incident to v, and check whether they form a contiguous arc in
# v's rotation in emb.
comp_face_edges = []
for (_, fd) in comp:
comp_face_edges.append(face_edges_set(fd[0]))
vertices = set()
for fes in comp_face_edges:
for e in fes:
vertices.update(e)
is_manifold = True
pinch_vertices = []
for v in vertices:
rot = emb[v] # cyclic neighbour order
# The faces incident to v in G are the triangles {v, rot[i], rot[i+1]}.
# Check which ones are in comp.
in_comp = []
for i in range(len(rot)):
u = rot[i]
w = rot[(i + 1) % len(rot)]
face_e = {frozenset({v, u}), frozenset({u, w}), frozenset({v, w})}
if any(face_e == fes for fes in comp_face_edges):
in_comp.append(True)
else:
in_comp.append(False)
# Count "runs" of True in cyclic sequence
n_true = sum(in_comp)
if n_true == 0:
continue
if n_true == len(in_comp):
continue # entire rotation in comp, vertex is interior
# Count transitions from False to True (cyclic)
n_runs = 0
for i in range(len(in_comp)):
if in_comp[i] and not in_comp[(i - 1) % len(in_comp)]:
n_runs += 1
if n_runs > 1:
is_manifold = False
pinch_vertices.append(v)
# R2 check: count boundary components.
# Build bdry_graph: undirected graph on vertices touching bdry_edges,
# edges = bdry_edges.
bdry_graph = defaultdict(set)
for e in bdry_edges:
u, w = list(e)
bdry_graph[u].add(w)
bdry_graph[w].add(u)
# Count connected components
seen = set()
n_components = 0
for v in bdry_graph:
if v in seen:
continue
n_components += 1
stack = [v]
while stack:
x = stack.pop()
if x in seen:
continue
seen.add(x)
for y in bdry_graph[x]:
if y not in seen:
stack.append(y)
return {'is_manifold': is_manifold,
'n_boundary': n_components,
'pinch_vertices': pinch_vertices,
'n_bdry_edges': len(bdry_edges),
'n_faces': len(comp)}
def search(n_min, n_max, max_violations=5, verbose=False):
r1_violations = []
r2_violations = []
summary = defaultdict(int)
for n in range(n_min, n_max + 1):
ntri = 0
for G in graphs.triangulations(n):
ntri += 1
G.is_planar(set_embedding=True)
emb = G.get_embedding()
faces = G.faces(embedding=emb)
for v_source in G.vertices():
dist = G.shortest_path_lengths(v_source)
face_depths = []
for face in faces:
verts = set()
for u, w in face:
verts.add(u); verts.add(w)
d = min(dist[u] for u in verts)
face_depths.append((face, d, verts))
max_d = max(fd[1] for fd in face_depths)
for target_d in range(max_d + 1):
comps = find_components(face_depths, target_d)
for comp_idx, comp in enumerate(comps):
result = check_component(comp, G, emb)
summary[('component', target_d)] += 1
if not result['is_manifold']:
summary[('r1_violation', target_d)] += 1
if len(r1_violations) < max_violations:
r1_violations.append({
'n': n, 'source': int(v_source),
'depth': target_d,
'graph_edges': sorted([sorted(e) for e in G.edges(labels=False)]),
'pinch_vertices': result['pinch_vertices'],
})
if result['is_manifold'] and result['n_boundary'] > 2:
summary[('r2_violation_manifold', target_d)] += 1
if len(r2_violations) < max_violations:
r2_violations.append({
'n': n, 'source': int(v_source),
'depth': target_d,
'n_boundary': result['n_boundary'],
'graph_edges': sorted([sorted(e) for e in G.edges(labels=False)]),
})
if verbose:
print(f"n={n}: scanned {ntri} triangulations")
sys.stdout.flush()
return r1_violations, r2_violations, summary
def main():
n_min = int(sys.argv[1]) if len(sys.argv) > 1 else 7
n_max = int(sys.argv[2]) if len(sys.argv) > 2 else 12
print(f"Searching maximal planar graphs n in [{n_min}, {n_max}]...")
r1s, r2s, summary = search(n_min, n_max, max_violations=5, verbose=True)
print()
print("=== Summary ===")
by_depth = defaultdict(lambda: {'components': 0, 'r1': 0, 'r2_manifold': 0})
for (kind, d), c in summary.items():
if kind == 'component':
by_depth[d]['components'] += c
elif kind == 'r1_violation':
by_depth[d]['r1'] += c
elif kind == 'r2_violation_manifold':
by_depth[d]['r2_manifold'] += c
print(f"{'depth':>6} {'components':>12} {'R1 viol':>10} {'R2 viol (manifold)':>20}")
for d in sorted(by_depth):
s = by_depth[d]
print(f"{d:>6} {s['components']:>12} {s['r1']:>10} {s['r2_manifold']:>20}")
print()
if r1s:
print(f"=== R1 violations (first {len(r1s)}) ===")
for v in r1s:
print(f" n={v['n']}, source={v['source']}, depth={v['depth']}, "
f"pinches={v['pinch_vertices']}")
if r2s:
print(f"=== R2 violations (manifold, n_bdry > 2) (first {len(r2s)}) ===")
for v in r2s:
print(f" n={v['n']}, source={v['source']}, depth={v['depth']}, "
f"n_bdry={v['n_boundary']}")
print(f" edges: {v['graph_edges']}")
if not r1s and not r2s:
print("No R1 or R2 violations found in this range.")
if __name__ == '__main__':
main()
@@ -0,0 +1,191 @@
"""Draw an actual maximal planar graph exhibiting an (R2) violation.
Uses the first such example found by experiments/check_r2.py:
n=10, source=4, depth=1, n_bdry=3.
"""
import os
import sys
from collections import defaultdict
from sage.all import Graph
from sage.graphs.graph_generators import graphs
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# Edges from the first n=10 R2-violating triangulation found.
EDGES = [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9),
(2, 3), (2, 8), (2, 9), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8),
(3, 10), (4, 5), (5, 6), (6, 7), (6, 10), (7, 8), (7, 10), (8, 9)]
SOURCE = 4
DEPTH = 1
def main():
G = Graph(EDGES)
G.is_planar(set_embedding=True)
emb = G.get_embedding()
dist = G.shortest_path_lengths(SOURCE)
print(f"BFS distances from {SOURCE}: {dict(dist)}")
faces = G.faces(embedding=emb)
face_depths = []
for face in faces:
verts = set()
for u, v in face:
verts.add(u); verts.add(v)
d = min(dist[v] for v in verts)
face_depths.append((face, d, verts))
# Find depth-DEPTH faces and their connected components
target_faces = [(i, fd) for i, fd in enumerate(face_depths)
if fd[1] == DEPTH]
print(f"Found {len(target_faces)} faces at depth {DEPTH}")
edge_to_idxs = defaultdict(list)
for k, (i, fd) in enumerate(target_faces):
for u, v in fd[0]:
edge_to_idxs[frozenset({u, v})].append(k)
n = len(target_faces)
adj = [[] for _ in range(n)]
for idxs in edge_to_idxs.values():
if len(idxs) >= 2:
for a in idxs:
for b in idxs:
if a != b:
adj[a].append(b)
visited = [False] * n
comps = []
for s in range(n):
if visited[s]:
continue
stack = [s]; comp = []
while stack:
x = stack.pop()
if visited[x]: continue
visited[x] = True; comp.append(target_faces[x])
for y in adj[x]:
if not visited[y]: stack.append(y)
comps.append(comp)
print(f"Connected components at depth {DEPTH}: {len(comps)} sizes "
f"{[len(c) for c in comps]}")
# Pick the largest one
comp = max(comps, key=lambda c: len(c))
# Compute boundary edges of comp
edge_count = defaultdict(int)
comp_face_edges = []
for _, fd in comp:
fe = {frozenset({u, v}) for u, v in fd[0]}
comp_face_edges.append(fe)
for e in fe:
edge_count[e] += 1
bdry_edges = {e for e, c in edge_count.items() if c == 1}
# Boundary connected components
bdry_graph = defaultdict(set)
for e in bdry_edges:
u, v = list(e)
bdry_graph[u].add(v)
bdry_graph[v].add(u)
seen = set()
bdry_cycles = []
for v in bdry_graph:
if v in seen: continue
comp_v = set(); stack = [v]
while stack:
x = stack.pop()
if x in seen: continue
seen.add(x); comp_v.add(x)
for y in bdry_graph[x]:
if y not in seen: stack.append(y)
bdry_cycles.append(comp_v)
print(f"Boundary components: {len(bdry_cycles)}, vertex sets: {bdry_cycles}")
# Compute layout using sage's planar layout
pos = G.layout(layout='planar')
# Plot
fig, ax = plt.subplots(figsize=(9, 9))
# Fill in depth-1 component faces
for _, fd in comp:
face = fd[0]
cycle_verts = [edge[0] for edge in face]
poly = plt.Polygon([pos[v] for v in cycle_verts],
facecolor='#fff3cd', edgecolor='none',
alpha=0.55, zorder=0)
ax.add_patch(poly)
# Draw all edges of G
for u, v in G.edges(labels=False):
x1, y1 = pos[u]; x2, y2 = pos[v]
ax.plot([x1, x2], [y1, y2], color='lightgray', linewidth=0.8,
zorder=1)
# Highlight boundary edges of comp (in 3 different colors)
colors = ['#d62728', '#1f77b4', '#2ca02c']
cyclic_label = ['boundary 1', 'boundary 2', 'boundary 3']
for i, bc_verts in enumerate(bdry_cycles):
for e in bdry_edges:
if e <= bc_verts:
u, v = list(e)
x1, y1 = pos[u]; x2, y2 = pos[v]
ax.plot([x1, x2], [y1, y2], color=colors[i % 3],
linewidth=3.0, zorder=2)
# Draw vertices, color-coded by BFS level
level_colors = {0: 'black', 1: '#1f77b4', 2: '#d62728'}
for v in G.vertices():
x, y = pos[v]
c = level_colors.get(dist[v], 'gray')
ax.plot(x, y, 'o', color=c, markersize=22, zorder=3)
ax.annotate(f"{v}", (x, y), color='white', ha='center', va='center',
fontsize=11, fontweight='bold', zorder=4)
# Add depth annotation
ax.annotate(f"$\\ell={dist[v]}$", (x, y),
xytext=(x + 0.05, y + 0.05), fontsize=9, zorder=4,
color='black',
bbox=dict(boxstyle='round,pad=0.15',
facecolor='white', edgecolor='none',
alpha=0.75))
# Legend
legend_items = [
plt.Line2D([], [], marker='o', color='w', markerfacecolor='black',
markersize=12, label='source $v_0$ ($\\ell=0$)'),
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#1f77b4',
markersize=12, label='$\\ell=1$'),
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#d62728',
markersize=12, label='$\\ell=2$'),
plt.Line2D([], [], color='#fff3cd', linewidth=12,
label=f'$R_{{C^\\prime}}$ (depth-{DEPTH} component, '
f'{len(comp)} faces)'),
plt.Line2D([], [], color='#d62728', linewidth=3,
label='boundary cycle 1'),
plt.Line2D([], [], color='#1f77b4', linewidth=3,
label='boundary cycle 2'),
plt.Line2D([], [], color='#2ca02c', linewidth=3,
label='boundary cycle 3'),
]
ax.legend(handles=legend_items, loc='upper right', fontsize=9,
framealpha=0.95)
ax.set_title(f"(R2) violation: depth-{DEPTH} component with "
f"3 boundary cycles\n(maximal planar graph, $n=10$, "
f"source $v_0={SOURCE}$)",
fontsize=12)
ax.set_aspect('equal')
ax.axis('off')
out = os.path.join(HERE, 'r2_violation_real_example.png')
plt.savefig(out, dpi=160, bbox_inches='tight')
plt.close()
print(f"wrote {out}")
if __name__ == '__main__':
main()
@@ -0,0 +1,181 @@
"""Draw the n=10 R2-violating example with a planar layout that puts
source v_0=4 in the centre. We pick one of the depth-2 face triangles
({2,8,9} or {6,7,10}) as the outer face, which forces sage's planar
layout to put it on the outside and v_0 in the interior."""
import os
import sys
from collections import defaultdict
from sage.all import Graph
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.path import Path
EDGES = [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9),
(2, 3), (2, 8), (2, 9), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8),
(3, 10), (4, 5), (5, 6), (6, 7), (6, 10), (7, 8), (7, 10), (8, 9)]
SOURCE = 4
DEPTH = 1
def main():
G = Graph(EDGES)
G.is_planar(set_embedding=True)
emb = G.get_embedding()
dist = G.shortest_path_lengths(SOURCE)
# Use sage's planar layout with external_face = the depth-2 face {2, 8, 9}.
# The 'external_face' parameter in sage takes a face (list of edges).
# We supply edges (2,8), (8,9), (9,2) in cyclic order.
# Force external face to be the depth-2 triangle {2, 8, 9} by
# giving an edge of that triangle.
pos = G.layout(layout='planar', external_face=(2, 8))
# Normalise positions
xs = [p[0] for p in pos.values()]
ys = [p[1] for p in pos.values()]
cx, cy = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2
scale = max(max(xs) - min(xs), max(ys) - min(ys))
pos = {v: ((p[0] - cx) / scale, (p[1] - cy) / scale)
for v, p in pos.items()}
# Compute depth-1 component
faces = G.faces(embedding=emb)
face_depths = []
for face in faces:
verts = set()
for u, v in face:
verts.add(u); verts.add(v)
d = min(dist[v] for v in verts)
face_depths.append((face, d, verts))
depth1_faces = [fd for fd in face_depths if fd[1] == DEPTH]
# Boundary edges
edge_count = defaultdict(int)
for face, _, _ in depth1_faces:
for u, v in face:
edge_count[frozenset({u, v})] += 1
bdry_edges = {e for e, c in edge_count.items() if c == 1}
bdry_graph = defaultdict(set)
for e in bdry_edges:
u, v = list(e)
bdry_graph[u].add(v)
bdry_graph[v].add(u)
seen = set()
bdry_cycles = []
for v in bdry_graph:
if v in seen: continue
cv = set(); stack = [v]
while stack:
x = stack.pop()
if x in seen: continue
seen.add(x); cv.add(x)
for y in bdry_graph[x]:
if y not in seen: stack.append(y)
bdry_cycles.append(cv)
# Sort: source-side first (= contains a level-1 vertex), then by min vertex
def cyc_key(c):
levels = {dist[v] for v in c}
return (min(levels), min(c))
bdry_cycles.sort(key=cyc_key)
print(f"Boundary cycles: {bdry_cycles}")
# Plot
fig, ax = plt.subplots(figsize=(10, 9))
# Fill the depth-1 face union with one polygon: use the boundary curves
# Construct the outer boundary curve and the inner hole curves.
# The outer-facing boundary cycle (level d, source side) — but with
# external_face being a level-2 triangle, the "outer face" of the
# picture is the depth-2 triangle, and the depth-1 region is between.
# Easier: just fill each depth-1 triangle separately.
for face, _, _ in depth1_faces:
verts = [e[0] for e in face]
xy = [pos[v] for v in verts]
poly = plt.Polygon(xy, facecolor='#fff3cd', edgecolor='#e8d8a8',
linewidth=0.4, alpha=0.85, zorder=1)
ax.add_patch(poly)
# Draw ALL non-boundary edges of G (interior and outside) in light grey
for u, v in G.edges(labels=False):
if frozenset({u, v}) in bdry_edges:
continue
x1, y1 = pos[u]; x2, y2 = pos[v]
ax.plot([x1, x2], [y1, y2], color='lightgray', linewidth=0.9,
zorder=2)
# Boundary cycles in different colors
bdry_colors = ['#d62728', '#1f77b4', '#2ca02c']
bdry_labels = [
f'source-side boundary on $L_{{1}}$ (= $\\{{1, 3, 5\\}}$)',
f'inner boundary A on $L_{{2}}$',
f'inner boundary B on $L_{{2}}$',
]
for i, cyc in enumerate(bdry_cycles):
for e in bdry_edges:
if e <= cyc:
u, v = list(e)
x1, y1 = pos[u]; x2, y2 = pos[v]
ax.plot([x1, x2], [y1, y2], color=bdry_colors[i],
linewidth=3.5, zorder=3)
# Draw vertices
level_colors = {0: 'black', 1: '#1f77b4', 2: '#d62728'}
for v in G.vertices():
x, y = pos[v]
c = level_colors.get(dist[v], 'gray')
ax.plot(x, y, 'o', color=c, markersize=22, zorder=4)
ax.annotate(f"{v}", (x, y), color='white', ha='center', va='center',
fontsize=11, fontweight='bold', zorder=5)
# Source label
sx, sy = pos[SOURCE]
ax.annotate(f'source $v_0={SOURCE}$\n($\\ell=0$)',
xy=(sx, sy), xytext=(sx, sy - 0.10),
fontsize=10, ha='center', va='top', color='black', zorder=6,
bbox=dict(boxstyle='round,pad=0.25',
facecolor='white', edgecolor='gray', alpha=0.95))
legend_items = [
plt.Line2D([], [], marker='o', color='w', markerfacecolor='black',
markersize=11, label=f'source ($\\ell=0$)'),
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#1f77b4',
markersize=11, label='$\\ell=1$'),
plt.Line2D([], [], marker='o', color='w', markerfacecolor='#d62728',
markersize=11, label='$\\ell=2$'),
patches.Patch(facecolor='#fff3cd', edgecolor='#e8d8a8',
label=f'$R_{{C^\\prime}}$ (depth-{DEPTH} component, '
f'{len(depth1_faces)} triangles)'),
]
for i, lbl in enumerate(bdry_labels):
legend_items.append(plt.Line2D([], [], color=bdry_colors[i],
linewidth=3.5, label=lbl))
ax.legend(handles=legend_items, loc='upper right', fontsize=9,
framealpha=0.95)
ax.set_title(
f'(R2) violation: depth-1 component with 3 boundary cycles\n'
f'planar embedding of n=10 maximal planar graph, '
f'source $v_0={SOURCE}$ at centre',
fontsize=12)
ax.set_aspect('equal')
ax.axis('off')
pad = 0.08
ax.set_xlim(min(p[0] for p in pos.values()) - pad,
max(p[0] for p in pos.values()) + pad)
ax.set_ylim(min(p[1] for p in pos.values()) - pad,
max(p[1] for p in pos.values()) + pad)
out = os.path.join(HERE, 'r2_violation_v2.png')
plt.savefig(out, dpi=160, bbox_inches='tight')
plt.close()
print(f'wrote {out}')
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@@ -0,0 +1,154 @@
"""Schematic illustration of an (R2)-violating tire component.
A connected depth-d face component R_{C'} that has THREE boundary
components: one toward the source side (level d cycle), and two toward
the depth->d side (two distinct deeper lobes each enclosed by R_{C'}).
This is the topological "pair of pants" / trinion: a planar 2-manifold
with three boundary curves, Euler characteristic -1.
"""
import math
import os
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import Circle, Polygon, FancyArrowPatch
def draw_r2_violation(filename):
fig, ax = plt.subplots(figsize=(8.5, 7))
# Outer boundary of R_{C'} -- a large ellipse-like blob
outer_r = 3.2
# Three holes
# Source hole on the left
source_center = (-1.6, 0.2)
source_r = 0.7
# Two deeper lobes on the right
lobe1_center = (1.3, 1.1)
lobe1_r = 0.55
lobe2_center = (1.3, -1.1)
lobe2_r = 0.55
# Draw the filled R_{C'} region: outer ellipse minus three discs.
# Using a Path with the outer circle and three reversed inner circles.
from matplotlib.path import Path
import numpy as np
def circle_verts(cx, cy, r, n=80, reverse=False):
ts = np.linspace(0, 2 * np.pi, n, endpoint=False)
if reverse:
ts = ts[::-1]
return [(cx + r * np.cos(t), cy + r * np.sin(t)) for t in ts]
outer_verts = circle_verts(0, 0, outer_r, n=120)
# Make outer ellipse instead of circle
outer_verts = [(1.4 * x, y) for (x, y) in outer_verts]
inner1 = circle_verts(*source_center, source_r, n=60, reverse=True)
inner2 = circle_verts(*lobe1_center, lobe1_r, n=60, reverse=True)
inner3 = circle_verts(*lobe2_center, lobe2_r, n=60, reverse=True)
verts = (outer_verts + [outer_verts[0]]
+ inner1 + [inner1[0]]
+ inner2 + [inner2[0]]
+ inner3 + [inner3[0]])
codes = ([Path.MOVETO] + [Path.LINETO] * (len(outer_verts) - 1) + [Path.CLOSEPOLY]
+ [Path.MOVETO] + [Path.LINETO] * (len(inner1) - 1) + [Path.CLOSEPOLY]
+ [Path.MOVETO] + [Path.LINETO] * (len(inner2) - 1) + [Path.CLOSEPOLY]
+ [Path.MOVETO] + [Path.LINETO] * (len(inner3) - 1) + [Path.CLOSEPOLY])
path = Path(verts, codes)
patch = patches.PathPatch(path, facecolor='#fff3cd', edgecolor='none',
alpha=0.85, zorder=1)
ax.add_patch(patch)
# Draw the three boundary curves on top
def plot_curve(verts, color, lw=2.5, label_pt=None, label_text=None,
label_offset=(0, 0), label_color=None):
xs = [v[0] for v in verts] + [verts[0][0]]
ys = [v[1] for v in verts] + [verts[0][1]]
ax.plot(xs, ys, color=color, linewidth=lw, zorder=2)
if label_pt is not None and label_text is not None:
ax.annotate(label_text, xy=label_pt,
xytext=(label_pt[0] + label_offset[0],
label_pt[1] + label_offset[1]),
fontsize=11, ha='center', va='center',
color=label_color or color, zorder=3,
bbox=dict(boxstyle='round,pad=0.3',
edgecolor='none', facecolor='white',
alpha=0.85))
plot_curve(outer_verts, '#1f77b4', lw=2.8)
plot_curve(inner1, '#d62728', lw=2.5)
plot_curve(inner2, '#d62728', lw=2.5)
plot_curve(inner3, '#d62728', lw=2.5)
# Source vertex marker in the source hole
sx, sy = source_center
ax.plot(sx, sy, 'o', color='black', markersize=12, zorder=4)
ax.annotate(r'$S = \{v_0\}$', xy=(sx, sy),
xytext=(sx, sy - 0.32),
fontsize=11, ha='center', va='top', zorder=4,
bbox=dict(boxstyle='round,pad=0.25',
edgecolor='gray', facecolor='white', alpha=0.95))
# Region labels
ax.annotate(r'$R_{C^\prime}$ (depth-$d$ region)',
xy=(-3.5, 2.6), fontsize=14, ha='left', va='center',
color='#9a7d2a', fontweight='bold')
# Labels for the three boundary cycles
ax.annotate(r'outer boundary' + '\n' + r'on $L_{d}$ (source side)',
xy=(-1.6 + 0.7 + 0.05, 0.2), xytext=(-2.0, -1.6),
fontsize=10, ha='center', color='#d62728',
arrowprops=dict(arrowstyle='->', color='#d62728', lw=1.2))
ax.annotate(r'lobe boundary on $L_{d+1}$',
xy=(1.3 + 0.55 - 0.05, 1.1), xytext=(3.0, 2.4),
fontsize=10, ha='left', color='#d62728',
arrowprops=dict(arrowstyle='->', color='#d62728', lw=1.2))
ax.annotate(r'lobe boundary on $L_{d+1}$',
xy=(1.3 + 0.55 - 0.05, -1.1), xytext=(3.0, -2.4),
fontsize=10, ha='left', color='#d62728',
arrowprops=dict(arrowstyle='->', color='#d62728', lw=1.2))
ax.annotate(r'outer ellipse $=$' + '\n' + r'outer face of $\Pi_G$',
xy=(-3.5 * 1.4 + 0.2, 0), xytext=(-4.8, 0),
fontsize=10, ha='center', color='#1f77b4',
arrowprops=dict(arrowstyle='->', color='#1f77b4', lw=1.2))
# Lobe interior annotations
ax.annotate('depth-$> d$\nlobe A',
xy=lobe1_center, fontsize=10, ha='center', va='center',
color='#444', style='italic')
ax.annotate('depth-$> d$\nlobe B',
xy=lobe2_center, fontsize=10, ha='center', va='center',
color='#444', style='italic')
# Title / caption text
ax.text(0, 3.4,
'(R2) violation: $R_{C^\\prime}$ has 3 boundary components',
fontsize=13, ha='center', fontweight='bold')
ax.text(0, -3.2,
r'$\chi(R_{C^\prime}) = V - E + F = 2 - n_{\mathrm{bdy}} = -1$',
fontsize=11, ha='center', color='#666')
ax.set_xlim(-5.5, 5.5)
ax.set_ylim(-3.6, 3.7)
ax.set_aspect('equal')
ax.axis('off')
plt.savefig(filename, dpi=160, bbox_inches='tight')
plt.close()
if __name__ == '__main__':
here = os.path.dirname(os.path.abspath(__file__))
out = os.path.join(here, 'r2_violation_schematic.png')
draw_r2_violation(out)
print(f'wrote {out}')
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+5 -4
View File
@@ -4,18 +4,19 @@
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Dual depth in a stacked-ring triangulation $G$ with level source $S = \{0\}$. Each $G$ vertex is labelled by its level $\ell $. Each bounded face carries a dual vertex (square, joined by dashed dual edges) coloured by its dual depth $\delta (d_f) = \qopname \relax m{min}_{v \in V(f)} \ell (v)$: the central fan has depth $0$, the inner annulus depth $1$, and the outer annulus depth $2$. The outer face (the level-$3$ triangle) is excluded from the inner dual and carries no dual vertex.}}{2}{}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Dual depth in a stacked-ring triangulation $G$ with level source $S = \{0\}$. Each $G$ vertex is labelled by its level $\ell $. Each bounded face carries a dual vertex (square, joined by dashed dual edges) coloured by its dual depth $\delta (d_f) = \qopname \relax m{min}_{v \in V(f)} \ell (v)$: the central fan has depth $0$, the inner annulus depth $1$, and the outer annulus depth $2$. The outer face (the level-$3$ triangle) is excluded from the inner dual and carries no dual vertex.}}{2}{}\protected@file@percent }
\newlabel{fig:dual-depth}{{1}{2}} \newlabel{fig:dual-depth}{{1}{2}}
\newlabel{def:tire-graph}{{1.5}{2}} \newlabel{def:tire-graph}{{1.5}{2}}
\newlabel{rem:tire-counts}{{1.6}{2}}
\citation{bauerfeld-pds}
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces A tire graph with non-degenerate boundaries: outer boundary $B_{\mathrm {out}}$ a $6$-cycle on vertices $0,\dots ,5$ (blue), inner boundary $B_{\mathrm {in}}$ a $4$-cycle on vertices $6,\dots ,9$ (red), inner outerplanar graph $O = B_{\mathrm {in}} \cup \{7\text {--}9\}$ (with one chord, orange), and $E_{\mathrm {ann}}$ (grey) tiling the annulus between $B_{\mathrm {out}}$ and $B_{\mathrm {in}}$ by ten triangular faces.}}{3}{}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces A tire graph with non-degenerate boundaries: outer boundary $B_{\mathrm {out}}$ a $6$-cycle on vertices $0,\dots ,5$ (blue), inner boundary $B_{\mathrm {in}}$ a $4$-cycle on vertices $6,\dots ,9$ (red), inner outerplanar graph $O = B_{\mathrm {in}} \cup \{7\text {--}9\}$ (with one chord, orange), and $E_{\mathrm {ann}}$ (grey) tiling the annulus between $B_{\mathrm {out}}$ and $B_{\mathrm {in}}$ by ten triangular faces.}}{3}{}\protected@file@percent }
\newlabel{fig:tire-example}{{2}{3}} \newlabel{fig:tire-example}{{2}{3}}
\newlabel{rem:tire-counts}{{1.6}{3}}
\newlabel{lem:tire-component}{{1.7}{3}} \newlabel{lem:tire-component}{{1.7}{3}}
\newlabel{rem:tire-component-degenerate}{{1.8}{4}} \citation{bauerfeld-pds}
\citation{bauerfeld-pds}
\bibcite{bauerfeld-pds}{1} \bibcite{bauerfeld-pds}{1}
\newlabel{tocindent-1}{0pt} \newlabel{tocindent-1}{0pt}
\newlabel{tocindent0}{12.7778pt} \newlabel{tocindent0}{12.7778pt}
\newlabel{tocindent1}{17.77782pt} \newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt} \newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt} \newlabel{tocindent3}{0pt}
\newlabel{rem:R1-R2-when}{{1.9}{5}} \newlabel{rem:tire-component-degenerate}{{1.8}{5}}
\newlabel{rem:R1-when}{{1.9}{5}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{5}{}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{5}{}\protected@file@percent }
\gdef \@abspage@last{5} \gdef \@abspage@last{5}
+25 -28
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 15:36 This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 25 MAY 2026 16:26
entering extended mode entering extended mode
restricted \write18 enabled. restricted \write18 enabled.
%&-line parsing enabled. %&-line parsing enabled.
@@ -201,42 +201,39 @@ Package pdftex.def Info: fig_dual_depth.png used on input line 106.
LaTeX Warning: `h' float specifier changed to `ht'. LaTeX Warning: `h' float specifier changed to `ht'.
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
<fig_tire_example.png, id=20, 559.64081pt x 375.804pt> [2 <./fig_dual_depth.png>]
<fig_tire_example.png, id=24, 559.64081pt x 375.804pt>
File: fig_tire_example.png Graphic file (type png) File: fig_tire_example.png Graphic file (type png)
<use fig_tire_example.png> <use fig_tire_example.png>
Package pdftex.def Info: fig_tire_example.png used on input line 154. Package pdftex.def Info: fig_tire_example.png used on input line 158.
(pdftex.def) Requested size: 280.79956pt x 188.56097pt. (pdftex.def) Requested size: 280.79956pt x 188.56097pt.
[3 <./fig_tire_example.png>] [4] [5] (./paper.aux) )
LaTeX Warning: `h' float specifier changed to `ht'.
[2 <./fig_dual_depth.png>] [3 <./fig_tire_example.png>] [4] [5] (./paper.aux) )
Here is how much of TeX's memory you used: Here is how much of TeX's memory you used:
3007 strings out of 478268 3007 strings out of 478268
42001 string characters out of 5846347 41998 string characters out of 5846347
345166 words of memory out of 5000000 343166 words of memory out of 5000000
21054 multiletter control sequences out of 15000+600000 21054 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,625b,289s stack positions out of 10000i,1000n,20000p,200000b,200000s 69i,8n,76p,625b,289s 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/t
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb>< exmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/te
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></ xmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/te
usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></u xmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/tex
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr mf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texm
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/l f-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-
ocal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/loc dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-di
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/ st/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist
texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/tex /fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/texmf-dist/fo
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texliv nts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts
e/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive /type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2 pe1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/202 e1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
2/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022 /public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/t ublic/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
exmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb> blic/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publ
Output written on paper.pdf (5 pages, 486337 bytes). ic/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (5 pages, 486084 bytes).
PDF statistics: PDF statistics:
105 PDF objects out of 1000 (max. 8388607) 105 PDF objects out of 1000 (max. 8388607)
61 compressed objects within 1 object stream 61 compressed objects within 1 object stream
Binary file not shown.
+94 -89
View File
@@ -116,18 +116,23 @@ vertex.}
\begin{definition}[Tire graph] \begin{definition}[Tire graph]
\label{def:tire-graph} \label{def:tire-graph}
A \emph{tire graph} consists of a plane graph $T$ together with two A \emph{tire graph} consists of a plane graph $T$ together with an
\emph{boundary parts} $B_{\mathrm{out}}, B_{\mathrm{in}} \subseteq T$ \emph{outer boundary} $B_{\mathrm{out}} \subseteq T$ and an \emph{inner
and an \emph{inner outerplanar graph} $O \subseteq T$, where each of outerplanar graph} $O \subseteq T$ with $V(B_{\mathrm{out}}) \cap V(O)
$B_{\mathrm{out}}$ and the outer-face boundary $B_{\mathrm{in}}$ of $O$ = \emptyset$, where
is either
\begin{itemize} \begin{itemize}
\item a simple cycle of length $\geq 3$, or \item $B_{\mathrm{out}}$ is either a simple cycle of length $\geq 3$
\item a single vertex (a \emph{degenerate} boundary), or a single vertex (a \emph{degenerate outer boundary});
\item $O$ is an outerplanar graph; its \emph{inner boundary}
$B_{\mathrm{in}}$ is the closed walk in $O$ that traces the
boundary of $O$'s outer face in the inherited embedding,
which is a simple cycle when $O$ is $2$-connected and a
non-simple closed walk in general (visiting bridges twice and
cut-vertices multiple times); if $|V(O)| = 1$, we say $T$ has
a \emph{degenerate inner boundary}.
\end{itemize} \end{itemize}
with at most one of $B_{\mathrm{out}}, B_{\mathrm{in}}$ degenerate, and At most one of $B_{\mathrm{out}}, B_{\mathrm{in}}$ may be degenerate.
$V(B_{\mathrm{out}}) \cap V(O) = \emptyset$. The vertex and edge sets The vertex and edge sets of $T$ are
of $T$ are
\[ \[
V(T) = V(B_{\mathrm{out}}) \cup V(O), V(T) = V(B_{\mathrm{out}}) \cup V(O),
\qquad \qquad
@@ -137,16 +142,15 @@ where $E_{\mathrm{ann}}$ --- the \emph{annular edges} --- has the
property that, in the plane embedding of $T$, the closed planar region property that, in the plane embedding of $T$, the closed planar region
$R$ bounded externally by $B_{\mathrm{out}}$ and internally by $R$ bounded externally by $B_{\mathrm{out}}$ and internally by
$B_{\mathrm{in}}$ is partitioned into triangular faces of $T$ whose $B_{\mathrm{in}}$ is partitioned into triangular faces of $T$ whose
union is $R$. The region $R$ is a closed annulus when both union is $R$.
$B_{\mathrm{out}}$ and $B_{\mathrm{in}}$ are cycles, and a closed disk
when exactly one of them is a single vertex.
We call $B_{\mathrm{out}}$ the \emph{outer boundary}, $O$ the When $B_{\mathrm{out}}$ is a simple cycle and $O$ is $2$-connected,
\emph{inner outerplanar graph}, and $B_{\mathrm{in}}$ the \emph{inner $R$ is a closed annulus. More generally, $R$ is a planar
boundary} of $T$. A tire graph in which $B_{\mathrm{out}}$ $2$-manifold with boundary whose inner boundary may be a closed walk
(respectively $B_{\mathrm{in}}$) is a single vertex is said to have a rather than a simple cycle, accommodating outerplanar inner graphs
\emph{degenerate outer (respectively inner) boundary}; in either case with bridges, cut-vertices, or multiple connected components. When
$T$ is a triangulated closed disk with that vertex as apex. either boundary is degenerate, $R$ is a closed disk with that vertex
as apex.
\end{definition} \end{definition}
\begin{figure}[h] \begin{figure}[h]
@@ -194,16 +198,17 @@ Assume:
equivalently, at every $v \in V_{C'}$ the faces of $F_{C'}$ equivalently, at every $v \in V_{C'}$ the faces of $F_{C'}$
incident to $v$ form a single contiguous arc in the rotation incident to $v$ form a single contiguous arc in the rotation
around $v$ in $\Pi_G$. around $v$ in $\Pi_G$.
\item[\emph{(R2)}] $R_{C'}$ has at most two boundary components.
\end{itemize} \end{itemize}
Then $C$, with the inherited embedding, is a tire graph in the sense of Then $C$, with the inherited embedding, is a tire graph in the sense of
Definition~\ref{def:tire-graph}. Its outer boundary Definition~\ref{def:tire-graph}. Its outer boundary
$B_{\mathrm{out}}$ is the side of $R_{C'}$ closer to $S$ in $\Pi_G$, $B_{\mathrm{out}}$ is the side of $R_{C'}$ closer to $S$ in $\Pi_G$,
namely the level-$d$ subgraph $G[V_{C'} \cap L_d]$; its inner boundary namely the level-$d$ subgraph $G[V_{C'} \cap L_d]$ (a simple cycle or
$B_{\mathrm{in}}$ is the side farther from $S$, namely the single vertex); its inner outerplanar graph is $O = G[V_{C'} \cap
level-$(d+1)$ subgraph $G[V_{C'} \cap L_{d+1}]$; and the triangular L_{d+1}]$, and its inner boundary $B_{\mathrm{in}}$ is the outer-face
faces of $C$ inside the closed boundary region are exactly the faces of boundary closed walk of $O$ in the inherited embedding (a simple cycle
$G$ in $F_{C'}$. when $O$ is $2$-connected, a non-simple closed walk in general). The
triangular faces of $C$ inside the closed boundary region are exactly
the faces of $G$ in $F_{C'}$.
\end{lemma} \end{lemma}
\begin{proof} \begin{proof}
@@ -246,44 +251,46 @@ incident to it (by R1, see below), both of the same type, so its
level is fixed. Therefore each boundary component of $\partial R_{C'}$ level is fixed. Therefore each boundary component of $\partial R_{C'}$
is monochromatic in level. is monochromatic in level.
\emph{Boundary components are simple cycles.} By hypothesis (R1), \emph{Boundary structure.} By hypothesis (R1), $R_{C'}$ is a
$R_{C'}$ is a $2$-manifold with boundary, so locally at any boundary $2$-manifold with boundary, so locally at any boundary point $p$ the
point $p$ the region $R_{C'}$ is homeomorphic to a half-disk and the region $R_{C'}$ is homeomorphic to a half-disk; the boundary
link of $p$ in $\partial R_{C'}$ is an arc with two endpoints. In $\partial R_{C'}$ is therefore a disjoint union of simple closed
particular, at every boundary vertex $v$ exactly two boundary edges curves in $|\Pi_G|$. Each such curve traces a closed walk in $G$
are incident, and the boundary walk traverses $v$ exactly once. Each visiting each of its vertices exactly once (a simple cycle), and the
boundary component is therefore a simple closed walk in $G$ --- a monochromaticity above forces the entire curve to lie in either
simple cycle, possibly degenerating to a single vertex if $v$ has no $L_d$ or $L_{d+1}$.
incident boundary edges (which happens precisely at the BFS endpoints
$d = 0$ with $S = \{v_0\}$, or where an entire level set $V_{C'} \cap
L_{d+1}$ is empty).
\emph{Topological type.} $R_{C'}$ is a connected, compact, planar \emph{Outer boundary.} Because $S$ lies on the outer face of $\Pi_G$,
$2$-manifold with boundary; planarity gives orientability and genus the boundary curve(s) of $R_{C'}$ on the $L_d$ side are closer to $S$
zero, so by the classification of surfaces $R_{C'}$ is homeomorphic in the embedding. In the inherited embedding of $C$, the unique
to a closed disk with $n - 1$ open disks removed, where $n \geq 1$ is unbounded face is the merged region containing the rest of $\Pi_G$
the number of boundary components. Hypothesis (R2) gives $n \leq 2$, outside $R_{C'}$ on the $S$ side, so its boundary --- a simple cycle
so $R_{C'}$ is either a closed disk ($n = 1$) or a closed annulus on $L_d$ (or a single vertex when $V_{C'} \cap L_d = \{v_0\}$, the
($n = 2$). $d = 0$ case) --- serves as $B_{\mathrm{out}}$. We set
$B_{\mathrm{out}} := G[V_{C'} \cap L_d]$ if this is a cycle, and
the single vertex $\{v_0\}$ in the degenerate case.
\emph{Tire structure.} Because $S$ lies on the outer face of $\Pi_G$, \emph{Inner outerplanar graph.} By Lemma~2.6 of \cite{bauerfeld-pds},
the level-$d$ vertices are closer to $S$ in $\Pi_G$ than the $G[V_{C'} \cap L_{d+1}]$ is outerplanar. We set $O :=
level-$(d+1)$ vertices; in either the annulus or disk case the G[V_{C'} \cap L_{d+1}]$. The boundary curve(s) of $R_{C'}$ on the
boundary cycle on the $L_d$ side is the boundary of $R_{C'}$ facing $L_{d+1}$ side are exactly the boundary of $O$'s outer face in the
$S$ (the ``outer'' boundary), and the $L_{d+1}$ side is the boundary inherited embedding; this outer-face boundary is a single closed walk
facing the interior (the ``inner'' boundary). This identifies that traces around $O$ from the outside, traversing any bridge edge
$B_{\mathrm{out}} = G[V_{C'} \cap L_d]$ and $B_{\mathrm{in}} = twice and visiting cut-vertices multiple times. This walk is the
G[V_{C'} \cap L_{d+1}]$. In the disk case ($n = 1$) one of the two inner boundary $B_{\mathrm{in}}$. No further restriction on $O$'s
level sets is a single vertex (the BFS endpoint at $d = 0$ with internal structure is needed: when $R_{C'}$ has more than two
$S = \{v_0\}$, or symmetrically at $d = D_{\max}$ where the inner boundary components in the surface-classification sense (i.e.\
side collapses to a deepest vertex), giving the degenerate-boundary several disjoint simple cycles on $L_{d+1}$), these correspond
case of Definition~\ref{def:tire-graph}. precisely to the multiple connected components or bridge crossings of
$O$, and the outer-face boundary closed walk of $O$ captures them
collectively.
The triangular faces inside the closed boundary region of $C$ are by \emph{Tire structure.} The triangular faces of $C$ inside the closed
construction the depth-$d$ faces in $F_{C'}$, and the edges of $C$ are boundary region are by construction the depth-$d$ faces in $F_{C'}$,
$E(B_{\mathrm{out}}) \cup E(O) \cup E_{\mathrm{ann}}$ where and the edges of $C$ are $E(B_{\mathrm{out}}) \cup E(O) \cup
$E_{\mathrm{ann}}$ are the edges of $G$ between $V_{C'} \cap L_d$ and E_{\mathrm{ann}}$ where $E_{\mathrm{ann}}$ are the edges of $G$
$V_{C'} \cap L_{d+1}$ that bound a face of $F_{C'}$. between $V_{C'} \cap L_d$ and $V_{C'} \cap L_{d+1}$ that bound a face
of $F_{C'}$.
\end{proof} \end{proof}
\begin{remark} \begin{remark}
@@ -299,39 +306,37 @@ the level-$D_{\max}$ cycle as the outer boundary.
\end{remark} \end{remark}
\begin{remark} \begin{remark}
\label{rem:R1-R2-when} \label{rem:R1-when}
The two hypotheses of Lemma~\ref{lem:tire-component} hold in many Hypothesis (R1) of Lemma~\ref{lem:tire-component} holds in many
natural settings but can fail in general: natural settings but can fail. (R1) fails at a \emph{pinch vertex}
$v \in V_{C'}$ when the faces of $F_{C'}$ incident to $v$ split into
\emph{(R1) and the pinch obstruction.} Hypothesis (R1) fails at a two or more disjoint arcs of the rotation around $v$ in $\Pi_G$.
\emph{pinch vertex} $v \in V_{C'}$ when the faces of $F_{C'}$ incident Such a $v$ has at least four neighbours $w_i, w_{i+1}, w_j, w_{j+1}$
to $v$ split into two or more disjoint arcs of the rotation around $v$ (with $i + 1 < j$) in cyclic order such that the faces
in $\Pi_G$. Such a $v$ has at least four neighbours $w_i, w_{i+1},
w_j, w_{j+1}$ (with $i + 1 < j$) in cyclic order such that the faces
$\{v, w_i, w_{i+1}\}$ and $\{v, w_j, w_{j+1}\}$ are both depth-$d$ $\{v, w_i, w_{i+1}\}$ and $\{v, w_j, w_{j+1}\}$ are both depth-$d$
(both endpoints at level $\geq d$) while at least one face in each of while at least one face in each of the rotation gaps between them
the rotation gaps between them carries depth $\neq d$. Concretely, carries depth $\neq d$. Concretely, this occurs precisely when the
this occurs precisely when the cyclic level sequence cyclic level sequence $\ell(w_1), \ldots, \ell(w_{\deg v})$ enters and
$\ell(w_1), \ldots, \ell(w_{\deg v})$ enters and leaves $\{d, d+1\}$ leaves $\{d, d+1\}$ more than once. Whenever such a $v$ exists and
more than once. Whenever such a $v$ exists and the two arcs are the two arcs are joined to a common component of $G'_d$ by some
joined to a common component of $G'_d$ by some \emph{other} path of \emph{other} path of depth-$d$ faces (not through $v$), the resulting
depth-$d$ faces (not through $v$), the resulting $R_{C'}$ is a wedge $R_{C'}$ is a wedge of two manifold regions at $v$, violating (R1).
of two manifold regions at $v$, violating (R1).
\emph{(R2) and the multi-hole obstruction.} Hypothesis (R2) fails Multi-hole topology (where $R_{C'}$ encloses several disjoint
when the depth-$d$ region $R_{C'}$ encloses two or more disjoint depth-$>d$ sub-regions) does \emph{not} require an additional
depth-$> d$ sub-regions. In a BFS the depth-$< d$ region (the BFS hypothesis: the inner outerplanar graph $O = G[V_{C'} \cap L_{d+1}]$
ball of radius $d - 1$) is connected, so at most one boundary captures the multi-hole structure as a disconnected (or
component of $R_{C'}$ can lie on the source side; (R2) is therefore non-$2$-connected) outerplanar graph, and its outer-face boundary
equivalent to ``the closure of the depth-$> d$ region adjacent to closed walk serves as $B_{\mathrm{in}}$. In particular, two disjoint
$R_{C'}$ has at most one connected component.'' Multi-hole topology $L_{d+1}$ triangles inside $R_{C'}$ joined by a bridge edge in $O$
arises when several disjoint depth-$> d$ ``lobes'' of $G$ sit inside give a $B_{\mathrm{in}}$ that traverses the bridge twice and visits
the same depth-$d$ component. the bridge endpoints twice each --- not a simple cycle, but a
well-defined closed walk on $V(O)$.
In the special case $d = 0$ with single-vertex source $S = \{v_0\}$ In the special case $d = 0$ with single-vertex source $S = \{v_0\}$
both hypotheses hold automatically: $R_{C'}$ is the star of $v_0$, (R1) holds automatically: $R_{C'}$ is the star of $v_0$, a topological
a topological closed disk with one boundary cycle (the link of $v_0$), closed disk with one boundary cycle (the link of $v_0$), giving a
giving a tire graph with degenerate inner boundary $\{v_0\}$. tire graph with degenerate outer boundary $\{v_0\}$.
\end{remark} \end{remark}
\begin{thebibliography}{9} \begin{thebibliography}{9}