idealpolyhedra / examples /test_volume_optimization.py
igriv's picture
Add HuggingFace Spaces support
e0ef700
#!/usr/bin/env python3
"""
Test hyperbolic volume optimization using Rivin's algorithm.
This script demonstrates:
1. Check that a triangulation is Delaunay realizable
2. Optimize the angle assignment to maximize hyperbolic volume
3. The optimization uses exact gradient and Hessian (diagonal!)
4. Rivin's theorem guarantees concavity, so there's a unique maximum
"""
import numpy as np
from scipy.spatial import Delaunay
import sys
sys.path.insert(0, '/home/igor/devel/ideal_poly_volume_toolkit')
from ideal_poly_volume_toolkit.rivin_delaunay import (
check_delaunay_realizability,
optimize_hyperbolic_volume,
format_realizability_report,
)
def test_volume_optimization(n_points=20, seed=42):
"""
Test volume optimization on a random Delaunay triangulation.
Args:
n_points: Number of random points
seed: Random seed
"""
np.random.seed(seed)
print("="*70)
print("HYPERBOLIC VOLUME OPTIMIZATION VIA RIVIN'S ALGORITHM")
print("="*70)
print()
print(f"Step 1: Generate {n_points} random points and compute Delaunay triangulation")
points = np.random.rand(n_points, 2)
tri = Delaunay(points)
triangles = [tuple(simplex) for simplex in tri.simplices]
print(f" ✓ Triangulation: {len(triangles)} triangles, {n_points} vertices")
print()
print("Step 2: Check Delaunay realizability and get initial angle assignment")
result_realizable = check_delaunay_realizability(triangles, verbose=True)
print()
if not result_realizable['realizable']:
print("✗ Triangulation is not realizable - cannot optimize volume")
return
print(format_realizability_report(result_realizable))
print()
print("Step 3: Optimize hyperbolic volume")
print(" Using quasi-Newton method with:")
print(" - Exact gradient: ∇Λ(θ) = -log(2sin(θ))")
print(" - Exact Hessian: ∇²Λ(θ) = -cot(θ) (diagonal!)")
print(" - Constraints: Rivin polytope (linear constraints)")
print()
result_opt = optimize_hyperbolic_volume(triangles, verbose=True)
if not result_opt['success']:
print(f"\n✗ Optimization failed: {result_opt['message']}")
return
print()
print("="*70)
print("OPTIMIZATION RESULTS")
print("="*70)
print(f"Success: ✓")
print(f"Iterations: {result_opt['n_iterations']}")
print(f"Optimal hyperbolic volume: {result_opt['volume']:.6f}")
print()
# Compare initial vs optimal angles
initial_angles = result_realizable['angles_radians']
optimal_angles = result_opt['angles']
# Compute initial volume
from ideal_poly_volume_toolkit.rivin_delaunay import lobachevsky_function
initial_volume = np.sum([lobachevsky_function(theta) for theta in initial_angles.flatten()])
print(f"Initial volume (from LP): {initial_volume:.6f}")
print(f"Optimal volume (optimized): {result_opt['volume']:.6f}")
print(f"Improvement: {result_opt['volume'] - initial_volume:.6f}")
print(f"Relative improvement: {100*(result_opt['volume'] - initial_volume)/abs(initial_volume):.2f}%")
print()
# Angle statistics
print("Angle statistics (radians):")
print(f" Initial - min: {initial_angles.min():.4f}, max: {initial_angles.max():.4f}, mean: {initial_angles.mean():.4f}")
print(f" Optimal - min: {optimal_angles.min():.4f}, max: {optimal_angles.max():.4f}, mean: {optimal_angles.mean():.4f}")
print()
print("Angle statistics (degrees):")
print(f" Initial - min: {np.degrees(initial_angles.min()):.2f}°, max: {np.degrees(initial_angles.max()):.2f}°")
print(f" Optimal - min: {np.degrees(optimal_angles.min()):.2f}°, max: {np.degrees(optimal_angles.max()):.2f}°")
print("="*70)
return result_opt
def test_hexagon_optimization():
"""
Test on a simple example: regular hexagon with center.
"""
print("\n" + "="*70)
print("SPECIAL CASE: Regular Hexagon Triangulated from Center")
print("="*70)
print()
# Regular hexagon with center
angles = np.linspace(0, 2*np.pi, 7)[:-1]
points = np.column_stack([np.cos(angles), np.sin(angles)])
center = np.array([[0.0, 0.0]])
all_points = np.vstack([center, points])
# Triangulate from center
triangles = [(0, i+1, ((i+1) % 6) + 1) for i in range(6)]
print("Configuration: 6 triangles, all sharing the center vertex")
print()
print("Step 1: Check realizability")
result_realizable = check_delaunay_realizability(triangles, verbose=False)
if not result_realizable['realizable']:
print("✗ Not realizable")
return
print(f" ✓ Realizable with min angle: {np.degrees(result_realizable['min_angle_radians']):.2f}°")
print()
print("Step 2: Optimize volume")
result_opt = optimize_hyperbolic_volume(triangles, verbose=False)
if not result_opt['success']:
print(f" ✗ Optimization failed")
return
from ideal_poly_volume_toolkit.rivin_delaunay import lobachevsky_function
initial_volume = np.sum([lobachevsky_function(theta)
for theta in result_realizable['angles_radians'].flatten()])
print(f" ✓ Optimization successful")
print(f" Iterations: {result_opt['n_iterations']}")
print(f" Initial volume: {initial_volume:.6f}")
print(f" Optimal volume: {result_opt['volume']:.6f}")
print(f" Improvement: {100*(result_opt['volume']-initial_volume)/abs(initial_volume):.2f}%")
print()
# Check if angles are equal (by symmetry, they should be for optimal volume)
optimal_angles = result_opt['angles']
print("Optimal angle distribution:")
print(f" All angles: {np.degrees(optimal_angles.flatten())}")
print(f" Std dev: {np.degrees(optimal_angles.std()):.4f}°")
print()
if optimal_angles.std() < 0.01:
print(" ✓ All angles are approximately equal (as expected by symmetry!)")
else:
print(" ⚠ Angles vary (unexpected for this symmetric configuration)")
print("="*70)
def main():
import argparse
parser = argparse.ArgumentParser(
description="Test hyperbolic volume optimization"
)
parser.add_argument(
"--points",
type=int,
default=20,
help="Number of random points (default: 20)",
)
parser.add_argument(
"--seed",
type=int,
default=42,
help="Random seed (default: 42)",
)
parser.add_argument(
"--example",
choices=["random", "hexagon", "both"],
default="random",
help="Which example to run (default: random)",
)
args = parser.parse_args()
print("\n" + "#"*70)
print("# Testing: Hyperbolic Volume Optimization")
print("#"*70 + "\n")
if args.example in ["random", "both"]:
test_volume_optimization(n_points=args.points, seed=args.seed)
if args.example in ["hexagon", "both"]:
test_hexagon_optimization()
if __name__ == "__main__":
main()