Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Generate and save triangulations with optimal angles for various n. | |
| """ | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| import numpy as np | |
| import json | |
| from datetime import datetime | |
| from scipy.spatial import Delaunay | |
| from ideal_poly_volume_toolkit.rivin_delaunay import check_delaunay_realizability, build_edge_adjacency | |
| from math import gcd | |
| from functools import reduce | |
| def lcm(a, b): | |
| """Compute least common multiple.""" | |
| return abs(a * b) // gcd(a, b) | |
| def analyze_and_save_configuration(n_vertices, seed, output_dir='results/data/large_configs'): | |
| """Generate, analyze, and save a configuration.""" | |
| print(f"\n{'='*70}") | |
| print(f"Configuration: n={n_vertices}, seed={seed}") | |
| print(f"{'='*70}") | |
| # Generate random points | |
| np.random.seed(seed) | |
| radii = np.sqrt(np.random.uniform(0, 1, n_vertices)) | |
| angles = np.random.uniform(0, 2*np.pi, n_vertices) | |
| vertices_complex = radii * np.exp(1j * angles) | |
| points = np.column_stack([vertices_complex.real, vertices_complex.imag]) | |
| print(f"Generated {n_vertices} random points") | |
| # Compute Delaunay triangulation | |
| tri = Delaunay(points) | |
| triangulation = [tuple(sorted(simplex)) for simplex in tri.simplices] | |
| triangulation = sorted(set(triangulation)) | |
| print(f"Triangulation: {len(triangulation)} triangles") | |
| # Get optimal angles from Rivin LP | |
| result = check_delaunay_realizability(triangulation, verbose=False, strict=False) | |
| if not result['realizable']: | |
| print("ERROR: Not realizable!") | |
| return None | |
| print(f"✓ Realizable") | |
| # Extract angles | |
| angles_scaled = result['angles'] | |
| angles_radians = angles_scaled * np.pi | |
| n_triangles = len(triangulation) | |
| angles_array = angles_radians.reshape((n_triangles, 3)) | |
| # Compute dihedral angles | |
| edge_adjacency = build_edge_adjacency(triangulation) | |
| dihedrals = [] | |
| for edge, opposite_corners in sorted(edge_adjacency.items()): | |
| if len(opposite_corners) == 2: | |
| angle1 = angles_array[opposite_corners[0][0], opposite_corners[0][1]] | |
| angle2 = angles_array[opposite_corners[1][0], opposite_corners[1][1]] | |
| dihedral = angle1 + angle2 | |
| normalized = dihedral / np.pi | |
| dihedrals.append({ | |
| 'edge': [int(edge[0]), int(edge[1])], | |
| 'angle_radians': float(dihedral), | |
| 'angle_degrees': float(np.degrees(dihedral)), | |
| 'normalized': float(normalized), | |
| }) | |
| print(f"Computed {len(dihedrals)} interior edge dihedrals") | |
| # Analyze rational structure | |
| # Check denominators up to 10*n | |
| max_denom = 10 * n_vertices | |
| denominators_found = set() | |
| for d in dihedrals: | |
| norm = d['normalized'] | |
| # Try to find rational approximation | |
| for q in range(1, min(max_denom + 1, 1000)): | |
| p = round(norm * q) | |
| if abs(norm - p/q) < 1e-10: | |
| denominators_found.add(q) | |
| d['rational_p'] = int(p) | |
| d['rational_q'] = int(q) | |
| d['rational_error'] = float(abs(norm - p/q)) | |
| break | |
| # Find LCM of all denominators | |
| if denominators_found: | |
| common_denominator = reduce(lcm, denominators_found) | |
| else: | |
| common_denominator = None | |
| print(f"Denominators found: {sorted(denominators_found)}") | |
| print(f"Common denominator (LCM): {common_denominator}") | |
| if common_denominator: | |
| print(f"Ratio q/n: {common_denominator/n_vertices:.3f}") | |
| # Check if all angles are rational with common denominator | |
| all_rational = all('rational_q' in d for d in dihedrals) | |
| if all_rational and common_denominator: | |
| # Verify all are multiples of 1/common_denominator | |
| all_multiples = True | |
| for d in dihedrals: | |
| p = round(d['normalized'] * common_denominator) | |
| error = abs(d['normalized'] - p/common_denominator) | |
| if error > 1e-10: | |
| all_multiples = False | |
| break | |
| if all_multiples: | |
| print(f"✓ ALL {len(dihedrals)} angles are exact multiples of π/{common_denominator}") | |
| # Prepare output data | |
| output_data = { | |
| 'metadata': { | |
| 'n_vertices': int(n_vertices), | |
| 'n_triangles': int(n_triangles), | |
| 'n_interior_edges': len(dihedrals), | |
| 'seed': int(seed), | |
| 'generated': datetime.now().isoformat(), | |
| }, | |
| 'vertex_positions': { | |
| 'real': vertices_complex.real.tolist(), | |
| 'imag': vertices_complex.imag.tolist(), | |
| }, | |
| 'triangulation': [[int(v) for v in tri] for tri in triangulation], | |
| 'face_angles': { | |
| 'radians': angles_array.tolist(), | |
| 'degrees': np.degrees(angles_array).tolist(), | |
| }, | |
| 'dihedral_angles': dihedrals, | |
| 'rational_structure': { | |
| 'all_rational': all_rational, | |
| 'denominators': sorted(denominators_found), | |
| 'common_denominator': int(common_denominator) if common_denominator else None, | |
| 'ratio_to_n': float(common_denominator / n_vertices) if common_denominator else None, | |
| } | |
| } | |
| # Save to file | |
| output_dir = Path(output_dir) | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| output_file = output_dir / f"n{n_vertices:03d}_seed{seed:03d}_triangulation.json" | |
| with open(output_file, 'w') as f: | |
| json.dump(output_data, f, indent=2) | |
| print(f"✓ Saved to: {output_file}") | |
| return output_data | |
| def main(): | |
| """Generate and save multiple configurations.""" | |
| print("═"*70) | |
| print("LARGE CONFIGURATION GENERATOR") | |
| print("Saving triangulations with optimal angles for various n") | |
| print("═"*70) | |
| # Configurations to generate | |
| configs = [ | |
| (30, 42), | |
| (40, 42), | |
| (50, 42), | |
| (60, 42), | |
| (70, 42), | |
| (80, 42), | |
| (89, 42), | |
| (100, 42), | |
| (89, 123), # Same n, different seed | |
| (89, 456), # Another seed for n=89 | |
| ] | |
| results = [] | |
| for n, seed in configs: | |
| result = analyze_and_save_configuration(n, seed) | |
| if result: | |
| results.append({ | |
| 'n': n, | |
| 'seed': seed, | |
| 'common_denom': result['rational_structure']['common_denominator'], | |
| 'ratio': result['rational_structure']['ratio_to_n'], | |
| }) | |
| # Create summary | |
| print(f"\n{'='*70}") | |
| print("SUMMARY") | |
| print(f"{'='*70}") | |
| print(f"\n{'n':>4} {'seed':>6} {'q (LCM)':>10} {'q/n':>8}") | |
| print("-"*30) | |
| for r in results: | |
| if r['common_denom']: | |
| print(f"{r['n']:>4} {r['seed']:>6} {r['common_denom']:>10} {r['ratio']:>8.3f}") | |
| else: | |
| print(f"{r['n']:>4} {r['seed']:>6} {'None':>10} {'N/A':>8}") | |
| # Save summary | |
| summary_file = Path('results/data/large_configs/SUMMARY.json') | |
| with open(summary_file, 'w') as f: | |
| json.dump({ | |
| 'generated': datetime.now().isoformat(), | |
| 'configurations': results, | |
| }, f, indent=2) | |
| print(f"\n✓ Summary saved to: {summary_file}") | |
| print(f"\nAll configurations saved to: results/data/large_configs/") | |
| if __name__ == '__main__': | |
| main() | |