Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| #!/usr/bin/env python3 | |
| """ | |
| General-purpose wrapper for optimizing ideal polyhedron volumes. | |
| Usage: | |
| python bin/optimize_polyhedron.py --vertices 7 --trials 10 | |
| python bin/optimize_polyhedron.py --vertices 12 --trials 20 --output custom_name.json | |
| """ | |
| import argparse | |
| import json | |
| import numpy as np | |
| import torch | |
| from datetime import datetime | |
| from pathlib import Path | |
| import sys | |
| from ideal_poly_volume_toolkit.geometry import ( | |
| delaunay_triangulation_indices, | |
| triangle_volume_from_points_torch, | |
| ) | |
| from scipy.optimize import differential_evolution | |
| def spherical_to_complex(theta, phi): | |
| """Convert spherical coordinates to complex via stereographic projection.""" | |
| return np.tan(theta/2) * np.exp(1j * phi) | |
| def compute_volume(params, n_vertices): | |
| """ | |
| Compute volume for a polyhedron with n_vertices. | |
| Fixed vertices to break symmetry: | |
| - z1 = 0 | |
| - z2 = 1 | |
| - z_∞ = ∞ (implicit in Delaunay triangulation) | |
| Remaining (n_vertices - 3) vertices are parameterized by spherical coords. | |
| """ | |
| # Fixed vertices: 0 and 1 (infinity is implicit) | |
| complex_points = [complex(0, 0), complex(1, 0)] | |
| # Parameterized vertices (2 params each: theta, phi) | |
| n_params = n_vertices - 3 | |
| for i in range(n_params): | |
| theta = params[2*i] | |
| phi = params[2*i + 1] | |
| z = spherical_to_complex(theta, phi) | |
| complex_points.append(z) | |
| Z_np = np.array(complex_points, dtype=np.complex128) | |
| # Delaunay triangulation | |
| try: | |
| idx = delaunay_triangulation_indices(Z_np) | |
| except: | |
| return 1000.0 # Penalty for degenerate configuration | |
| # Convert to torch for volume computation | |
| Z_torch = torch.tensor(Z_np, dtype=torch.complex128) | |
| # Compute total volume | |
| total_volume = 0 | |
| for (i, j, k) in idx: | |
| try: | |
| vol = triangle_volume_from_points_torch( | |
| Z_torch[i], Z_torch[j], Z_torch[k], series_terms=96 | |
| ) | |
| total_volume += vol.item() | |
| except: | |
| return 1000.0 # Penalty for invalid configuration | |
| return -total_volume # Negative for minimization | |
| def analyze_structure(Z_np, idx): | |
| """Analyze the combinatorial structure of the triangulation.""" | |
| n_vertices = len(Z_np) | |
| n_faces = len(idx) | |
| # Count edges | |
| edges = set() | |
| for (i, j, k) in idx: | |
| edges.add((min(i,j), max(i,j))) | |
| edges.add((min(i,k), max(i,k))) | |
| edges.add((min(j,k), max(j,k))) | |
| n_edges = len(edges) | |
| # Vertex degrees | |
| vertex_degrees = [0] * n_vertices | |
| for edge in edges: | |
| vertex_degrees[edge[0]] += 1 | |
| vertex_degrees[edge[1]] += 1 | |
| # Euler characteristic check | |
| euler_char = n_vertices - n_edges + n_faces | |
| return { | |
| 'n_vertices': n_vertices, | |
| 'n_faces': n_faces, | |
| 'n_edges': n_edges, | |
| 'euler_characteristic': euler_char, | |
| 'vertex_degrees': sorted(vertex_degrees), | |
| } | |
| def reconstruct_vertices(params, n_vertices): | |
| """Reconstruct complex vertices from parameters.""" | |
| complex_points = [complex(0, 0), complex(1, 0)] | |
| n_params = n_vertices - 3 | |
| for i in range(n_params): | |
| theta = params[2*i] | |
| phi = params[2*i + 1] | |
| z = spherical_to_complex(theta, phi) | |
| complex_points.append(z) | |
| return np.array(complex_points, dtype=np.complex128) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description='Optimize ideal polyhedron volumes', | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| %(prog)s --vertices 7 --trials 10 | |
| %(prog)s --vertices 12 --trials 20 --maxiter 300 | |
| %(prog)s --vertices 20 --trials 5 --output results/data/my_20vertex.json | |
| """ | |
| ) | |
| parser.add_argument('--vertices', '-v', type=int, required=True, | |
| help='Number of vertices (must be >= 4)') | |
| parser.add_argument('--trials', '-t', type=int, default=10, | |
| help='Number of optimization trials (default: 10)') | |
| parser.add_argument('--maxiter', '-m', type=int, default=200, | |
| help='Max iterations per trial (default: 200)') | |
| parser.add_argument('--popsize', '-p', type=int, default=15, | |
| help='Population size for differential evolution (default: 15)') | |
| parser.add_argument('--output', '-o', type=str, default=None, | |
| help='Output JSON file (default: results/data/{n}vertex_optimization_TIMESTAMP.json)') | |
| parser.add_argument('--seed', '-s', type=int, default=42, | |
| help='Random seed base (default: 42)') | |
| args = parser.parse_args() | |
| if args.vertices < 4: | |
| print("Error: Number of vertices must be at least 4") | |
| sys.exit(1) | |
| # Setup output file | |
| if args.output is None: | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| output_file = f"results/data/{args.vertices}vertex_optimization_{timestamp}.json" | |
| else: | |
| output_file = args.output | |
| # Ensure output directory exists | |
| Path(output_file).parent.mkdir(parents=True, exist_ok=True) | |
| # Calculate number of parameters (2 per free vertex) | |
| n_free_vertices = args.vertices - 3 | |
| n_params = n_free_vertices * 2 | |
| bounds = [(0.1, np.pi - 0.1), (0, 2*np.pi)] * n_free_vertices | |
| print("=" * 70) | |
| print(f"Ideal Polyhedron Volume Optimization") | |
| print("=" * 70) | |
| print(f"Vertices: {args.vertices}") | |
| print(f"Free vertices: {n_free_vertices} (parameterized)") | |
| print(f"Parameters: {n_params} (spherical coordinates)") | |
| print(f"Trials: {args.trials}") | |
| print(f"Max iterations: {args.maxiter}") | |
| print(f"Population: {args.popsize}") | |
| print(f"Output: {output_file}") | |
| print("=" * 70) | |
| best_volume = 0 | |
| best_params = None | |
| all_results = [] | |
| for trial in range(args.trials): | |
| print(f"\nTrial {trial + 1}/{args.trials}...") | |
| result = differential_evolution( | |
| lambda p: compute_volume(p, args.vertices), | |
| bounds, | |
| maxiter=args.maxiter, | |
| popsize=args.popsize, | |
| seed=args.seed + trial, | |
| polish=True, | |
| disp=False | |
| ) | |
| volume = -result.fun | |
| print(f" Volume: {volume:.8f}") | |
| print(f" Success: {result.success}") | |
| print(f" Iterations: {result.nit}") | |
| all_results.append({ | |
| 'trial': trial + 1, | |
| 'volume': float(volume), | |
| 'params': result.x.tolist(), | |
| 'success': bool(result.success), | |
| 'iterations': int(result.nit), | |
| 'function_evals': int(result.nfev) | |
| }) | |
| if volume > best_volume: | |
| best_volume = volume | |
| best_params = result.x | |
| print(f" → NEW BEST!") | |
| # Analyze best configuration | |
| print("\n" + "=" * 70) | |
| print("BEST RESULT:") | |
| print("=" * 70) | |
| print(f"Volume: {best_volume:.10f}") | |
| # Reconstruct and analyze | |
| Z_np = reconstruct_vertices(best_params, args.vertices) | |
| idx = delaunay_triangulation_indices(Z_np) | |
| structure = analyze_structure(Z_np, idx) | |
| print(f"\nCombinatorial structure:") | |
| print(f" Vertices: {structure['n_vertices']}") | |
| print(f" Edges: {structure['n_edges']}") | |
| print(f" Faces: {structure['n_faces']}") | |
| print(f" Euler char: {structure['euler_characteristic']} (should be 2 for sphere)") | |
| print(f" Vertex degrees: {structure['vertex_degrees']}") | |
| # Save results | |
| output_data = { | |
| 'metadata': { | |
| 'timestamp': datetime.now().isoformat(), | |
| 'n_vertices': args.vertices, | |
| 'n_trials': args.trials, | |
| 'maxiter': args.maxiter, | |
| 'popsize': args.popsize, | |
| 'seed_base': args.seed, | |
| }, | |
| 'best': { | |
| 'volume': float(best_volume), | |
| 'params': best_params.tolist(), | |
| 'vertices_real': Z_np.real.tolist(), | |
| 'vertices_imag': Z_np.imag.tolist(), | |
| 'structure': structure, | |
| 'triangulation': [list(map(int, tri)) for tri in idx], | |
| }, | |
| 'all_trials': all_results, | |
| } | |
| with open(output_file, 'w') as f: | |
| json.dump(output_data, f, indent=2) | |
| print(f"\nResults saved to: {output_file}") | |
| print("=" * 70) | |
| if __name__ == '__main__': | |
| main() | |