#!/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()