Move plot_to_data_uri to lib/colored_graphs and rename to plot_colored_graph_to_data_uri
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,13 @@
|
|||||||
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
||||||
import json
|
|
||||||
import base64
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast, TypedDict, Literal
|
from typing import Any, cast, TypedDict, Literal
|
||||||
from sage.all import graphs, Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import graphs, Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
from lib.tutte_embedding import tutte_embedding
|
from lib.planar_embedding import get_embedding_from_pos
|
||||||
from lib.planar_embedding import outer_face, get_embedding_from_pos
|
|
||||||
from lib.colored_graphs import ColoredGraphId, VertexColoring, save_colored_graph
|
from lib.colored_graphs import ColoredGraphId, VertexColoring, save_colored_graph
|
||||||
from lib.operations import Operation, operation_to_string, operation_sequence_id
|
from lib.operations import Operation, save_operation_sequence
|
||||||
|
|
||||||
DIR = Path(__file__).parent
|
DIR = Path(__file__).parent
|
||||||
PALETTE = ['red', 'blue', 'green', 'yellow']
|
|
||||||
|
|
||||||
def _neighbors_form_cycle(g: Graph, v: Any) -> bool:
|
def _neighbors_form_cycle(g: Graph, v: Any) -> bool:
|
||||||
"""Return True if the neighbors of v induce a cycle in g."""
|
"""Return True if the neighbors of v induce a cycle in g."""
|
||||||
@@ -134,38 +130,5 @@ starting_coloring = {v: i for i, cls in enumerate(starting_coloring_classes) for
|
|||||||
_, _, initial_canon_id = save_colored_graph(G, starting_coloring)
|
_, _, initial_canon_id = save_colored_graph(G, starting_coloring)
|
||||||
G.is_planar(set_embedding=True, set_pos=True)
|
G.is_planar(set_embedding=True, set_pos=True)
|
||||||
|
|
||||||
def strip_graphs(obj: Any) -> Any:
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
return {k: strip_graphs(v) for k, v in obj.items() if not isinstance(v, Graph)}
|
|
||||||
if isinstance(obj, list):
|
|
||||||
return [strip_graphs(v) for v in obj]
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def plot_to_data_uri(g: Graph, coloring: VertexColoring) -> str:
|
|
||||||
import tempfile
|
|
||||||
vertex_colors: defaultdict[str, list[Any]] = defaultdict(list)
|
|
||||||
for v, c in coloring.items():
|
|
||||||
vertex_colors[PALETTE[c]].append(v)
|
|
||||||
if g.get_pos() is None:
|
|
||||||
g.is_planar(set_embedding=True, set_pos=True)
|
|
||||||
g.set_pos(tutte_embedding(g, outer_face(g)))
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.png', delete=True) as f:
|
|
||||||
g.plot(vertex_colors=dict(vertex_colors)).save(f.name)
|
|
||||||
png_bytes = Path(f.name).read_bytes()
|
|
||||||
return f"data:image/png;base64,{base64.b64encode(png_bytes).decode()}"
|
|
||||||
|
|
||||||
def save_operation_sequence(op_sequence: list[ReduceOperation], g: Graph, coloring: VertexColoring) -> str:
|
|
||||||
"""Save op_sequence as JSON and Markdown under data/operations/<seq_id>. Returns the sequence id."""
|
|
||||||
op_seq_id = operation_sequence_id(op_sequence)
|
|
||||||
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(strip_graphs(op_sequence), indent=2))
|
|
||||||
md_lines = [f"## start\n\n})"]
|
|
||||||
for op in op_sequence:
|
|
||||||
meta_json = json.dumps(op['meta'])
|
|
||||||
md_lines.append(f"## {op['name']} {meta_json}\n\n})")
|
|
||||||
(op_dir / "colored_pentagon_contractions.md").write_text("\n".join(md_lines) + "\n")
|
|
||||||
return op_seq_id
|
|
||||||
|
|
||||||
op_sequence = reduce(G, starting_coloring, initial_canon_id)
|
op_sequence = reduce(G, starting_coloring, initial_canon_id)
|
||||||
save_operation_sequence(op_sequence, G, starting_coloring)
|
save_operation_sequence(op_sequence, G, starting_coloring, DIR)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"""Utilities for canonizing and saving colored graphs."""
|
"""Utilities for canonizing and saving colored graphs."""
|
||||||
import base64
|
import base64
|
||||||
|
import tempfile
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast, TypedDict
|
from typing import Any, cast, TypedDict
|
||||||
|
|
||||||
from sage.all import Graph, save, load # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph, save, load # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
|
from lib.tutte_embedding import tutte_embedding
|
||||||
|
from lib.planar_embedding import outer_face
|
||||||
|
|
||||||
DIR = Path(__file__).parent.parent
|
DIR = Path(__file__).parent.parent
|
||||||
PALETTE = ['red', 'blue', 'green', 'yellow']
|
PALETTE = ['red', 'blue', 'green', 'yellow']
|
||||||
@@ -37,6 +40,19 @@ def canonize_colored_graph(g: Graph, coloring: VertexColoring) -> tuple[Graph, V
|
|||||||
return canonical, canonical_coloring, ColoredGraphId(graph_id=graph_id, coloring_id=coloring_id)
|
return canonical, canonical_coloring, ColoredGraphId(graph_id=graph_id, coloring_id=coloring_id)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_colored_graph_to_data_uri(g: Graph, coloring: VertexColoring) -> str:
|
||||||
|
vertex_colors: defaultdict[str, list[Any]] = defaultdict(list)
|
||||||
|
for v, c in coloring.items():
|
||||||
|
vertex_colors[PALETTE[c]].append(v)
|
||||||
|
if g.get_pos() is None:
|
||||||
|
g.is_planar(set_embedding=True, set_pos=True)
|
||||||
|
g.set_pos(tutte_embedding(g, outer_face(g)))
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=True) as f:
|
||||||
|
g.plot(vertex_colors=dict(vertex_colors)).save(f.name)
|
||||||
|
png_bytes = Path(f.name).read_bytes()
|
||||||
|
return f"data:image/png;base64,{base64.b64encode(png_bytes).decode()}"
|
||||||
|
|
||||||
|
|
||||||
def save_colored_graph(g: Graph, coloring: VertexColoring) -> tuple[Graph, VertexColoring, ColoredGraphId]:
|
def save_colored_graph(g: Graph, coloring: VertexColoring) -> tuple[Graph, VertexColoring, ColoredGraphId]:
|
||||||
"""
|
"""
|
||||||
Canonize g and coloring, save to disk, and return the canonical forms with id.
|
Canonize g and coloring, save to disk, and return the canonical forms with id.
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
"""Utilities for converting integer colorings to named colors."""
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
COLORS = ['blue', 'red', 'green', 'yellow', 'purple']
|
|
||||||
|
|
||||||
def convert_coloring(
|
|
||||||
coloring: dict[Any, int],
|
|
||||||
vertices: list[Any] | None = None
|
|
||||||
) -> dict[str, list[Any]]:
|
|
||||||
"""Convert an integer coloring dict to a dict mapping color names to vertex lists."""
|
|
||||||
colors: dict[str, list[Any]] = {}
|
|
||||||
for k, v in coloring.items():
|
|
||||||
if vertices and k not in vertices:
|
|
||||||
continue
|
|
||||||
color = COLORS[v]
|
|
||||||
if color not in colors:
|
|
||||||
colors[color] = []
|
|
||||||
colors[color].append(k)
|
|
||||||
return colors
|
|
||||||
+23
-1
@@ -1,7 +1,9 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, TypedDict
|
from typing import Any, TypedDict
|
||||||
from sage.all import Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
from sage.all import Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
from lib.colored_graphs import ColoredGraphId, VertexColoring
|
from lib.colored_graphs import ColoredGraphId, VertexColoring, plot_colored_graph_to_data_uri
|
||||||
|
|
||||||
class Operation(TypedDict):
|
class Operation(TypedDict):
|
||||||
"""Information about a change made to a (colored) graph"""
|
"""Information about a change made to a (colored) graph"""
|
||||||
@@ -22,3 +24,23 @@ def operation_to_string(op: Operation) -> str:
|
|||||||
def operation_sequence_id(ops: list[Operation]) -> str:
|
def operation_sequence_id(ops: list[Operation]) -> str:
|
||||||
joined = "\n".join(operation_to_string(op) for op in ops)
|
joined = "\n".join(operation_to_string(op) for op in ops)
|
||||||
return hashlib.sha256(joined.encode()).hexdigest()
|
return hashlib.sha256(joined.encode()).hexdigest()
|
||||||
|
|
||||||
|
def strip_graphs(obj: Any) -> Any:
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return {k: strip_graphs(v) for k, v in obj.items() if not isinstance(v, Graph)}
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [strip_graphs(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def save_operation_sequence(op_sequence: list[Operation], g: Graph, coloring: VertexColoring, save_dir: Path) -> str:
|
||||||
|
"""Save op_sequence as JSON and Markdown under save_dir/data/operations/<seq_id>. Returns the sequence id."""
|
||||||
|
op_seq_id = operation_sequence_id(op_sequence)
|
||||||
|
op_dir = save_dir / "data" / "operations" / op_seq_id
|
||||||
|
op_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(op_dir / "colored_pentagon_contractions.json").write_text(json.dumps(strip_graphs(op_sequence), indent=2))
|
||||||
|
md_lines = [f"## start\n\n})"]
|
||||||
|
for op in op_sequence:
|
||||||
|
meta_json = json.dumps(op['meta'])
|
||||||
|
md_lines.append(f"## {op['name']} {meta_json}\n\n})")
|
||||||
|
(op_dir / "colored_pentagon_contractions.md").write_text("\n".join(md_lines) + "\n")
|
||||||
|
return op_seq_id
|
||||||
|
|||||||
Reference in New Issue
Block a user