Files
math-research/lib/tutte_embedding.py
T
didericis 03f92494f1 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>
2026-04-21 21:02:22 -04:00

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