#!/usr/bin/env python3 """ Background optimization to find maximal 20-vertex ideal polyhedron. The regular dodecahedron (20 vertices) is NOT arithmetic, but the maximal configuration might be! Run with: nohup python optimize_20vertex_background.py > 20vertex_log.txt 2>&1 & """ import numpy as np import torch import torch.optim as optim from ideal_poly_volume_toolkit.geometry import ideal_poly_volume_via_delaunay import json from datetime import datetime import time import signal import sys import os # Global variables for graceful shutdown should_exit = False best_volume = 0.0 best_config = None def signal_handler(sig, frame): global should_exit print(f"\nReceived signal {sig}. Saving current best result and exiting...") should_exit = True # Register signal handlers signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def optimize_20vertex_volume(seed=42, max_iterations=10000, lr=0.1): """Optimize volume of 20-vertex ideal polyhedron.""" global best_volume, best_config torch.manual_seed(seed) np.random.seed(seed) print(f"\n{'='*70}") print(f"Starting optimization with seed {seed} at {datetime.now()}") print(f"{'='*70}") # Known volume of regular dodecahedron (20 vertices) dodecahedron_vol = 3 * 10.74350778 # Approximately, using Milnor's formula print(f"Regular dodecahedron volume (approx): {dodecahedron_vol:.6f}") # Initialize 17 free vertices (3 fixed at 0, 1, infinity) # Start with vertices roughly on a circle but with some randomness n_free = 17 angles = 2 * np.pi * np.arange(n_free) / n_free radii = 0.5 + 0.3 * np.random.randn(n_free) # Add more variation to break symmetry angles += 0.1 * np.random.randn(n_free) real_parts = radii * np.cos(angles) imag_parts = radii * np.sin(angles) # Add some points inside and outside for i in range(5): idx = np.random.randint(n_free) real_parts[idx] += np.random.randn() * 0.3 imag_parts[idx] += np.random.randn() * 0.3 z_real = torch.tensor(real_parts, dtype=torch.float32, requires_grad=True) z_imag = torch.tensor(imag_parts, dtype=torch.float32, requires_grad=True) # Optimizer with adaptive learning rate optimizer = optim.Adam([z_real, z_imag], lr=lr) scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'max', patience=200, factor=0.5) # Track progress history = [] last_save_time = time.time() save_interval = 60 # Save every minute for iteration in range(max_iterations): if should_exit: break optimizer.zero_grad() # Build vertex list vertices = [torch.tensor(0.0+0j, dtype=torch.complex64), torch.tensor(1.0+0j, dtype=torch.complex64)] # Add free vertices for i in range(n_free): vertices.append(torch.complex(z_real[i], z_imag[i])) vertices = torch.stack(vertices) # Compute volume volume = ideal_poly_volume_via_delaunay(vertices, mode='fast', series_terms=200) if torch.isfinite(volume) and volume > 0: # Negative because we want to maximize loss = -volume loss.backward() # Gradient clipping torch.nn.utils.clip_grad_norm_([z_real, z_imag], max_norm=1.0) optimizer.step() current_vol = volume.item() scheduler.step(current_vol) # Track best configuration if current_vol > best_volume: best_volume = current_vol best_config = { 'volume': current_vol, 'vertices': vertices.detach().cpu().numpy().tolist(), 'seed': seed, 'iteration': iteration, 'timestamp': str(datetime.now()) } print(f"New best! Iteration {iteration}: volume = {current_vol:.8f} " f"(ratio to dodecahedron: {current_vol/dodecahedron_vol:.6f})") # Progress update if iteration % 100 == 0: current_lr = optimizer.param_groups[0]['lr'] print(f"Iteration {iteration}: volume = {current_vol:.8f}, " f"lr = {current_lr:.6f}") history.append(current_vol) # Periodic save current_time = time.time() if current_time - last_save_time > save_interval: save_intermediate_result(best_config, history) last_save_time = current_time return best_config, history def save_intermediate_result(config, history): """Save intermediate results to file.""" if config is None: return result = { 'best_configuration': config, 'optimization_history': history[-1000:], # Last 1000 values 'status': 'in_progress' } filename = f'20vertex_optimization_intermediate.json' with open(filename, 'w') as f: json.dump(result, f, indent=2) print(f"Saved intermediate result to {filename}") def run_multiple_seeds(n_seeds=10, max_iterations=10000): """Run optimization with multiple random seeds.""" global best_volume, best_config all_results = [] for trial in range(n_seeds): if should_exit: break seed = trial * 17 # Use different seeds config, history = optimize_20vertex_volume(seed=seed, max_iterations=max_iterations) if config is not None: all_results.append({ 'seed': seed, 'final_volume': config['volume'], 'configuration': config, 'converged_iteration': len(history) }) # Save final results final_result = { 'best_configuration': best_config, 'all_trials': all_results, 'summary': { 'num_trials': len(all_results), 'best_volume': best_volume, 'dodecahedron_volume': 32.23052334, # More precise value 'ratio_to_dodecahedron': best_volume / 32.23052334 if best_volume > 0 else 0 }, 'timestamp': str(datetime.now()), 'status': 'completed' if not should_exit else 'interrupted' } with open('20vertex_optimization_final.json', 'w') as f: json.dump(final_result, f, indent=2) print("\n" + "="*70) print("OPTIMIZATION COMPLETE!") print("="*70) print(f"Best volume found: {best_volume:.8f}") print(f"Ratio to dodecahedron: {best_volume/32.23052334:.6f}") print(f"Results saved to 20vertex_optimization_final.json") if __name__ == "__main__": print("Starting 20-vertex ideal polyhedron optimization") print("This will run in the background and save results periodically.") print("To stop gracefully, use: kill -SIGTERM ") print(f"Process ID: {os.getpid()}") # Import os for getpid import os # Run optimization run_multiple_seeds(n_seeds=10, max_iterations=10000) print("\nOptimization finished!")