77093cb0b0
Add 21-vertex and 24-vertex examples showing recursive lopsidedness at d=2. Empirically confirm that the iterated algorithm (balanced switch when available, preprocess otherwise) drives every face to depth 0 on all tested configurations. Frame the remaining open question as identifying a strictly-decreasing monovariant under unbalanced preprocessing switches. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
197 lines
6.2 KiB
Python
197 lines
6.2 KiB
Python
"""Build a 24-vertex L_k where every F'_i (depth-1 neighbour of F) is
|
|
lopsided AND each G_i (depth-1 face inside the arm) is also lopsided.
|
|
Then iterate preprocessing and see how many steps it takes."""
|
|
import sys, os
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
import math
|
|
import networkx as nx
|
|
|
|
n = 24
|
|
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)]
|
|
outer_set = {frozenset(e) for e in OUTER_EDGES}
|
|
|
|
|
|
def face_edges(f):
|
|
return {frozenset((f[0], f[1])), frozenset((f[1], f[2])),
|
|
frozenset((f[0], f[2]))}
|
|
|
|
|
|
def compute_depths(faces):
|
|
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)
|
|
B = [i for i, f in enumerate(faces)
|
|
if len(face_edges(f) & outer_set) >= 1]
|
|
if not B:
|
|
return {i: float('inf') for i in range(len(faces))}
|
|
return {i: min(nx.shortest_path_length(D, i, b) for b in B)
|
|
for i in range(len(faces))}
|
|
|
|
|
|
U0, U1, U2 = 0, 8, 16
|
|
|
|
# Per arm (a, b) with b = a + 8:
|
|
# F'_i = (a, a+2, b)
|
|
# E_i = (a, a+1, a+2) -- ear
|
|
# G_i = (a+2, a+4, b)
|
|
# E'_i = (a+2, a+3, a+4) -- ear
|
|
# K_i = (a+4, a+6, b)
|
|
# ears (a+4, a+5, a+6) and (a+6, a+7, b)
|
|
|
|
|
|
def arm(a, b):
|
|
chords = [(a, a + 2), (a, b), (a + 2, a + 4), (a + 2, b),
|
|
(a + 4, a + 6), (a + 4, b), (a + 6, b)]
|
|
faces = [
|
|
(a, a + 1, a + 2),
|
|
(a, a + 2, b), # F'_i (depth 1, lopsided)
|
|
(a + 2, a + 3, a + 4),
|
|
(a + 2, a + 4, b), # G_i (depth 1, lopsided -- its K_i is depth 1)
|
|
(a + 4, a + 5, a + 6),
|
|
(a + 4, a + 6, b), # K_i (depth 1, balanced -- both ears depth 0)
|
|
(a + 6, a + 7, b),
|
|
]
|
|
return chords, faces
|
|
|
|
|
|
CHORDS = [(U0, U1), (U1, U2), (U0, U2)]
|
|
FACES = [(U0, U1, U2)]
|
|
for (a, b) in [(0, 8), (8, 16), (16, 24 % n)]:
|
|
if b == 0:
|
|
# arm from 16 to 0; treat 0 as the apex
|
|
c, f = arm(a, n) # use 24 as a placeholder for 0
|
|
# Re-map vertex 24 -> 0
|
|
c = [tuple(0 if v == n else v for v in e) for e in c]
|
|
f = [tuple(0 if v == n else v for v in vt) for vt in f]
|
|
else:
|
|
c, f = arm(a, b)
|
|
CHORDS.extend(c)
|
|
FACES.extend(f)
|
|
|
|
# Dedup chords (the apex chords (U0,U1), (U1,U2), (U0,U2) get re-added)
|
|
CHORDS = list(set(frozenset(c) for c in CHORDS))
|
|
CHORDS = [tuple(sorted(c)) for c in CHORDS]
|
|
|
|
depth = compute_depths(FACES)
|
|
print(f'Total faces: {len(FACES)}')
|
|
for i, f in enumerate(FACES):
|
|
print(f' {f} -> depth {depth[i]}')
|
|
|
|
max_d = max(depth.values())
|
|
print(f'\nMax depth: {max_d}')
|
|
|
|
|
|
def check_balanced(F_idx, faces, depth_):
|
|
F = faces[F_idx]
|
|
fe = face_edges(F)
|
|
for e in fe:
|
|
if e in outer_set:
|
|
continue
|
|
cands = [j for j in range(len(faces))
|
|
if j != F_idx and e in face_edges(faces[j])]
|
|
if not cands:
|
|
continue
|
|
Fp_idx = cands[0]
|
|
if depth_[Fp_idx] != depth_[F_idx] - 1:
|
|
continue
|
|
Fp = faces[Fp_idx]
|
|
fpe = face_edges(Fp)
|
|
d = depth_[F_idx]
|
|
ok = True
|
|
for e2 in fpe:
|
|
if e2 == e:
|
|
continue
|
|
if e2 in outer_set:
|
|
continue
|
|
others = [j for j in range(len(faces))
|
|
if j != Fp_idx and e2 in face_edges(faces[j])]
|
|
if not others or depth_[others[0]] != d - 2:
|
|
ok = False
|
|
break
|
|
if ok:
|
|
return True, F_idx, Fp_idx, e
|
|
return False, None, None, None
|
|
|
|
|
|
def apply_switch(faces, chords, uv, wx):
|
|
u, v = uv
|
|
w, x = wx
|
|
new_chords = [c for c in chords if set(c) != {u, v}] + \
|
|
[tuple(sorted((w, x)))]
|
|
new_faces = [f for f in faces
|
|
if set(f) != {u, v, w} and set(f) != {u, v, x}]
|
|
new_faces.append(tuple(sorted((u, w, x))))
|
|
new_faces.append(tuple(sorted((v, w, x))))
|
|
return new_faces, new_chords
|
|
|
|
|
|
def find_third_vertices(faces, uv):
|
|
u, v = uv
|
|
thirds = []
|
|
for f in faces:
|
|
if u in f and v in f:
|
|
for vert in f:
|
|
if vert not in (u, v):
|
|
thirds.append(vert)
|
|
break
|
|
return thirds
|
|
|
|
|
|
# Iteratively preprocess
|
|
faces = list(FACES)
|
|
chords = list(CHORDS)
|
|
print('\nStarting preprocessing loop on the 24-vertex example...')
|
|
|
|
for step in range(20):
|
|
depth = compute_depths(faces)
|
|
d_max = max(depth.values())
|
|
max_d_faces = [i for i, d in depth.items() if d == d_max]
|
|
F_idx = max_d_faces[0]
|
|
F = faces[F_idx]
|
|
print(f'\nStep {step}: max depth = {d_max}, F = {F}')
|
|
if d_max == 0:
|
|
print('All depths are 0. DONE.')
|
|
break
|
|
|
|
ok, _, fp_idx, e = check_balanced(F_idx, faces, depth)
|
|
if ok:
|
|
Fp = faces[fp_idx]
|
|
print(f' balanced switch exists on edge {tuple(e)} with F\' = {Fp}.')
|
|
# Apply the balanced switch
|
|
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, chords = apply_switch(faces, chords, (u, v), (w, x))
|
|
print(f' applied balanced switch ({u},{v}) -> ({w},{x})')
|
|
continue
|
|
|
|
# Preprocess: pick any depth-(d-1) neighbour and switch
|
|
Fset = set(F)
|
|
chosen = None
|
|
for e_test in [frozenset((F[0], F[1])), frozenset((F[1], F[2])),
|
|
frozenset((F[0], F[2]))]:
|
|
if e_test in outer_set:
|
|
continue
|
|
cands = [j for j in range(len(faces))
|
|
if j != F_idx and e_test in face_edges(faces[j])]
|
|
if cands and depth[cands[0]] == d_max - 1:
|
|
chosen = (e_test, cands[0])
|
|
break
|
|
if chosen is None:
|
|
print(' No depth-(d-1) neighbour; cannot preprocess.')
|
|
break
|
|
e, fp_idx = chosen
|
|
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]
|
|
print(f' preprocessing (unbalanced) switch ({u},{v}) -> ({w},{x})')
|
|
faces, chords = apply_switch(faces, chords, (u, v), (w, x))
|
|
|
|
print('\nFinal depth distribution:', sorted(compute_depths(faces).values()))
|