idealpolyhedra / examples /analysis /combinatorial_mixture_analysis.py
igriv's picture
Major reorganization and feature additions
d7d27f0
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()