9302a86ea8
New docs: - docs/em_spectrum_overlay.html: Full EM spectrum with Khra/Gixx lines, physics markers, cell-size slider - docs/2026-03-28_170500_cto-report_fractal-echo-analysis.txt: CTO fractal echo analysis report New scripts (Beast sweep infrastructure + analyzers): - scripts/nuclear_magic_analyzer.py: shell structure, peak clustering, mode counting, GUE tests - scripts/physics_domain_analysis.py: 4-domain physics (nuclear shells, Brillouin, band gaps, GUE) - scripts/direct_zmq_sweep.py: ZMQ direct sweep driver - scripts/sweep_real.py: real sweep execution - scripts/analyze_sweep.py, comprehensive_analysis.py: sweep analysis tools - scripts/execute_prime_mapping.py: prime lattice mapping - scripts/extrapolate_findings.py, check_extrapolation.py: extrapolation tools - scripts/eta_calc.py, time_estimate.py, updated_estimate.py, show_pattern.py: utilities Prime analysis (root): - navigator_prime_analysis.py, navigator_prime_analysis_v2.py Updated: - navigator/mock_lbm_daemon.py, navigator/telemetry_server.py
630 lines
28 KiB
Python
630 lines
28 KiB
Python
#!/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()
|