Add bridge switch / bridge-derived level graph; set up exhaustive test
- Define bridge switch (E/O switch whose new same-parity edge is a bridge in its parity subgraph) and bridge-derived level graph in the paper. Note that bridge switches preserve bipartite parity subgraphs, so every bridge-derived level graph is automatically valid. - Discover the E/O-switch relation is directed (irreversible when a switch produces a cross-parity edge); T*_9 reaches an ELG forward but no ELG reaches it, explaining why it is not derived. This rules out a simple switch-invariant characterization. - Bridge orbits are far smaller than full E/O orbits (~10^4 vs ~10^8 for some labellings), making exhaustive search feasible. Each of the 4 open duals has ~150 valid parity partitions; exhaustive bridge-orbit search per partition can decide bridge-derivability conclusively. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
|||||||
|
"""Test whether the 4 open Holton-McKay duals are bridge-derived level
|
||||||
|
graphs: reachable from an Even Level Graph by bridge switches (E/O
|
||||||
|
switches whose new same-parity edge is a bridge in its parity subgraph).
|
||||||
|
|
||||||
|
Because bridge switches keep parity subgraphs bipartite, the reachable
|
||||||
|
set is much smaller than the full E/O orbit. We search BACKWARD from the
|
||||||
|
dual: a bridge-switch predecessor of G is G' such that G' -> G is a
|
||||||
|
bridge switch. We look for an ELG (matching the orbit labelling) in the
|
||||||
|
backward bridge-orbit.
|
||||||
|
"""
|
||||||
|
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 networkx as nx
|
||||||
|
import time
|
||||||
|
from load_holton_mckay import parse_planar_code
|
||||||
|
from tutte_dual_treecolor import dual_triangulation
|
||||||
|
from test_conjecture import bfs_levels, is_even_level_graph
|
||||||
|
|
||||||
|
|
||||||
|
def sig(G):
|
||||||
|
return frozenset(frozenset(e) for e in G.edges())
|
||||||
|
|
||||||
|
|
||||||
|
def parity_subgraph(G, labels, parity):
|
||||||
|
nodes = [v for v in G.nodes() if labels[v] % 2 == parity]
|
||||||
|
return G.subgraph(nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def edge_is_bridge_in_parity(H, labels, a, b):
|
||||||
|
"""Given triangulation H that CONTAINS edge ab, with a,b same parity:
|
||||||
|
is ab a bridge of its parity subgraph (i.e., not on any cycle)?
|
||||||
|
For cross-parity ab, returns True (enters no parity subgraph)."""
|
||||||
|
if labels[a] % 2 != labels[b] % 2:
|
||||||
|
return True
|
||||||
|
sub = parity_subgraph(H, labels, labels[a] % 2).copy()
|
||||||
|
sub.remove_edge(a, b)
|
||||||
|
return not nx.has_path(sub, a, b)
|
||||||
|
|
||||||
|
|
||||||
|
def forward_bridge_neighbors(G, labels):
|
||||||
|
"""Forward bridge switches from G."""
|
||||||
|
ok, emb = nx.check_planarity(G)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
for u, v in list(G.edges()):
|
||||||
|
if labels[u] % 2 != labels[v] % 2:
|
||||||
|
continue # only flip same-parity edges
|
||||||
|
f1 = emb.traverse_face(u, v)
|
||||||
|
f2 = emb.traverse_face(v, u)
|
||||||
|
if len(f1) != 3 or len(f2) != 3:
|
||||||
|
continue
|
||||||
|
w = next(a for a in f1 if a != u and a != v)
|
||||||
|
x = next(b for b in f2 if b != u and b != v)
|
||||||
|
if w == x or G.has_edge(w, x):
|
||||||
|
continue
|
||||||
|
Gp = G.copy(); Gp.remove_edge(u, v); Gp.add_edge(w, x)
|
||||||
|
# bridge condition checked on the post-switch state Gp:
|
||||||
|
if not edge_is_bridge_in_parity(Gp, labels, w, x):
|
||||||
|
continue
|
||||||
|
yield Gp
|
||||||
|
|
||||||
|
|
||||||
|
def backward_bridge_neighbors(G, labels):
|
||||||
|
"""States G' with G' -> G a bridge switch. G' = G - uv + wx where uv
|
||||||
|
is an edge of G, wx its diagonal, wx same-parity (it was the flipped
|
||||||
|
edge in G'), and in G' the edge uv (the *new* edge of the switch
|
||||||
|
G'->G) must be a bridge in G'-minus-uv's parity subgraph...
|
||||||
|
|
||||||
|
We must reconstruct: G' has wx (same-parity), flipping wx gives uv.
|
||||||
|
For G'->G to be a *bridge* switch, the NEW edge uv (added to G when
|
||||||
|
going G'->G) must be a bridge in G's parity subgraph if uv is
|
||||||
|
same-parity. So check the bridge condition on uv in (G without uv)."""
|
||||||
|
ok, emb = nx.check_planarity(G)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
for u, v in list(G.edges()):
|
||||||
|
f1 = emb.traverse_face(u, v)
|
||||||
|
f2 = emb.traverse_face(v, u)
|
||||||
|
if len(f1) != 3 or len(f2) != 3:
|
||||||
|
continue
|
||||||
|
w = next(a for a in f1 if a != u and a != v)
|
||||||
|
x = next(b for b in f2 if b != u and b != v)
|
||||||
|
if w == x or G.has_edge(w, x):
|
||||||
|
continue
|
||||||
|
if labels[w] % 2 != labels[x] % 2:
|
||||||
|
continue # wx must be same-parity to be flippable in G'
|
||||||
|
# Switch G' -> G adds edge uv to the post-state G. Bridge switch
|
||||||
|
# requires uv to be a bridge of its parity subgraph in G:
|
||||||
|
if not edge_is_bridge_in_parity(G, labels, u, v):
|
||||||
|
continue
|
||||||
|
Gp = G.copy(); Gp.remove_edge(u, v); Gp.add_edge(w, x)
|
||||||
|
yield Gp
|
||||||
|
|
||||||
|
|
||||||
|
def search_bridge_derived(G, labels, max_states=2_000_000, time_limit=600):
|
||||||
|
"""Backward bridge-orbit BFS; return an ELG (matching labels) if found."""
|
||||||
|
t0 = time.time()
|
||||||
|
seen = {sig(G)}
|
||||||
|
frontier = [G]
|
||||||
|
rounds = 0
|
||||||
|
while frontier and len(seen) < max_states:
|
||||||
|
if time.time() - t0 > time_limit:
|
||||||
|
return None, len(seen), rounds, 'timeout'
|
||||||
|
new = []
|
||||||
|
for H in frontier:
|
||||||
|
for s in H.nodes():
|
||||||
|
ok, lvls = is_even_level_graph(H, frozenset({s}))
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
same = all(lvls[u] % 2 == labels[u] % 2 for u in H.nodes())
|
||||||
|
opp = all(lvls[u] % 2 != labels[u] % 2 for u in H.nodes())
|
||||||
|
if same or opp:
|
||||||
|
return H, len(seen), rounds, 'found'
|
||||||
|
for Hp in backward_bridge_neighbors(H, labels):
|
||||||
|
sg = sig(Hp)
|
||||||
|
if sg not in seen:
|
||||||
|
seen.add(sg); new.append(Hp)
|
||||||
|
frontier = new
|
||||||
|
rounds += 1
|
||||||
|
return None, len(seen), rounds, ('exhausted' if not frontier else 'capped')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
graphs = parse_planar_code('experiments/nonham38m4.pc')
|
||||||
|
for i in [0, 3, 4, 5]:
|
||||||
|
G, _ = dual_triangulation(graphs[i][0])
|
||||||
|
print(f'=== dual {i} ===')
|
||||||
|
found = False
|
||||||
|
for src in list(G.nodes()):
|
||||||
|
labels = bfs_levels(G, frozenset({src}))
|
||||||
|
result, n_states, rnds, status = search_bridge_derived(
|
||||||
|
G, labels, max_states=500000, time_limit=120)
|
||||||
|
tag = 'FOUND ELG' if result is not None else 'none'
|
||||||
|
print(f' src={src}: {tag} ({status}, {n_states} states, {rnds} rounds)')
|
||||||
|
if result is not None:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
print(f' dual {i}: bridge-derived = {found}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
"""Verify the directedness of the E/O-switch relation, and its effect on
|
||||||
|
'derived'. Compute, for T*_9 (n=9, known NOT derived) with a fixed
|
||||||
|
labelling:
|
||||||
|
- forward orbit: states reachable FROM T*_9 (T*_9 ->* H)
|
||||||
|
- backward orbit: states that reach T*_9 (H ->* T*_9)
|
||||||
|
and check each for an Even Level Graph.
|
||||||
|
|
||||||
|
'Derived' (directed) = some ELG reaches G = ELG in backward orbit.
|
||||||
|
"""
|
||||||
|
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 networkx as nx
|
||||||
|
from triangulation_gen import enumerate_all_triangulations
|
||||||
|
from test_conjecture import bfs_levels, is_even_level_graph
|
||||||
|
|
||||||
|
|
||||||
|
def sig(G):
|
||||||
|
return frozenset(frozenset(e) for e in G.edges())
|
||||||
|
|
||||||
|
|
||||||
|
def forward_neighbors(G, labels):
|
||||||
|
ok, emb = nx.check_planarity(G)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
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)
|
||||||
|
yield Gp
|
||||||
|
|
||||||
|
|
||||||
|
def backward_neighbors(G, labels):
|
||||||
|
"""States G' with G' ->forward G. G' = G - f + diag_G(f) for each edge
|
||||||
|
f of G whose diagonal is same-parity (so flipping it in G' is valid)."""
|
||||||
|
ok, emb = nx.check_planarity(G)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
for u, v in list(G.edges()):
|
||||||
|
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
|
||||||
|
# diagonal of edge uv is wx; predecessor un-flips: remove uv? No.
|
||||||
|
# Predecessor G' has edge wx (same-parity), flipping wx gives uv...
|
||||||
|
# We want G = G' - (wx) + (uv). So G' = G - uv + wx, and the flipped
|
||||||
|
# edge in G' is wx which must be same-parity.
|
||||||
|
if labels[w] % 2 != labels[x] % 2:
|
||||||
|
continue
|
||||||
|
Gp = G.copy(); Gp.remove_edge(u, v); Gp.add_edge(w, x)
|
||||||
|
yield Gp
|
||||||
|
|
||||||
|
|
||||||
|
def orbit(G_start, labels, neighbor_fn, max_states=300000):
|
||||||
|
seen = {sig(G_start): G_start}
|
||||||
|
frontier = [G_start]
|
||||||
|
while frontier and len(seen) < max_states:
|
||||||
|
new = []
|
||||||
|
for H in frontier:
|
||||||
|
for Hp in neighbor_fn(H, labels):
|
||||||
|
s = sig(Hp)
|
||||||
|
if s not in seen:
|
||||||
|
seen[s] = Hp
|
||||||
|
new.append(Hp)
|
||||||
|
frontier = new
|
||||||
|
return list(seen.values())
|
||||||
|
|
||||||
|
|
||||||
|
def has_elg(orbit_states, labels):
|
||||||
|
"""Find an Even Level Graph in the orbit whose BFS-parity (from some
|
||||||
|
source) MATCHES the orbit labelling (up to global parity swap).
|
||||||
|
Only such a graph is a valid 'derived from this ELG' witness."""
|
||||||
|
for H in orbit_states:
|
||||||
|
for s in H.nodes():
|
||||||
|
ok, lvls = is_even_level_graph(H, frozenset({s}))
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
same = all(lvls[u] % 2 == labels[u] % 2 for u in H.nodes())
|
||||||
|
opp = all(lvls[u] % 2 != labels[u] % 2 for u in H.nodes())
|
||||||
|
if same or opp:
|
||||||
|
return True, H, s
|
||||||
|
return False, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tris = enumerate_all_triangulations(9)
|
||||||
|
Tstar = next(G for G in tris
|
||||||
|
if sorted([G.degree(v) for v in G.nodes()], reverse=True)
|
||||||
|
== [5, 5, 5, 5, 5, 5, 4, 4, 4])
|
||||||
|
|
||||||
|
src = 0
|
||||||
|
labels = bfs_levels(Tstar, frozenset({src}))
|
||||||
|
print(f'T*_9, labelling = BFS parity from source {src}')
|
||||||
|
|
||||||
|
fwd = orbit(Tstar, labels, forward_neighbors)
|
||||||
|
f_elg, _, _ = has_elg(fwd, labels)
|
||||||
|
print(f' FORWARD orbit (T*_9 ->* H): size {len(fwd)}, contains ELG: {f_elg}')
|
||||||
|
|
||||||
|
bwd = orbit(Tstar, labels, backward_neighbors)
|
||||||
|
b_elg, He, se = has_elg(bwd, labels)
|
||||||
|
print(f' BACKWARD orbit (H ->* T*_9): size {len(bwd)}, contains ELG: {b_elg}')
|
||||||
|
if b_elg:
|
||||||
|
print(f' -> ELG predecessor exists (src {se}); T*_9 WOULD be derived')
|
||||||
|
else:
|
||||||
|
print(f' -> no ELG reaches T*_9; T*_9 is NOT derived (directed)')
|
||||||
|
|
||||||
|
print(f'\nConclusion: directedness matters = {f_elg != b_elg}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
"""Exhaustive bridge-derived test for the 4 open Holton-McKay duals.
|
||||||
|
|
||||||
|
Step A (gauge): for each dual, count the valid parity partitions
|
||||||
|
(bipartitions L with both parity subgraphs bipartite) and measure a few
|
||||||
|
bridge-orbit sizes run to FULL exhaustion (no timeout).
|
||||||
|
|
||||||
|
Step B (decide): for each dual, for every valid parity partition L,
|
||||||
|
exhaust the backward bridge-orbit and look for an ELG with BFS-parity L.
|
||||||
|
YES if found for any L; NO (conclusive) if none found for all L.
|
||||||
|
"""
|
||||||
|
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 networkx as nx
|
||||||
|
import time
|
||||||
|
from load_holton_mckay import parse_planar_code
|
||||||
|
from tutte_dual_treecolor import dual_triangulation
|
||||||
|
from test_conjecture import is_even_level_graph
|
||||||
|
from bridge_derived_test import (
|
||||||
|
sig, parity_subgraph, edge_is_bridge_in_parity,
|
||||||
|
backward_bridge_neighbors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def valid_parity_partitions(G):
|
||||||
|
"""Yield labels-dicts for every bipartition (fix node 0 in even class)
|
||||||
|
such that both induced parity subgraphs are bipartite. Labels are 0
|
||||||
|
(even) / 1 (odd)."""
|
||||||
|
nodes = sorted(G.nodes())
|
||||||
|
n = len(nodes)
|
||||||
|
assert nodes[0] == 0
|
||||||
|
for mask in range(2 ** (n - 1)):
|
||||||
|
labels = {0: 0}
|
||||||
|
for i in range(n - 1):
|
||||||
|
labels[nodes[i + 1]] = (mask >> i) & 1
|
||||||
|
even = [v for v in nodes if labels[v] == 0]
|
||||||
|
odd = [v for v in nodes if labels[v] == 1]
|
||||||
|
if not odd:
|
||||||
|
continue
|
||||||
|
if nx.is_bipartite(G.subgraph(even)) and nx.is_bipartite(G.subgraph(odd)):
|
||||||
|
yield labels
|
||||||
|
|
||||||
|
|
||||||
|
def is_elg_with_parity(H, labels):
|
||||||
|
"""Is H an Even Level Graph for some source whose BFS-parity matches
|
||||||
|
`labels` (up to global swap)? Only even-class vertices can be the
|
||||||
|
source (level 0 is even)."""
|
||||||
|
even = [v for v in H.nodes() if labels[v] == 0]
|
||||||
|
odd_set = set(v for v in H.nodes() if labels[v] == 1)
|
||||||
|
for s in even:
|
||||||
|
# quick reject: all neighbours of s must be odd-class (level 1)
|
||||||
|
if any(nb not in odd_set for nb in H.neighbors(s)):
|
||||||
|
# could still match under global swap; handle swap separately
|
||||||
|
pass
|
||||||
|
ok, lvls = is_even_level_graph(H, frozenset({s}))
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
if all(lvls[u] % 2 == labels[u] for u in H.nodes()):
|
||||||
|
return True
|
||||||
|
# global swap: source in odd class, BFS-parity is complement of labels
|
||||||
|
odd = [v for v in H.nodes() if labels[v] == 1]
|
||||||
|
for s in odd:
|
||||||
|
ok, lvls = is_even_level_graph(H, frozenset({s}))
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
if all(lvls[u] % 2 != labels[u] for u in H.nodes()):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def exhaust_bridge_orbit_for_elg(G, labels, cap=3_000_000):
|
||||||
|
"""Exhaust backward bridge-orbit (no ELG check during BFS), then scan
|
||||||
|
for an ELG witness. Returns ('found',H) / ('exhausted',size) /
|
||||||
|
('capped',size)."""
|
||||||
|
seen = {sig(G): G}
|
||||||
|
frontier = [G]
|
||||||
|
while frontier and len(seen) < cap:
|
||||||
|
new = []
|
||||||
|
for H in frontier:
|
||||||
|
for Hp in backward_bridge_neighbors(H, labels):
|
||||||
|
sg = sig(Hp)
|
||||||
|
if sg not in seen:
|
||||||
|
seen[sg] = Hp
|
||||||
|
new.append(Hp)
|
||||||
|
frontier = new
|
||||||
|
status = 'capped' if frontier else 'exhausted'
|
||||||
|
if status == 'exhausted':
|
||||||
|
for H in seen.values():
|
||||||
|
if is_elg_with_parity(H, labels):
|
||||||
|
return 'found', H
|
||||||
|
return status, len(seen)
|
||||||
|
|
||||||
|
|
||||||
|
def decide_dual(i, cap=3_000_000, log=print):
|
||||||
|
graphs = parse_planar_code('experiments/nonham38m4.pc')
|
||||||
|
G, _ = dual_triangulation(graphs[i][0])
|
||||||
|
parts = list(valid_parity_partitions(G))
|
||||||
|
log(f'dual {i}: {len(parts)} valid parity partitions')
|
||||||
|
any_capped = False
|
||||||
|
max_orbit = 0
|
||||||
|
t0 = time.time()
|
||||||
|
for j, labels in enumerate(parts):
|
||||||
|
st, info = exhaust_bridge_orbit_for_elg(G, labels, cap=cap)
|
||||||
|
if st == 'found':
|
||||||
|
log(f' partition {j}: FOUND ELG -> dual {i} IS bridge-derived '
|
||||||
|
f'({time.time()-t0:.0f}s)')
|
||||||
|
return 'bridge-derived'
|
||||||
|
if st == 'capped':
|
||||||
|
any_capped = True
|
||||||
|
log(f' partition {j}: orbit exceeded cap ({info}); inconclusive')
|
||||||
|
else:
|
||||||
|
max_orbit = max(max_orbit, info)
|
||||||
|
if (j + 1) % 25 == 0:
|
||||||
|
log(f' ...{j+1}/{len(parts)} partitions, max orbit {max_orbit}, '
|
||||||
|
f'{time.time()-t0:.0f}s')
|
||||||
|
if any_capped:
|
||||||
|
log(f' dual {i}: no witness, but some orbits hit cap -> INCONCLUSIVE '
|
||||||
|
f'({time.time()-t0:.0f}s)')
|
||||||
|
return 'inconclusive'
|
||||||
|
log(f' dual {i}: NOT bridge-derived (all {len(parts)} orbits exhausted, '
|
||||||
|
f'max orbit {max_orbit}, {time.time()-t0:.0f}s)')
|
||||||
|
return 'not-bridge-derived'
|
||||||
|
|
||||||
|
|
||||||
|
def gauge(dual_indices):
|
||||||
|
graphs = parse_planar_code('experiments/nonham38m4.pc')
|
||||||
|
for i in dual_indices:
|
||||||
|
G, _ = dual_triangulation(graphs[i][0])
|
||||||
|
t0 = time.time()
|
||||||
|
parts = list(valid_parity_partitions(G))
|
||||||
|
print(f'dual {i}: {len(parts)} valid parity partitions '
|
||||||
|
f'(enumerated in {time.time()-t0:.1f}s)')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys as _s
|
||||||
|
if len(_s.argv) > 1 and _s.argv[1] == 'gauge':
|
||||||
|
gauge([0, 3, 4, 5])
|
||||||
|
elif len(_s.argv) > 1:
|
||||||
|
for idx in _s.argv[1:]:
|
||||||
|
decide_dual(int(idx))
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
"""Hunt for a structural invariant of E/O-switch orbits that separates
|
||||||
|
'derived from an ELG' from 'not derived'.
|
||||||
|
|
||||||
|
Strategy: at n=9, T*_9 is NOT a derived level graph (it is intertwining-
|
||||||
|
only). Compare its E/O orbit (under a fixed labelling) against the orbit
|
||||||
|
of a graph that IS derived. Look for an invariant constant on orbits
|
||||||
|
that differs between the two.
|
||||||
|
"""
|
||||||
|
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 networkx as nx
|
||||||
|
from triangulation_gen import enumerate_all_triangulations
|
||||||
|
from test_conjecture import canonical_sig, bfs_levels, is_even_level_graph
|
||||||
|
|
||||||
|
|
||||||
|
def eo_switch_neighbors(G, labels):
|
||||||
|
"""Yield all triangulations reachable from G by one E/O switch
|
||||||
|
(flip a same-parity edge). Directed: this is the FORWARD relation."""
|
||||||
|
ok, emb = nx.check_planarity(G)
|
||||||
|
if not ok:
|
||||||
|
return
|
||||||
|
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)
|
||||||
|
yield Gp
|
||||||
|
|
||||||
|
|
||||||
|
def undirected_orbit(G_start, labels, max_states=200000):
|
||||||
|
"""Connected component of G_start in the UNDIRECTED switch graph
|
||||||
|
(treat each switch as bidirectional by also exploring forward from
|
||||||
|
every reached state -- since a forward switch's reverse is itself a
|
||||||
|
forward switch from the target when the new edge is same-parity, and
|
||||||
|
we explore all forward edges from every node, the BFS closure equals
|
||||||
|
the weakly-connected component)."""
|
||||||
|
sig0 = frozenset(frozenset(e) for e in G_start.edges())
|
||||||
|
seen = {sig0: G_start}
|
||||||
|
frontier = [G_start]
|
||||||
|
while frontier and len(seen) < max_states:
|
||||||
|
new = []
|
||||||
|
for H in frontier:
|
||||||
|
for Hp in eo_switch_neighbors(H, labels):
|
||||||
|
sig = frozenset(frozenset(e) for e in Hp.edges())
|
||||||
|
if sig not in seen:
|
||||||
|
seen[sig] = Hp
|
||||||
|
new.append(Hp)
|
||||||
|
frontier = new
|
||||||
|
return list(seen.values())
|
||||||
|
|
||||||
|
|
||||||
|
def parity_subgraph_edge_counts(G, labels):
|
||||||
|
even = [v for v in G.nodes() if labels[v] % 2 == 0]
|
||||||
|
odd = [v for v in G.nodes() if labels[v] % 2 == 1]
|
||||||
|
e_even = G.subgraph(even).number_of_edges()
|
||||||
|
e_odd = G.subgraph(odd).number_of_edges()
|
||||||
|
cross = G.number_of_edges() - e_even - e_odd
|
||||||
|
return e_even, e_odd, cross
|
||||||
|
|
||||||
|
|
||||||
|
def orbit_report(G, labels, name):
|
||||||
|
orbit = undirected_orbit(G, labels)
|
||||||
|
has_elg = any(
|
||||||
|
any(is_even_level_graph(H, frozenset({s}))[0] for s in H.nodes())
|
||||||
|
for H in orbit
|
||||||
|
)
|
||||||
|
# Invariant candidates over the orbit
|
||||||
|
ec = set()
|
||||||
|
for H in orbit:
|
||||||
|
ec.add(parity_subgraph_edge_counts(H, labels))
|
||||||
|
print(f'{name}: orbit size {len(orbit)}, contains ELG: {has_elg}')
|
||||||
|
print(f' (e_even, e_odd, cross) values in orbit: {sorted(ec)}')
|
||||||
|
return orbit, has_elg
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
tris = enumerate_all_triangulations(9)
|
||||||
|
# T*_9 is the iso class that is NOT derived (intertwining-only).
|
||||||
|
# We earlier identified it as iso index 49 via canonical_sig matching
|
||||||
|
# the degree sequence (5,5,5,5,5,5,4,4,4).
|
||||||
|
Tstar = None
|
||||||
|
for G in tris:
|
||||||
|
if sorted([G.degree(v) for v in G.nodes()], reverse=True) == \
|
||||||
|
[5, 5, 5, 5, 5, 4, 4, 4, 4]:
|
||||||
|
pass
|
||||||
|
for G in tris:
|
||||||
|
ds = sorted([G.degree(v) for v in G.nodes()], reverse=True)
|
||||||
|
if ds == [5, 5, 5, 5, 5, 5, 4, 4, 4]:
|
||||||
|
Tstar = G
|
||||||
|
break
|
||||||
|
print(f'T*_9 degree seq: '
|
||||||
|
f'{sorted([Tstar.degree(v) for v in Tstar.nodes()], reverse=True)}')
|
||||||
|
|
||||||
|
# Pick a labelling of T*_9: a valid parity partition (bipartite parity
|
||||||
|
# subgraphs). Use one we know: V_E={0,1,3,6}? But labels here come from
|
||||||
|
# the enumerate ordering. Instead, search labellings = BFS parities.
|
||||||
|
print('\n--- T*_9 (NOT derived) orbits under BFS-source labellings ---')
|
||||||
|
for s in list(Tstar.nodes()):
|
||||||
|
labels = bfs_levels(Tstar, frozenset({s}))
|
||||||
|
orbit, has_elg = orbit_report(Tstar, labels, f'T*_9 src={s}')
|
||||||
|
if has_elg:
|
||||||
|
print(' ^ unexpectedly found ELG')
|
||||||
|
|
||||||
|
# A derived graph: pick one that is an ELG itself.
|
||||||
|
print('\n--- A derived graph (an ELG) for comparison ---')
|
||||||
|
for G in tris:
|
||||||
|
elg_src = next((s for s in G.nodes()
|
||||||
|
if is_even_level_graph(G, frozenset({s}))[0]), None)
|
||||||
|
if elg_src is not None:
|
||||||
|
labels = bfs_levels(G, frozenset({elg_src}))
|
||||||
|
orbit_report(G, labels, f'derived(ELG) src={elg_src}')
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -34,18 +34,20 @@
|
|||||||
\newlabel{sec:even-level-graphs}{{4}{3}{Even Level Graphs}{section.4}{}}
|
\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{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{thm:even-level-4colorable}{{4.2}{3}{}{theorem.4.2}{}}
|
||||||
\citation{holton-mckay}
|
|
||||||
\newlabel{def:derived-level-graph}{{4.3}{4}{Derived level graph}{theorem.4.3}{}}
|
\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{def:bridge-switch}{{4.4}{4}{Bridge switch}{theorem.4.4}{}}
|
||||||
\newlabel{thm:intertwining-iff-hamiltonian-dual}{{4.5}{4}{}{theorem.4.5}{}}
|
\newlabel{def:bridge-derived-level-graph}{{4.5}{4}{Bridge-derived level graph}{theorem.4.5}{}}
|
||||||
\newlabel{conj:every-triangulation-derived}{{4.6}{4}{}{theorem.4.6}{}}
|
\newlabel{def:intertwining-tree}{{4.6}{4}{Intertwining tree}{theorem.4.6}{}}
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Empirical status}}{4}{section*.1}\protected@file@percent }
|
\newlabel{thm:intertwining-iff-hamiltonian-dual}{{4.7}{4}{}{theorem.4.7}{}}
|
||||||
|
\citation{holton-mckay}
|
||||||
\bibcite{holton-mckay}{1}
|
\bibcite{holton-mckay}{1}
|
||||||
|
\newlabel{conj:every-triangulation-derived}{{4.8}{5}{}{theorem.4.8}{}}
|
||||||
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Empirical status}}{5}{section*.1}\protected@file@percent }
|
||||||
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The boundary case $n = 21$}}{5}{section*.2}\protected@file@percent }
|
||||||
\newlabel{tocindent-1}{0pt}
|
\newlabel{tocindent-1}{0pt}
|
||||||
\newlabel{tocindent0}{14.69437pt}
|
\newlabel{tocindent0}{14.69437pt}
|
||||||
\newlabel{tocindent1}{17.77782pt}
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
\newlabel{tocindent2}{0pt}
|
\newlabel{tocindent2}{0pt}
|
||||||
\newlabel{tocindent3}{0pt}
|
\newlabel{tocindent3}{0pt}
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The boundary case $n = 21$}}{5}{section*.2}\protected@file@percent }
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{6}{section*.3}\protected@file@percent }
|
||||||
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{5}{section*.3}\protected@file@percent }
|
\gdef \@abspage@last{6}
|
||||||
\gdef \@abspage@last{5}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 21 MAY 2026 20:44
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 21 MAY 2026 23:45
|
||||||
entering extended mode
|
entering extended mode
|
||||||
restricted \write18 enabled.
|
restricted \write18 enabled.
|
||||||
%&-line parsing enabled.
|
%&-line parsing enabled.
|
||||||
@@ -387,24 +387,24 @@ LaTeX Warning: `h' float specifier changed to `ht'.
|
|||||||
ng>] [4]
|
ng>] [4]
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 324.
|
(hyperref) removing `math shift' on input line 355.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 324.
|
(hyperref) removing `math shift' on input line 355.
|
||||||
|
|
||||||
[5] (./paper.aux)
|
[5] [6] (./paper.aux)
|
||||||
Package rerunfilecheck Info: File `paper.out' has not changed.
|
Package rerunfilecheck Info: File `paper.out' has not changed.
|
||||||
(rerunfilecheck) Checksum: AECCB746CF11915BCB68F1E7FF8075A7;1047.
|
(rerunfilecheck) Checksum: AECCB746CF11915BCB68F1E7FF8075A7;1047.
|
||||||
)
|
)
|
||||||
Here is how much of TeX's memory you used:
|
Here is how much of TeX's memory you used:
|
||||||
9737 strings out of 478268
|
9742 strings out of 478268
|
||||||
150746 string characters out of 5846347
|
150825 string characters out of 5846347
|
||||||
453840 words of memory out of 5000000
|
454933 words of memory out of 5000000
|
||||||
27645 multiletter control sequences out of 15000+600000
|
27647 multiletter control sequences out of 15000+600000
|
||||||
475666 words of font info for 53 fonts, out of 8000000 for 9000
|
475666 words of font info for 53 fonts, out of 8000000 for 9000
|
||||||
1302 hyphenation exceptions out of 8191
|
1302 hyphenation exceptions out of 8191
|
||||||
69i,8n,76p,781b,427s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
69i,8n,76p,781b,504s 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/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/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/cmex10.pfb>
|
||||||
@@ -418,10 +418,10 @@ 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
|
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
|
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>
|
ve/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||||
Output written on paper.pdf (5 pages, 545810 bytes).
|
Output written on paper.pdf (6 pages, 548304 bytes).
|
||||||
PDF statistics:
|
PDF statistics:
|
||||||
162 PDF objects out of 1000 (max. 8388607)
|
171 PDF objects out of 1000 (max. 8388607)
|
||||||
119 compressed objects within 2 object streams
|
127 compressed objects within 2 object streams
|
||||||
30 named destinations out of 1000 (max. 500000)
|
33 named destinations out of 1000 (max. 500000)
|
||||||
77 words of extra memory for PDF output out of 10000 (max. 10000000)
|
77 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -250,6 +250,37 @@ A derived level graph $G'$ is \emph{valid} if both $E_{G,S}(G')$ and
|
|||||||
$O_{G,S}(G')$ contain only even cycles.
|
$O_{G,S}(G')$ contain only even cycles.
|
||||||
\end{definition}
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{definition}[Bridge switch]
|
||||||
|
\label{def:bridge-switch}
|
||||||
|
Let $G'$ be a triangulation reached from an Even Level Graph $G$, with
|
||||||
|
parity classes inherited from $G$ as in
|
||||||
|
Definition~\ref{def:derived-level-graph}. An edge switch on an edge
|
||||||
|
$e \in E \cup O$ of $G'$, replacing $uvw, uvx$ by the edge $wx$, is a
|
||||||
|
\emph{bridge switch} if either
|
||||||
|
\begin{itemize}
|
||||||
|
\item the new edge $wx$ is a cross-parity edge (one endpoint even, the
|
||||||
|
other odd), so $wx$ enters neither parity subgraph; or
|
||||||
|
\item $wx$ is a same-parity edge and is a \emph{bridge} in the parity
|
||||||
|
subgraph it joins -- that is, $w$ and $x$ lie in different connected
|
||||||
|
components of that parity subgraph, so adding $wx$ creates no new cycle.
|
||||||
|
\end{itemize}
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{definition}[Bridge-derived level graph]
|
||||||
|
\label{def:bridge-derived-level-graph}
|
||||||
|
A \emph{bridge-derived level graph} of an Even Level Graph $G$ is a
|
||||||
|
triangulation obtained from $G$ by a sequence of bridge switches
|
||||||
|
(Definition~\ref{def:bridge-switch}).
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
Because a bridge switch never closes a cycle in a parity subgraph, it
|
||||||
|
never introduces an odd cycle there. As an Even Level Graph has
|
||||||
|
bipartite parity subgraphs (every level cycle is even), every
|
||||||
|
bridge-derived level graph has bipartite parity subgraphs as well, and
|
||||||
|
so is automatically a valid derived level graph. Equivalently, the
|
||||||
|
first Betti number of each parity subgraph is non-increasing along any
|
||||||
|
sequence of bridge switches.
|
||||||
|
|
||||||
\begin{definition}[Intertwining tree]
|
\begin{definition}[Intertwining tree]
|
||||||
\label{def:intertwining-tree}
|
\label{def:intertwining-tree}
|
||||||
A maximal planar graph $G$ is an \emph{intertwining tree} if its
|
A maximal planar graph $G$ is an \emph{intertwining tree} if its
|
||||||
|
|||||||
Reference in New Issue
Block a user