Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from scipy.spatial import Delaunay | |
| from ideal_poly_volume_toolkit.geometry import ideal_poly_volume_via_delaunay | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| print("Combinatorial Mixture Effects in Ideal Polyhedra Volume Distributions") | |
| print("="*70) | |
| print("\nInvestigating how different triangulations create mixture distributions\n") | |
| def analyze_triangulation_changes(n_samples=10000, n_vertices=5, seed=42): | |
| """Track how triangulation changes as we move vertices""" | |
| np.random.seed(seed) | |
| print(f"Analyzing {n_vertices} vertices with {n_vertices-3} free points...") | |
| # We'll track triangulation "signatures" and their volumes | |
| triangulation_types = {} | |
| volumes_by_type = {} | |
| # Fixed vertices at 0, 1, infinity | |
| n_free = n_vertices - 3 | |
| for i in range(n_samples): | |
| if i % 1000 == 0 and i > 0: | |
| print(f" Progress: {i}/{n_samples}") | |
| # Generate random points on sphere | |
| sphere_points = np.random.randn(n_free, 3) | |
| sphere_points = sphere_points / np.linalg.norm(sphere_points, axis=1, keepdims=True) | |
| # Convert to complex via stereographic projection | |
| vertices = [0+0j, 1+0j] # Fixed vertices | |
| valid = True | |
| for p in sphere_points: | |
| x, y, z = p | |
| if abs(z - 1) < 0.01: # Skip north pole | |
| valid = False | |
| break | |
| w = complex(x/(1-z), y/(1-z)) | |
| # Skip if too close to fixed points | |
| if abs(w) < 0.01 or abs(w-1) < 0.01: | |
| valid = False | |
| break | |
| vertices.append(w) | |
| if not valid: | |
| continue | |
| vertices = np.array(vertices) | |
| try: | |
| # Get triangulation | |
| points_2d = np.column_stack([vertices.real, vertices.imag]) | |
| tri = Delaunay(points_2d) | |
| # Create a "signature" for this triangulation | |
| # Sort the simplices to get a canonical form | |
| simplices_sorted = np.sort(tri.simplices, axis=1) | |
| simplices_sorted = np.sort(simplices_sorted, axis=0) | |
| signature = tuple(map(tuple, simplices_sorted)) | |
| # Compute volume | |
| volume = ideal_poly_volume_via_delaunay(vertices, mode='fast') | |
| if signature not in triangulation_types: | |
| triangulation_types[signature] = 0 | |
| volumes_by_type[signature] = [] | |
| triangulation_types[signature] += 1 | |
| volumes_by_type[signature].append(volume) | |
| except: | |
| continue | |
| return triangulation_types, volumes_by_type | |
| # Run analysis for 5 vertices | |
| print("\nAnalyzing 5-vertex configurations...") | |
| types_5, volumes_5 = analyze_triangulation_changes(10000, 5) | |
| print(f"\nFound {len(types_5)} different triangulation types") | |
| print("\nMost common triangulation types:") | |
| sorted_types = sorted(types_5.items(), key=lambda x: x[1], reverse=True) | |
| for i, (sig, count) in enumerate(sorted_types[:5]): | |
| print(f" Type {i+1}: {count} occurrences ({count/100:.1f}%)") | |
| mean_vol = np.mean(volumes_5[sig]) | |
| std_vol = np.std(volumes_5[sig]) | |
| print(f" Mean volume: {mean_vol:.4f}, Std: {std_vol:.4f}") | |
| # Analyze mixture components | |
| fig, axes = plt.subplots(2, 2, figsize=(12, 10)) | |
| # 1. Distribution of triangulation type frequencies | |
| ax = axes[0, 0] | |
| frequencies = list(types_5.values()) | |
| ax.hist(frequencies, bins=30, alpha=0.7, color='blue', edgecolor='black') | |
| ax.set_xlabel('Number of Occurrences') | |
| ax.set_ylabel('Count') | |
| ax.set_title(f'Distribution of Triangulation Type Frequencies ({len(types_5)} types)') | |
| ax.set_yscale('log') | |
| # 2. Volume distributions for top triangulation types | |
| ax = axes[0, 1] | |
| colors = ['red', 'blue', 'green', 'orange', 'purple'] | |
| for i, (sig, count) in enumerate(sorted_types[:5]): | |
| if count > 50: # Only plot if we have enough samples | |
| vols = volumes_5[sig] | |
| ax.hist(vols, bins=30, alpha=0.5, density=True, | |
| label=f'Type {i+1} (n={count})', color=colors[i % len(colors)]) | |
| ax.set_xlabel('Volume') | |
| ax.set_ylabel('Density') | |
| ax.set_title('Volume Distributions by Triangulation Type') | |
| ax.legend() | |
| # 3. Mean volume vs frequency | |
| ax = axes[1, 0] | |
| mean_vols = [] | |
| counts = [] | |
| for sig, count in types_5.items(): | |
| if count > 10: # Need enough samples for reliable mean | |
| mean_vols.append(np.mean(volumes_5[sig])) | |
| counts.append(count) | |
| ax.scatter(counts, mean_vols, alpha=0.6) | |
| ax.set_xlabel('Frequency of Triangulation Type') | |
| ax.set_ylabel('Mean Volume') | |
| ax.set_title('Mean Volume vs Triangulation Frequency') | |
| ax.set_xscale('log') | |
| # 4. Analysis summary | |
| ax = axes[1, 1] | |
| ax.text(0.5, 0.9, "Mixture Distribution Analysis", | |
| fontsize=14, weight='bold', ha='center', transform=ax.transAxes) | |
| analysis_text = f""" | |
| 5 vertices: {len(types_5)} triangulation types found | |
| Top type: {sorted_types[0][1]/100:.1f}% of configurations | |
| Top 5 types: {sum(x[1] for x in sorted_types[:5])/100:.1f}% of configurations | |
| Key findings: | |
| • Most configurations use common triangulations | |
| • Rare triangulations create distribution tails | |
| • Each type has its own volume distribution | |
| • Overall distribution is a weighted mixture | |
| This explains: | |
| → Smooth overall distribution (averaging) | |
| → Potential for subtle multimodality | |
| → Deviations from pure CLT behavior | |
| """ | |
| ax.text(0.05, 0.05, analysis_text, fontsize=10, | |
| ha='left', va='bottom', transform=ax.transAxes, | |
| family='monospace') | |
| ax.axis('off') | |
| plt.tight_layout() | |
| plt.savefig('combinatorial_mixture_analysis.png', dpi=150) | |
| print("\nSaved analysis to combinatorial_mixture_analysis.png") | |
| # Now let's see if this explains the Beta distribution shape | |
| print("\n\nTesting mixture hypothesis:") | |
| print("-"*50) | |
| # Combine all volumes | |
| all_volumes = [] | |
| for vols in volumes_5.values(): | |
| all_volumes.extend(vols) | |
| all_volumes = np.array(all_volumes) | |
| # Fit Beta distribution | |
| from scipy import stats | |
| scaled_vols = all_volumes / np.max(all_volumes) | |
| alpha, beta, loc, scale = stats.beta.fit(scaled_vols) | |
| print(f"Overall Beta fit: α={alpha:.2f}, β={beta:.2f}") | |
| print(f"Mean volume across all types: {np.mean(all_volumes):.4f}") | |
| print(f"Std deviation: {np.std(all_volumes):.4f}") | |
| # Check for multimodality | |
| from scipy.stats import gaussian_kde | |
| kde = gaussian_kde(all_volumes) | |
| x_range = np.linspace(0, np.max(all_volumes), 1000) | |
| density = kde(x_range) | |
| # Find local maxima | |
| from scipy.signal import find_peaks | |
| peaks, _ = find_peaks(density, prominence=0.1) | |
| print(f"\nNumber of significant modes in distribution: {len(peaks)}") | |
| if len(peaks) > 1: | |
| print("Evidence of multimodality due to mixture effects!") | |
| else: | |
| print("Distribution appears unimodal despite mixture") | |
| plt.close() |