chore: SAVEPOINT
This commit is contained in:
@@ -1,93 +1,121 @@
|
|||||||
from sage.all import graphs, Graph
|
"""Example: colored pentagon reduction on a random 20-vertex triangulation."""
|
||||||
|
import base64
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
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
|
DIR = Path(__file__).parent
|
||||||
|
|
||||||
PALETTE = ['red', 'blue', 'green', 'yellow']
|
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)
|
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():
|
for v, c in coloring.items():
|
||||||
vertex_colors[PALETTE[c]].append(v)
|
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 = DIR / "data" / label
|
||||||
out_dir.mkdir(exist_ok=True)
|
out_dir.mkdir(exist_ok=True)
|
||||||
g.plot(vertex_colors=dict(vertex_colors), title=title).save(out_dir / filename)
|
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'.
|
def _neighbors_form_cycle(g: Graph, v: Any) -> bool:
|
||||||
# merge_vertices([v0, v1, v2]) folds v1 and v2 into v0.
|
"""Return True if the neighbors of v induce a cycle in g."""
|
||||||
G_prime = G.copy()
|
return bool(cast(Graph, g.subgraph(g.neighbors(v))).is_cycle())
|
||||||
G_prime.delete_vertex(v0)
|
|
||||||
|
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()
|
coloring_prime = coloring.copy()
|
||||||
del coloring_prime[v0]
|
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):
|
def squish(
|
||||||
# Among v0's neighbors, find two with the same color (v1 and v2).
|
g: Graph,
|
||||||
neighbor_by_color = defaultdict(list)
|
coloring: VertexColoring,
|
||||||
for v in G.neighbors(v0):
|
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)
|
neighbor_by_color[coloring[v]].append(v)
|
||||||
|
|
||||||
v1, v2 = next(
|
v1, v2 = next(
|
||||||
(vs[0], vs[1]) for vs in neighbor_by_color.values() if len(vs) >= 2
|
(vs[0], vs[1]) for vs in neighbor_by_color.values() if len(vs) >= 2
|
||||||
)
|
)
|
||||||
print(f"Shared-color neighbors: v1 = {v1}, v2 = {v2} (color {coloring[v1]})")
|
print(f"Shared-color neighbors: v1={v1}, v2={v2} (color {coloring[v1]})")
|
||||||
|
|
||||||
# Contract v1 and v2 into v0 to obtain the minor G'.
|
g_prime = g.copy()
|
||||||
# merge_vertices([v0, v1, v2]) folds v1 and v2 into v0.
|
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 = {v: c for v, c in coloring.items() if v not in (v1, v2)}
|
||||||
coloring_prime[v0] = coloring[v1]
|
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):
|
def reduce(g: Graph, coloring: VertexColoring, step: int = 1) -> None:
|
||||||
# 2. Find a proper 4-coloring.
|
"""Repeatedly apply pluck/squish reductions until no candidates remain."""
|
||||||
# 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.
|
|
||||||
print(f"Coloring: {coloring}")
|
print(f"Coloring: {coloring}")
|
||||||
|
|
||||||
degree_4_candidates = []
|
degree_4_candidates: list[Any] = []
|
||||||
degree_5_candidates = []
|
degree_5_candidates: list[Any] = []
|
||||||
|
|
||||||
# Pick the first degree 5 vertex where the neighbors form a wheel
|
for v in g.vertices():
|
||||||
for v in G.vertices():
|
if g.degree(v) == 3 and _neighbors_form_cycle(g, v):
|
||||||
if G.degree(v) == 3 and G.subgraph(G.neighbors(v)).is_cycle():
|
g_prime, coloring_prime = pluck(g, coloring, v, 'triangle', step)
|
||||||
return pluck(G, coloring, v, 'triangle', step)
|
return reduce(g_prime, coloring_prime, step + 1)
|
||||||
elif G.degree(v) == 4 and G.subgraph(G.neighbors(v)).is_cycle():
|
if g.degree(v) == 4 and _neighbors_form_cycle(g, v):
|
||||||
degree_4_candidates.append(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)
|
degree_5_candidates.append(v)
|
||||||
|
|
||||||
for v in degree_4_candidates:
|
if degree_4_candidates:
|
||||||
return squish(G, coloring, v, 'square', step)
|
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:
|
if degree_5_candidates:
|
||||||
return squish(G, coloring, v, 'triangle', step)
|
g_prime, coloring_prime = squish(g, coloring, degree_5_candidates[0], 'triangle', step)
|
||||||
|
return reduce(g_prime, coloring_prime, step + 1)
|
||||||
|
|
||||||
print("DONE")
|
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))
|
G = next(graphs.planar_graphs(20, minimum_degree=5))
|
||||||
print(f"G: {G.order()} vertices, {G.size()} edges")
|
print(f"G: {G.order()} vertices, {G.size()} edges")
|
||||||
print(f"Degree sequence: {sorted(G.degree_sequence(), reverse=True)}")
|
print(f"Degree sequence: {sorted(G.degree_sequence(), reverse=True)}")
|
||||||
coloring_classes = G.coloring()
|
starting_coloring_classes = G.coloring()
|
||||||
coloring = {v: i for i, cls in enumerate(coloring_classes) for v in cls}
|
starting_coloring = {v: i for i, cls in enumerate(starting_coloring_classes) for v in cls}
|
||||||
plot_colored(G, coloring, "Start", f"step_{0:04d}.png")
|
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",
|
"reportUnknownMemberType": "none",
|
||||||
"reportUnknownArgumentType": "warning",
|
"reportUnknownArgumentType": "none",
|
||||||
"reportUnknownParameterType": "warning",
|
"reportUnknownParameterType": "warning",
|
||||||
"reportMissingParameterType": "warning",
|
"reportMissingParameterType": "warning",
|
||||||
"reportUnknownVariableType": "warning"
|
"reportUnknownVariableType": "none"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,9 @@ run_sage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lint() {
|
lint() {
|
||||||
"$VENV_PYTHON" -m pyright lib/ --pythonpath "$SAGE_PYTHON_PATH"
|
local path="${1:-lib/}"
|
||||||
"$VENV_PYTHON" -m pylint 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}')" \
|
--init-hook="import sys; sys.path.insert(0, '${SAGE_SITE_PACKAGES}'); sys.path.insert(0, '${SCRIPT_DIR}')" \
|
||||||
--disable=fixme
|
--disable=fixme
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,9 @@ _run_sh() {
|
|||||||
sage)
|
sage)
|
||||||
_files
|
_files
|
||||||
;;
|
;;
|
||||||
|
lint)
|
||||||
|
_files
|
||||||
|
;;
|
||||||
completion)
|
completion)
|
||||||
_values 'shell' 'zsh'
|
_values 'shell' 'zsh'
|
||||||
;;
|
;;
|
||||||
@@ -143,7 +147,8 @@ sage)
|
|||||||
run_sage "$@"
|
run_sage "$@"
|
||||||
;;
|
;;
|
||||||
lint)
|
lint)
|
||||||
lint
|
shift
|
||||||
|
lint "$@"
|
||||||
;;
|
;;
|
||||||
completion)
|
completion)
|
||||||
shift
|
shift
|
||||||
|
|||||||
Reference in New Issue
Block a user