Add small-n ELG counting experiment (iso, rooted)
count_elgs.py enumerates triangulation iso-classes and counts Even Level Graphs (G,v) per n: iso-classes (sources up to Aut) and flag-rooted (4E/|Aut| * s, an exact integer since Aut acts freely on flags). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
Count Even Level Graphs (ELGs) for small n.
|
||||
|
||||
An ELG is a pair (G, S) where G is a plane triangulation on n vertices,
|
||||
S = {v} a single source vertex, and every level subgraph L_k (vertices at
|
||||
BFS-distance k from v) is bipartite -- equivalently, every level cycle is
|
||||
even (Definition: even-level-graph, sibling paper).
|
||||
|
||||
We report per n:
|
||||
- tri : # iso-classes of plane triangulations (maximal planar graphs)
|
||||
- elg_iso : # iso-classes of ELG *pairs* (G,v), i.e. valid sources counted
|
||||
up to Aut(G)
|
||||
- elg_root : # flag-rooted ELGs = sum over iso-classes of 4E/|Aut(G)| * s(G),
|
||||
E = 3n-6, s(G) = # valid sources. Aut(G) acts freely on the 4E
|
||||
flags, so each term is an exact integer -- the small-integer,
|
||||
automorphism-free count (the natural one for closed forms).
|
||||
"""
|
||||
import time
|
||||
import networkx as nx
|
||||
from networkx.algorithms.isomorphism import GraphMatcher
|
||||
|
||||
from triangulation_gen import enumerate_all_triangulations
|
||||
|
||||
|
||||
def is_even_level_graph(G, source):
|
||||
"""Every level subgraph L_k (BFS distance k from source) is bipartite,
|
||||
equivalently every level cycle is even. Mirrors test_conjecture.py in
|
||||
the even_level_graph_generators paper."""
|
||||
levels = {v: float("inf") for v in G.nodes()}
|
||||
from collections import deque
|
||||
dq = deque()
|
||||
for s in source:
|
||||
levels[s] = 0
|
||||
dq.append(s)
|
||||
while dq:
|
||||
v = dq.popleft()
|
||||
for w in G[v]:
|
||||
if levels[w] > levels[v] + 1:
|
||||
levels[w] = levels[v] + 1
|
||||
dq.append(w)
|
||||
if any(l == float("inf") for l in levels.values()):
|
||||
return False, None # disconnected: source can't reach all vertices
|
||||
for k in range(max(levels.values()) + 1):
|
||||
L_k = G.subgraph([v for v in G.nodes() if levels[v] == k])
|
||||
if not nx.is_bipartite(L_k):
|
||||
return False, None
|
||||
return True, levels
|
||||
|
||||
|
||||
def automorphisms(G):
|
||||
"""All automorphisms of G as node->node dict mappings."""
|
||||
return list(GraphMatcher(G, G).isomorphisms_iter())
|
||||
|
||||
|
||||
def valid_sources(G):
|
||||
"""Vertices v such that (G, {v}) is an ELG."""
|
||||
return [v for v in G.nodes()
|
||||
if is_even_level_graph(G, frozenset({v}))[0]]
|
||||
|
||||
|
||||
def source_orbits(G, sources, autos):
|
||||
"""Number of Aut(G)-orbits among the given source vertices."""
|
||||
src = set(sources)
|
||||
seen, orbits = set(), 0
|
||||
for v in sources:
|
||||
if v in seen:
|
||||
continue
|
||||
orbits += 1
|
||||
for a in autos:
|
||||
seen.add(a[v])
|
||||
return orbits
|
||||
|
||||
|
||||
def count_for_n(n):
|
||||
tris = enumerate_all_triangulations(n)
|
||||
flags = 4 * (3 * n - 6) # # flags of any n-vertex triangulation, E = 3n-6
|
||||
elg_iso = 0
|
||||
elg_root = 0
|
||||
n_elg_tris = 0 # triangulations admitting at least one ELG source
|
||||
for G in tris:
|
||||
srcs = valid_sources(G)
|
||||
if not srcs:
|
||||
continue
|
||||
n_elg_tris += 1
|
||||
autos = automorphisms(G)
|
||||
aut_size = len(autos)
|
||||
elg_iso += source_orbits(G, srcs, autos)
|
||||
# Aut acts freely on flags, so flags//aut_size is exact per class.
|
||||
assert flags % aut_size == 0, (n, aut_size, flags)
|
||||
elg_root += (flags // aut_size) * len(srcs)
|
||||
return {
|
||||
"tri": len(tris),
|
||||
"tri_with_elg": n_elg_tris,
|
||||
"elg_iso": elg_iso,
|
||||
"elg_root": elg_root,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6, 7, 8]
|
||||
print(f"{'n':>3} {'tri':>6} {'tri+ELG':>8} {'elg_iso':>8} "
|
||||
f"{'elg_root':>9} {'time':>6}")
|
||||
for n in ns:
|
||||
t0 = time.time()
|
||||
r = count_for_n(n)
|
||||
print(f"{n:>3} {r['tri']:>6} {r['tri_with_elg']:>8} "
|
||||
f"{r['elg_iso']:>8} {r['elg_root']:>9} "
|
||||
f"{time.time()-t0:>5.1f}s")
|
||||
Reference in New Issue
Block a user