Add get_embedding_from_pos to derive combinatorial embedding from vertex positions

Replaces is_planar(set_embedding=True) calls in pluck and squish so the embedding
stays consistent with inherited positions across reduction steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 12:42:10 -04:00
parent 92db9a5513
commit 52ba816a90
+11 -2
View File
@@ -155,6 +155,15 @@ def tutte_embedding(g: Graph, outer: list[Any]) -> dict[Any, tuple[float, float]
return pos
def get_embedding_from_pos(g: Graph) -> dict[Any, list[Any]]:
"""Compute a combinatorial planar embedding by sorting each vertex's neighbors by angle."""
import math
pos = g.get_pos()
return {
v: sorted(g.neighbors(v), key=lambda w: math.atan2(pos[w][1] - pos[v][1], pos[w][0] - pos[v][0]))
for v in g.vertices()
}
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())
@@ -174,7 +183,7 @@ def pluck(g: Graph, coloring: VertexColoring, v0: Any) -> tuple[Graph, VertexCol
g_prime.delete_vertex(v0)
if (pos := g.get_pos()) is not None:
g_prime.set_pos({v: p for v, p in pos.items() if v != v0})
g_prime.is_planar(set_embedding=True)
g_prime.set_embedding(get_embedding_from_pos(g_prime))
coloring_prime = coloring.copy()
del coloring_prime[v0]
return g_prime, coloring_prime
@@ -211,7 +220,7 @@ def squish(g: Graph, coloring: VertexColoring, v0: Any) -> tuple[Graph, VertexCo
g_prime.merge_vertices([v0, v1, v2])
if (pos := g.get_pos()) is not None:
g_prime.set_pos({v: p for v, p in pos.items() if v not in (v1, v2)})
g_prime.is_planar(set_embedding=True)
g_prime.set_embedding(get_embedding_from_pos(g_prime))
coloring_prime = {v: c for v, c in coloring.items() if v not in (v1, v2)}
coloring_prime[v0] = coloring[v1]
return g_prime, coloring_prime, v1, v2