Move tutte_embedding to lib, add bash completion, and fix NixOS setup
- Replace iterative tutte_embedding in lib with numpy direct-solve version from example.py - Import tutte_embedding into example.py from lib instead of defining it locally - Fix g._embedding -> g.get_embedding() in outer_face - Add bash completion to run.sh alongside existing zsh completion - Use nix-shell -p gcc for plantri build step on NixOS Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+35
-47
@@ -1,57 +1,45 @@
|
||||
"""Module for performing tutte embeddings"""
|
||||
from typing import Iterable, cast, Any
|
||||
from sage.all import vector, Graph, cos, pi, sin # type: ignore
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from sage.all import Graph # type: ignore
|
||||
|
||||
|
||||
def create_tutte_embedding(
|
||||
graph: Graph,
|
||||
outer_face: list[Any],
|
||||
max_iter: int=50,
|
||||
set_pos: bool = True) -> dict[Any, Any]:
|
||||
"""
|
||||
Performs a tutte force embedding with a prescribed outer face on
|
||||
a given sage graph.
|
||||
def tutte_embedding(g: Graph, outer: list[Any]) -> dict[Any, tuple[float, float]]:
|
||||
"""Compute a Tutte embedding fixing outer on a convex polygon, solving for inner vertices."""
|
||||
|
||||
For a description on tutte embeddings, see the video [here](\
|
||||
https://www.youtube.com/watch?v=mEzPPMhR8XE).
|
||||
"""
|
||||
radius = 1000
|
||||
pos: dict[Any, Any] = {}
|
||||
num_outer_points = len(outer_face)
|
||||
for i, v in enumerate(outer_face):
|
||||
pos[v] = (
|
||||
radius * cos((i / num_outer_points) * 2 * pi),
|
||||
radius * sin((i / num_outer_points) * 2 * pi)
|
||||
)
|
||||
outer_set = set(outer)
|
||||
inner = [v for v in g.vertices() if v not in outer_set]
|
||||
|
||||
# start off setting all other points to (0, 0)
|
||||
num_inner_points = cast(int, graph.order()) - num_outer_points
|
||||
pos: dict[Any, tuple[float, float]] = {}
|
||||
for i, v in enumerate(outer):
|
||||
angle = 2 * math.pi * i / len(outer)
|
||||
pos[v] = (math.cos(angle), math.sin(angle))
|
||||
|
||||
for i, v in enumerate(cast(Iterable[Any], graph.vertices())): # type: ignore
|
||||
if v in pos:
|
||||
continue
|
||||
pos[v] = (
|
||||
radius/3 * cos((i / num_inner_points) * 2 * pi),
|
||||
radius/3 * sin((i / num_inner_points) * 2 * pi)
|
||||
)
|
||||
if not inner:
|
||||
return pos
|
||||
|
||||
i = 0
|
||||
while i < max_iter:
|
||||
for v in cast(Iterable[Any], graph.vertices()): # type: ignore
|
||||
if v in outer_face:
|
||||
continue
|
||||
neighbors = cast(list[Any], graph.neighbors(v)) # type: ignore
|
||||
deg = len(neighbors)
|
||||
x_desired = sum(map(lambda n: pos[n][0], neighbors)) / deg
|
||||
y_desired = sum(map(lambda n: pos[n][1], neighbors)) / deg
|
||||
pos[v] = tuple(
|
||||
vector(pos[v]) - vector([ # type: ignore
|
||||
pos[v][0] - x_desired, pos[v][1] - y_desired
|
||||
])
|
||||
)
|
||||
i += 1
|
||||
inner_idx = {v: i for i, v in enumerate(inner)}
|
||||
n = len(inner)
|
||||
A = np.zeros((n, n))
|
||||
bx = np.zeros(n)
|
||||
by = np.zeros(n)
|
||||
|
||||
if set_pos:
|
||||
graph.set_pos(pos) # type: ignore
|
||||
for i, v in enumerate(inner):
|
||||
neighbors = g.neighbors(v)
|
||||
deg = len(neighbors)
|
||||
A[i, i] = 1.0
|
||||
for w in neighbors:
|
||||
if w in inner_idx:
|
||||
A[i, inner_idx[w]] = -1.0 / deg
|
||||
else:
|
||||
bx[i] += pos[w][0] / deg
|
||||
by[i] += pos[w][1] / deg
|
||||
|
||||
x = np.linalg.solve(A, bx)
|
||||
y = np.linalg.solve(A, by)
|
||||
for i, v in enumerate(inner):
|
||||
pos[v] = (float(x[i]), float(y[i]))
|
||||
|
||||
return pos
|
||||
|
||||
Reference in New Issue
Block a user