idealpolyhedra / examples /test_rigid_construction.py
igriv's picture
Add HuggingFace Spaces support
e0ef700
#!/usr/bin/env python3
"""Test rigid geometric construction from Rivin LP angles."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import numpy as np
from scipy.spatial import Delaunay as scipy_Delaunay
from ideal_poly_volume_toolkit.plantri_interface import find_plantri_executable
from ideal_poly_volume_toolkit.planar_utils import extract_faces_from_planar_embedding
from ideal_poly_volume_toolkit.rivin_delaunay import (
check_delaunay_realizability,
compute_triangle_angle
)
from ideal_poly_volume_toolkit.geometric_realization import realize_from_angles_rigid
import subprocess
def get_octahedron():
"""Get the octahedron triangulation."""
plantri = find_plantri_executable()
args = [plantri, '-pc3', '-a', '6']
result = subprocess.run(args, capture_output=True, text=True)
triangulations = []
for line in result.stdout.split('\n'):
line = line.strip()
if not line or line.startswith('>'):
continue
parts = line.split(maxsplit=1)
if len(parts) != 2:
continue
n = int(parts[0])
adj_str = parts[1]
adj = {}
for v_idx, neighbor_str in enumerate(adj_str.split(',')):
neighbors = [ord(c) - ord('a') for c in neighbor_str]
adj[v_idx] = neighbors
closed_tri = extract_faces_from_planar_embedding(n, adj)
planar_tri = [tri for tri in closed_tri if 0 not in tri]
if planar_tri:
triangulations.append(planar_tri)
return triangulations[6] # The octahedron
if __name__ == '__main__':
triangles = get_octahedron()
print("="*70)
print("RIGID GEOMETRIC CONSTRUCTION TEST - OCTAHEDRON")
print("="*70)
print(f"\nTriangles: {triangles}")
# Check strict realizability
result = check_delaunay_realizability(triangles, verbose=False, strict=True)
if not result['realizable']:
print("Not realizable!")
sys.exit(1)
print(f"✓ Realizable (strict mode)")
# Extract ALL angles from LP (in scaled units where π = 1)
angles_scaled = result['angles']
n_triangles = len(triangles)
# Convert from scaled units to radians
angles_radians = angles_scaled * np.pi
print(f"\nLP solution gives ALL angles for ALL triangles:")
for i, tri in enumerate(triangles):
ang = angles_radians.reshape((n_triangles, 3))[i]
print(f" Triangle {tri}: {np.degrees(ang)} degrees (sum={np.degrees(ang.sum()):.1f}°)")
# Rigid construction
print(f"\n{'='*70}")
print("RIGID CONSTRUCTION")
print(f"{'='*70}\n")
construction = realize_from_angles_rigid(
triangles,
angles_radians.reshape((n_triangles, 3)),
verbose=True
)
if not construction['success']:
print(f"\n✗ Construction failed: {construction['message']}")
sys.exit(1)
print(f"\n✓ Construction successful!")
points = construction['points']
vertex_list = construction['vertex_list']
vertex_to_idx = {v: i for i, v in enumerate(vertex_list)}
print(f"\nPoint positions:")
for i, v in enumerate(vertex_list):
print(f" v{v}: ({points[i, 0]:10.6f}, {points[i, 1]:10.6f})")
# Verify triangulation
print(f"\n{'='*70}")
print("VERIFICATION")
print(f"{'='*70}")
# Check Delaunay triangulation
tri = scipy_Delaunay(points)
realized_triangles = set()
for simplex in tri.simplices:
v0, v1, v2 = [vertex_list[i] for i in simplex]
realized_triangles.add(tuple(sorted([v0, v1, v2])))
expected_triangles = set(tuple(sorted(t)) for t in triangles)
print(f"\nExpected triangles: {len(expected_triangles)}")
print(f"Realized triangles: {len(realized_triangles)}")
if expected_triangles == realized_triangles:
print(f"✓ Triangulation matches perfectly!")
else:
print(f"✗ Triangulation mismatch")
missing = expected_triangles - realized_triangles
extra = realized_triangles - expected_triangles
if missing:
print(f" Missing: {missing}")
if extra:
print(f" Extra: {extra}")
# Check angles
print(f"\nAngle verification:")
max_error = 0.0
total_error_sq = 0.0
for i, tri in enumerate(triangles):
v0, v1, v2 = tri
p0 = points[vertex_to_idx[v0]]
p1 = points[vertex_to_idx[v1]]
p2 = points[vertex_to_idx[v2]]
angle0 = compute_triangle_angle(p0, p1, p2)
angle1 = compute_triangle_angle(p1, p2, p0)
angle2 = compute_triangle_angle(p2, p0, p1)
actual = np.array([angle0, angle1, angle2])
target = angles_radians.reshape((n_triangles, 3))[i]
error = np.abs(target - actual)
max_error = max(max_error, np.max(error))
total_error_sq += np.sum(error**2)
print(f"\n Triangle {tri}:")
print(f" Target: {np.degrees(target)}")
print(f" Actual: {np.degrees(actual)}")
print(f" Error: {np.degrees(error)} deg")
rms_error = np.sqrt(total_error_sq / (n_triangles * 3))
print(f"\n{'='*70}")
print(f"SUMMARY")
print(f"{'='*70}")
print(f"Max angle error: {np.degrees(max_error):.6f}°")
print(f"RMS angle error: {np.degrees(rms_error):.6f}°")
print(f"Triangulation: {'✓ MATCH' if expected_triangles == realized_triangles else '✗ MISMATCH'}")