"""Census of flip-symmetric maximal planar graphs. A maximal planar graph G is *flip-symmetric* if some admissible edge uv satisfies G^flip(uv) ~= G, where flip(uv) replaces uv with wx (the third vertices of the two triangular faces incident to uv), and admissible means wx is not already an edge of G. """ from typing import Iterator from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module def triangular_face_pairs(g: Graph) -> dict[frozenset, tuple]: """For each edge of g (a triangulation), return the two third-vertices of its incident triangular faces, keyed by frozenset({u, v}).""" g_emb = g.copy() if not g_emb.is_planar(set_embedding=True): raise ValueError("graph is not planar") pairs: dict[frozenset, list] = {} for face in g_emb.faces(): if len(face) != 3: raise ValueError("graph is not a triangulation") a, b, c = face[0][0], face[1][0], face[2][0] for u, v, third in [(a, b, c), (b, c, a), (c, a, b)]: pairs.setdefault(frozenset((u, v)), []).append(third) return {k: tuple(v) for k, v in pairs.items()} def is_flip_symmetric(g: Graph) -> bool: """Return True iff g admits some admissible flip uv -> wx with G^flip ~= G.""" pairs = triangular_face_pairs(g) for u, v in g.edges(labels=False): thirds = pairs.get(frozenset((u, v))) if thirds is None or len(thirds) != 2: continue w, x = thirds if g.has_edge(w, x): continue flipped = g.copy() flipped.delete_edge(u, v) flipped.add_edge(w, x) if g.is_isomorphic(flipped): return True return False def census(min_order: int, max_order: int, minimum_degree: int | None = None) -> None: """Enumerate every maximal planar graph of order in [min_order, max_order] (optionally restricted by minimum_degree) and print counts and density of flip-symmetric graphs at each order.""" for n in range(min_order, max_order + 1): kwargs = {"minimum_connectivity": 3, "maximum_face_size": 3} if minimum_degree is not None: kwargs["minimum_degree"] = minimum_degree total = 0 flip_sym = 0 gen: Iterator[Graph] = graphs.planar_graphs(n, **kwargs) for g in gen: total += 1 if is_flip_symmetric(g): flip_sym += 1 if total % 1000 == 0: print(f" order {n}: {total} checked, {flip_sym} flip-symmetric") density = (flip_sym / total) if total else 0.0 tag = f" (min_degree {minimum_degree})" if minimum_degree is not None else "" print(f"n={n}{tag}: total={total} flip_symmetric={flip_sym} density={density:.6f}") if __name__ == "__main__": import sys max_order = int(sys.argv[1]) if len(sys.argv) > 1 else 12 min_order = int(sys.argv[2]) if len(sys.argv) > 2 else 4 min_deg = int(sys.argv[3]) if len(sys.argv) > 3 else None census(min_order, max_order, minimum_degree=min_deg)