03f92494f1
- 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>
46 lines
1.2 KiB
Python
46 lines
1.2 KiB
Python
"""Module for performing tutte embeddings"""
|
|
import math
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
from sage.all import Graph # type: ignore
|
|
|
|
|
|
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."""
|
|
|
|
outer_set = set(outer)
|
|
inner = [v for v in g.vertices() if v not in outer_set]
|
|
|
|
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))
|
|
|
|
if not inner:
|
|
return pos
|
|
|
|
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)
|
|
|
|
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
|