Add Even Level Graph Generators paper + extend Level Switching reachability
- New paper papers/even_level_graph_generators/: defines Even Level Graph (every level cycle even), derived level graphs, intertwining trees, and the disjunction conjecture (every maximal planar graph is a derived level graph or intertwining tree). Empirically tested through n=11: every iso class is at least an intertwining tree, so the disjunction holds trivially in this range. The intertwining tree disjunct fails at the Tutte graph dual (n=25), so the disjunction becomes non-trivial past some unknown threshold. - Level Switching paper: adds Section 4 (Reachability via edge switches) with the two-step argument (Sleator-Tarjan-Thurston for Case 1; face-merges for Case 2) and Theorem 4.1 (O(n) edge switches suffice to reach all-depth-0). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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}')
|
||||
Reference in New Issue
Block a user