diff --git a/colored_pentagon_reduction/example.py b/colored_pentagon_reduction/example.py index f896268..7d8e783 100644 --- a/colored_pentagon_reduction/example.py +++ b/colored_pentagon_reduction/example.py @@ -1,5 +1,7 @@ """Example: colored pentagon reduction on a random 20-vertex triangulation.""" import base64 +import hashlib +import json from collections import defaultdict from pathlib import Path from typing import Any, cast, TypedDict, Literal @@ -29,6 +31,16 @@ class CanonicalColoredGraph(TypedDict): graph: Graph coloring: VertexColoring +def colored_graph_id_to_string(cid: ColoredGraphId) -> str: + return f"{cid['graph_id']} {cid['coloring_id']}" + +def op_to_transform_id(op: Operation) -> str: + return f"{colored_graph_id_to_string(op['before'])} -> {colored_graph_id_to_string(op['after'])}" + +def operation_sequence_id(ops: list[Operation]) -> str: + joined = "\n".join(op_to_transform_id(op) for op in ops) + return hashlib.sha256(joined.encode()).hexdigest() + def canonize_colored_graph(g: Graph, coloring: VertexColoring) -> ColoredGraphId: """Mutate g and coloring to canonical labels and return a canonical ColoredGraphId""" canonical, cert = cast( @@ -56,7 +68,7 @@ def save_colored_graph(g: Graph, coloring: VertexColoring) -> tuple[Graph, Verte If already saved, load and return the cached graph. """ cid = canonize_colored_graph(g, coloring) - out_dir = DIR / "data" / cid['graph_id'] / cid['coloring_id'] + out_dir = DIR / "data" / "graphs" / cid['graph_id'] / cid['coloring_id'] if (out_dir / "graph.sobj").exists(): g_canon = cast(Graph, load(str(out_dir / 'graph'))) return g_canon, coloring, cid @@ -97,7 +109,7 @@ def pluck(g: Graph, coloring: VertexColoring, v0: Any) -> tuple[Graph, VertexCol class SquishMeta(TypedDict): """Meta information about the squish operation""" v0: Any - v_merged: set[Any] + v_merged: list[Any] class SquishOperation(Operation): """ @@ -128,21 +140,12 @@ def squish(g: Graph, coloring: VertexColoring, v0: Any) -> tuple[Graph, VertexCo return g_prime, coloring_prime, v1, v2 -Step = tuple[str, str] # (name, title) - -def reduction_operation_to_string(op: SquishOperation | PluckOperation): - """String representation of the given operation""" - if op['name'] == 'squish': - meta = op['meta'] - vm = list(sorted(op['meta']['v_merged'])) - return f"squish_(v0={meta['v0']}, v1={vm[0]}, v2={vm[1]}" - if op['name'] == 'pluck': - meta = op['meta'] - return f"pluck_(v0={meta['v0']}" +Step = SquishOperation | PluckOperation def reduce( g: Graph, coloring: VertexColoring, + before_cid: ColoredGraphId, step: int = 1, steps: list[Step] | None = None, ) -> list[Step]: @@ -159,10 +162,9 @@ def reduce( if g.degree(v) == 3 and _neighbors_form_cycle(g, v): g_prime, coloring_prime = pluck(g, coloring, v) print(f"\nG' (after pluck v0={v}): {g_prime.order()} vertices, {g_prime.size()} edges") - name, title = f"step_{step:04d}_(triangle)", f"G' (after pluck for v0={v})" - steps.append((name, title)) - save_colored_graph(g_prime, coloring_prime) - return reduce(g_prime, coloring_prime, step + 1, steps) + _, _, after_cid = save_colored_graph(g_prime, coloring_prime) + steps.append(PluckOperation(name='pluck', meta=PluckMeta(v0=v), before=before_cid, after=after_cid)) + return reduce(g_prime, coloring_prime, after_cid, step + 1, steps) if g.degree(v) == 4 and _neighbors_form_cycle(g, v): degree_4_candidates.append(v) elif g.degree(v) == 5 and _neighbors_form_cycle(g, v): @@ -173,20 +175,18 @@ def reduce( g_prime, coloring_prime, v1, v2 = squish(g, coloring, v0) print(f"Shared-color neighbors: v1={v1}, v2={v2} (color {coloring[v1]})") print(f"\nG' (after squish v0={v0}): {g_prime.order()} vertices, {g_prime.size()} edges") - name, title = f"step_{step:04d}_(square)", f"G' (after squish for v0={v0})" - steps.append((name, title)) - save_colored_graph(g_prime, coloring_prime) - return reduce(g_prime, coloring_prime, step + 1, steps) + _, _, after_cid = save_colored_graph(g_prime, coloring_prime) + steps.append(SquishOperation(name='squish', meta=SquishMeta(v0=v0, v_merged=[v1, v2]), before=before_cid, after=after_cid)) + return reduce(g_prime, coloring_prime, after_cid, step + 1, steps) if degree_5_candidates: v0 = degree_5_candidates[0] g_prime, coloring_prime, v1, v2 = squish(g, coloring, v0) print(f"Shared-color neighbors: v1={v1}, v2={v2} (color {coloring[v1]})") print(f"\nG' (after squish v0={v0}): {g_prime.order()} vertices, {g_prime.size()} edges") - name, title = f"step_{step:04d}_(pentagon)", f"G' (after squish for v0={v0})" - steps.append((name, title)) - save_colored_graph(g_prime, coloring_prime) - return reduce(g_prime, coloring_prime, step + 1, steps) + _, _, after_cid = save_colored_graph(g_prime, coloring_prime) + steps.append(SquishOperation(name='squish', meta=SquishMeta(v0=v0, v_merged=[v1, v2]), before=before_cid, after=after_cid)) + return reduce(g_prime, coloring_prime, after_cid, step + 1, steps) print("DONE") return steps @@ -197,6 +197,25 @@ print(f"G: {G.order()} vertices, {G.size()} edges") print(f"Degree sequence: {sorted(G.degree_sequence(), reverse=True)}") starting_coloring_classes = G.coloring() starting_coloring = {v: i for i, cls in enumerate(starting_coloring_classes) for v in cls} -save_colored_graph(G, starting_coloring) +_, _, initial_cid = save_colored_graph(G, starting_coloring) -reduce(G, starting_coloring) +steps = reduce(G, starting_coloring, initial_cid) +print("\nSteps:") +print(json.dumps(steps, indent=2)) + +op_seq_id = operation_sequence_id(steps) +op_dir = DIR / "data" / "operations" / op_seq_id +op_dir.mkdir(parents=True, exist_ok=True) +(op_dir / "colored_pentagon_contractions.json").write_text(json.dumps(steps, indent=2)) + +def img_data_uri(cid: ColoredGraphId) -> str: + png_bytes = (DIR / "data" / "graphs" / cid['graph_id'] / cid['coloring_id'] / "graph.png").read_bytes() + return f"data:image/png;base64,{base64.b64encode(png_bytes).decode()}" + +md_lines = [f"## start\n\n![start]({img_data_uri(steps[0]['before'])})"] +for step in steps: + b = step['before'] + a = step['after'] + meta_json = json.dumps(step['meta']) + md_lines.append(f"## {step['name']} {meta_json}\n\n![b]({img_data_uri(a)})") +(op_dir / "colored_pentagon_contractions.md").write_text("\n".join(md_lines) + "\n")