Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Test the full Rivin algorithm pipeline: | |
| 1. Start with random points in 2D | |
| 2. Compute Delaunay triangulation | |
| 3. Check realizability (should pass for Delaunay) | |
| 4. Optimize angles for maximum hyperbolic volume | |
| 5. Reconstruct geometric realization from optimal angles | |
| 6. Verify that reconstructed geometry has correct angles | |
| This demonstrates the complete workflow from geometry β angles β optimized angles β new geometry. | |
| """ | |
| import numpy as np | |
| from scipy.spatial import Delaunay | |
| import matplotlib.pyplot as plt | |
| 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, | |
| realize_angles_as_points, | |
| refine_geometry_for_volume, | |
| compute_triangle_angle, | |
| ) | |
| def verify_angles(points, triangles, target_angles, vertex_list): | |
| """ | |
| Verify that the reconstructed points have the correct angles. | |
| Args: | |
| points: np.ndarray of shape (n_vertices, 2) | |
| triangles: List of triangles | |
| target_angles: Array of target angles | |
| vertex_list: Mapping from point index to vertex ID | |
| Returns: | |
| RMS angle error in radians | |
| """ | |
| vertex_to_idx = {v: i for i, v in enumerate(vertex_list)} | |
| errors = [] | |
| for tri_id, (v0, v1, v2) in enumerate(triangles): | |
| p0 = points[vertex_to_idx[v0]] | |
| p1 = points[vertex_to_idx[v1]] | |
| p2 = points[vertex_to_idx[v2]] | |
| # Compute actual angles | |
| 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 = target_angles[tri_id] | |
| errors.extend((actual - target)**2) | |
| return np.sqrt(np.mean(errors)) | |
| def test_full_pipeline(n_points=15, seed=42, plot=False): | |
| """ | |
| Test the complete pipeline. | |
| Args: | |
| n_points: Number of random points | |
| seed: Random seed | |
| plot: If True, plot original and reconstructed geometries | |
| """ | |
| np.random.seed(seed) | |
| print("="*70) | |
| print("FULL RIVIN ALGORITHM PIPELINE") | |
| print("="*70) | |
| print() | |
| # Step 1: Generate random points | |
| print(f"Step 1: Generate {n_points} random points") | |
| original_points = np.random.rand(n_points, 2) | |
| print(f" β Points generated") | |
| print() | |
| # Step 2: Compute Delaunay triangulation | |
| print("Step 2: Compute Delaunay triangulation") | |
| tri = Delaunay(original_points) | |
| triangles = [tuple(simplex) for simplex in tri.simplices] | |
| print(f" β Triangulation: {len(triangles)} triangles") | |
| print() | |
| # Step 3: Check realizability | |
| print("Step 3: Check Delaunay realizability") | |
| result_check = check_delaunay_realizability(triangles, verbose=False) | |
| if not result_check['realizable']: | |
| print(" β Not realizable (unexpected!)") | |
| return | |
| print(f" β Realizable with min angle: {np.degrees(result_check['min_angle_radians']):.2f}Β°") | |
| print() | |
| # Step 4: Optimize angles for maximum volume | |
| print("Step 4: Optimize angles for maximum hyperbolic 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_check['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() | |
| # Step 5: Reconstruct geometry from optimal angles | |
| print("Step 5: Reconstruct geometry from optimal angles") | |
| result_reconstruct = realize_angles_as_points( | |
| triangles, | |
| result_opt['angles'], | |
| verbose=True | |
| ) | |
| if not result_reconstruct['success']: | |
| if not result_reconstruct.get('triangulation_preserved', True): | |
| print(f" β Reconstruction failed: triangulation not preserved") | |
| else: | |
| print(f" β Reconstruction failed") | |
| return | |
| reconstructed_points = result_reconstruct['points'] | |
| vertex_list = result_reconstruct['vertex_list'] | |
| print() | |
| # Step 6: Refine geometry via direct volume optimization | |
| print("Step 6: Refine geometry via direct BFGS volume optimization") | |
| print(" (This nails down the exact geometry while preserving triangulation)") | |
| print() | |
| result_refined = refine_geometry_for_volume( | |
| reconstructed_points, | |
| vertex_list, | |
| triangles, | |
| verbose=True | |
| ) | |
| if not result_refined['success']: | |
| print(f" β Refinement failed or triangulation changed") | |
| if not result_refined['triangulation_preserved']: | |
| print(f" Warning: Delaunay triangulation changed during refinement!") | |
| refined_points = reconstructed_points # Fall back to unrefined | |
| else: | |
| refined_points = result_refined['points'] | |
| print() | |
| # Step 7: Verify angles | |
| print("Step 7: Verify final angles") | |
| rms_error_refined = verify_angles(refined_points, triangles, result_opt['angles'], vertex_list) | |
| print(f" RMS angle error (refined): {rms_error_refined:.6f} radians = {np.degrees(rms_error_refined):.4f}Β°") | |
| if rms_error_refined < 0.001: | |
| print(f" β Excellent agreement!") | |
| elif rms_error_refined < 0.01: | |
| print(f" β Good agreement") | |
| else: | |
| print(f" β Moderate agreement") | |
| print() | |
| # Compare volumes | |
| if result_refined['success'] and result_refined['volume'] is not None: | |
| print(f"Volume comparison:") | |
| print(f" Target (from angle opt): {result_opt['volume']:.6f}") | |
| print(f" Achieved (refined geom): {result_refined['volume']:.6f}") | |
| print(f" Difference: {abs(result_refined['volume'] - result_opt['volume']):.6f}") | |
| print() | |
| # Compare original vs reconstructed | |
| print("="*70) | |
| print("COMPARISON: Original vs Reconstructed") | |
| print("="*70) | |
| print(f"Original points: {original_points.shape[0]} vertices") | |
| print(f"Refined points: {refined_points.shape[0]} vertices") | |
| print() | |
| # Compute angles in original geometry | |
| print("Angle comparison:") | |
| vertex_to_idx_orig = {i: i for i in range(n_points)} | |
| vertex_to_idx_recon = {v: i for i, v in enumerate(vertex_list)} | |
| angle_diffs = [] | |
| for tri_id, (v0, v1, v2) in enumerate(triangles): | |
| # Original angles | |
| p0_orig = original_points[v0] | |
| p1_orig = original_points[v1] | |
| p2_orig = original_points[v2] | |
| angle0_orig = compute_triangle_angle(p0_orig, p1_orig, p2_orig) | |
| angle1_orig = compute_triangle_angle(p1_orig, p2_orig, p0_orig) | |
| angle2_orig = compute_triangle_angle(p2_orig, p0_orig, p1_orig) | |
| orig_angles = np.array([angle0_orig, angle1_orig, angle2_orig]) | |
| # Reconstructed angles | |
| p0_recon = reconstructed_points[vertex_to_idx_recon[v0]] | |
| p1_recon = reconstructed_points[vertex_to_idx_recon[v1]] | |
| p2_recon = reconstructed_points[vertex_to_idx_recon[v2]] | |
| angle0_recon = compute_triangle_angle(p0_recon, p1_recon, p2_recon) | |
| angle1_recon = compute_triangle_angle(p1_recon, p2_recon, p0_recon) | |
| angle2_recon = compute_triangle_angle(p2_recon, p0_recon, p1_recon) | |
| recon_angles = np.array([angle0_recon, angle1_recon, angle2_recon]) | |
| # Optimal angles | |
| opt_angles = result_opt['angles'][tri_id] | |
| angle_diffs.append(np.abs(orig_angles - opt_angles)) | |
| angle_diffs = np.concatenate(angle_diffs) | |
| print(f" Average angle change: {np.degrees(np.mean(angle_diffs)):.2f}Β°") | |
| print(f" Max angle change: {np.degrees(np.max(angle_diffs)):.2f}Β°") | |
| print() | |
| # Plot if requested | |
| if plot: | |
| fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6)) | |
| # Plot original | |
| ax1.triplot(original_points[:, 0], original_points[:, 1], tri.simplices, 'b-', linewidth=0.5) | |
| ax1.plot(original_points[:, 0], original_points[:, 1], 'ro', markersize=4) | |
| ax1.set_title(f'Original Delaunay Triangulation\n({len(triangles)} triangles)') | |
| ax1.set_aspect('equal') | |
| ax1.grid(True, alpha=0.3) | |
| # Plot reconstructed | |
| # Need to create triangles with reconstructed indices | |
| recon_triangles = [] | |
| for v0, v1, v2 in triangles: | |
| i0 = vertex_to_idx_recon[v0] | |
| i1 = vertex_to_idx_recon[v1] | |
| i2 = vertex_to_idx_recon[v2] | |
| recon_triangles.append([i0, i1, i2]) | |
| recon_triangles = np.array(recon_triangles) | |
| ax2.triplot(reconstructed_points[:, 0], reconstructed_points[:, 1], recon_triangles, 'g-', linewidth=0.5) | |
| ax2.plot(reconstructed_points[:, 0], reconstructed_points[:, 1], 'ro', markersize=4) | |
| ax2.set_title(f'Reconstructed from Optimal Angles\n(Volume improved by {100*(result_opt["volume"]-initial_volume)/abs(initial_volume):.1f}%)') | |
| ax2.set_aspect('equal') | |
| ax2.grid(True, alpha=0.3) | |
| plt.tight_layout() | |
| plt.savefig('rivin_pipeline_comparison.png', dpi=150, bbox_inches='tight') | |
| print(f"Plot saved to: rivin_pipeline_comparison.png") | |
| print("="*70) | |
| print("PIPELINE COMPLETE β") | |
| print("="*70) | |
| return { | |
| 'original_points': original_points, | |
| 'reconstructed_points': reconstructed_points, | |
| 'triangles': triangles, | |
| 'optimal_angles': result_opt['angles'], | |
| 'volume_improvement': 100*(result_opt['volume']-initial_volume)/abs(initial_volume), | |
| 'angle_reconstruction_error': rms_error_refined, | |
| } | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser( | |
| description="Test full Rivin algorithm pipeline" | |
| ) | |
| parser.add_argument( | |
| "--points", | |
| type=int, | |
| default=15, | |
| help="Number of random points (default: 15)", | |
| ) | |
| parser.add_argument( | |
| "--seed", | |
| type=int, | |
| default=42, | |
| help="Random seed (default: 42)", | |
| ) | |
| parser.add_argument( | |
| "--plot", | |
| action="store_true", | |
| help="Generate comparison plot", | |
| ) | |
| args = parser.parse_args() | |
| print("\n" + "#"*70) | |
| print("# Full Rivin Algorithm Pipeline Test") | |
| print("#"*70 + "\n") | |
| result = test_full_pipeline( | |
| n_points=args.points, | |
| seed=args.seed, | |
| plot=args.plot | |
| ) | |
| if __name__ == "__main__": | |
| main() | |