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>
@@ -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()
|
||||
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
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
|
||||
@@ -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}')
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 199 KiB |
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||