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,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:]}')
|
||||
Reference in New Issue
Block a user