#!/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 Output saved to: ../results/nuclear_magic_analysis_.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 = 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_normalized.mean():.4f}") out.append(f" Normalized var: {np.var(s_normalized):.4f}") out.append(f" Normalized : {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 ") 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()