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:
@@ -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 |
@@ -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 }
|
||||
\newlabel{fig:dual-depth}{{1}{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 }
|
||||
\newlabel{fig:tire-example}{{2}{3}}
|
||||
\newlabel{rem:tire-counts}{{1.6}{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}
|
||||
\newlabel{tocindent-1}{0pt}
|
||||
\newlabel{tocindent0}{12.7778pt}
|
||||
\newlabel{tocindent1}{17.77782pt}
|
||||
\newlabel{tocindent2}{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 }
|
||||
\gdef \@abspage@last{5}
|
||||
|
||||
@@ -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
|
||||
restricted \write18 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'.
|
||||
|
||||
[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)
|
||||
<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.
|
||||
|
||||
|
||||
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||
|
||||
[2 <./fig_dual_depth.png>] [3 <./fig_tire_example.png>] [4] [5] (./paper.aux) )
|
||||
|
||||
[3 <./fig_tire_example.png>] [4] [5] (./paper.aux) )
|
||||
Here is how much of TeX's memory you used:
|
||||
3007 strings out of 478268
|
||||
42001 string characters out of 5846347
|
||||
345166 words of memory out of 5000000
|
||||
41998 string characters out of 5846347
|
||||
343166 words of memory out of 5000000
|
||||
21054 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,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/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb><
|
||||
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></
|
||||
usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></u
|
||||
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr
|
||||
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/l
|
||||
ocal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/loc
|
||||
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/
|
||||
texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/tex
|
||||
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texliv
|
||||
e/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/2
|
||||
022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/202
|
||||
2/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022
|
||||
/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/t
|
||||
exmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||
Output written on paper.pdf (5 pages, 486337 bytes).
|
||||
</usr/local/texlive/2022/t
|
||||
exmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/te
|
||||
xmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/te
|
||||
xmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/tex
|
||||
mf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texm
|
||||
f-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-
|
||||
dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-di
|
||||
st/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist
|
||||
/fonts/type1/public/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/texmf-dist/fo
|
||||
nts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts
|
||||
/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
|
||||
pe1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
|
||||
e1/public/amsfonts/cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
|
||||
/public/amsfonts/cm/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
|
||||
ublic/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
|
||||
blic/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publ
|
||||
ic/amsfonts/symbols/msam10.pfb>
|
||||
Output written on paper.pdf (5 pages, 486084 bytes).
|
||||
PDF statistics:
|
||||
105 PDF objects out of 1000 (max. 8388607)
|
||||
61 compressed objects within 1 object stream
|
||||
|
||||
Binary file not shown.
@@ -116,18 +116,23 @@ vertex.}
|
||||
|
||||
\begin{definition}[Tire graph]
|
||||
\label{def:tire-graph}
|
||||
A \emph{tire graph} consists of a plane graph $T$ together with two
|
||||
\emph{boundary parts} $B_{\mathrm{out}}, B_{\mathrm{in}} \subseteq T$
|
||||
and an \emph{inner outerplanar graph} $O \subseteq T$, where each of
|
||||
$B_{\mathrm{out}}$ and the outer-face boundary $B_{\mathrm{in}}$ of $O$
|
||||
is either
|
||||
A \emph{tire graph} consists of a plane graph $T$ together with an
|
||||
\emph{outer boundary} $B_{\mathrm{out}} \subseteq T$ and an \emph{inner
|
||||
outerplanar graph} $O \subseteq T$ with $V(B_{\mathrm{out}}) \cap V(O)
|
||||
= \emptyset$, where
|
||||
\begin{itemize}
|
||||
\item a simple cycle of length $\geq 3$, or
|
||||
\item a single vertex (a \emph{degenerate} boundary),
|
||||
\item $B_{\mathrm{out}}$ is either a simple cycle of length $\geq 3$
|
||||
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}
|
||||
with at most one of $B_{\mathrm{out}}, B_{\mathrm{in}}$ degenerate, and
|
||||
$V(B_{\mathrm{out}}) \cap V(O) = \emptyset$. The vertex and edge sets
|
||||
of $T$ are
|
||||
At most one of $B_{\mathrm{out}}, B_{\mathrm{in}}$ may be degenerate.
|
||||
The vertex and edge sets of $T$ are
|
||||
\[
|
||||
V(T) = V(B_{\mathrm{out}}) \cup V(O),
|
||||
\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
|
||||
$R$ bounded externally by $B_{\mathrm{out}}$ and internally by
|
||||
$B_{\mathrm{in}}$ is partitioned into triangular faces of $T$ whose
|
||||
union is $R$. The region $R$ is a closed annulus when both
|
||||
$B_{\mathrm{out}}$ and $B_{\mathrm{in}}$ are cycles, and a closed disk
|
||||
when exactly one of them is a single vertex.
|
||||
union is $R$.
|
||||
|
||||
We call $B_{\mathrm{out}}$ the \emph{outer boundary}, $O$ the
|
||||
\emph{inner outerplanar graph}, and $B_{\mathrm{in}}$ the \emph{inner
|
||||
boundary} of $T$. A tire graph in which $B_{\mathrm{out}}$
|
||||
(respectively $B_{\mathrm{in}}$) is a single vertex is said to have a
|
||||
\emph{degenerate outer (respectively inner) boundary}; in either case
|
||||
$T$ is a triangulated closed disk with that vertex as apex.
|
||||
When $B_{\mathrm{out}}$ is a simple cycle and $O$ is $2$-connected,
|
||||
$R$ is a closed annulus. More generally, $R$ is a planar
|
||||
$2$-manifold with boundary whose inner boundary may be a closed walk
|
||||
rather than a simple cycle, accommodating outerplanar inner graphs
|
||||
with bridges, cut-vertices, or multiple connected components. When
|
||||
either boundary is degenerate, $R$ is a closed disk with that vertex
|
||||
as apex.
|
||||
\end{definition}
|
||||
|
||||
\begin{figure}[h]
|
||||
@@ -194,16 +198,17 @@ Assume:
|
||||
equivalently, at every $v \in V_{C'}$ the faces of $F_{C'}$
|
||||
incident to $v$ form a single contiguous arc in the rotation
|
||||
around $v$ in $\Pi_G$.
|
||||
\item[\emph{(R2)}] $R_{C'}$ has at most two boundary components.
|
||||
\end{itemize}
|
||||
Then $C$, with the inherited embedding, is a tire graph in the sense of
|
||||
Definition~\ref{def:tire-graph}. Its outer boundary
|
||||
$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
|
||||
$B_{\mathrm{in}}$ is the side farther from $S$, namely the
|
||||
level-$(d+1)$ subgraph $G[V_{C'} \cap L_{d+1}]$; and the triangular
|
||||
faces of $C$ inside the closed boundary region are exactly the faces of
|
||||
$G$ in $F_{C'}$.
|
||||
namely the level-$d$ subgraph $G[V_{C'} \cap L_d]$ (a simple cycle or
|
||||
single vertex); its inner outerplanar graph is $O = G[V_{C'} \cap
|
||||
L_{d+1}]$, and its inner boundary $B_{\mathrm{in}}$ is the outer-face
|
||||
boundary closed walk of $O$ in the inherited embedding (a simple cycle
|
||||
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}
|
||||
|
||||
\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'}$
|
||||
is monochromatic in level.
|
||||
|
||||
\emph{Boundary components are simple cycles.} By hypothesis (R1),
|
||||
$R_{C'}$ is a $2$-manifold with boundary, so locally at any boundary
|
||||
point $p$ the region $R_{C'}$ is homeomorphic to a half-disk and the
|
||||
link of $p$ in $\partial R_{C'}$ is an arc with two endpoints. In
|
||||
particular, at every boundary vertex $v$ exactly two boundary edges
|
||||
are incident, and the boundary walk traverses $v$ exactly once. Each
|
||||
boundary component is therefore a simple closed walk in $G$ --- a
|
||||
simple cycle, possibly degenerating to a single vertex if $v$ has no
|
||||
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{Boundary structure.} By hypothesis (R1), $R_{C'}$ is a
|
||||
$2$-manifold with boundary, so locally at any boundary point $p$ the
|
||||
region $R_{C'}$ is homeomorphic to a half-disk; the boundary
|
||||
$\partial R_{C'}$ is therefore a disjoint union of simple closed
|
||||
curves in $|\Pi_G|$. Each such curve traces a closed walk in $G$
|
||||
visiting each of its vertices exactly once (a simple cycle), and the
|
||||
monochromaticity above forces the entire curve to lie in either
|
||||
$L_d$ or $L_{d+1}$.
|
||||
|
||||
\emph{Topological type.} $R_{C'}$ is a connected, compact, planar
|
||||
$2$-manifold with boundary; planarity gives orientability and genus
|
||||
zero, so by the classification of surfaces $R_{C'}$ is homeomorphic
|
||||
to a closed disk with $n - 1$ open disks removed, where $n \geq 1$ is
|
||||
the number of boundary components. Hypothesis (R2) gives $n \leq 2$,
|
||||
so $R_{C'}$ is either a closed disk ($n = 1$) or a closed annulus
|
||||
($n = 2$).
|
||||
\emph{Outer boundary.} Because $S$ lies on the outer face of $\Pi_G$,
|
||||
the boundary curve(s) of $R_{C'}$ on the $L_d$ side are closer to $S$
|
||||
in the embedding. In the inherited embedding of $C$, the unique
|
||||
unbounded face is the merged region containing the rest of $\Pi_G$
|
||||
outside $R_{C'}$ on the $S$ side, so its boundary --- a simple cycle
|
||||
on $L_d$ (or a single vertex when $V_{C'} \cap L_d = \{v_0\}$, the
|
||||
$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$,
|
||||
the level-$d$ vertices are closer to $S$ in $\Pi_G$ than the
|
||||
level-$(d+1)$ vertices; in either the annulus or disk case the
|
||||
boundary cycle on the $L_d$ side is the boundary of $R_{C'}$ facing
|
||||
$S$ (the ``outer'' boundary), and the $L_{d+1}$ side is the boundary
|
||||
facing the interior (the ``inner'' boundary). This identifies
|
||||
$B_{\mathrm{out}} = G[V_{C'} \cap L_d]$ and $B_{\mathrm{in}} =
|
||||
G[V_{C'} \cap L_{d+1}]$. In the disk case ($n = 1$) one of the two
|
||||
level sets is a single vertex (the BFS endpoint at $d = 0$ with
|
||||
$S = \{v_0\}$, or symmetrically at $d = D_{\max}$ where the inner
|
||||
side collapses to a deepest vertex), giving the degenerate-boundary
|
||||
case of Definition~\ref{def:tire-graph}.
|
||||
\emph{Inner outerplanar graph.} By Lemma~2.6 of \cite{bauerfeld-pds},
|
||||
$G[V_{C'} \cap L_{d+1}]$ is outerplanar. We set $O :=
|
||||
G[V_{C'} \cap L_{d+1}]$. The boundary curve(s) of $R_{C'}$ on the
|
||||
$L_{d+1}$ side are exactly the boundary of $O$'s outer face in the
|
||||
inherited embedding; this outer-face boundary is a single closed walk
|
||||
that traces around $O$ from the outside, traversing any bridge edge
|
||||
twice and visiting cut-vertices multiple times. This walk is the
|
||||
inner boundary $B_{\mathrm{in}}$. No further restriction on $O$'s
|
||||
internal structure is needed: when $R_{C'}$ has more than two
|
||||
boundary components in the surface-classification sense (i.e.\
|
||||
several disjoint simple cycles on $L_{d+1}$), these correspond
|
||||
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
|
||||
construction the depth-$d$ faces in $F_{C'}$, and the edges of $C$ are
|
||||
$E(B_{\mathrm{out}}) \cup E(O) \cup E_{\mathrm{ann}}$ where
|
||||
$E_{\mathrm{ann}}$ are the edges of $G$ between $V_{C'} \cap L_d$ and
|
||||
$V_{C'} \cap L_{d+1}$ that bound a face of $F_{C'}$.
|
||||
\emph{Tire structure.} The triangular faces of $C$ inside the closed
|
||||
boundary region are by construction the depth-$d$ faces in $F_{C'}$,
|
||||
and the edges of $C$ are $E(B_{\mathrm{out}}) \cup E(O) \cup
|
||||
E_{\mathrm{ann}}$ where $E_{\mathrm{ann}}$ are the edges of $G$
|
||||
between $V_{C'} \cap L_d$ and $V_{C'} \cap L_{d+1}$ that bound a face
|
||||
of $F_{C'}$.
|
||||
\end{proof}
|
||||
|
||||
\begin{remark}
|
||||
@@ -299,39 +306,37 @@ the level-$D_{\max}$ cycle as the outer boundary.
|
||||
\end{remark}
|
||||
|
||||
\begin{remark}
|
||||
\label{rem:R1-R2-when}
|
||||
The two hypotheses of Lemma~\ref{lem:tire-component} hold in many
|
||||
natural settings but can fail in general:
|
||||
|
||||
\emph{(R1) and the pinch obstruction.} Hypothesis (R1) fails at a
|
||||
\emph{pinch vertex} $v \in V_{C'}$ when the faces of $F_{C'}$ incident
|
||||
to $v$ split into two or more disjoint arcs of the rotation around $v$
|
||||
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
|
||||
\label{rem:R1-when}
|
||||
Hypothesis (R1) of Lemma~\ref{lem:tire-component} holds in many
|
||||
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
|
||||
two or more disjoint arcs of the rotation around $v$ 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$
|
||||
(both endpoints at level $\geq d$) while at least one face in each of
|
||||
the rotation gaps between them carries depth $\neq d$. Concretely,
|
||||
this occurs precisely when the cyclic level sequence
|
||||
$\ell(w_1), \ldots, \ell(w_{\deg v})$ enters and leaves $\{d, d+1\}$
|
||||
more than once. Whenever such a $v$ exists and the two arcs are
|
||||
joined to a common component of $G'_d$ by some \emph{other} path of
|
||||
depth-$d$ faces (not through $v$), the resulting $R_{C'}$ is a wedge
|
||||
of two manifold regions at $v$, violating (R1).
|
||||
while at least one face in each of the rotation gaps between them
|
||||
carries depth $\neq d$. Concretely, this occurs precisely when the
|
||||
cyclic level sequence $\ell(w_1), \ldots, \ell(w_{\deg v})$ enters and
|
||||
leaves $\{d, d+1\}$ more than once. Whenever such a $v$ exists and
|
||||
the two arcs are joined to a common component of $G'_d$ by some
|
||||
\emph{other} path of depth-$d$ faces (not through $v$), the resulting
|
||||
$R_{C'}$ is a wedge of two manifold regions at $v$, violating (R1).
|
||||
|
||||
\emph{(R2) and the multi-hole obstruction.} Hypothesis (R2) fails
|
||||
when the depth-$d$ region $R_{C'}$ encloses two or more disjoint
|
||||
depth-$> d$ sub-regions. In a BFS the depth-$< d$ region (the BFS
|
||||
ball of radius $d - 1$) is connected, so at most one boundary
|
||||
component of $R_{C'}$ can lie on the source side; (R2) is therefore
|
||||
equivalent to ``the closure of the depth-$> d$ region adjacent to
|
||||
$R_{C'}$ has at most one connected component.'' Multi-hole topology
|
||||
arises when several disjoint depth-$> d$ ``lobes'' of $G$ sit inside
|
||||
the same depth-$d$ component.
|
||||
Multi-hole topology (where $R_{C'}$ encloses several disjoint
|
||||
depth-$>d$ sub-regions) does \emph{not} require an additional
|
||||
hypothesis: the inner outerplanar graph $O = G[V_{C'} \cap L_{d+1}]$
|
||||
captures the multi-hole structure as a disconnected (or
|
||||
non-$2$-connected) outerplanar graph, and its outer-face boundary
|
||||
closed walk serves as $B_{\mathrm{in}}$. In particular, two disjoint
|
||||
$L_{d+1}$ triangles inside $R_{C'}$ joined by a bridge edge in $O$
|
||||
give a $B_{\mathrm{in}}$ that traverses the bridge twice and visits
|
||||
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\}$
|
||||
both hypotheses hold automatically: $R_{C'}$ is the star of $v_0$,
|
||||
a topological closed disk with one boundary cycle (the link of $v_0$),
|
||||
giving a tire graph with degenerate inner boundary $\{v_0\}$.
|
||||
(R1) holds automatically: $R_{C'}$ is the star of $v_0$, a topological
|
||||
closed disk with one boundary cycle (the link of $v_0$), giving a
|
||||
tire graph with degenerate outer boundary $\{v_0\}$.
|
||||
\end{remark}
|
||||
|
||||
\begin{thebibliography}{9}
|
||||
|
||||
Reference in New Issue
Block a user