Files
resonance-engine/scripts/nuclear_magic_analyzer.py
T

630 lines
28 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Nuclear Magic Number Analyzer
Analyzes EM sweep data for signatures of nuclear shell structure
in the coherence mode spectrum of the Resonance Engine lattice.
Six analyses:
1. Coherence peak clustering — find & cluster coherence maxima
2. Mode counting vs shell degeneracy — compare distinct mode counts to magic numbers
3. Gap structure — coherence gap ratios vs nuclear shell gaps
4. Omega-resolved shell occupancy — occupied-state count per omega slice
5. 2D torus mode comparison — lattice mode degeneracies vs observed peaks
6. GUE pair correlation — nearest-neighbor spacing vs Wigner surmise
Usage:
python3 nuclear_magic_analyzer.py <sweep_csv>
Output saved to: ../results/nuclear_magic_analysis_<timestamp>.txt
"""
import sys
import os
import numpy as np
import pandas as pd
from datetime import datetime
from collections import Counter
# ── Nuclear physics constants ──────────────────────────────────────
MAGIC_NUMBERS = [2, 8, 20, 28, 50, 82, 126]
# Shell degeneracies (2j+1 for each filled subshell up to each magic closure)
SHELL_DEGENERACIES = {
2: [2], # 1s1/2
8: [2, 4, 2], # 1s, 1p3/2, 1p1/2
20: [2, 4, 2, 6, 4, 2], # sd shell
28: [2, 4, 2, 6, 4, 2, 8], # f7/2
50: [2, 4, 2, 6, 4, 2, 8, 6, 10, 4, 2],
82: [2, 4, 2, 6, 4, 2, 8, 6, 10, 4, 2, 12, 8, 6, 4, 2],
}
# Gap ratios between successive magic numbers
MAGIC_GAPS = np.diff(MAGIC_NUMBERS[:6]).astype(float)
MAGIC_GAP_RATIOS = MAGIC_GAPS / MAGIC_GAPS[0] # normalized to first gap
def load_sweep(csv_path):
"""Load and validate sweep CSV."""
df = pd.read_csv(csv_path)
required = ['omega', 'khra_amp', 'gixx_amp', 'coherence', 'asymmetry', 'vorticity_mean']
missing = [c for c in required if c not in df.columns]
if missing:
print(f"ERROR: Missing columns: {missing}")
sys.exit(1)
return df
# ═══════════════════════════════════════════════════════════════════
# Analysis 1: Coherence Peak Clustering
# ═══════════════════════════════════════════════════════════════════
def analysis_coherence_peaks(df, out):
out.append("=" * 70)
out.append("ANALYSIS 1: Coherence Peak Clustering")
out.append("=" * 70)
# Group by omega, find max coherence per omega slice
omega_groups = df.groupby('omega')
omega_vals = sorted(df['omega'].unique())
peak_data = []
for omega in omega_vals:
group = omega_groups.get_group(omega)
idx_max = group['coherence'].idxmax()
row = group.loc[idx_max]
peak_data.append({
'omega': omega,
'coherence': row['coherence'],
'khra_amp': row['khra_amp'],
'gixx_amp': row['gixx_amp'],
'asymmetry': row['asymmetry'],
'vorticity': row['vorticity_mean'],
})
peaks_df = pd.DataFrame(peak_data)
coh_values = peaks_df['coherence'].values
global_mean = coh_values.mean()
global_std = coh_values.std()
out.append(f"\nPeak coherence per omega slice:")
out.append(f" Mean: {global_mean:.6f} Std: {global_std:.6f}")
out.append(f" Range: [{coh_values.min():.6f}, {coh_values.max():.6f}]")
out.append("")
# Identify significant peaks (> mean + 1 sigma)
threshold = global_mean + global_std
strong_peaks = peaks_df[peaks_df['coherence'] > threshold]
out.append(f"Strong peaks (>{threshold:.6f}):")
if len(strong_peaks) == 0:
out.append(" None above threshold — trying mean + 0.5*sigma...")
threshold = global_mean + 0.5 * global_std
strong_peaks = peaks_df[peaks_df['coherence'] > threshold]
for _, row in strong_peaks.iterrows():
out.append(f" Ω={row['omega']:.1f} Coh={row['coherence']:.6f} "
f"K={row['khra_amp']:.3f} G={row['gixx_amp']:.4f}")
# Cluster adjacent peaks
if len(strong_peaks) > 0:
clusters = []
current_cluster = [strong_peaks.iloc[0]['omega']]
for i in range(1, len(strong_peaks)):
if strong_peaks.iloc[i]['omega'] - strong_peaks.iloc[i-1]['omega'] <= 0.15:
current_cluster.append(strong_peaks.iloc[i]['omega'])
else:
clusters.append(current_cluster)
current_cluster = [strong_peaks.iloc[i]['omega']]
clusters.append(current_cluster)
out.append(f"\n {len(clusters)} cluster(s) of strong peaks:")
for i, cl in enumerate(clusters):
center = np.mean(cl)
out.append(f" Cluster {i+1}: Ω ∈ [{min(cl):.1f}, {max(cl):.1f}], center={center:.2f}, width={len(cl)}")
out.append(f"\nFull peak table:")
out.append(f" {'Omega':>6} {'Coherence':>10} {'Khra':>6} {'Gixx':>7} {'Asym':>8} {'Vort':>10}")
for _, row in peaks_df.iterrows():
marker = " *" if row['coherence'] > threshold else " "
out.append(f" {row['omega']:6.1f} {row['coherence']:10.6f} {row['khra_amp']:6.3f} "
f"{row['gixx_amp']:7.4f} {row['asymmetry']:8.4f} {row['vorticity']:10.6f}{marker}")
return peaks_df
# ═══════════════════════════════════════════════════════════════════
# Analysis 2: Mode Counting vs Nuclear Shell Degeneracies
# ═══════════════════════════════════════════════════════════════════
def analysis_mode_counting(df, out):
out.append("")
out.append("=" * 70)
out.append("ANALYSIS 2: Mode Counting vs Nuclear Shell Degeneracies")
out.append("=" * 70)
omega_vals = sorted(df['omega'].unique())
# For each omega slice, count distinct coherence levels
# "Distinct" = separated by more than a tolerance
all_coherences = df['coherence'].values
resolution = np.std(all_coherences) * 0.1 # adaptive resolution
if resolution < 1e-6:
resolution = 1e-4
out.append(f"\nCoherence resolution (tolerance): {resolution:.6f}")
mode_counts = {}
for omega in omega_vals:
group = df[df['omega'] == omega]
coh_sorted = np.sort(group['coherence'].values)
# Count distinct levels: merge values within resolution
modes = [coh_sorted[0]]
for c in coh_sorted[1:]:
if c - modes[-1] > resolution:
modes.append(c)
mode_counts[omega] = len(modes)
out.append(f"\nDistinct coherence modes per omega slice:")
out.append(f" {'Omega':>6} {'Modes':>6} {'Nearest Magic':>14} {'Δ':>4}")
total_modes = []
for omega in omega_vals:
n = mode_counts[omega]
total_modes.append(n)
nearest_magic = min(MAGIC_NUMBERS, key=lambda m: abs(m - n))
delta = n - nearest_magic
marker = " <<<" if delta == 0 else ""
out.append(f" {omega:6.1f} {n:6d} {nearest_magic:14d} {delta:+4d}{marker}")
# Overall statistics
mode_arr = np.array(total_modes)
out.append(f"\n Mode count range: [{mode_arr.min()}, {mode_arr.max()}]")
out.append(f" Mean modes: {mode_arr.mean():.1f}")
# Cumulative mode count across all omega
all_coh = np.sort(df['coherence'].unique())
distinct_global = [all_coh[0]]
for c in all_coh[1:]:
if c - distinct_global[-1] > resolution:
distinct_global.append(c)
out.append(f" Total distinct global modes: {len(distinct_global)}")
# Compare to magic numbers
out.append(f"\n Magic number proximity:")
for mn in MAGIC_NUMBERS[:6]:
hits = [omega for omega, n in mode_counts.items() if n == mn]
if hits:
out.append(f" N={mn}: matched at Ω = {', '.join(f'{h:.1f}' for h in hits)}")
else:
closest = min(mode_counts.items(), key=lambda x: abs(x[1] - mn))
out.append(f" N={mn}: no exact match (closest: Ω={closest[0]:.1f} with {closest[1]} modes)")
return mode_counts
# ═══════════════════════════════════════════════════════════════════
# Analysis 3: Gap Structure
# ═══════════════════════════════════════════════════════════════════
def analysis_gap_structure(df, out):
out.append("")
out.append("=" * 70)
out.append("ANALYSIS 3: Gap Structure (Coherence Gaps vs Nuclear Shell Gaps)")
out.append("=" * 70)
# Global coherence spectrum: sort all unique values, compute gaps
coh_all = np.sort(df['coherence'].unique())
gaps = np.diff(coh_all)
out.append(f"\nGlobal coherence spectrum: {len(coh_all)} unique values")
out.append(f" Value range: [{coh_all[0]:.6f}, {coh_all[-1]:.6f}]")
out.append(f" Total span: {coh_all[-1] - coh_all[0]:.6f}")
if len(gaps) > 0:
out.append(f"\nGap statistics:")
out.append(f" Mean gap: {gaps.mean():.6f}")
out.append(f" Std gap: {gaps.std():.6f}")
out.append(f" Min gap: {gaps.min():.6f}")
out.append(f" Max gap: {gaps.max():.6f}")
# Find the largest gaps — these correspond to "shell closures"
n_top = min(10, len(gaps))
top_idx = np.argsort(gaps)[-n_top:][::-1]
out.append(f"\n Top {n_top} largest gaps (shell boundaries):")
out.append(f" {'Rank':>4} {'Gap':>10} {'Below':>10} {'Above':>10} {'Ratio':>8}")
gap_ratios = []
for rank, idx in enumerate(top_idx):
ratio = gaps[idx] / gaps.mean() if gaps.mean() > 0 else 0
gap_ratios.append(gaps[idx])
out.append(f" {rank+1:4d} {gaps[idx]:10.6f} {coh_all[idx]:10.6f} "
f"{coh_all[idx+1]:10.6f} {ratio:8.2f}x")
# Compare gap ratios to nuclear shell gap ratios
if len(gap_ratios) >= 5:
observed_ratios = np.array(gap_ratios[:5]) / gap_ratios[0]
out.append(f"\n Gap ratio comparison (top 5 gaps, normalized to largest):")
out.append(f" Observed: {', '.join(f'{r:.3f}' for r in observed_ratios)}")
out.append(f" Nuclear: {', '.join(f'{r:.3f}' for r in MAGIC_GAP_RATIOS)}")
correlation = np.corrcoef(observed_ratios, MAGIC_GAP_RATIOS[:5])[0, 1]
out.append(f" Pearson correlation: {correlation:.4f}")
# Per-omega gap structure
out.append(f"\n Per-omega max gap:")
omega_vals = sorted(df['omega'].unique())
for omega in omega_vals:
group = df[df['omega'] == omega]
coh_sorted = np.sort(group['coherence'].values)
g = np.diff(coh_sorted)
if len(g) > 0:
max_gap = g.max()
mean_gap = g.mean()
ratio = max_gap / mean_gap if mean_gap > 0 else 0
out.append(f" Ω={omega:.1f}: max_gap={max_gap:.6f} mean_gap={mean_gap:.6f} "
f"ratio={ratio:.2f}x")
# ═══════════════════════════════════════════════════════════════════
# Analysis 4: Omega-Resolved Shell Occupancy
# ═══════════════════════════════════════════════════════════════════
def analysis_shell_occupancy(df, out):
out.append("")
out.append("=" * 70)
out.append("ANALYSIS 4: Omega-Resolved Shell Occupancy")
out.append("=" * 70)
global_mean = df['coherence'].mean()
global_std = df['coherence'].std()
# Define "shells" as coherence bands
n_shells = 6
coh_min = df['coherence'].min()
coh_max = df['coherence'].max()
shell_edges = np.linspace(coh_min, coh_max + 1e-9, n_shells + 1)
out.append(f"\nShell definition: {n_shells} equal-width coherence bands")
out.append(f" Coherence range: [{coh_min:.6f}, {coh_max:.6f}]")
out.append(f" Shell width: {(coh_max - coh_min) / n_shells:.6f}")
out.append("")
omega_vals = sorted(df['omega'].unique())
# Build occupancy matrix: omega × shell
occupancy = np.zeros((len(omega_vals), n_shells), dtype=int)
for i, omega in enumerate(omega_vals):
group = df[df['omega'] == omega]
for j in range(n_shells):
count = ((group['coherence'] >= shell_edges[j]) &
(group['coherence'] < shell_edges[j+1])).sum()
occupancy[i, j] = count
# Display occupancy matrix
header = f" {'Omega':>6} " + " ".join(f"S{j+1:d}" for j in range(n_shells)) + " Total Pattern"
out.append(header)
for i, omega in enumerate(omega_vals):
row = occupancy[i]
total = row.sum()
# Binary pattern: 1 if occupied, 0 if not
pattern = "".join("" if x > 0 else "·" for x in row)
out.append(f" {omega:6.1f} " + " ".join(f"{x:2d}" for x in row) +
f" {total:5d} {pattern}")
# Count unique occupancy patterns
patterns = ["".join("1" if x > 0 else "0" for x in occupancy[i]) for i in range(len(omega_vals))]
pattern_counts = Counter(patterns)
out.append(f"\n Unique occupancy patterns: {len(pattern_counts)}")
for pat, count in sorted(pattern_counts.items(), key=lambda x: -x[1]):
visual = "".join("" if c == "1" else "·" for c in pat)
out.append(f" {visual} ({pat}): {count} omega values")
# Shell filling: total occupancy per shell across all omega
shell_totals = occupancy.sum(axis=0)
out.append(f"\n Total occupancy per shell:")
for j in range(n_shells):
bar = "" * (shell_totals[j] // 2) if shell_totals[j] > 0 else ""
out.append(f" S{j+1} [{shell_edges[j]:.5f} {shell_edges[j+1]:.5f}]: "
f"{shell_totals[j]:4d} {bar}")
# Compare to nuclear filling order
if len(omega_vals) >= 5:
# "Closed shell" = omega where all points fall in same shell
closed = []
for i, omega in enumerate(omega_vals):
nonzero = np.count_nonzero(occupancy[i])
if nonzero == 1:
filled_shell = np.argmax(occupancy[i])
closed.append((omega, filled_shell + 1))
out.append(f"\n Closed-shell configurations (all points in one band):")
if closed:
for omega, shell in closed:
out.append(f" Ω={omega:.1f} → Shell {shell}")
else:
out.append(f" None found (points spread across multiple bands)")
# ═══════════════════════════════════════════════════════════════════
# Analysis 5: 2D Torus Mode Comparison
# ═══════════════════════════════════════════════════════════════════
def analysis_torus_modes(df, out):
out.append("")
out.append("=" * 70)
out.append("ANALYSIS 5: 2D Torus Mode Comparison")
out.append("=" * 70)
# On a 2D torus (periodic lattice), modes are labeled (n, m)
# with energy ~ n² + m². Degeneracy = # of (n,m) pairs giving same E.
# This is the sum-of-two-squares function r₂(E).
max_E = 50
torus_degeneracy = {}
for n in range(-int(np.sqrt(max_E)) - 1, int(np.sqrt(max_E)) + 2):
for m in range(-int(np.sqrt(max_E)) - 1, int(np.sqrt(max_E)) + 2):
E = n * n + m * m
if 0 < E <= max_E:
torus_degeneracy[E] = torus_degeneracy.get(E, 0) + 1
torus_energies = sorted(torus_degeneracy.keys())
torus_degens = [torus_degeneracy[E] for E in torus_energies]
out.append(f"\nTheoretical 2D torus modes (E = n² + m², E ≤ {max_E}):")
out.append(f" Representable energies: {len(torus_energies)}")
out.append(f" Cumulative modes at each energy:")
cumulative = np.cumsum(torus_degens)
out.append(f" {'E':>4} {'Degen':>6} {'Cumul':>6} {'Magic?':>7}")
for E, d, c in zip(torus_energies, torus_degens, cumulative):
magic_hit = " <<<" if c in MAGIC_NUMBERS else ""
out.append(f" {E:4d} {d:6d} {c:6d}{magic_hit}")
# Compare torus cumulative degeneracies to magic numbers
magic_hits = []
for mn in MAGIC_NUMBERS[:6]:
if mn in cumulative.tolist():
idx = cumulative.tolist().index(mn)
magic_hits.append((mn, torus_energies[idx]))
out.append(f"\n Torus shell closures matching magic numbers:")
if magic_hits:
for mn, E in magic_hits:
out.append(f" Magic N={mn} occurs at torus energy E={E}")
else:
out.append(f" No exact matches")
# Find nearest
for mn in MAGIC_NUMBERS[:6]:
nearest_idx = np.argmin(np.abs(cumulative - mn))
out.append(f" Magic N={mn}: nearest cumulative = {cumulative[nearest_idx]} at E={torus_energies[nearest_idx]}")
# Now compare to observed data
# Use coherence as proxy for "energy level"
# Count modes in the observed spectrum and compare degeneracies
omega_vals = sorted(df['omega'].unique())
resolution = np.std(df['coherence'].values) * 0.1
if resolution < 1e-6:
resolution = 1e-4
# Per-omega mode degeneracy: count how many (khra, gixx) pairs
# give the same coherence level (within resolution)
out.append(f"\n Observed mode degeneracies per omega:")
out.append(f" {'Omega':>6} {'Modes':>6} {'Max Degen':>10} {'Degen Pattern':>20}")
for omega in omega_vals:
group = df[df['omega'] == omega]
coh_sorted = np.sort(group['coherence'].values)
# Bin into distinct modes
modes = []
current_mode = [coh_sorted[0]]
for c in coh_sorted[1:]:
if c - current_mode[-1] > resolution:
modes.append(len(current_mode))
current_mode = [c]
else:
current_mode.append(c)
modes.append(len(current_mode))
# modes[] now holds the degeneracy of each mode
max_degen = max(modes)
pattern = ",".join(str(d) for d in modes[:8])
if len(modes) > 8:
pattern += "..."
out.append(f" {omega:6.1f} {len(modes):6d} {max_degen:10d} {pattern:>20}")
# Correlation between observed degeneracy spectrum and torus degeneracies
# Use the full dataset: histogram of degeneracies
all_coh = np.sort(df['coherence'].values)
global_modes = []
current_mode = [all_coh[0]]
for c in all_coh[1:]:
if c - current_mode[-1] > resolution:
global_modes.append(len(current_mode))
current_mode = [c]
else:
current_mode.append(c)
global_modes.append(len(current_mode))
obs_degen_hist = Counter(global_modes)
torus_degen_hist = Counter(torus_degens)
out.append(f"\n Degeneracy histograms:")
out.append(f" {'Degen':>6} {'Observed':>9} {'Torus':>6}")
all_degens = sorted(set(list(obs_degen_hist.keys()) + list(torus_degen_hist.keys())))
for d in all_degens[:15]:
out.append(f" {d:6d} {obs_degen_hist.get(d, 0):9d} {torus_degen_hist.get(d, 0):6d}")
# ═══════════════════════════════════════════════════════════════════
# Analysis 6: GUE Pair Correlation (Random Matrix Theory)
# ═══════════════════════════════════════════════════════════════════
def analysis_gue_correlation(df, out):
out.append("")
out.append("=" * 70)
out.append("ANALYSIS 6: GUE Pair Correlation (Random Matrix Theory)")
out.append("=" * 70)
# Nearest-neighbor spacing distribution
# For GUE (β=2): P(s) = (32/π²) s² exp(-4s²/π) (Wigner surmise)
# For Poisson: P(s) = exp(-s)
# Normalize spacings to mean = 1
coh_sorted = np.sort(df['coherence'].unique())
spacings = np.diff(coh_sorted)
if len(spacings) < 5:
out.append("\n Insufficient unique coherence values for spacing analysis.")
return
mean_spacing = spacings.mean()
if mean_spacing > 0:
s_normalized = spacings / mean_spacing # normalize to <s> = 1
else:
out.append("\n Zero mean spacing — all values identical.")
return
out.append(f"\nSpacing statistics (normalized to mean=1):")
out.append(f" N unique levels: {len(coh_sorted)}")
out.append(f" N spacings: {len(spacings)}")
out.append(f" Raw mean spacing: {mean_spacing:.6e}")
out.append(f" Normalized <s>: {s_normalized.mean():.4f}")
out.append(f" Normalized var: {np.var(s_normalized):.4f}")
out.append(f" Normalized <s²>: {np.mean(s_normalized**2):.4f}")
# GUE prediction: var(s) = (4 - π) * π / (2π²) ≈ 0.178
# Poisson prediction: var(s) = 1.0
gue_var = (4 - np.pi) * np.pi / (2 * np.pi**2)
obs_var = np.var(s_normalized)
out.append(f"\n Variance comparison:")
out.append(f" Observed: {obs_var:.4f}")
out.append(f" GUE (β=2): {gue_var:.4f}")
out.append(f" Poisson: 1.0000")
if abs(obs_var - gue_var) < abs(obs_var - 1.0):
out.append(f" → Closer to GUE (level repulsion present)")
else:
out.append(f" → Closer to Poisson (uncorrelated levels)")
# Histogram of normalized spacings
n_bins = 20
bin_edges = np.linspace(0, max(4.0, s_normalized.max()), n_bins + 1)
hist, _ = np.histogram(s_normalized, bins=bin_edges, density=True)
bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])
# Theoretical curves
gue_pdf = (32.0 / (np.pi**2)) * bin_centers**2 * np.exp(-4.0 * bin_centers**2 / np.pi)
poisson_pdf = np.exp(-bin_centers)
out.append(f"\n Spacing distribution P(s):")
out.append(f" {'s':>6} {'Observed':>9} {'GUE':>7} {'Poisson':>8}")
for i in range(n_bins):
out.append(f" {bin_centers[i]:6.2f} {hist[i]:9.4f} {gue_pdf[i]:7.4f} {poisson_pdf[i]:8.4f}")
# Chi-squared goodness of fit (manual, no scipy)
# Against GUE and Poisson
chi2_gue = 0
chi2_poisson = 0
bins_used = 0
for i in range(n_bins):
if gue_pdf[i] > 0.01: # only use bins with sufficient expected density
chi2_gue += (hist[i] - gue_pdf[i])**2 / gue_pdf[i]
bins_used += 1
if poisson_pdf[i] > 0.01:
chi2_poisson += (hist[i] - poisson_pdf[i])**2 / poisson_pdf[i]
out.append(f"\n Goodness of fit (χ²-like, lower is better):")
out.append(f" vs GUE: {chi2_gue:.4f} (over {bins_used} bins)")
out.append(f" vs Poisson: {chi2_poisson:.4f}")
if chi2_gue < chi2_poisson:
out.append(f" → GUE is better fit")
else:
out.append(f" → Poisson is better fit")
# Number variance Σ²(L): count fluctuations in intervals of length L
out.append(f"\n Number variance Σ²(L):")
out.append(f" {'L':>6} {'Σ²(obs)':>9} {'GUE':>7} {'Poisson':>8}")
for L in [0.5, 1.0, 1.5, 2.0, 3.0, 5.0]:
# Count how many spacings fall in windows of size L*mean_spacing
window = L * mean_spacing
counts = []
for start_idx in range(len(coh_sorted) - 1):
start_val = coh_sorted[start_idx]
# Count levels in [start_val, start_val + window)
n_in_window = np.sum((coh_sorted >= start_val) & (coh_sorted < start_val + window))
counts.append(n_in_window)
counts = np.array(counts, dtype=float)
sigma2_obs = np.var(counts) if len(counts) > 0 else 0
# GUE: Σ²(L) ≈ (2/π²)(ln(2πL) + γ + 1) for large L (γ = Euler-Mascheroni)
gamma_em = 0.5772156649
sigma2_gue = (2.0 / np.pi**2) * (np.log(2 * np.pi * L) + gamma_em + 1) if L > 0 else 0
sigma2_poisson = L # Poisson: Σ²(L) = L
out.append(f" {L:6.1f} {sigma2_obs:9.4f} {sigma2_gue:7.4f} {sigma2_poisson:8.4f}")
# ═══════════════════════════════════════════════════════════════════
# Main
# ═══════════════════════════════════════════════════════════════════
def main():
if len(sys.argv) < 2:
# Auto-find latest sweep CSV
sweep_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"sweep_results")
csvs = sorted([f for f in os.listdir(sweep_dir) if f.startswith("em_direct_sweep") and f.endswith(".csv")])
if not csvs:
print("Usage: python3 nuclear_magic_analyzer.py <sweep_csv>")
print(" No sweep CSVs found in sweep_results/")
sys.exit(1)
csv_path = os.path.join(sweep_dir, csvs[-1])
print(f"Auto-selected latest sweep: {csvs[-1]}")
else:
csv_path = sys.argv[1]
if not os.path.exists(csv_path):
print(f"ERROR: File not found: {csv_path}")
sys.exit(1)
df = load_sweep(csv_path)
print(f"Loaded {len(df)} data points from {os.path.basename(csv_path)}")
print(f" Omega range: {df['omega'].min():.1f} {df['omega'].max():.1f}")
print(f" Coherence range: {df['coherence'].min():.6f} {df['coherence'].max():.6f}")
print()
out = []
out.append("╔══════════════════════════════════════════════════════════════════════╗")
out.append("║ NUCLEAR MAGIC NUMBER ANALYSIS — RESONANCE ENGINE ║")
out.append("╚══════════════════════════════════════════════════════════════════════╝")
out.append(f"Source: {os.path.basename(csv_path)}")
out.append(f"Points: {len(df)}")
out.append(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
out.append(f"Omega: {df['omega'].min():.1f} {df['omega'].max():.1f} ({df['omega'].nunique()} steps)")
out.append(f"Coherence: {df['coherence'].min():.6f} {df['coherence'].max():.6f}")
# Run all six analyses
peaks_df = analysis_coherence_peaks(df, out)
mode_counts = analysis_mode_counting(df, out)
analysis_gap_structure(df, out)
analysis_shell_occupancy(df, out)
analysis_torus_modes(df, out)
analysis_gue_correlation(df, out)
# Summary
out.append("")
out.append("=" * 70)
out.append("SUMMARY")
out.append("=" * 70)
best_omega = peaks_df.loc[peaks_df['coherence'].idxmax()]
out.append(f"\n Best coherence: {best_omega['coherence']:.6f} at "
f"Ω={best_omega['omega']:.1f} K={best_omega['khra_amp']:.3f} G={best_omega['gixx_amp']:.4f}")
mode_arr = np.array(list(mode_counts.values()))
out.append(f" Mode count range: {mode_arr.min()} {mode_arr.max()}")
magic_matches = sum(1 for n in mode_counts.values() if n in MAGIC_NUMBERS)
out.append(f" Omega slices matching a magic number: {magic_matches}/{len(mode_counts)}")
out.append(f"\n Nuclear magic numbers for reference: {MAGIC_NUMBERS}")
out.append("")
# Print to stdout
report = "\n".join(out)
print(report)
# Save to file
results_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"results")
os.makedirs(results_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(results_dir, f"nuclear_magic_analysis_{timestamp}.txt")
with open(output_path, 'w', encoding='utf-8') as f:
f.write(report)
print(f"\nSaved to: {output_path}")
if __name__ == "__main__":
main()