chore: SAVEPOINT
This commit is contained in:
@@ -1,39 +1,69 @@
|
||||
from sage.all import graphs, Graph
|
||||
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
||||
import base64
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
import base64
|
||||
from typing import Any, cast
|
||||
|
||||
from sage.all import graphs, Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||
|
||||
DIR = Path(__file__).parent
|
||||
|
||||
PALETTE = ['red', 'blue', 'green', 'yellow']
|
||||
|
||||
def plot_colored(g, coloring, title, filename):
|
||||
VertexColoring = dict[Any, Any]
|
||||
|
||||
|
||||
def plot_colored(g: Graph, coloring: VertexColoring, title: str, filename: str) -> None:
|
||||
"""
|
||||
Save a plot of g with vertices colored in a file according to it's
|
||||
graph canonization and coloring
|
||||
"""
|
||||
g.is_planar(set_embedding=True, set_pos=True)
|
||||
vertex_colors = defaultdict(list)
|
||||
vertex_colors: defaultdict[str, list[Any]] = defaultdict(list)
|
||||
for v, c in coloring.items():
|
||||
vertex_colors[PALETTE[c]].append(v)
|
||||
label = base64.urlsafe_b64encode(g.canonical_label().graph6_string().encode()).decode()
|
||||
canonical = cast(Graph, g.canonical_label())
|
||||
label = base64.urlsafe_b64encode(
|
||||
canonical.graph6_string().encode()
|
||||
).decode()
|
||||
out_dir = DIR / "data" / label
|
||||
out_dir.mkdir(exist_ok=True)
|
||||
g.plot(vertex_colors=dict(vertex_colors), title=title).save(out_dir / filename)
|
||||
|
||||
def pluck(G: Graph, coloring, v0, kind, step=1):
|
||||
# Contract v1 and v2 into v0 to obtain the minor G'.
|
||||
# merge_vertices([v0, v1, v2]) folds v1 and v2 into v0.
|
||||
G_prime = G.copy()
|
||||
G_prime.delete_vertex(v0)
|
||||
|
||||
def _neighbors_form_cycle(g: Graph, v: Any) -> bool:
|
||||
"""Return True if the neighbors of v induce a cycle in g."""
|
||||
return bool(cast(Graph, g.subgraph(g.neighbors(v))).is_cycle())
|
||||
|
||||
def pluck(
|
||||
g: Graph,
|
||||
coloring: VertexColoring,
|
||||
v0: Any,
|
||||
kind: str,
|
||||
step: int = 1
|
||||
) -> tuple[Graph, VertexColoring]:
|
||||
"""Delete v0 from g and recurse."""
|
||||
g_prime = g.copy()
|
||||
g_prime.delete_vertex(v0)
|
||||
coloring_prime = coloring.copy()
|
||||
del coloring_prime[v0]
|
||||
print(f"\nG' (after reduction): {G_prime.order()} vertices, {G_prime.size()} edges")
|
||||
print(f"\nG' (after pluck): {g_prime.order()} vertices, {g_prime.size()} edges")
|
||||
plot_colored(
|
||||
g_prime, coloring_prime,
|
||||
f"G' (after pluck for v0={v0})",
|
||||
f"step_{step:04d}_({kind}).png",
|
||||
)
|
||||
return g_prime, coloring_prime
|
||||
|
||||
# Plot before and after side by side.
|
||||
plot_colored(G_prime, coloring_prime, f"G' (after pluck for v0={v0})", f"step_{step:04d}_({kind}).png")
|
||||
return reduce(G_prime, coloring_prime, step+1)
|
||||
|
||||
def squish(G: Graph, coloring, v0, kind, step=1):
|
||||
# Among v0's neighbors, find two with the same color (v1 and v2).
|
||||
neighbor_by_color = defaultdict(list)
|
||||
for v in G.neighbors(v0):
|
||||
def squish(
|
||||
g: Graph,
|
||||
coloring: VertexColoring,
|
||||
v0: Any, kind: str,
|
||||
step: int = 1
|
||||
) -> tuple[Graph, VertexColoring]:
|
||||
"""Contract two same-colored neighbors of v0 into v0 and recurse."""
|
||||
neighbor_by_color: defaultdict[Any, list[Any]] = defaultdict(list)
|
||||
for v in g.neighbors(v0):
|
||||
neighbor_by_color[coloring[v]].append(v)
|
||||
|
||||
v1, v2 = next(
|
||||
@@ -41,53 +71,51 @@ def squish(G: Graph, coloring, v0, kind, step=1):
|
||||
)
|
||||
print(f"Shared-color neighbors: v1={v1}, v2={v2} (color {coloring[v1]})")
|
||||
|
||||
# Contract v1 and v2 into v0 to obtain the minor G'.
|
||||
# merge_vertices([v0, v1, v2]) folds v1 and v2 into v0.
|
||||
G_prime = G.copy()
|
||||
G_prime.merge_vertices([v0, v1, v2])
|
||||
g_prime = g.copy()
|
||||
g_prime.merge_vertices([v0, v1, v2])
|
||||
coloring_prime = {v: c for v, c in coloring.items() if v not in (v1, v2)}
|
||||
coloring_prime[v0] = coloring[v1]
|
||||
print(f"\nG' (after reduction): {G_prime.order()} vertices, {G_prime.size()} edges")
|
||||
print(f"\nG' (after squish): {g_prime.order()} vertices, {g_prime.size()} edges")
|
||||
plot_colored(
|
||||
g_prime, coloring_prime,
|
||||
f"G' (after squish for v0={v0}, v1={v1}, v2={v2})",
|
||||
f"step_{step:04d}_({kind}).png",
|
||||
)
|
||||
return g_prime, coloring_prime
|
||||
|
||||
# Plot before and after side by side.
|
||||
plot_colored(G_prime, coloring_prime, f"G' (after squish for v0={v0}, v1={v1}, v2={v2})", f"step_{step:04d}_({kind}).png")
|
||||
return reduce(G_prime, coloring_prime, step+1)
|
||||
|
||||
def reduce(G, coloring, step=1):
|
||||
# 2. Find a proper 4-coloring.
|
||||
# G.coloring() returns a partition of vertices into color classes.
|
||||
# By pigeonhole (5 neighbors, at most 3 available colors for each
|
||||
# degree-5 vertex), the reduction step below always succeeds.
|
||||
def reduce(g: Graph, coloring: VertexColoring, step: int = 1) -> None:
|
||||
"""Repeatedly apply pluck/squish reductions until no candidates remain."""
|
||||
print(f"Coloring: {coloring}")
|
||||
|
||||
degree_4_candidates = []
|
||||
degree_5_candidates = []
|
||||
degree_4_candidates: list[Any] = []
|
||||
degree_5_candidates: list[Any] = []
|
||||
|
||||
# Pick the first degree 5 vertex where the neighbors form a wheel
|
||||
for v in G.vertices():
|
||||
if G.degree(v) == 3 and G.subgraph(G.neighbors(v)).is_cycle():
|
||||
return pluck(G, coloring, v, 'triangle', step)
|
||||
elif G.degree(v) == 4 and G.subgraph(G.neighbors(v)).is_cycle():
|
||||
for v in g.vertices():
|
||||
if g.degree(v) == 3 and _neighbors_form_cycle(g, v):
|
||||
g_prime, coloring_prime = pluck(g, coloring, v, 'triangle', step)
|
||||
return reduce(g_prime, coloring_prime, step + 1)
|
||||
if g.degree(v) == 4 and _neighbors_form_cycle(g, v):
|
||||
degree_4_candidates.append(v)
|
||||
elif G.degree(v) == 5 and G.subgraph(G.neighbors(v)).is_cycle():
|
||||
elif g.degree(v) == 5 and _neighbors_form_cycle(g, v):
|
||||
degree_5_candidates.append(v)
|
||||
|
||||
for v in degree_4_candidates:
|
||||
return squish(G, coloring, v, 'square', step)
|
||||
if degree_4_candidates:
|
||||
g_prime, coloring_prime = squish(g, coloring, degree_4_candidates[0], 'square', step)
|
||||
return reduce(g_prime, coloring_prime, step + 1)
|
||||
|
||||
for v in degree_5_candidates:
|
||||
return squish(G, coloring, v, 'triangle', step)
|
||||
if degree_5_candidates:
|
||||
g_prime, coloring_prime = squish(g, coloring, degree_5_candidates[0], 'triangle', step)
|
||||
return reduce(g_prime, coloring_prime, step + 1)
|
||||
|
||||
print("DONE")
|
||||
return
|
||||
|
||||
# 1. Generate a maximal planar graph (triangulation) on 14 vertices
|
||||
# with minimum degree 5, via plantri.
|
||||
|
||||
G = next(graphs.planar_graphs(20, minimum_degree=5))
|
||||
print(f"G: {G.order()} vertices, {G.size()} edges")
|
||||
print(f"Degree sequence: {sorted(G.degree_sequence(), reverse=True)}")
|
||||
coloring_classes = G.coloring()
|
||||
coloring = {v: i for i, cls in enumerate(coloring_classes) for v in cls}
|
||||
plot_colored(G, coloring, "Start", f"step_{0:04d}.png")
|
||||
starting_coloring_classes = G.coloring()
|
||||
starting_coloring = {v: i for i, cls in enumerate(starting_coloring_classes) for v in cls}
|
||||
plot_colored(G, starting_coloring, "Start", f"step_{0:04d}.png")
|
||||
|
||||
reduce(G, coloring)
|
||||
reduce(G, starting_coloring)
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"reportUnknownMemberType": "warning",
|
||||
"reportUnknownArgumentType": "warning",
|
||||
"reportUnknownMemberType": "none",
|
||||
"reportUnknownArgumentType": "none",
|
||||
"reportUnknownParameterType": "warning",
|
||||
"reportMissingParameterType": "warning",
|
||||
"reportUnknownVariableType": "warning"
|
||||
"reportUnknownVariableType": "none"
|
||||
}
|
||||
|
||||
@@ -66,8 +66,9 @@ run_sage() {
|
||||
}
|
||||
|
||||
lint() {
|
||||
"$VENV_PYTHON" -m pyright lib/ --pythonpath "$SAGE_PYTHON_PATH"
|
||||
"$VENV_PYTHON" -m pylint lib/ \
|
||||
local path="${1:-lib/}"
|
||||
"$VENV_PYTHON" -m pyright "$path" --pythonpath "$SAGE_PYTHON_PATH"
|
||||
"$VENV_PYTHON" -m pylint "$path" \
|
||||
--init-hook="import sys; sys.path.insert(0, '${SAGE_SITE_PACKAGES}'); sys.path.insert(0, '${SCRIPT_DIR}')" \
|
||||
--disable=fixme
|
||||
}
|
||||
@@ -111,6 +112,9 @@ _run_sh() {
|
||||
sage)
|
||||
_files
|
||||
;;
|
||||
lint)
|
||||
_files
|
||||
;;
|
||||
completion)
|
||||
_values 'shell' 'zsh'
|
||||
;;
|
||||
@@ -143,7 +147,8 @@ sage)
|
||||
run_sage "$@"
|
||||
;;
|
||||
lint)
|
||||
lint
|
||||
shift
|
||||
lint "$@"
|
||||
;;
|
||||
completion)
|
||||
shift
|
||||
|
||||
Reference in New Issue
Block a user