"""Empirical test of the derived-level-graph conjecture for n=6..8. For each iso class of maximal planar graphs G': Search for an Even Level Graph G (some iso class, some level source) such that G' is in the iso-class orbit of G under E/O-edge switches. """ import sys import os sys.path.insert(0, '/Users/didericis/Code/math-research/papers/' 'level_resolutions_of_maximal_planar_graphs/experiments') import time import itertools import networkx as nx from triangulation_gen import enumerate_all_triangulations def canonical_sig(G): """Iso-class signature: WL hash via Weisfeiler-Lehman or just sorted edge tuples up to vertex relabelling. We use nx.weisfeiler_lehman_graph_hash.""" return nx.weisfeiler_lehman_graph_hash(G) def labelled_sig(G, labels): """Signature that respects both graph structure and a vertex labelling (even/odd). Two graphs match iff there's an iso preserving labels.""" H = G.copy() for v in H.nodes(): H.nodes[v]['label'] = labels[v] return nx.weisfeiler_lehman_graph_hash(H, node_attr='label') def bfs_levels(G, source_set): """BFS from a set of source vertices in G. Returns dict v -> level.""" levels = {v: float('inf') for v in G.nodes()} frontier = list(source_set) for v in frontier: levels[v] = 0 d = 0 while frontier: next_frontier = [] for u in frontier: for w in G.neighbors(u): if levels[w] > d + 1: levels[w] = d + 1 next_frontier.append(w) frontier = next_frontier d += 1 return levels def get_level_sources(G): """Yield each vertex as a possible level source (singleton set).""" for v in G.nodes(): yield frozenset({v}) def is_even_level_graph(G, source): """Check: every level cycle of G (BFS from source) has even length.""" levels = bfs_levels(G, source) if any(l == float('inf') for l in levels.values()): return False, None max_l = max(levels.values()) for k in range(max_l + 1): L_k_nodes = [v for v in G.nodes() if levels[v] == k] L_k = G.subgraph(L_k_nodes) if L_k.number_of_edges() == 0: continue # Check bipartiteness (equivalent to no odd cycles) if not nx.is_bipartite(L_k): return False, None return True, levels def is_valid_parity_partition(G, labels): """Both induced subgraphs G[V_E] and G[V_O] are bipartite.""" V_E = [v for v in G.nodes() if labels[v] % 2 == 0] V_O = [v for v in G.nodes() if labels[v] % 2 == 1] return nx.is_bipartite(G.subgraph(V_E)) and nx.is_bipartite(G.subgraph(V_O)) def E_O_switches(G, labels): """Yield all triangulations reachable from G by one E/O-edge switch. An E/O-edge has both endpoints of the same parity in `labels`.""" ip, emb = nx.check_planarity(G) if not ip: return yielded = set() 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) sig = frozenset(frozenset(e) for e in Gp.edges()) if sig in yielded: continue yielded.add(sig) yield Gp def bfs_orbit(G_start, labels, max_states=200000): """BFS in E/O-switch graph starting from G_start (with given labels). Returns the set of iso classes (unlabelled) reachable.""" seen_labelled = {frozenset(frozenset(e) for e in G_start.edges())} iso_classes_reached = {canonical_sig(G_start)} frontier = [G_start] rounds = 0 while frontier and len(seen_labelled) < max_states: new = [] for G in frontier: for Gp in E_O_switches(G, labels): sig = frozenset(frozenset(e) for e in Gp.edges()) if sig in seen_labelled: continue seen_labelled.add(sig) iso_classes_reached.add(canonical_sig(Gp)) new.append(Gp) frontier = new rounds += 1 return iso_classes_reached, len(seen_labelled), rounds def test_for_n(n): print(f'\n=== n = {n} ===') t0 = time.time() tris = enumerate_all_triangulations(n) print(f' {len(tris)} iso classes of triangulations') iso_class_sigs = {canonical_sig(T) for T in tris} # Set of iso classes that ARE derived level graphs (of some ELG) derived_iso_classes = set() for i, G in enumerate(tris): if len(derived_iso_classes) == len(iso_class_sigs): break # early exit: everything is already covered for source in get_level_sources(G): is_elg, levels = is_even_level_graph(G, source) if not is_elg: continue reached, n_lbld, rnds = bfs_orbit(G, levels) new_count = len(reached - derived_iso_classes) derived_iso_classes.update(reached) if new_count > 0: print(f' iso[{i}] source={sorted(source)}: ELG, ' f'orbit adds {new_count} new iso classes ' f'(orbit size {len(reached)}, ' f'{n_lbld} labelled, {rnds} rounds, ' f'total {len(derived_iso_classes)}/' f'{len(iso_class_sigs)})') missing = iso_class_sigs - derived_iso_classes print(f' TOTAL: {len(derived_iso_classes)} / {len(iso_class_sigs)} ' f'iso classes are derived level graphs') print(f' missing: {len(missing)}') print(f' elapsed: {time.time() - t0:.1f}s') return len(missing) == 0 if __name__ == '__main__': import sys ns = [int(x) for x in sys.argv[1:]] if len(sys.argv) > 1 else [6, 7, 8] for n in ns: ok = test_for_n(n) print(f' conjecture holds for n={n}: {ok}')