Add Even Level Graph Generators paper + extend Level Switching reachability

- New paper papers/even_level_graph_generators/: defines Even Level
  Graph (every level cycle even), derived level graphs, intertwining
  trees, and the disjunction conjecture (every maximal planar graph is
  a derived level graph or intertwining tree). Empirically tested
  through n=11: every iso class is at least an intertwining tree, so
  the disjunction holds trivially in this range. The intertwining tree
  disjunct fails at the Tutte graph dual (n=25), so the disjunction
  becomes non-trivial past some unknown threshold.

- Level Switching paper: adds Section 4 (Reachability via edge
  switches) with the two-step argument (Sleator-Tarjan-Thurston for
  Case 1; face-merges for Case 2) and Theorem 4.1 (O(n) edge switches
  suffice to reach all-depth-0).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 16:44:39 -04:00
parent 082ee31966
commit c947ce75ff
29 changed files with 2180 additions and 23 deletions
@@ -0,0 +1,38 @@
"""Plot iso[49] at n=9, the counterexample to Conjecture 4.4."""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
'level_resolutions_of_maximal_planar_graphs/experiments')
import networkx as nx
import matplotlib.pyplot as plt
from triangulation_gen import enumerate_all_triangulations
OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
tris = enumerate_all_triangulations(9)
G = tris[49]
print(f'iso[49] degree sequence: {sorted([G.degree(v) for v in G.nodes()], reverse=True)}')
print(f'iso[49] edges: {sorted(G.edges())}')
# Use planar layout
_, emb = nx.check_planarity(G)
pos = nx.combinatorial_embedding_to_pos(emb)
fig, ax = plt.subplots(figsize=(8, 7))
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#333', width=1.5)
# Color vertices by degree
degree_color = {4: '#3b82f6', 5: '#dc2626'}
node_colors = [degree_color[G.degree(v)] for v in G.nodes()]
nx.draw_networkx_nodes(G, pos, ax=ax, node_color=node_colors,
node_size=600, edgecolors='black', linewidths=1.2)
nx.draw_networkx_labels(G, pos, ax=ax, font_color='white',
font_size=11, font_weight='bold')
ax.set_aspect('equal'); ax.axis('off')
ax.set_title('iso[49] at $n=9$: degree sequence (5,5,5,5,5,5,4,4,4).\n'
'NOT a valid derived level graph of any Even Level Graph.\n'
'Blue = degree 4, Red = degree 5.', fontsize=11)
fig.tight_layout()
out = os.path.join(OUT_DIR, 'fig_n9_counterexample.png')
fig.savefig(out, dpi=180, bbox_inches='tight')
plt.close(fig)
print(f'wrote {out}')
@@ -0,0 +1,92 @@
"""Show a valid parity partition of iso[49] at n=9, with the induced
4-coloring."""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
'level_resolutions_of_maximal_planar_graphs/experiments')
import networkx as nx
import matplotlib.pyplot as plt
from triangulation_gen import enumerate_all_triangulations
OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
tris = enumerate_all_triangulations(9)
G = tris[49]
V_E = (0, 1, 3, 6)
V_O = (2, 4, 5, 7, 8)
# 2-color each induced subgraph
def bipart_coloring(subg):
# BFS-based 2-coloring; returns dict v -> 0/1
cmap = {}
for start in subg.nodes():
if start in cmap: continue
cmap[start] = 0
frontier = [start]
while frontier:
new = []
for u in frontier:
for w in subg.neighbors(u):
if w not in cmap:
cmap[w] = 1 - cmap[u]
new.append(w)
frontier = new
return cmap
cE = bipart_coloring(G.subgraph(V_E))
cO = bipart_coloring(G.subgraph(V_O))
# 4-color: even gets red/blue, odd gets yellow/green
COLOR_RED, COLOR_BLUE = '#dc2626', '#3b82f6'
COLOR_YELLOW, COLOR_GREEN = '#eab308', '#16a34a'
four_color = {}
for v in V_E:
four_color[v] = COLOR_RED if cE[v] == 0 else COLOR_BLUE
for v in V_O:
four_color[v] = COLOR_YELLOW if cO[v] == 0 else COLOR_GREEN
# Verify proper 4-coloring
for u, v in G.edges():
assert four_color[u] != four_color[v], \
f'edge ({u},{v}) violates 4-coloring: {four_color[u]} vs {four_color[v]}'
print('4-coloring is proper.')
_, emb = nx.check_planarity(G)
pos = nx.combinatorial_embedding_to_pos(emb)
fig, ax = plt.subplots(figsize=(9, 8))
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#333', width=1.5)
node_colors = [four_color[v] for v in G.nodes()]
nx.draw_networkx_nodes(G, pos, ax=ax, node_color=node_colors,
node_size=700, edgecolors='black', linewidths=1.3)
nx.draw_networkx_labels(G, pos, ax=ax, font_color='white',
font_size=12, font_weight='bold')
ax.set_aspect('equal'); ax.axis('off')
# Legend
import matplotlib.patches as mpatches
legend_handles = [
mpatches.Patch(color=COLOR_RED, label=r'even-parity vertex, bipartition class 0'),
mpatches.Patch(color=COLOR_BLUE, label=r'even-parity vertex, bipartition class 1'),
mpatches.Patch(color=COLOR_YELLOW, label=r'odd-parity vertex, bipartition class 0'),
mpatches.Patch(color=COLOR_GREEN, label=r'odd-parity vertex, bipartition class 1'),
]
ax.legend(handles=legend_handles, loc='lower center',
bbox_to_anchor=(0.5, -0.08), ncol=2, fontsize=9, frameon=False)
ax.set_title(f'iso[49] with a valid parity partition\n'
f'$V_E = \\{{0, 1, 3, 6\\}}$, '
f'$V_O = \\{{2, 4, 5, 7, 8\\}}$\n'
f'(both induced subgraphs bipartite; 4-coloring derived)',
fontsize=11)
fig.tight_layout()
out = os.path.join(OUT_DIR, 'fig_n9_valid_partition.png')
fig.savefig(out, dpi=180, bbox_inches='tight')
plt.close(fig)
print(f'wrote {out}')
# Also report the induced subgraphs
GE = G.subgraph(V_E)
GO = G.subgraph(V_O)
print(f'G[V_E] = {sorted(GE.edges())} bipartite={nx.is_bipartite(GE)}')
print(f'G[V_O] = {sorted(GO.edges())} bipartite={nx.is_bipartite(GO)}')
@@ -0,0 +1,172 @@
"""Empirical test of the derived-level-graph conjecture for n=6..8.
For each iso class of maximal planar graphs G':
Search for an Even Level Graph G (some iso class, some level source)
such that G' is in the iso-class orbit of G under E/O-edge switches.
"""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
'level_resolutions_of_maximal_planar_graphs/experiments')
import time
import itertools
import networkx as nx
from triangulation_gen import enumerate_all_triangulations
def canonical_sig(G):
"""Iso-class signature: WL hash via Weisfeiler-Lehman or just sorted
edge tuples up to vertex relabelling. We use nx.weisfeiler_lehman_graph_hash."""
return nx.weisfeiler_lehman_graph_hash(G)
def labelled_sig(G, labels):
"""Signature that respects both graph structure and a vertex labelling
(even/odd). Two graphs match iff there's an iso preserving labels."""
H = G.copy()
for v in H.nodes():
H.nodes[v]['label'] = labels[v]
return nx.weisfeiler_lehman_graph_hash(H, node_attr='label')
def bfs_levels(G, source_set):
"""BFS from a set of source vertices in G. Returns dict v -> level."""
levels = {v: float('inf') for v in G.nodes()}
frontier = list(source_set)
for v in frontier:
levels[v] = 0
d = 0
while frontier:
next_frontier = []
for u in frontier:
for w in G.neighbors(u):
if levels[w] > d + 1:
levels[w] = d + 1
next_frontier.append(w)
frontier = next_frontier
d += 1
return levels
def get_level_sources(G):
"""Yield each vertex as a possible level source (singleton set)."""
for v in G.nodes():
yield frozenset({v})
def is_even_level_graph(G, source):
"""Check: every level cycle of G (BFS from source) has even length."""
levels = bfs_levels(G, source)
if any(l == float('inf') for l in levels.values()):
return False, None
max_l = max(levels.values())
for k in range(max_l + 1):
L_k_nodes = [v for v in G.nodes() if levels[v] == k]
L_k = G.subgraph(L_k_nodes)
if L_k.number_of_edges() == 0:
continue
# Check bipartiteness (equivalent to no odd cycles)
if not nx.is_bipartite(L_k):
return False, None
return True, levels
def is_valid_parity_partition(G, labels):
"""Both induced subgraphs G[V_E] and G[V_O] are bipartite."""
V_E = [v for v in G.nodes() if labels[v] % 2 == 0]
V_O = [v for v in G.nodes() if labels[v] % 2 == 1]
return nx.is_bipartite(G.subgraph(V_E)) and nx.is_bipartite(G.subgraph(V_O))
def E_O_switches(G, labels):
"""Yield all triangulations reachable from G by one E/O-edge switch.
An E/O-edge has both endpoints of the same parity in `labels`."""
ip, emb = nx.check_planarity(G)
if not ip:
return
yielded = set()
for u, v in list(G.edges()):
if labels[u] % 2 != labels[v] % 2:
continue
f1 = emb.traverse_face(u, v)
f2 = emb.traverse_face(v, u)
if len(f1) != 3 or len(f2) != 3:
continue
w = next(x for x in f1 if x != u and x != v)
x = next(y for y in f2 if y != u and y != v)
if w == x or G.has_edge(w, x):
continue
Gp = G.copy()
Gp.remove_edge(u, v)
Gp.add_edge(w, x)
sig = frozenset(frozenset(e) for e in Gp.edges())
if sig in yielded:
continue
yielded.add(sig)
yield Gp
def bfs_orbit(G_start, labels, max_states=200000):
"""BFS in E/O-switch graph starting from G_start (with given labels).
Returns the set of iso classes (unlabelled) reachable."""
seen_labelled = {frozenset(frozenset(e) for e in G_start.edges())}
iso_classes_reached = {canonical_sig(G_start)}
frontier = [G_start]
rounds = 0
while frontier and len(seen_labelled) < max_states:
new = []
for G in frontier:
for Gp in E_O_switches(G, labels):
sig = frozenset(frozenset(e) for e in Gp.edges())
if sig in seen_labelled:
continue
seen_labelled.add(sig)
iso_classes_reached.add(canonical_sig(Gp))
new.append(Gp)
frontier = new
rounds += 1
return iso_classes_reached, len(seen_labelled), rounds
def test_for_n(n):
print(f'\n=== n = {n} ===')
t0 = time.time()
tris = enumerate_all_triangulations(n)
print(f' {len(tris)} iso classes of triangulations')
iso_class_sigs = {canonical_sig(T) for T in tris}
# Set of iso classes that ARE derived level graphs (of some ELG)
derived_iso_classes = set()
for i, G in enumerate(tris):
if len(derived_iso_classes) == len(iso_class_sigs):
break # early exit: everything is already covered
for source in get_level_sources(G):
is_elg, levels = is_even_level_graph(G, source)
if not is_elg:
continue
reached, n_lbld, rnds = bfs_orbit(G, levels)
new_count = len(reached - derived_iso_classes)
derived_iso_classes.update(reached)
if new_count > 0:
print(f' iso[{i}] source={sorted(source)}: ELG, '
f'orbit adds {new_count} new iso classes '
f'(orbit size {len(reached)}, '
f'{n_lbld} labelled, {rnds} rounds, '
f'total {len(derived_iso_classes)}/'
f'{len(iso_class_sigs)})')
missing = iso_class_sigs - derived_iso_classes
print(f' TOTAL: {len(derived_iso_classes)} / {len(iso_class_sigs)} '
f'iso classes are derived level graphs')
print(f' missing: {len(missing)}')
print(f' elapsed: {time.time() - t0:.1f}s')
return len(missing) == 0
if __name__ == '__main__':
import sys
ns = [int(x) for x in sys.argv[1:]] if len(sys.argv) > 1 else [6, 7, 8]
for n in ns:
ok = test_for_n(n)
print(f' conjecture holds for n={n}: {ok}')
@@ -0,0 +1,120 @@
"""Test the disjunction: every maximal planar graph is a valid derived
level graph, an intertwining tree, or both. Iterates n=6..12, stops if
a counterexample is found."""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
'level_resolutions_of_maximal_planar_graphs/experiments')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import time
import itertools
import networkx as nx
from triangulation_gen import enumerate_all_triangulations
from test_conjecture import (
canonical_sig, bfs_levels, get_level_sources,
is_even_level_graph, bfs_orbit
)
def is_intertwining_tree(G):
"""Search for a 2-partition (A, B) such that G[A] and G[B] are trees."""
nodes = list(G.nodes())
n = len(nodes)
# Try all 2^(n-1) partitions (fix node 0 in A by convention)
for mask in range(1, 2 ** (n - 1)):
A = [nodes[0]] + [nodes[i + 1] for i in range(n - 1) if (mask >> i) & 1]
B = [nodes[i + 1] for i in range(n - 1) if not ((mask >> i) & 1)]
if not A or not B:
continue
GA = G.subgraph(A)
GB = G.subgraph(B)
if nx.is_tree(GA) and nx.is_tree(GB):
return True, (tuple(A), tuple(B))
return False, None
def derived_level_graph_iso_classes(tris):
"""Compute the set of iso class signatures that are derived level
graphs of some Even Level Graph."""
iso_class_sigs = {canonical_sig(T) for T in tris}
derived = set()
for G in tris:
if len(derived) == len(iso_class_sigs):
break
for source in get_level_sources(G):
is_elg, levels = is_even_level_graph(G, source)
if not is_elg:
continue
reached, _, _ = bfs_orbit(G, levels)
derived.update(reached)
return derived
def test_n(n):
t0 = time.time()
tris = enumerate_all_triangulations(n)
iso_sigs = [canonical_sig(T) for T in tris]
derived = derived_level_graph_iso_classes(tris)
n_derived = sum(1 for s in iso_sigs if s in derived)
# For iso classes that are NOT derived, check intertwining tree
counterexamples = []
n_intertwining_only = 0
n_both = 0
for i, G in enumerate(tris):
is_derived = iso_sigs[i] in derived
is_inter, partition = is_intertwining_tree(G)
if is_derived and is_inter:
n_both += 1
elif is_derived:
pass # only derived
elif is_inter:
n_intertwining_only += 1
else:
counterexamples.append((i, G))
n_total = len(tris)
n_only_derived = n_derived - n_both
elapsed = time.time() - t0
return {
'n': n,
'total': n_total,
'derived': n_derived,
'intertwining_only': n_intertwining_only,
'both': n_both,
'only_derived': n_only_derived,
'counterexamples': counterexamples,
'elapsed': elapsed,
}
def main():
results = []
for n in [6, 7, 8, 9, 10, 11, 12]:
print(f'\n=== n = {n} ===')
r = test_n(n)
results.append(r)
print(f' total iso classes: {r["total"]}')
print(f' derived only: {r["only_derived"]}')
print(f' intertwining only: {r["intertwining_only"]}')
print(f' both: {r["both"]}')
print(f' counterexamples: {len(r["counterexamples"])}')
print(f' elapsed: {r["elapsed"]:.1f}s')
if r['counterexamples']:
print(f' COUNTEREXAMPLE FOUND. Stopping.')
for i, G in r['counterexamples'][:3]:
print(f' iso[{i}] degree seq = '
f'{sorted([G.degree(v) for v in G.nodes()], reverse=True)}')
break
print('\n=== Final summary ===')
print(f'{"n":>3} {"total":>6} {"deriv":>6} {"inter":>6} {"both":>6} {"missing":>8}')
for r in results:
cov = r['only_derived'] + r['intertwining_only'] + r['both']
missing = r['total'] - cov
print(f'{r["n"]:>3} {r["total"]:>6} {r["only_derived"]:>6} '
f'{r["intertwining_only"]:>6} {r["both"]:>6} {missing:>8}')
if __name__ == '__main__':
main()
@@ -0,0 +1,141 @@
"""Test whether the dual of the Tutte graph (46-vertex 3-connected planar
cubic non-Hamiltonian) admits a tree coloring.
The Lederberg-Bosak-Barnette graph (38 vertices) is the smallest known
counterexample to Tait's conjecture but isn't directly available in
networkx; the Tutte graph (1946 original counterexample) is.
"""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
'level_resolutions_of_maximal_planar_graphs/experiments')
import networkx as nx
import time
def dual_triangulation(G):
"""Build the planar dual of a 3-connected planar cubic graph.
Each face of G becomes a vertex of the dual; each edge of G between
two faces becomes a dual edge. Cubic G ⇒ every dual face is a
triangle ⇒ dual is a triangulation."""
ok, emb = nx.check_planarity(G)
assert ok
faces = []
seen = set()
for u, v in G.edges():
for src, dst in [(u, v), (v, u)]:
face = tuple(emb.traverse_face(src, dst))
key = frozenset(face)
if key in seen:
continue
seen.add(key)
faces.append(face)
# The "outer" face is the one with the largest vertex set (heuristic);
# for connectivity of the dual, we just include all faces.
face_of_edge = {} # edge (u, v) -> set of face indices it borders
for i, face in enumerate(faces):
for j in range(len(face)):
a, b = face[j], face[(j + 1) % len(face)]
key = frozenset((a, b))
face_of_edge.setdefault(key, []).append(i)
D = nx.Graph()
D.add_nodes_from(range(len(faces)))
for key, face_ids in face_of_edge.items():
if len(face_ids) == 2:
D.add_edge(face_ids[0], face_ids[1])
# If len > 2 or 1, multi-edges or self-loops would appear; for
# 3-connected G this shouldn't happen.
return D, faces
def is_tree(subg):
return nx.is_tree(subg) if subg.number_of_nodes() > 0 else True
def has_tree_property_for_some_pairing(G, coloring):
pairings = [({0, 1}, {2, 3}), ({0, 2}, {1, 3}), ({0, 3}, {1, 2})]
for p1, p2 in pairings:
V1 = [v for v in G.nodes() if coloring[v] in p1]
V2 = [v for v in G.nodes() if coloring[v] in p2]
if is_tree(G.subgraph(V1)) and is_tree(G.subgraph(V2)):
return True, (p1, p2)
return False, None
def find_tree_coloring(G, time_limit=60.0):
"""Backtracking search for a 4-coloring with the tree property."""
nodes = list(G.nodes())
n = len(nodes)
colors = [None] * n
adj = {v: set(G.neighbors(v)) for v in nodes}
idx_of = {v: i for i, v in enumerate(nodes)}
t0 = time.time()
visited = [0]
def bt(i):
if time.time() - t0 > time_limit:
return None
visited[0] += 1
if i == n:
coloring = dict(zip(nodes, colors))
ok, pair = has_tree_property_for_some_pairing(G, coloring)
return (coloring, pair) if ok else None
v = nodes[i]
forbidden = set()
for w in adj[v]:
if idx_of[w] < i:
forbidden.add(colors[idx_of[w]])
for c in range(4):
if c in forbidden:
continue
colors[i] = c
r = bt(i + 1)
if r is not None:
return r
colors[i] = None
return None
return bt(0), visited[0]
def main():
G = nx.tutte_graph()
print(f'Tutte graph: {G.number_of_nodes()} vertices, '
f'{G.number_of_edges()} edges, planar=True, cubic, 3-connected, '
f'non-Hamiltonian (Tutte 1946).')
D, faces = dual_triangulation(G)
print(f'Dual: {D.number_of_nodes()} vertices, '
f'{D.number_of_edges()} edges')
print(f' is_triangulation (3n-6 edges): '
f'{D.number_of_edges() == 3 * D.number_of_nodes() - 6}')
print(f' degree sequence (sorted desc): '
f'{sorted([D.degree(v) for v in D.nodes()], reverse=True)}')
print('Searching for a tree coloring...')
t0 = time.time()
result, n_visited = find_tree_coloring(D, time_limit=120.0)
elapsed = time.time() - t0
if result is None:
print(f' no tree coloring found within time limit '
f'({elapsed:.1f}s, {n_visited} states visited)')
else:
coloring, pair = result
print(f' tree coloring FOUND ({elapsed:.1f}s, {n_visited} states).')
print(f' pairing: {pair}')
p1, p2 = pair
V1 = [v for v in D.nodes() if coloring[v] in p1]
V2 = [v for v in D.nodes() if coloring[v] in p2]
sub1 = D.subgraph(V1)
sub2 = D.subgraph(V2)
print(f' D[V1] (colors {p1}): {len(V1)} vertices, '
f'{sub1.number_of_edges()} edges, tree={nx.is_tree(sub1)}')
print(f' D[V2] (colors {p2}): {len(V2)} vertices, '
f'{sub2.number_of_edges()} edges, tree={nx.is_tree(sub2)}')
if __name__ == '__main__':
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

@@ -0,0 +1,46 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\providecommand\HyperFirstAtBeginDocument{\AtBeginDocument}
\HyperFirstAtBeginDocument{\ifx\hyper@anchor\@undefined
\global\let\oldcontentsline\contentsline
\gdef\contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}}
\global\let\oldnewlabel\newlabel
\gdef\newlabel#1#2{\newlabelxx{#1}#2}
\gdef\newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}}
\AtEndDocument{\ifx\hyper@anchor\@undefined
\let\contentsline\oldcontentsline
\let\newlabel\oldnewlabel
\fi}
\fi}
\global\let\hyper@last\relax
\gdef\HyperFirstAtBeginDocument#1{#1}
\providecommand\HyField@AuxAddToFields[1]{}
\providecommand\HyField@AuxAddToCoFields[2]{}
\@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Introduction}}{1}{section.1}\protected@file@percent }
\@writefile{toc}{\contentsline {section}{\tocsection {}{2}{Definitions}}{1}{section.2}\protected@file@percent }
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces BFS levels from the degree-$3$ vertex source $S = \{4\}$. The source is level $0$, its three neighbours are level $1$, and the remaining vertices are level $2$. Colour encodes the level.}}{1}{figure.1}\protected@file@percent }
\newlabel{fig:levels}{{1}{1}{BFS levels from the degree-$3$ vertex source $S = \{4\}$. The source is level $0$, its three neighbours are level $1$, and the remaining vertices are level $2$. Colour encodes the level}{figure.1}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces A level cycle in the triangulation of Figure\nonbreakingspace \ref {fig:levels}. The triangle $1\!-\!2\!-\!3$ is a simple cycle whose three vertices all lie at level $1$, so it is a level cycle at level $1$.}}{2}{figure.2}\protected@file@percent }
\newlabel{fig:level-cycle}{{2}{2}{A level cycle in the triangulation of Figure~\ref {fig:levels}. The triangle $1\!-\!2\!-\!3$ is a simple cycle whose three vertices all lie at level $1$, so it is a level cycle at level $1$}{figure.2}{}}
\newlabel{def:edge-switch}{{2.4}{2}{Edge switch}{theorem.2.4}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces An edge switch on the level cycle of Figure\nonbreakingspace \ref {fig:level-cycle}. The chosen cycle edge $1\!-\!2$ is shared by the triangular faces $(0,1,2)$ and $(1,2,4)$; the switch deletes $1\!-\!2$ (red, left) and inserts $0\!-\!4$ (green, right). Vertex colours indicate the original levels in $G$.}}{2}{figure.3}\protected@file@percent }
\newlabel{fig:edge-switch}{{3}{2}{An edge switch on the level cycle of Figure~\ref {fig:level-cycle}. The chosen cycle edge $1\!-\!2$ is shared by the triangular faces $(0,1,2)$ and $(1,2,4)$; the switch deletes $1\!-\!2$ (red, left) and inserts $0\!-\!4$ (green, right). Vertex colours indicate the original levels in $G$}{figure.3}{}}
\@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces Parity subgraphs of $G' = T$ with respect to the level structure of Figure\nonbreakingspace \ref {fig:levels} (here we take $G = G' = T$). Left: $T$ with vertices coloured by $\ell _G \nonscript \mskip -\medmuskip \mkern 5mu\mathbin {\mathgroup \symoperators mod}\penalty 900 \mkern 5mu\nonscript \mskip -\medmuskip 2$ (blue $=$ even, orange $=$ odd). Middle: the even parity subgraph $E_{G,S}(G')$, induced on $\{0, 4, 5, 6\}$; only edges with both endpoints even appear. Right: the odd parity subgraph $O_{G,S}(G')$, induced on $\{1, 2, 3\}$; the highlighted triangle shows that $O_{G,S}(G')$ is not bipartite for this choice of $G'$.}}{3}{figure.4}\protected@file@percent }
\newlabel{fig:parity-subgraph}{{4}{3}{Parity subgraphs of $G' = T$ with respect to the level structure of Figure~\ref {fig:levels} (here we take $G = G' = T$). Left: $T$ with vertices coloured by $\ell _G \bmod 2$ (blue $=$ even, orange $=$ odd). Middle: the even parity subgraph $E_{G,S}(G')$, induced on $\{0, 4, 5, 6\}$; only edges with both endpoints even appear. Right: the odd parity subgraph $O_{G,S}(G')$, induced on $\{1, 2, 3\}$; the highlighted triangle shows that $O_{G,S}(G')$ is not bipartite for this choice of $G'$}{figure.4}{}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{3}{Outerplanarity of level components}}{3}{section.3}\protected@file@percent }
\newlabel{sec:outerplanar-components}{{3}{3}{Outerplanarity of level components}{section.3}{}}
\newlabel{thm:outerplanar-component}{{3.1}{3}{}{theorem.3.1}{}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{4}{Even Level Graphs}}{3}{section.4}\protected@file@percent }
\newlabel{sec:even-level-graphs}{{4}{3}{Even Level Graphs}{section.4}{}}
\newlabel{def:even-level-graph}{{4.1}{3}{Even Level Graph}{theorem.4.1}{}}
\newlabel{thm:even-level-4colorable}{{4.2}{3}{}{theorem.4.2}{}}
\newlabel{tocindent-1}{0pt}
\newlabel{tocindent0}{14.69437pt}
\newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt}
\newlabel{def:derived-level-graph}{{4.3}{4}{Derived level graph}{theorem.4.3}{}}
\newlabel{def:intertwining-tree}{{4.4}{4}{Intertwining tree}{theorem.4.4}{}}
\newlabel{conj:every-triangulation-derived}{{4.5}{4}{}{theorem.4.5}{}}
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Empirical status}}{4}{section*.1}\protected@file@percent }
\gdef \@abspage@last{4}
@@ -0,0 +1,418 @@
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 21 MAY 2026 16:14
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
**paper.tex
(./paper.tex
LaTeX2e <2021-11-15> patch level 1
L3 programming layer <2022-02-24>
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
Document Class: amsart 2020/05/29 v2.20.6
\linespacing=\dimen138
\normalparindent=\dimen139
\normaltopskip=\skip47
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
Package: amsmath 2021/10/15 v2.17l AMS math features
\@mathmargin=\skip48
For additional information on amsmath, use the `?' option.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
Package: amstext 2021/08/26 v2.01 AMS text
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
File: amsgen.sty 1999/11/30 v2.0 generic functions
\@emptytoks=\toks16
\ex@=\dimen140
))
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
\pmbraise@=\dimen141
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
Package: amsopn 2021/08/26 v2.02 operator names
)
\inf@bad=\count185
LaTeX Info: Redefining \frac on input line 234.
\uproot@=\count186
\leftroot@=\count187
LaTeX Info: Redefining \overline on input line 399.
\classnum@=\count188
\DOTSCASE@=\count189
LaTeX Info: Redefining \ldots on input line 496.
LaTeX Info: Redefining \dots on input line 499.
LaTeX Info: Redefining \cdots on input line 620.
\Mathstrutbox@=\box50
\strutbox@=\box51
\big@size=\dimen142
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
\macc@depth=\count190
\c@MaxMatrixCols=\count191
\dotsspace@=\muskip16
\c@parentequation=\count192
\dspbrk@lvl=\count193
\tag@help=\toks17
\row@=\count194
\column@=\count195
\maxfields@=\count196
\andhelp@=\toks18
\eqnshift@=\dimen143
\alignsep@=\dimen144
\tagshift@=\dimen145
\tagwidth@=\dimen146
\totwidth@=\dimen147
\lineht@=\dimen148
\@envbody=\toks19
\multlinegap=\skip49
\multlinetaggap=\skip50
\mathdisplay@stack=\toks20
LaTeX Info: Redefining \[ on input line 2938.
LaTeX Info: Redefining \] on input line 2939.
)
LaTeX Font Info: Trying to load font information for U+msa on input line 397
.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
\symAMSa=\mathgroup4
\symAMSb=\mathgroup5
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
)
\copyins=\insert199
\abstractbox=\box52
\listisep=\skip51
\c@part=\count197
\c@section=\count198
\c@subsection=\count266
\c@subsubsection=\count267
\c@paragraph=\count268
\c@subparagraph=\count269
\c@figure=\count270
\c@table=\count271
\abovecaptionskip=\skip52
\belowcaptionskip=\skip53
\captionindent=\dimen149
\thm@style=\toks21
\thm@bodyfont=\toks22
\thm@headfont=\toks23
\thm@notefont=\toks24
\thm@headpunct=\toks25
\thm@preskip=\skip54
\thm@postskip=\skip55
\thm@headsep=\skip56
\dth@everypar=\toks26
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/hyperref/hyperref.sty
Package: hyperref 2022-02-21 v7.00n Hypertext links for LaTeX
(/usr/local/texlive/2022/texmf-dist/tex/generic/ltxcmds/ltxcmds.sty
Package: ltxcmds 2020-05-10 v1.25 LaTeX kernel commands for general use (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/iftex.sty
Package: iftex 2022/02/03 v1.0f TeX engine tests
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/pdftexcmds/pdftexcmds.sty
Package: pdftexcmds 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/infwarerr/infwarerr.sty
Package: infwarerr 2019/12/03 v1.5 Providing info/warning/error messages (HO)
)
Package pdftexcmds Info: \pdf@primitive is available.
Package pdftexcmds Info: \pdf@ifprimitive is available.
Package pdftexcmds Info: \pdfdraftmode found.
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
\KV@toks@=\toks27
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/kvsetkeys/kvsetkeys.sty
Package: kvsetkeys 2019/12/15 v1.18 Key value parser (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/kvdefinekeys/kvdefinekeys.sty
Package: kvdefinekeys 2019-12-19 v1.6 Define keys (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/pdfescape/pdfescape.sty
Package: pdfescape 2019/12/09 v1.15 Implements pdfTeX's escape features (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/hycolor/hycolor.sty
Package: hycolor 2020-01-27 v1.10 Color options for hyperref/bookmark (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/letltxmacro/letltxmacro.sty
Package: letltxmacro 2019/12/03 v1.6 Let assignment for LaTeX macros (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/auxhook/auxhook.sty
Package: auxhook 2019-12-17 v1.6 Hooks for auxiliary files (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/kvoptions/kvoptions.sty
Package: kvoptions 2020-10-07 v3.14 Key value format for package options (HO)
)
\@linkdim=\dimen150
\Hy@linkcounter=\count272
\Hy@pagecounter=\count273
(/usr/local/texlive/2022/texmf-dist/tex/latex/hyperref/pd1enc.def
File: pd1enc.def 2022-02-21 v7.00n Hyperref: PDFDocEncoding definition (HO)
Now handling font encoding PD1 ...
... no UTF-8 mapping file for font encoding PD1
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/intcalc/intcalc.sty
Package: intcalc 2019/12/15 v1.3 Expandable calculations with integers (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/etexcmds/etexcmds.sty
Package: etexcmds 2019/12/15 v1.7 Avoid name clashes with e-TeX commands (HO)
)
\Hy@SavedSpaceFactor=\count274
(/usr/local/texlive/2022/texmf-dist/tex/latex/hyperref/puenc.def
File: puenc.def 2022-02-21 v7.00n Hyperref: PDF Unicode definition (HO)
Now handling font encoding PU ...
... no UTF-8 mapping file for font encoding PU
)
Package hyperref Info: Hyper figures OFF on input line 4137.
Package hyperref Info: Link nesting OFF on input line 4142.
Package hyperref Info: Hyper index ON on input line 4145.
Package hyperref Info: Plain pages OFF on input line 4152.
Package hyperref Info: Backreferencing OFF on input line 4157.
Package hyperref Info: Implicit mode ON; LaTeX internals redefined.
Package hyperref Info: Bookmarks ON on input line 4390.
\c@Hy@tempcnt=\count275
(/usr/local/texlive/2022/texmf-dist/tex/latex/url/url.sty
\Urlmuskip=\muskip17
Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc.
)
LaTeX Info: Redefining \url on input line 4749.
\XeTeXLinkMargin=\dimen151
(/usr/local/texlive/2022/texmf-dist/tex/generic/bitset/bitset.sty
Package: bitset 2019/12/09 v1.3 Handle bit-vector datatype (HO)
(/usr/local/texlive/2022/texmf-dist/tex/generic/bigintcalc/bigintcalc.sty
Package: bigintcalc 2019/12/15 v1.5 Expandable calculations on big integers (HO
)
))
\Fld@menulength=\count276
\Field@Width=\dimen152
\Fld@charsize=\dimen153
Package hyperref Info: Hyper figures OFF on input line 6027.
Package hyperref Info: Link nesting OFF on input line 6032.
Package hyperref Info: Hyper index ON on input line 6035.
Package hyperref Info: backreferencing OFF on input line 6042.
Package hyperref Info: Link coloring OFF on input line 6047.
Package hyperref Info: Link coloring with OCG OFF on input line 6052.
Package hyperref Info: PDF/A mode OFF on input line 6057.
LaTeX Info: Redefining \ref on input line 6097.
LaTeX Info: Redefining \pageref on input line 6101.
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/atbegshi-ltx.sty
Package: atbegshi-ltx 2021/01/10 v1.0c Emulation of the original atbegshi
package with kernel methods
)
\Hy@abspage=\count277
\c@Item=\count278
\c@Hfootnote=\count279
)
Package hyperref Info: Driver (autodetected): hpdftex.
(/usr/local/texlive/2022/texmf-dist/tex/latex/hyperref/hpdftex.def
File: hpdftex.def 2022-02-21 v7.00n Hyperref driver for pdfTeX
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/atveryend-ltx.sty
Package: atveryend-ltx 2020/08/19 v1.0a Emulation of the original atveryend pac
kage
with kernel methods
)
\Fld@listcount=\count280
\c@bookmark@seq@number=\count281
(/usr/local/texlive/2022/texmf-dist/tex/latex/rerunfilecheck/rerunfilecheck.sty
Package: rerunfilecheck 2019/12/05 v1.9 Rerun checks for auxiliary files (HO)
(/usr/local/texlive/2022/texmf-dist/tex/generic/uniquecounter/uniquecounter.sty
Package: uniquecounter 2019/12/15 v1.4 Provide unlimited unique counter (HO)
)
Package uniquecounter Info: New unique counter `rerunfilecheck' on input line 2
86.
)
\Hy@SectionHShift=\skip57
) (/usr/local/texlive/2022/texmf-dist/tex/latex/enumitem/enumitem.sty
Package: enumitem 2019/06/20 v3.9 Customized lists
\labelindent=\skip58
\enit@outerparindent=\dimen154
\enit@toks=\toks28
\enit@inbox=\box53
\enit@count@id=\count282
\enitdp@description=\count283
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
)
Package graphics Info: Driver file: pdftex.def on input line 107.
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
))
\Gin@req@height=\dimen155
\Gin@req@width=\dimen156
)
\c@theorem=\count284
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
\l__color_backend_stack_int=\count285
\l__pdf_internal_box=\box54
)
(./paper.aux)
\openout1 = `paper.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for PD1/pdf/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Checking defaults for PU/pdf/m/n on input line 60.
LaTeX Font Info: ... okay on input line 60.
LaTeX Font Info: Trying to load font information for U+msa on input line 60.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
)
LaTeX Font Info: Trying to load font information for U+msb on input line 60.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
)
Package hyperref Info: Link coloring OFF on input line 60.
(/usr/local/texlive/2022/texmf-dist/tex/latex/hyperref/nameref.sty
Package: nameref 2021-04-02 v2.47 Cross-referencing by name of section
(/usr/local/texlive/2022/texmf-dist/tex/latex/refcount/refcount.sty
Package: refcount 2019/12/15 v3.6 Data extraction from label references (HO)
)
(/usr/local/texlive/2022/texmf-dist/tex/generic/gettitlestring/gettitlestring.s
ty
Package: gettitlestring 2019/12/15 v1.6 Cleanup title references (HO)
)
\c@section@level=\count286
)
LaTeX Info: Redefining \ref on input line 60.
LaTeX Info: Redefining \pageref on input line 60.
LaTeX Info: Redefining \nameref on input line 60.
(./paper.out) (./paper.out)
\@outlinefile=\write3
\openout3 = `paper.out'.
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
[Loading MPS to PDF converter (version 2006.09.02).]
\scratchcounter=\count287
\scratchdimen=\dimen157
\scratchbox=\box55
\nofMPsegments=\count288
\nofMParguments=\count289
\everyMPshowfont=\toks29
\MPscratchCnt=\count290
\MPscratchDim=\dimen158
\MPnumerator=\count291
\makeMPintoPDFobject=\count292
\everyMPtoPDFconversion=\toks30
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
85.
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
e
))
<fig_levels.png, id=24, 454.21695pt x 391.34206pt>
File: fig_levels.png Graphic file (type png)
<use fig_levels.png>
Package pdftex.def Info: fig_levels.png used on input line 108.
(pdftex.def) Requested size: 198.0011pt x 170.59666pt.
<fig_level_cycle.png, id=26, 452.04884pt x 391.34206pt>
File: fig_level_cycle.png Graphic file (type png)
<use fig_level_cycle.png>
Package pdftex.def Info: fig_level_cycle.png used on input line 122.
(pdftex.def) Requested size: 198.0011pt x 171.40878pt.
LaTeX Warning: `h' float specifier changed to `ht'.
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map} <./fig
_levels.png>]
<fig_edge_switch.png, id=48, 859.65166pt x 378.33345pt>
File: fig_edge_switch.png Graphic file (type png)
<use fig_edge_switch.png>
Package pdftex.def Info: fig_edge_switch.png used on input line 141.
(pdftex.def) Requested size: 341.9989pt x 150.51671pt.
<fig_parity_subgraph.png, id=50, 1076.46165pt x 319.79475pt>
File: fig_parity_subgraph.png Graphic file (type png)
<use fig_parity_subgraph.png>
Package pdftex.def Info: fig_parity_subgraph.png used on input line 159.
(pdftex.def) Requested size: 360.0pt x 106.9477pt.
LaTeX Warning: `h' float specifier changed to `ht'.
[2 <./fig_level_cycle.png> <./fig_edge_switch.png>] [3 <./fig_parity_subgraph.p
ng>] [4] (./paper.aux)
Package rerunfilecheck Info: File `paper.out' has not changed.
(rerunfilecheck) Checksum: DC9FD452C972FF1938BE7060890FAD7C;765.
)
Here is how much of TeX's memory you used:
9727 strings out of 478268
150592 string characters out of 5846347
450778 words of memory out of 5000000
27640 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,781b,427s 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><
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></u
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/lo
cal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local
/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/
texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/te
xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/tex
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texli
ve/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (4 pages, 530157 bytes).
PDF statistics:
145 PDF objects out of 1000 (max. 8388607)
103 compressed objects within 2 object streams
25 named destinations out of 1000 (max. 500000)
61 words of extra memory for PDF output out of 10000 (max. 10000000)
@@ -0,0 +1,5 @@
\BOOKMARK [1][-]{section.1}{\376\377\0001\000.\000\040\000I\000n\000t\000r\000o\000d\000u\000c\000t\000i\000o\000n}{}% 1
\BOOKMARK [1][-]{section.2}{\376\377\0002\000.\000\040\000D\000e\000f\000i\000n\000i\000t\000i\000o\000n\000s}{}% 2
\BOOKMARK [1][-]{section.3}{\376\377\0003\000.\000\040\000O\000u\000t\000e\000r\000p\000l\000a\000n\000a\000r\000i\000t\000y\000\040\000o\000f\000\040\000l\000e\000v\000e\000l\000\040\000c\000o\000m\000p\000o\000n\000e\000n\000t\000s}{}% 3
\BOOKMARK [1][-]{section.4}{\376\377\0004\000.\000\040\000E\000v\000e\000n\000\040\000L\000e\000v\000e\000l\000\040\000G\000r\000a\000p\000h\000s}{}% 4
\BOOKMARK [2][-]{section*.1}{\376\377\000E\000m\000p\000i\000r\000i\000c\000a\000l\000\040\000s\000t\000a\000t\000u\000s}{section.4}% 5
Binary file not shown.
@@ -0,0 +1,286 @@
%% filename: amsart-template.tex
%% version: 1.1
%% date: 2014/07/24
%%
%% American Mathematical Society
%% Technical Support
%% Publications Technical Group
%% 201 Charles Street
%% Providence, RI 02904
%% USA
%% tel: (401) 455-4080
%% (800) 321-4267 (USA and Canada only)
%% fax: (401) 331-3842
%% email: tech-support@ams.org
%%
%% Copyright 2008-2010, 2014 American Mathematical Society.
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3c
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%% http://www.latex-project.org/lppl.txt
%% and version 1.3c or later is part of all distributions of LaTeX
%% version 2005/12/01 or later.
%%
%% This work has the LPPL maintenance status `maintained'.
%%
%% The Current Maintainer of this work is the American Mathematical
%% Society.
%%
%% ====================================================================
% AMS-LaTeX v.2 template for use with amsart
%
% Remove any commented or uncommented macros you do not use.
\documentclass{amsart}
\usepackage{hyperref}
\usepackage{enumitem}
\usepackage{graphicx}
\newtheorem{theorem}{Theorem}[section]
\newtheorem{lemma}[theorem]{Lemma}
\newtheorem{proposition}[theorem]{Proposition}
\theoremstyle{definition}
\newtheorem{definition}[theorem]{Definition}
\newtheorem{example}[theorem]{Example}
\newtheorem{xca}[theorem]{Exercise}
\newtheorem{conjecture}[theorem]{Conjecture}
\newtheorem{question}[theorem]{Question}
\newtheorem{observation}[theorem]{Observation}
\theoremstyle{remark}
\newtheorem{remark}[theorem]{Remark}
\numberwithin{equation}{section}
\begin{document}
\title{Even Level Graph Generators}
% Remove any unused author tags.
% author one information
\author{Eric Bauerfeld}
\address{}
\curraddr{}
\email{}
\thanks{}
\subjclass[2010]{Primary }
\keywords{}
\date{}
\dedicatory{}
\begin{abstract}
\end{abstract}
\maketitle
\section{Introduction}
\section{Definitions}
Throughout, $G = (V, E)$ is a plane maximal planar graph (a triangulation)
with a fixed planar embedding $\Pi_G$. We write $|V| = n$, so $|E| = 3n - 6$
and $G$ has $2n - 4$ triangular faces.
\begin{definition}[Level source]
A \emph{level source} of $G$ is any vertex $v \in V$; we write
$S = \{v\}$ for the level-0 source.
\end{definition}
\begin{definition}[Levels]
Given a level source $S \subseteq V$, the \emph{level} of $v \in V$ is
$\ell_G(v) = \mathrm{dist}_G(v, S)$, the graph distance from $v$ to the nearest
source vertex.
\end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=0.55\textwidth]{fig_levels.png}
\caption{BFS levels from the degree-$3$ vertex source $S = \{4\}$.
The source is level $0$, its three neighbours are level $1$, and the
remaining vertices are level $2$. Colour encodes the level.}
\label{fig:levels}
\end{figure}
\begin{definition}[Level cycle]
A \emph{level cycle} of $G$ (with respect to a level source $S$) is a
simple cycle in $G$ all of whose vertices have the same level.
\end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=0.55\textwidth]{fig_level_cycle.png}
\caption{A level cycle in the triangulation of Figure~\ref{fig:levels}.
The triangle $1\!-\!2\!-\!3$ is a simple cycle whose three vertices all
lie at level $1$, so it is a level cycle at level $1$.}
\label{fig:level-cycle}
\end{figure}
\begin{definition}[Edge switch]
\label{def:edge-switch}
Let $G$ be a triangulation with level source $S$, and let $e = uv$ be an
edge of a level cycle of $G$. The \emph{edge switch} at $e$ is the edge
flip on $e$: writing $uvw$ and $uvx$ for the two triangular faces of $G$
containing $e$, the edge $uv$ is removed and the edge $wx$ is added. As
with any edge flip, the result is a triangulation on the same vertex set
provided $w$ and $x$ are non-adjacent in $G$.
\end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=0.95\textwidth]{fig_edge_switch.png}
\caption{An edge switch on the level cycle of
Figure~\ref{fig:level-cycle}. The chosen cycle edge $1\!-\!2$ is shared
by the triangular faces $(0,1,2)$ and $(1,2,4)$; the switch deletes
$1\!-\!2$ (red, left) and inserts $0\!-\!4$ (green, right). Vertex
colours indicate the original levels in $G$.}
\label{fig:edge-switch}
\end{figure}
\begin{definition}[Parity subgraph]
Let $G$ be a triangulation with level source $S$, and let $G'$ be a triangulation
on the same vertex set as $G$. The \emph{even parity subgraph} $E_{G,S}(G')$ is
the subgraph of $G'$ induced by $\{v \in V : \ell_G(v) \equiv 0 \pmod 2\}$. The
\emph{odd parity subgraph} is defined analogously for odd $\ell_G$.
\end{definition}
\begin{figure}[h]
\centering
\includegraphics[width=\textwidth]{fig_parity_subgraph.png}
\caption{Parity subgraphs of $G' = T$ with respect to the level structure of
Figure~\ref{fig:levels} (here we take $G = G' = T$). Left: $T$ with vertices
coloured by $\ell_G \bmod 2$ (blue $=$ even, orange $=$ odd). Middle: the
even parity subgraph $E_{G,S}(G')$, induced on $\{0, 4, 5, 6\}$; only
edges with both endpoints even appear. Right: the odd parity subgraph
$O_{G,S}(G')$, induced on $\{1, 2, 3\}$; the highlighted triangle shows
that $O_{G,S}(G')$ is not bipartite for this choice of $G'$.}
\label{fig:parity-subgraph}
\end{figure}
\section{Outerplanarity of level components}
\label{sec:outerplanar-components}
For each integer $k \geq 0$ and each $(G, S)$, write $L_k$ for the
subgraph of $G$ induced by the level-$k$ vertices. A \emph{level
component} of $G$ (with respect to $S$) is a connected component of
some $L_k$.
\begin{theorem}
\label{thm:outerplanar-component}
For every plane triangulation $G$ and every level source $S$ of $G$,
every level component of $G$ is outerplanar.
\end{theorem}
\begin{proof}
Since every subgraph of an outerplanar graph is outerplanar, it suffices
to show that each level subgraph $L_k$ is outerplanar. For $k = 0$,
$L_0 = S$ is a single vertex and is trivially outerplanar.
Fix $k \geq 1$ and let $D_k$ be the drawing of $L_k$ inherited from
$\Pi_G$. Let $F^\ast$ be the face of $D_k$ containing the source.
Suppose for contradiction that some $u \in L_k$ does not lie on
$\partial F^\ast$, so $u$ lies on the boundary of some other face of
$D_k$. Take any path $P$ in $G$ from $v_0 \in S$ to $u$. As a curve in
$\Pi_G$, $P$ starts in $F^\ast$ and ends at a point off $\partial
F^\ast$, so it must transition from $F^\ast$ to a different face of
$D_k$; in a planar embedding this can happen only at a vertex of
$D_k$, that is, at a level-$k$ vertex $w$ on $P$. Either $w \neq u$
(so $P$ has length $\geq \mathrm{dist}_G(S, w) + 1 \geq k + 1$), or
$w = u$ (contradicting $u \notin \partial F^\ast$). Since every
$S$-to-$u$ path has length $\geq k + 1$, $\mathrm{dist}_G(S, u) \geq
k + 1$, contradicting $u \in L_k$.
\end{proof}
\section{Even Level Graphs}
\label{sec:even-level-graphs}
\begin{definition}[Even Level Graph]
\label{def:even-level-graph}
A plane triangulation $G$ with level source $S$ is an \emph{Even Level
Graph} if every level cycle of $G$ has even length.
\end{definition}
\begin{theorem}
\label{thm:even-level-4colorable}
Every Even Level Graph is $4$-colorable.
\end{theorem}
\begin{proof}
Since adjacent vertices in $G$ have levels differing by at most $1$,
any edge between two same-parity endpoints in fact connects two
vertices at the same level. Hence
\[
E_{G,S}(G) \;=\; \bigsqcup_{i \geq 0} L_{2i},
\qquad
O_{G,S}(G) \;=\; \bigsqcup_{i \geq 0} L_{2i+1},
\]
and each $L_k$ is bipartite because its cycles are level cycles of
$G$, which have even length by hypothesis. Choose a $2$-coloring of
$E_{G,S}(G)$ in $\{\text{red}, \text{blue}\}$ and a $2$-coloring of
$O_{G,S}(G)$ in $\{\text{yellow}, \text{green}\}$. Same-parity edges
of $G$ are properly colored by the respective bipartition;
opposite-parity edges connect $\{\text{red}, \text{blue}\}$ to
$\{\text{yellow}, \text{green}\}$. The combined assignment is a
proper $4$-coloring of $G$.
\end{proof}
\begin{definition}[Derived level graph]
\label{def:derived-level-graph}
Let $G$ be an Even Level Graph with level source $S$, and let $E$ and
$O$ denote the edge sets of the even and odd parity subgraphs
$E_{G,S}(G)$ and $O_{G,S}(G)$. A \emph{derived level graph} of $G$ is
a triangulation $G'$ on the same vertex set as $G$ obtained by a
sequence of edge switches (Definition~\ref{def:edge-switch}), each
acting on an edge of $E$ or of $O$. We do not update $E$ or $O$ to
reflect the level structure of intermediate triangulations: throughout
the sequence, an edge is classified as belonging to $E$ (resp.\ $O$) if
and only if both of its endpoints have even (resp.\ odd) level in $G$.
A derived level graph $G'$ is \emph{valid} if both $E_{G,S}(G')$ and
$O_{G,S}(G')$ contain only even cycles.
\end{definition}
\begin{definition}[Intertwining tree]
\label{def:intertwining-tree}
A maximal planar graph $G$ is an \emph{intertwining tree} if its
vertex set can be partitioned into two sets $A$ and $B$ such that
both induced subgraphs $G[A]$ and $G[B]$ are trees.
\end{definition}
\begin{conjecture}
\label{conj:every-triangulation-derived}
Every maximal planar graph is a valid derived level graph of some Even
Level Graph, an intertwining tree, or both.
\end{conjecture}
\subsection*{Empirical status}
For each isomorphism class of maximal planar graphs on $n$ vertices,
we ask whether (i) some isomorphic representative is reachable from
some Even Level Graph via $E/O$-edge switches (``derived''), and/or
(ii) it is an intertwining tree. The conjecture holds for the class
iff at least one of (i), (ii) holds.
\begin{center}
\begin{tabular}{rcccccc}
$n$ & \# iso & derived only & inter.\ only & both & missing & status \\\hline
$6$ & $2$ & $0$ & $0$ & $2$ & $0$ & holds \\
$7$ & $5$ & $0$ & $0$ & $5$ & $0$ & holds \\
$8$ & $14$ & $0$ & $0$ & $14$ & $0$ & holds \\
$9$ & $50$ & $0$ & $1$ & $49$ & $0$ & holds \\
$10$ & $233$ & $0$ & $0$ & $233$ & $0$ & holds \\
$11$ & $1249$ & $0$ & $0$ & $1249$ & $0$ & holds \\
\end{tabular}
\end{center}
\end{document}
@@ -0,0 +1,151 @@
"""User-proposed simpler algorithm:
1) Assign each face x = min dual-tree distance to any leaf face (ear).
2) When face F at depth d has no balanced surface switch, edge-switch
on the edge between F and F's neighbour with the smallest x.
3) Repeat until the number of depth-d faces has strictly decreased.
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import math
import networkx as nx
from stress_test_termination import (
compute_depths, apply_switch, check_balanced, face_edges
)
def compute_x(faces, outer_set):
"""x(F) = min dual-tree distance from F to any leaf face."""
D = nx.Graph()
D.add_nodes_from(range(len(faces)))
for i, fi in enumerate(faces):
for j, fj in enumerate(faces):
if i < j and face_edges(fi) & face_edges(fj):
D.add_edge(i, j)
leaves = [i for i in D.nodes if D.degree(i) == 1]
x = {}
for i in range(len(faces)):
if not leaves:
x[i] = float('inf')
continue
x[i] = min(nx.shortest_path_length(D, i, lf) for lf in leaves)
return x, D
def neighbour_at_edge(faces, F_idx, e):
for j, fj in enumerate(faces):
if j != F_idx and e in face_edges(fj):
return j
return None
def run_leaf_distance_algorithm(faces, outer_set, max_steps=500,
verbose=True):
"""Run the algorithm. If F admits a balanced switch, take it; else
pick the neighbour with smallest x and switch on that edge."""
total = 0
log = []
for step in range(max_steps):
depth = compute_depths(faces, outer_set)
d_max = max(depth.values())
if d_max == 0:
log.append(f'terminated at step {step}')
return faces, total, log
max_d_faces = [i for i, d in depth.items() if d == d_max]
F_idx = max_d_faces[0]
F = faces[F_idx]
ok, _, fp_idx, e = check_balanced(F_idx, faces, depth, outer_set)
if ok:
Fp = faces[fp_idx]
u, v = tuple(e)
w = [vert for vert in F if vert not in (u, v)][0]
x = [vert for vert in Fp if vert not in (u, v)][0]
log.append(f'step {step}: BALANCED on edge ({u},{v}) '
f'with F\' = {Fp}')
faces = apply_switch(faces, (u, v), (w, x))
total += 1
continue
# Find x values
x_vals, D = compute_x(faces, outer_set)
# Among neighbours of F (interior edges, hence in dual graph),
# pick the one with smallest x.
nb_choices = []
for e_test in face_edges(F):
if e_test in outer_set:
continue
nb_idx = neighbour_at_edge(faces, F_idx, e_test)
if nb_idx is None:
continue
nb_choices.append((x_vals[nb_idx], e_test, nb_idx))
if not nb_choices:
log.append(f'step {step}: no interior neighbour; stuck')
break
nb_choices.sort()
x_min, e_chosen, nb_idx = nb_choices[0]
Fnb = faces[nb_idx]
u, v = tuple(e_chosen)
w = [vert for vert in F if vert not in (u, v)][0]
xv = [vert for vert in Fnb if vert not in (u, v)][0]
log.append(f'step {step}: x-preprocess on edge ({u},{v}) '
f'(F\'={Fnb}, x={x_min}; other choices: '
f'{[(c[0]) for c in nb_choices[1:]]})')
faces = apply_switch(faces, (u, v), (w, xv))
total += 1
log.append(f'hit max_steps; final max depth = '
f'{max(compute_depths(faces, outer_set).values())}')
return faces, total, log
# ----- TEST CASE 1: 9-vertex example -----
n9 = 9
outer9 = [(i, (i + 1) % n9) for i in range(n9)]
outer_set9 = {frozenset(e) for e in outer9}
faces9 = [
(0, 1, 2), (0, 2, 3), (3, 4, 5), (3, 5, 6),
(6, 7, 8), (6, 8, 0), (0, 3, 6),
]
print('=== 9-vertex example ===')
_, total, log = run_leaf_distance_algorithm(faces9, outer_set9)
print('\n'.join(log))
print(f'total switches: {total}\n')
# ----- TEST CASE 2: 24-vertex doubly-lopsided example -----
n24 = 24
outer24 = [(i, (i + 1) % n24) for i in range(n24)]
outer_set24 = {frozenset(e) for e in outer24}
def arm(a, b):
return [
(a, a + 1, a + 2), (a, a + 2, b), (a + 2, a + 3, a + 4),
(a + 2, a + 4, b), (a + 4, a + 5, a + 6), (a + 4, a + 6, b),
(a + 6, a + 7, b),
]
faces24 = [(0, 8, 16)]
for (a, b) in [(0, 8), (8, 16), (16, 24)]:
fs = arm(a, b)
fs = [tuple(0 if v == 24 else v for v in vt) for vt in fs]
faces24.extend(fs)
print('=== 24-vertex doubly-lopsided example ===')
_, total, log = run_leaf_distance_algorithm(faces24, outer_set24)
print('\n'.join(log[:25]))
if len(log) > 25:
print(f'... ({len(log) - 25} more lines)')
print(f'total switches: {total}\n')
# ----- TEST CASE 3: random outerplanar n=40 (one of the previously-slow seeds) -----
from stress_test_termination import random_triangulation
seed = 94476710
outer, _, faces = random_triangulation(40, seed=seed)
outer_set = {frozenset(e) for e in outer}
print(f'=== Random n=40 (seed={seed}) with the new algorithm ===')
_, total, log = run_leaf_distance_algorithm(faces, outer_set, max_steps=1000)
print(f'total switches: {total} (random algorithm with cap=10000 needed 863)')
print(f'first 5 lines: {log[:5]}')
print(f'last 2 lines: {log[-2:]}')
@@ -0,0 +1,140 @@
"""Leaf-distance algorithm with the additional rule:
Maintain a `blocked` set of edges; an edge cannot be flipped while
the count x of depth-d faces (d = current max depth) is unchanged.
When x decreases (a depth-d face is removed via a balanced switch),
clear `blocked`. Also clear `blocked` when d itself decreases."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.backends.backend_pdf import PdfPages
import math
from leaf_distance_algorithm import compute_x
from stress_test_termination import (
compute_depths, apply_switch, check_balanced, face_edges,
random_triangulation
)
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
def neighbour_at_edge(faces, F_idx, e):
for j, fj in enumerate(faces):
if j != F_idx and e in face_edges(fj):
return j
return None
def run_with_blocking(faces, outer_set, max_steps=10000):
"""Run with the blocked-edge rule."""
log = []
blocked = set()
prev_x = None
prev_d = None
for step in range(max_steps):
depth = compute_depths(faces, outer_set)
d_max = max(depth.values())
n_at_max = sum(1 for d in depth.values() if d == d_max)
if prev_d is not None and (d_max < prev_d or
(d_max == prev_d and n_at_max < prev_x)):
blocked = set() # reset: count of depth-d faces dropped
prev_d = d_max
prev_x = n_at_max
if d_max == 0:
log.append(f'TERMINATED at step {step}')
return faces, log
max_d_faces = [i for i, d in depth.items() if d == d_max]
F_idx = max_d_faces[0]
F = faces[F_idx]
ok, _, fp_idx, e = check_balanced(F_idx, faces, depth, outer_set)
if ok:
Fp = faces[fp_idx]
u, v = tuple(e)
w = [vert for vert in F if vert not in (u, v)][0]
xv = [vert for vert in Fp if vert not in (u, v)][0]
log.append(f'step {step}: BALANCED on ({u},{v}) (d={d_max},x={n_at_max})')
blocked.add(frozenset((u, v)))
faces = apply_switch(faces, (u, v), (w, xv))
continue
# Preprocessing: pick lowest-x unblocked neighbour
x_vals, _ = compute_x(faces, outer_set)
nb_choices = []
for e_test in face_edges(F):
if e_test in outer_set or e_test in blocked:
continue
nb_idx = neighbour_at_edge(faces, F_idx, e_test)
if nb_idx is None:
continue
nb_choices.append((x_vals[nb_idx], e_test, nb_idx))
if not nb_choices:
log.append(f'step {step}: STUCK -- all interior edges blocked '
f'or none available; max depth={d_max}, x={n_at_max}')
return faces, log
nb_choices.sort()
_, e_chosen, fp_idx = nb_choices[0]
Fp = faces[fp_idx]
u, v = tuple(e_chosen)
w = [vert for vert in F if vert not in (u, v)][0]
xv = [vert for vert in Fp if vert not in (u, v)][0]
log.append(f'step {step}: preprocess ({u},{v}) (d={d_max},x={n_at_max}, '
f'#blocked={len(blocked)})')
blocked.add(frozenset((u, v)))
faces = apply_switch(faces, (u, v), (w, xv))
log.append(f'hit max_steps')
return faces, log
# ----- run on the stuck n=40 case -----
seed = 94476710
outer, _, faces = random_triangulation(40, seed=seed)
outer_set = {frozenset(e) for e in outer}
print(f'Running blocked algorithm on n=40, seed={seed}...')
faces_after, log = run_with_blocking(faces, outer_set, max_steps=5000)
print(f'Result: {log[-1]}')
print(f'Total log lines: {len(log)}')
print('First 25 lines:')
for line in log[:25]:
print(f' {line}')
print('Last 5 lines:')
for line in log[-5:]:
print(f' {line}')
# ----- also test on 9-vertex and 24-vertex -----
print('\n=== 9-vertex ===')
n9 = 9
outer9 = [(i, (i+1) % n9) for i in range(n9)]
outer_set9 = {frozenset(e) for e in outer9}
faces9 = [
(0, 1, 2), (0, 2, 3), (3, 4, 5), (3, 5, 6),
(6, 7, 8), (6, 8, 0), (0, 3, 6),
]
_, log9 = run_with_blocking(faces9, outer_set9)
for line in log9[:5]:
print(f' {line}')
print('\n=== 24-vertex doubly-lopsided ===')
n24 = 24
outer24 = [(i, (i+1) % n24) for i in range(n24)]
outer_set24 = {frozenset(e) for e in outer24}
def arm(a, b):
return [
(a, a+1, a+2), (a, a+2, b), (a+2, a+3, a+4),
(a+2, a+4, b), (a+4, a+5, a+6), (a+4, a+6, b),
(a+6, a+7, b),
]
faces24 = [(0, 8, 16)]
for (a, b) in [(0, 8), (8, 16), (16, 24)]:
fs = arm(a, b)
fs = [tuple(0 if v == 24 else v for v in vt) for vt in fs]
faces24.extend(fs)
_, log24 = run_with_blocking(faces24, outer_set24)
print(f' total: {len(log24) - 1} steps')
print(f' final: {log24[-1]}')
@@ -0,0 +1,140 @@
"""Generate a multi-page PDF showing the L_k state after each edge
switch, on the n=40 stuck example."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import math
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.backends.backend_pdf import PdfPages
from leaf_distance_algorithm import compute_x
from stress_test_termination import (
compute_depths, apply_switch, check_balanced, face_edges,
random_triangulation
)
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
def neighbour_at_edge(faces, F_idx, e):
for j, fj in enumerate(faces):
if j != F_idx and e in face_edges(fj):
return j
return None
def positions(n):
return {i: (math.cos(math.radians(90 - i * 360 / n)),
math.sin(math.radians(90 - i * 360 / n)))
for i in range(n)}
def draw_state(ax, faces, n, outer_set, switched_edge=None,
action=None, step=None):
POS = positions(n)
depth = compute_depths(faces, outer_set)
palette = {0: '#86efac', 1: '#fde68a', 2: '#fca5a5'}
edge_pal = {0: '#16a34a', 1: '#d97706', 2: '#dc2626'}
for f in faces:
d = depth.get(faces.index(f), 0)
# Find depth by iterating
for i, ff in enumerate(faces):
if ff == f:
d = depth[i]
break
poly = Polygon([POS[v] for v in f], closed=True,
facecolor=palette.get(d, '#ddd'),
edgecolor=edge_pal.get(d, '#333'),
linewidth=0.7, alpha=0.6, zorder=0)
ax.add_patch(poly)
# Draw all edges
all_edges = set()
for f in faces:
all_edges.update(face_edges(f))
outer_edges = [tuple(e) for e in all_edges if e in outer_set]
chord_edges = [tuple(e) for e in all_edges if e not in outer_set]
for (a, b) in outer_edges + chord_edges:
color = '#333'; lw = 0.5
if switched_edge and {a, b} == set(switched_edge):
color = '#dc2626' if action == 'preprocess' else '#16a34a'
lw = 2.5
ax.plot([POS[a][0], POS[b][0]], [POS[a][1], POS[b][1]],
color=color, linewidth=lw, zorder=1)
# Vertices
for i, (x, y) in POS.items():
ax.scatter([x], [y], s=70, c='#1f2937', edgecolors='black',
linewidths=0.3, zorder=2)
ax.text(x, y, str(i), ha='center', va='center',
fontsize=5, color='white', fontweight='bold', zorder=3)
ax.set_aspect('equal'); ax.axis('off')
ax.set_xlim(-1.2, 1.2); ax.set_ylim(-1.2, 1.2)
d_max = max(depth.values())
n_d1 = sum(1 for d in depth.values() if d == 1)
n_d2 = sum(1 for d in depth.values() if d == 2)
title = f'step {step}'
if action and switched_edge:
title += f': {action} on {tuple(sorted(switched_edge))}'
title += f' (max={d_max}, #d1={n_d1}, #d2={n_d2})'
ax.set_title(title, fontsize=9)
def run_and_record(faces, outer_set, n, max_steps=80):
"""Run algorithm, capturing the state and the action at each step.
Returns list of (faces_at_start_of_step, action, edge)."""
records = []
for step in range(max_steps):
depth = compute_depths(faces, outer_set)
d_max = max(depth.values())
if d_max == 0:
records.append((list(faces), None, None))
return records, faces
max_d_faces = [i for i, d in depth.items() if d == d_max]
F_idx = max_d_faces[0]
F = faces[F_idx]
ok, _, fp_idx, e = check_balanced(F_idx, faces, depth, outer_set)
if ok:
action = 'BALANCED'
else:
x_vals, _ = compute_x(faces, outer_set)
nb_choices = []
for e_test in face_edges(F):
if e_test in outer_set:
continue
nb_idx = neighbour_at_edge(faces, F_idx, e_test)
if nb_idx is None:
continue
nb_choices.append((x_vals[nb_idx], e_test, nb_idx))
if not nb_choices:
records.append((list(faces), 'STUCK', None))
return records, faces
nb_choices.sort()
_, e, fp_idx = nb_choices[0]
action = 'preprocess'
Fp = faces[fp_idx]
u, v = tuple(e)
w = [vert for vert in F if vert not in (u, v)][0]
x = [vert for vert in Fp if vert not in (u, v)][0]
records.append((list(faces), action, (u, v)))
faces = apply_switch(faces, (u, v), (w, x))
return records, faces
seed = 94476710
outer, _, faces = random_triangulation(40, seed=seed)
outer_set = {frozenset(e) for e in outer}
print('recording trajectory...')
records, _ = run_and_record(faces, outer_set, 40, max_steps=80)
print(f'recorded {len(records)} steps')
out_pdf = os.path.join(OUT_DIR, 'fig_n40_every_step.pdf')
with PdfPages(out_pdf) as pdf:
for step, (state_faces, action, edge) in enumerate(records):
fig, ax = plt.subplots(figsize=(7, 7))
draw_state(ax, state_faces, 40, outer_set,
switched_edge=edge, action=action, step=step)
pdf.savefig(fig, dpi=120, bbox_inches='tight')
plt.close(fig)
print(f'wrote {out_pdf}')
@@ -0,0 +1,122 @@
"""Plot the depth distribution over time for the random n=40 trajectory
under the leaf-distance algorithm. Shows whether progress is being made
or whether the algorithm is just grinding."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import matplotlib.pyplot as plt
from leaf_distance_algorithm import compute_x
from stress_test_termination import (
compute_depths, apply_switch, check_balanced, face_edges,
random_triangulation
)
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
def neighbour_at_edge(faces, F_idx, e):
for j, fj in enumerate(faces):
if j != F_idx and e in face_edges(fj):
return j
return None
def run_with_full_tracking(faces, outer_set, max_steps=3000):
"""Run algorithm; record at each step: (#faces at each depth), and
whether the step was BALANCED or x-preprocess."""
history = []
for step in range(max_steps):
depth = compute_depths(faces, outer_set)
d_max = max(depth.values())
# Record current state
d_dist = {}
for d in depth.values():
d_dist[d] = d_dist.get(d, 0) + 1
history.append({'step': step, 'd_max': d_max, 'd_dist': dict(d_dist)})
if d_max == 0:
return history, faces
max_d_faces = [i for i, d in depth.items() if d == d_max]
F_idx = max_d_faces[0]
F = faces[F_idx]
ok, _, fp_idx, e = check_balanced(F_idx, faces, depth, outer_set)
if ok:
history[-1]['action'] = 'BALANCED'
Fp = faces[fp_idx]
u, v = tuple(e)
w = [vert for vert in F if vert not in (u, v)][0]
x = [vert for vert in Fp if vert not in (u, v)][0]
faces = apply_switch(faces, (u, v), (w, x))
continue
history[-1]['action'] = 'preprocess'
x_vals, _ = compute_x(faces, outer_set)
nb_choices = []
for e_test in face_edges(F):
if e_test in outer_set:
continue
nb_idx = neighbour_at_edge(faces, F_idx, e_test)
if nb_idx is None:
continue
nb_choices.append((x_vals[nb_idx], e_test, nb_idx))
if not nb_choices:
return history, faces
nb_choices.sort()
_, e_chosen, nb_idx = nb_choices[0]
Fnb = faces[nb_idx]
u, v = tuple(e_chosen)
w = [vert for vert in F if vert not in (u, v)][0]
xv = [vert for vert in Fnb if vert not in (u, v)][0]
faces = apply_switch(faces, (u, v), (w, xv))
return history, faces
seed = 94476710
outer, _, faces = random_triangulation(40, seed=seed)
outer_set = {frozenset(e) for e in outer}
print(f'Running algorithm on n=40, seed={seed}...')
history, final_faces = run_with_full_tracking(faces, outer_set, max_steps=3000)
print(f'finished at step {len(history) - 1}, '
f'final max depth = {history[-1]["d_max"]}')
# Plot count of depth-d faces for d = 1 and 2 over time, and mark
# balanced vs preprocessing steps
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 7), sharex=True)
steps = [h['step'] for h in history]
n_d1 = [h['d_dist'].get(1, 0) for h in history]
n_d2 = [h['d_dist'].get(2, 0) for h in history]
balanced_steps = [h['step'] for h in history
if h.get('action') == 'BALANCED']
preprocess_steps = [h['step'] for h in history
if h.get('action') == 'preprocess']
ax1.plot(steps, n_d1, color='#d97706', linewidth=1.2, label='depth-1 faces')
ax1.plot(steps, n_d2, color='#dc2626', linewidth=1.2, label='depth-2 faces')
ax1.set_ylabel('count of faces at depth')
ax1.legend(loc='upper right')
ax1.set_title(f'n=40 trajectory under leaf-distance algorithm (seed={seed}, '
f'{len(history) - 1} steps)')
ax1.grid(alpha=0.3)
# Add markers showing balanced vs preprocess at bottom
ax2.scatter(balanced_steps, [1] * len(balanced_steps),
color='#16a34a', s=8, label=f'BALANCED ({len(balanced_steps)} steps)')
ax2.scatter(preprocess_steps, [0] * len(preprocess_steps),
color='#3b82f6', s=8, label=f'preprocess ({len(preprocess_steps)} steps)')
ax2.set_yticks([0, 1])
ax2.set_yticklabels(['preprocess', 'BALANCED'])
ax2.set_xlabel('step')
ax2.legend(loc='upper right')
ax2.grid(alpha=0.3)
fig.tight_layout()
out = os.path.join(OUT_DIR, 'fig_n40_trajectory.png')
fig.savefig(out, dpi=150, bbox_inches='tight')
plt.close(fig)
print(f'wrote {out}')
# Summary
print(f'\n#BALANCED switches: {len(balanced_steps)}')
print(f'#preprocess switches: {len(preprocess_steps)}')
print(f'Max depth-1 count seen: {max(n_d1)}')
print(f'Max depth-2 count seen: {max(n_d2)}')
@@ -0,0 +1,158 @@
"""Annotated diagram so the user can answer the v_c-rotation
clarification questions.
Shows the 9-vertex L_k with e_0 = (0, 3) highlighted, both candidate
v_c vertices (0 and 3) labelled, and the four (v_c, direction) fans
laid out around each."""
import os
import math
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, FancyArrowPatch
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
n = 9
POS = {i: (math.cos(math.radians(90 - i * 360 / n)),
math.sin(math.radians(90 - i * 360 / n))) for i in range(n)}
OUTER_EDGES = [(i, (i + 1) % n) for i in range(n)]
CHORDS = [(0, 2), (0, 3), (3, 5), (3, 6), (0, 6), (6, 8)]
# Inner faces
FACES = {
(0, 1, 2): 0,
(0, 2, 3): 0,
(3, 4, 5): 0,
(3, 5, 6): 0,
(6, 7, 8): 0,
(6, 8, 0): 0,
(0, 3, 6): 1, # F
}
F_idx_face = (0, 3, 6)
Fp_face = (0, 2, 3)
e0 = (0, 3)
def cw_order_around(v):
"""Sort vertices adjacent to v by clockwise angle (decreasing
angle from v, starting at angle 90)."""
adj = set()
for f in FACES:
if v in f:
for u in f:
if u != v:
adj.add(u)
# angle of each adjacent vertex
def angle(u):
return (90 - u * 360 / n) % 360
return sorted(adj, key=lambda u: -angle(u))
fig, axes = plt.subplots(1, 2, figsize=(14, 7))
palette = {0: '#86efac', 1: '#fde68a'}
edge_pal = {0: '#16a34a', 1: '#d97706'}
def draw_base(ax, title):
# Fill faces by depth
for face, depth in FACES.items():
poly = Polygon([POS[v] for v in face], closed=True,
facecolor=palette[depth], edgecolor=edge_pal[depth],
linewidth=1.2, alpha=0.5, zorder=0)
ax.add_patch(poly)
# Edges
for (a, b) in OUTER_EDGES + CHORDS:
color, lw = '#333', 1.2
if {a, b} == set(e0):
color, lw = '#dc2626', 3.4 # e_0 highlighted
ax.plot([POS[a][0], POS[b][0]], [POS[a][1], POS[b][1]],
color=color, linewidth=lw, zorder=1)
# Vertices
for i, (x, y) in POS.items():
color = '#3b82f6' if i in e0 else '#1f2937'
size = 470 if i in e0 else 280
ax.scatter([x], [y], s=size, c=color, edgecolors='black',
linewidths=1.2, zorder=2)
ax.text(x, y, str(i), ha='center', va='center',
fontsize=11 if i in e0 else 9,
color='white', fontweight='bold', zorder=3)
# Annotate F and F'
cx_F = sum(POS[v][0] for v in F_idx_face) / 3
cy_F = sum(POS[v][1] for v in F_idx_face) / 3
ax.text(cx_F, cy_F + 0.1, r'$F$', ha='center', fontsize=14,
color='#92400e', fontweight='bold', zorder=4)
ax.text(cx_F, cy_F - 0.1, '(depth 1)', ha='center', fontsize=9,
color='#92400e', zorder=4)
cx_Fp = sum(POS[v][0] for v in Fp_face) / 3
cy_Fp = sum(POS[v][1] for v in Fp_face) / 3
ax.text(cx_Fp + 0.1, cy_Fp, r"$F'$" + '\n(depth 0)', ha='center',
fontsize=10, color='#16a34a', fontweight='bold', zorder=4)
# F''s outer-cycle edge: (2, 3)
px, py = POS[2], POS[3]
mid = ((px[0] + py[0]) / 2, (px[1] + py[1]) / 2)
ax.annotate("outer edge of $F'$",
xy=(mid[0] + 0.05, mid[1] - 0.05),
xytext=(1.35, 0.5),
fontsize=9, color='#16a34a',
arrowprops=dict(arrowstyle='->', color='#16a34a',
lw=1.0))
ax.set_aspect('equal'); ax.axis('off')
ax.set_xlim(-1.5, 1.7); ax.set_ylim(-1.4, 1.4)
ax.set_title(title, fontsize=12)
def annotate_fan(ax, vc, label):
"""Draw arrows around v_c showing CW order of edges."""
cw_neighbours = cw_order_around(vc)
# rotate so the e_0 neighbour comes first
e0_other = [u for u in e0 if u != vc][0]
idx = cw_neighbours.index(e0_other)
cw_neighbours = cw_neighbours[idx:] + cw_neighbours[:idx]
px, py = POS[vc]
# Draw a partial arc around v_c
for i, u in enumerate(cw_neighbours):
# midpoint between v_c and u, slightly inside
ux, uy = POS[u]
midx = px * 0.7 + ux * 0.3
midy = py * 0.7 + uy * 0.3
# is edge on outer cycle?
is_outer = (min(vc, u), max(vc, u)) in OUTER_EDGES or \
(max(vc, u), min(vc, u)) in OUTER_EDGES
marker_color = '#1d4ed8' if not is_outer else '#dc2626'
marker = 'o'
ax.scatter([midx], [midy], s=120, c=marker_color, marker=marker,
edgecolors='white', linewidths=1.5, zorder=5)
ax.text(midx, midy, str(i + 1), ha='center', va='center',
fontsize=8, color='white', fontweight='bold', zorder=6)
# Label
ax.text(px + 0.05, py + 0.25, label, ha='center',
fontsize=11, color='#1d4ed8', fontweight='bold')
draw_base(axes[0], r'Option A: $v_c = 0$ (CW order: 1=$(0,3)$, 2=$(0,6)$, 3=$(0,8)$ outer ...)')
annotate_fan(axes[0], 0, r'$v_c = 0$')
draw_base(axes[1], r'Option B: $v_c = 3$ (CW order: 1=$(0,3)$, 2=$(2,3)$ outer ...)')
annotate_fan(axes[1], 3, r'$v_c = 3$')
# Add legend below
fig.text(0.5, 0.02,
'Blue circles = chord edges (would be switched). '
'Red circles = outer-cycle edges (algorithm stops here). '
'Numbers = clockwise order around $v_c$ starting from $e_0$.',
ha='center', fontsize=10)
fig.tight_layout(rect=[0, 0.04, 1, 1])
out = os.path.join(OUT_DIR, 'fig_v_c_question.png')
fig.savefig(out, dpi=180, bbox_inches='tight')
plt.close(fig)
print(f'wrote {out}')
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

+12 -2
View File
@@ -51,10 +51,20 @@
\@writefile{lof}{\contentsline {figure}{\numberline {9}{\ignorespaces Recursive lopsidedness at $d = 2$. Left: $F = (0,8,16)$ depth $2$, every arm doubly-lopsided. Middle: one preprocessing switch $(0,8) \DOTSB \mapstochar \rightarrow (2,16)$ exposes the first lopsided layer; the new depth-$2$ face $(2,8,16)$ still has no balanced switch. Right: a second preprocessing switch $(8,2) \DOTSB \mapstochar \rightarrow (4,16)$ reaches the inner balanced face $K_0 = (4,6,8)$, whose two non-$F$ neighbours are both ears; the depth-$2$ face $(4,8,16)$ now admits a balanced surface switch on edge $(4,8)$.}}{8}{figure.9}\protected@file@percent }
\newlabel{fig:d2-recursive}{{9}{8}{Recursive lopsidedness at $d = 2$. Left: $F = (0,8,16)$ depth $2$, every arm doubly-lopsided. Middle: one preprocessing switch $(0,8) \mapsto (2,16)$ exposes the first lopsided layer; the new depth-$2$ face $(2,8,16)$ still has no balanced switch. Right: a second preprocessing switch $(8,2) \mapsto (4,16)$ reaches the inner balanced face $K_0 = (4,6,8)$, whose two non-$F$ neighbours are both ears; the depth-$2$ face $(4,8,16)$ now admits a balanced surface switch on edge $(4,8)$}{figure.9}{}}
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Empirical termination}}{8}{section*.4}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{What the natural monovariants do not give us}}{9}{section*.5}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Empirical termination on random configurations}}{9}{section*.6}\protected@file@percent }
\newlabel{q:preprocessing-terminates}{{3.6}{9}{}{theorem.3.6}{}}
\@writefile{toc}{\contentsline {section}{\tocsection {}{4}{Reachability via edge switches}}{9}{section.4}\protected@file@percent }
\newlabel{sec:reachability}{{4}{9}{Reachability via edge switches}{section.4}{}}
\citation{sleator-tarjan-thurston}
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Two cases on the layer below $k$}}{10}{section*.7}\protected@file@percent }
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Combining}}{10}{section*.8}\protected@file@percent }
\newlabel{thm:reachability}{{4.1}{10}{}{theorem.4.1}{}}
\bibcite{sleator-tarjan-thurston}{1}
\newlabel{tocindent-1}{0pt}
\newlabel{tocindent0}{14.69437pt}
\newlabel{tocindent1}{17.77782pt}
\newlabel{tocindent2}{0pt}
\newlabel{tocindent3}{0pt}
\newlabel{q:preprocessing-terminates}{{3.6}{9}{}{theorem.3.6}{}}
\gdef \@abspage@last{9}
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{11}{section*.9}\protected@file@percent }
\gdef \@abspage@last{11}
+30 -21
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) 20 MAY 2026 23:18
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 21 MAY 2026 14:28
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
@@ -353,12 +353,12 @@ Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
e
))
<fig_level_source.png, id=32, 715.11165pt x 317.988pt>
<fig_level_source.png, id=56, 715.11165pt x 317.988pt>
File: fig_level_source.png Graphic file (type png)
<use fig_level_source.png>
Package pdftex.def Info: fig_level_source.png used on input line 105.
(pdftex.def) Requested size: 306.0022pt x 136.07228pt.
<fig_levels.png, id=34, 454.21695pt x 391.34206pt>
<fig_levels.png, id=58, 454.21695pt x 391.34206pt>
File: fig_levels.png Graphic file (type png)
<use fig_levels.png>
Package pdftex.def Info: fig_levels.png used on input line 122.
@@ -367,7 +367,7 @@ Package pdftex.def Info: fig_levels.png used on input line 122.
LaTeX Warning: `h' float specifier changed to `ht'.
<fig_level_cycle.png, id=35, 452.04884pt x 391.34206pt>
<fig_level_cycle.png, id=59, 452.04884pt x 391.34206pt>
File: fig_level_cycle.png Graphic file (type png)
<use fig_level_cycle.png>
Package pdftex.def Info: fig_level_cycle.png used on input line 136.
@@ -377,7 +377,7 @@ LaTeX Warning: `h' float specifier changed to `ht'.
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map} <./fig
_level_source.png>]
<fig_edge_switch.png, id=57, 859.65166pt x 378.33345pt>
<fig_edge_switch.png, id=81, 859.65166pt x 378.33345pt>
File: fig_edge_switch.png Graphic file (type png)
<use fig_edge_switch.png>
Package pdftex.def Info: fig_edge_switch.png used on input line 155.
@@ -386,7 +386,7 @@ Package pdftex.def Info: fig_edge_switch.png used on input line 155.
LaTeX Warning: `h' float specifier changed to `ht'.
<fig_parity_subgraph.png, id=59, 1076.46165pt x 319.79475pt>
<fig_parity_subgraph.png, id=83, 1076.46165pt x 319.79475pt>
File: fig_parity_subgraph.png Graphic file (type png)
<use fig_parity_subgraph.png>
Package pdftex.def Info: fig_parity_subgraph.png used on input line 173.
@@ -395,7 +395,7 @@ Package pdftex.def Info: fig_parity_subgraph.png used on input line 173.
LaTeX Warning: `h' float specifier changed to `ht'.
[2 <./fig_levels.png> <./fig_level_cycle.png>]
<fig_facial_depth.png, id=72, 482.40225pt x 498.663pt>
<fig_facial_depth.png, id=96, 482.40225pt x 498.663pt>
File: fig_facial_depth.png Graphic file (type png)
<use fig_facial_depth.png>
Package pdftex.def Info: fig_facial_depth.png used on input line 200.
@@ -413,7 +413,7 @@ bal-anced-ness gives $[](\OML/cmm/m/it/10 A[]\OT1/cmr/m/n/10 ) = \OML/cmm/m/it/
/m/n/10 ) \OMS/cmsy/m/n/10 ^^T
[]
<fig_no_balanced_switch.png, id=107, 483.0045pt x 498.2615pt>
<fig_no_balanced_switch.png, id=131, 483.0045pt x 498.2615pt>
File: fig_no_balanced_switch.png Graphic file (type png)
<use fig_no_balanced_switch.png>
Package pdftex.def Info: fig_no_balanced_switch.png used on input line 395.
@@ -421,7 +421,7 @@ Package pdftex.def Info: fig_no_balanced_switch.png used on input line 395.
LaTeX Warning: `h' float specifier changed to `ht'.
<fig_preprocessing.png, id=110, 998.932pt x 513.5185pt>
<fig_preprocessing.png, id=134, 998.932pt x 513.5185pt>
File: fig_preprocessing.png Graphic file (type png)
<use fig_preprocessing.png>
Package pdftex.def Info: fig_preprocessing.png used on input line 426.
@@ -462,7 +462,7 @@ cmm/m/it/10 ; \OT1/cmr/m/n/10 14)\OML/cmm/m/it/10 ; \OT1/cmr/m/n/10 (17\OML/cmm
/m/it/10 ; \OT1/cmr/m/n/10 19\OML/cmm/m/it/10 ; \OT1/cmr/m/n/10 0)$
[]
<fig_d2_recursive.png, id=128, 1293.633pt x 447.64888pt>
<fig_d2_recursive.png, id=152, 1293.633pt x 447.64888pt>
File: fig_d2_recursive.png Graphic file (type png)
<use fig_d2_recursive.png>
Package pdftex.def Info: fig_d2_recursive.png used on input line 477.
@@ -472,15 +472,24 @@ Overfull \hbox (44.02832pt too wide) detected at line 496
[][]
[]
[8 <./fig_d2_recursive.png>] [9] (./paper.aux)
[8 <./fig_d2_recursive.png>] [9]
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref) removing `math shift' on input line 581.
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref) removing `math shift' on input line 581.
[10] [11] (./paper.aux)
Package rerunfilecheck Info: File `paper.out' has not changed.
(rerunfilecheck) Checksum: DB53A88C1A1F5BD90EDB1F1E02E41C38;1447.
(rerunfilecheck) Checksum: EB616E34045D97804AC077E984931199;2673.
)
Here is how much of TeX's memory you used:
9791 strings out of 478268
151840 string characters out of 5846347
458685 words of memory out of 5000000
27682 multiletter control sequences out of 15000+600000
9811 strings out of 478268
152095 string characters out of 5846347
458816 words of memory out of 5000000
27691 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,9n,76p,781b,448s stack positions out of 10000i,1000n,20000p,200000b,200000s
@@ -498,10 +507,10 @@ xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr/local/texl
ive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texli
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive
/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
Output written on paper.pdf (9 pages, 1268281 bytes).
Output written on paper.pdf (11 pages, 1286221 bytes).
PDF statistics:
215 PDF objects out of 1000 (max. 8388607)
156 compressed objects within 2 object streams
42 named destinations out of 1000 (max. 500000)
102 words of extra memory for PDF output out of 10000 (max. 10000000)
259 PDF objects out of 1000 (max. 8388607)
198 compressed objects within 2 object streams
53 named destinations out of 1000 (max. 500000)
150 words of extra memory for PDF output out of 10000 (max. 10000000)
+6
View File
@@ -5,3 +5,9 @@
\BOOKMARK [2][-]{section*.2}{\376\377\000P\000r\000e\000p\000r\000o\000c\000e\000s\000s\000i\000n\000g\000\040\000t\000o\000w\000a\000r\000d\000\040\000b\000a\000l\000a\000n\000c\000e\000d\000\040\000s\000w\000i\000t\000c\000h\000e\000s}{section.3}% 5
\BOOKMARK [2][-]{section*.3}{\376\377\000T\000h\000e\000\040\000d\000\040\0002\000\040\000a\000n\000a\000l\000o\000g\000\040\000a\000n\000d\000\040\000r\000e\000c\000u\000r\000s\000i\000v\000e\000\040\000l\000o\000p\000s\000i\000d\000e\000d\000n\000e\000s\000s}{section.3}% 6
\BOOKMARK [2][-]{section*.4}{\376\377\000E\000m\000p\000i\000r\000i\000c\000a\000l\000\040\000t\000e\000r\000m\000i\000n\000a\000t\000i\000o\000n}{section.3}% 7
\BOOKMARK [2][-]{section*.5}{\376\377\000W\000h\000a\000t\000\040\000t\000h\000e\000\040\000n\000a\000t\000u\000r\000a\000l\000\040\000m\000o\000n\000o\000v\000a\000r\000i\000a\000n\000t\000s\000\040\000d\000o\000\040\000n\000o\000t\000\040\000g\000i\000v\000e\000\040\000u\000s}{section.3}% 8
\BOOKMARK [2][-]{section*.6}{\376\377\000E\000m\000p\000i\000r\000i\000c\000a\000l\000\040\000t\000e\000r\000m\000i\000n\000a\000t\000i\000o\000n\000\040\000o\000n\000\040\000r\000a\000n\000d\000o\000m\000\040\000c\000o\000n\000f\000i\000g\000u\000r\000a\000t\000i\000o\000n\000s}{section.3}% 9
\BOOKMARK [1][-]{section.4}{\376\377\0004\000.\000\040\000R\000e\000a\000c\000h\000a\000b\000i\000l\000i\000t\000y\000\040\000v\000i\000a\000\040\000e\000d\000g\000e\000\040\000s\000w\000i\000t\000c\000h\000e\000s}{}% 10
\BOOKMARK [2][-]{section*.7}{\376\377\000T\000w\000o\000\040\000c\000a\000s\000e\000s\000\040\000o\000n\000\040\000t\000h\000e\000\040\000l\000a\000y\000e\000r\000\040\000b\000e\000l\000o\000w\000\040\000k}{section.4}% 11
\BOOKMARK [2][-]{section*.8}{\376\377\000C\000o\000m\000b\000i\000n\000i\000n\000g}{section.4}% 12
\BOOKMARK [1][-]{section*.9}{\376\377\000R\000e\000f\000e\000r\000e\000n\000c\000e\000s}{}% 13
Binary file not shown.
+103
View File
@@ -561,4 +561,107 @@ reached from the current maximum-depth face by a preprocessing path of
length bounded by the dual-tree diameter of $L_k$?
\end{question}
\section{Reachability via edge switches}
\label{sec:reachability}
We now sketch a positive termination argument that bypasses the local
question of balanced surface switches entirely: instead of insisting
that each switch strictly improve facial depth, we show that any
$L_k$ can be transformed by edge switches into a configuration in which
every face has depth $0$. Throughout this section we adopt the
\emph{stable-labelling convention}: the level $\ell_G(v)$ of each
vertex is fixed at the start of the procedure (by BFS from $S$ in the
initial triangulation $G$) and reused thereafter, even after edge
switches modify the triangulation. In particular, the level-$k$
subgraph $L_k$ of the current triangulation always means ``the
subgraph induced on the vertices labelled $k$ at the start''.
\subsection*{Two cases on the layer below $k$}
We split on whether any $L_k$-face has a higher-level vertex in its
interior in the planar embedding inherited from $\Pi_G$.
\textbf{Case 1: every inner face of $L_k$ is a triangle and contains
no vertex of level $\geq k+1$ in its interior.}
Under this hypothesis, for every chord $uv \in L_k$ the two $G$-triangles
at $uv$ have their third vertices in $L_k$ (since the interior of the
two $L_k$-faces adjacent to $uv$ in $\Pi_G$ contains no other vertex of
$G$). The edge switch at $uv$ is therefore always in Case~(ii) of
Proposition~\ref{prop:balanced-descent}, and acts as a flip of the
chord $uv$ in $L_k$ regarded purely as a maximal outerplanar graph.
Maximal outerplanar graphs on $n$ labelled vertices (arranged on a
common outer cycle) are exactly triangulations of a convex $n$-gon.
The set of such triangulations, with chord flips as edges, is the
1-skeleton of the associahedron and is connected; in fact any two
triangulations are joined by $O(n)$ chord flips~\cite{sleator-tarjan-thurston}.
A \emph{fan triangulation} -- the triangulation obtained by adding
chords from a single apex vertex $v_0$ to every other vertex -- has
every inner triangle bounded by an outer-cycle edge (namely the side
opposite $v_0$ in that triangle), so every face of a fan triangulation
lies in $\mathcal{B}$ and has depth $0$.
Combining: in Case~1, $L_k$ can be transformed into a fan triangulation
by $O(n)$ edge switches, after which every face has depth $0$.
\textbf{Case 2: some $L_k$-face $F$ has a vertex of level $\geq k+1$
in its interior.}
Pick any edge $uv$ of $\partial F$. The $G$-triangle at $uv$ on the
$F$-side has its third vertex $w$ inside $F$, so $w$ is a vertex of
level $\geq k+1$ and in particular $w \notin L_k$. The edge switch
at $uv$ is therefore in Case~(i) of
Proposition~\ref{prop:balanced-descent}: the edge $uv$ is removed from
$L_k$, no new edge is added to $L_k$, and the face $F$ merges with the
$L_k$-face on the opposite side of $uv$ into a single larger face. The
number of inner faces of $L_k$ strictly decreases by $1$.
\subsection*{Combining}
\begin{theorem}
\label{thm:reachability}
Under the stable-labelling convention, every $L_k$ can be transformed
by edge switches into a configuration in which every inner face of
$L_k$ has facial depth $0$, in $O(n)$ edge switches.
\end{theorem}
\begin{proof}[Proof sketch]
Apply Case~2 repeatedly while $L_k$ has any inner face with a
higher-level vertex in its interior. Each application reduces the
number of inner faces of $L_k$ by $1$, so after at most $|L_k| - 2 \leq
n - 2$ such steps we reach one of:
\begin{itemize}
\item A configuration satisfying the hypothesis of Case~1, in which
case Case~1 finishes the job in $O(n)$ flips by reaching a fan
triangulation.
\item A configuration in which $L_k$ has only one inner face -- i.e.,
$L_k$ consists of only its outer cycle, with no chords. The unique
inner face is bounded by all $n$ outer-cycle edges, so it lies in
$\mathcal{B}$ and has depth $0$.
\end{itemize}
Both outcomes leave every face at depth $0$. The total step count is
at most $(n - 2) + O(n) = O(n)$.
\end{proof}
\begin{remark}
Theorem~\ref{thm:reachability} settles the existence question
Question~\ref{q:preprocessing-terminates} affirmatively in the
following sense: \emph{some} sequence of edge switches drives every
face to depth $0$ in $O(n)$ steps. It does not, however, identify the
sequence by a local rule (the leaf-distance algorithm of
Section~\ref{sec:reachability}'s preceding discussion), and in
particular the question of which \emph{rule} produces such a sequence
without backtracking remains open.
\end{remark}
\begin{thebibliography}{9}
\bibitem{sleator-tarjan-thurston}
D.~D. Sleator, R.~E. Tarjan, W.~P. Thurston.
\emph{Rotation distance, triangulations, and hyperbolic geometry}.
Journal of the American Mathematical Society, 1988.
\end{thebibliography}
\end{document}