Skip to content

Commit fd00401

Browse files
committed
Paper 12: Add Code/, trace.yaml, reproduce.py, meta.yaml, README
Full paper structure following PACSeries conventions: - Code/trace.yaml: 10 experiments traced to source repos - Code/experiments/: 9 experiment scripts copied from midnight/ - Code/reproduce.py: reproduction entry point with data download instructions - Data/ directories for catalogs and results - meta.yaml with key results and pre-registration info - README.md with reproduction instructions Pre-registration commit 193c87d linked in trace.yaml. Data sources: SDSS DR16 (MgII, FeII), DR12 (CIV), XQR-30 (multi-ion).
1 parent f5ff7fd commit fd00401

13 files changed

Lines changed: 3710 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
"""
2+
exp_03 -- Photon Archaeology: Alpha Invariance and SEC-Encoded Line Widths
3+
4+
Midnight Initiative, Thread 1 (Photon Archaeology)
5+
6+
Hypothesis: Ancient photons carry two independent signatures. Spectral line
7+
RATIOS encode PAC structure (ADE graph eigenvalues) and are epoch-invariant.
8+
Spectral line WIDTHS encode SEC state (disequilibrium at the current cascade
9+
level) and are epoch-dependent.
10+
11+
DFT predicts alpha_EM is structurally invariant: alpha = 2/(3*phi*F_10) *
12+
(1 - F_10/(4*pi*F_7^2)). Every component is either a Fibonacci number
13+
(integer) or phi (the unique PAC fixed point). No parameter can drift.
14+
This contradicts Webb et al. (Delta_alpha/alpha ~ 10^{-5}).
15+
16+
Tests:
17+
T1: Alpha formula within 6 ppm, perturbation of any component breaks it
18+
T2: A_8 line ratios match hydrogen <5%, identical at all z
19+
T3: Line widths vary >1% across z, correlated with cascade disequilibrium
20+
T4: Clean PAC/SEC separation — ratio variability = 0, width variability > 1%
21+
22+
Sources: M1/M6/M8 (alpha), M9 (cascade clock), M-R exp_04/20/24
23+
"""
24+
25+
import sys
26+
import numpy as np
27+
from pathlib import Path
28+
from scipy.stats import spearmanr
29+
30+
MIDNIGHT_ROOT = Path(__file__).resolve().parent.parent
31+
EXPERIMENTS_ROOT = MIDNIGHT_ROOT.parent
32+
33+
sys.path.insert(0, str(MIDNIGHT_ROOT / "core"))
34+
sys.path.insert(0, str(EXPERIMENTS_ROOT / "milestone-r" / "core"))
35+
sys.path.insert(0, str(EXPERIMENTS_ROOT / "milestone9" / "core"))
36+
37+
from phase_rate import (
38+
PHI, INV_PHI, LN_PHI, PI,
39+
save_midnight_results, _convert_numpy,
40+
)
41+
from radiation_physics import (
42+
ALPHA_EM_DFT, RYDBERG_EV,
43+
line_width_from_disequilibrium,
44+
fib,
45+
)
46+
from infodynamics import (
47+
CascadeClock, z_to_lookback, B_DFT, cascade_clock,
48+
cascade_clock_fit,
49+
)
50+
51+
ALPHA_EM_CODATA = 7.2973525693e-3
52+
F3 = fib(3) # 2
53+
F4 = fib(4) # 3
54+
F7 = fib(7) # 13
55+
F10 = fib(10) # 55
56+
57+
58+
def alpha_from_components(f3, f4, phi, f10, f7):
59+
"""Compute alpha from the five components."""
60+
return f3 / (f4 * phi * f10) * (1.0 - f10 / (4.0 * PI * f7**2))
61+
62+
63+
# ============================================================
64+
# T1: Alpha invariance is structural
65+
# ============================================================
66+
67+
def test_T1_alpha_invariance():
68+
"""T1: Alpha formula has no continuously deformable parameter."""
69+
print("\n T1: Alpha invariance is structural, not parametric")
70+
71+
alpha_dft = alpha_from_components(F3, F4, PHI, F10, F7)
72+
ppm_base = abs(alpha_dft - ALPHA_EM_CODATA) / ALPHA_EM_CODATA * 1e6
73+
within_6ppm = ppm_base < 6.0
74+
print(f" alpha_DFT = {alpha_dft:.10e}")
75+
print(f" CODATA = {ALPHA_EM_CODATA:.10e}")
76+
print(f" Deviation: {ppm_base:.1f} ppm (<6: {within_6ppm})")
77+
78+
perturbation = 0.001 # 0.1%
79+
components = {
80+
'F3 (=2, binary charge)': (F3 * (1 + perturbation), F4, PHI, F10, F7),
81+
'F4 (=3, spatial dims)': (F3, F4 * (1 + perturbation), PHI, F10, F7),
82+
'phi (golden ratio)': (F3, F4, PHI * (1 + perturbation), F10, F7),
83+
'F10 (=55, EM depth)': (F3, F4, PHI, F10 * (1 + perturbation), F7),
84+
'F7 (=13, gauge closure)': (F3, F4, PHI, F10, F7 * (1 + perturbation)),
85+
}
86+
87+
all_sensitive = True
88+
sensitivity_results = {}
89+
for name, args in components.items():
90+
alpha_pert = alpha_from_components(*args)
91+
ppm_pert = abs(alpha_pert - ALPHA_EM_CODATA) / ALPHA_EM_CODATA * 1e6
92+
ratio = ppm_pert / ppm_base if ppm_base > 0 else float('inf')
93+
sensitive = ratio > 5
94+
all_sensitive = all_sensitive and sensitive
95+
sensitivity_results[name] = {'ppm': float(ppm_pert), 'ratio': float(ratio)}
96+
print(f" Perturb {name}: {ppm_pert:.0f} ppm ({ratio:.0f}x base)")
97+
98+
# Fixed-point verification
99+
fp_error = abs(PHI**2 - PHI - 1.0)
100+
fp_ok = fp_error < 1e-14
101+
print(f" phi^2 - phi - 1 = {fp_error:.2e} (<1e-14: {fp_ok})")
102+
103+
passed = within_6ppm and all_sensitive and fp_ok
104+
print(f" -> {'PASS' if passed else 'FAIL'}")
105+
106+
return {
107+
'test': 'T1_alpha_invariance',
108+
'alpha_dft': float(alpha_dft),
109+
'alpha_codata': float(ALPHA_EM_CODATA),
110+
'ppm': float(ppm_base),
111+
'sensitivity': sensitivity_results,
112+
'all_sensitive': all_sensitive,
113+
'fixed_point_error': float(fp_error),
114+
'PASS': passed,
115+
}
116+
117+
118+
# ============================================================
119+
# T2: Line ratios are epoch-invariant
120+
# ============================================================
121+
122+
def hydrogen_ratio(n, m):
123+
"""Hydrogen transition energy ratio E_n→m / E_Rydberg = |1/m² - 1/n²|."""
124+
return abs(1.0/m**2 - 1.0/n**2)
125+
126+
127+
def test_T2_epoch_invariant_ratios():
128+
"""T2: A_8 spectral line ratios match hydrogen and don't drift with z."""
129+
print("\n T2: Line ratios are PAC-determined and epoch-invariant")
130+
131+
# Build A_8 path graph
132+
n = 8
133+
adj = np.zeros((n, n))
134+
for i in range(n - 1):
135+
adj[i, i+1] = adj[i+1, i] = 1.0
136+
137+
D = np.diag(np.sum(adj, axis=1))
138+
L = D - adj
139+
eigvals = np.sort(np.linalg.eigvalsh(L))
140+
pos = eigvals[eigvals > 1e-10]
141+
E = np.sort(1.0 / pos)[::-1]
142+
143+
# Compare transition ratios: Lyman series (m=1)
144+
transitions = [(2,1), (3,1), (4,1), (3,2), (4,2), (5,2)]
145+
errors = []
146+
details = []
147+
for n_upper, m_lower in transitions:
148+
if n_upper - 1 >= len(E) or m_lower - 1 >= len(E):
149+
continue
150+
graph_ratio = abs(E[m_lower-1] - E[n_upper-1]) / E[0]
151+
h_ratio = hydrogen_ratio(n_upper, m_lower)
152+
if h_ratio > 0:
153+
rel_error = abs(graph_ratio - h_ratio) / h_ratio
154+
errors.append(rel_error)
155+
details.append({
156+
'transition': f'{n_upper}->{m_lower}',
157+
'graph': float(graph_ratio),
158+
'hydrogen': float(h_ratio),
159+
'error': float(rel_error),
160+
})
161+
162+
max_error = max(errors) if errors else 1.0
163+
matches_hydrogen = max_error < 0.05
164+
print(f" A_8 vs hydrogen max error: {max_error:.1%} (<5%: {matches_hydrogen})")
165+
166+
# Epoch invariance: same ratios at all z
167+
z_values = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
168+
ratios_at_z = {}
169+
for z in z_values:
170+
ratios_at_z[z] = [d['graph'] for d in details]
171+
172+
all_identical = all(
173+
np.allclose(ratios_at_z[z], ratios_at_z[0.0]) for z in z_values
174+
)
175+
print(f" Ratios identical across z={z_values}: {all_identical}")
176+
177+
passed = matches_hydrogen and all_identical
178+
print(f" -> {'PASS' if passed else 'FAIL'}")
179+
180+
return {
181+
'test': 'T2_epoch_invariant_ratios',
182+
'graph': 'A_8',
183+
'n_transitions': len(details),
184+
'max_error': float(max_error),
185+
'matches_hydrogen': matches_hydrogen,
186+
'all_identical_across_z': all_identical,
187+
'transition_details': details,
188+
'PASS': passed,
189+
}
190+
191+
192+
# ============================================================
193+
# T3: Line widths are epoch-dependent
194+
# ============================================================
195+
196+
def test_T3_epoch_dependent_widths():
197+
"""T3: Line widths vary with redshift via cascade clock disequilibrium."""
198+
print("\n T3: Line widths are SEC-determined and epoch-dependent")
199+
200+
# Fit cascade clock
201+
a_clock, slope, rms = cascade_clock_fit(constrained=True)
202+
print(f" Cascade clock: a={a_clock:.3f}, slope=1/ln(phi)={slope:.4f}")
203+
204+
# Build A_6 graph for line width computation
205+
n_graph = 6
206+
adj = np.zeros((n_graph, n_graph))
207+
for i in range(n_graph - 1):
208+
adj[i, i+1] = adj[i+1, i] = 1.0
209+
210+
z_values = [0.1, 0.3, 0.5, 0.7, 1.0, 1.5, 2.0, 2.5, 3.0]
211+
cascade_data = []
212+
213+
for z in z_values:
214+
t_look = z_to_lookback(z)
215+
N_z = cascade_clock(t_look, a_clock, B_DFT)
216+
N_z = max(N_z, 1.0)
217+
218+
# Disequilibrium: 1.0 at integer N (transition), 0.0 at half-integer (settled)
219+
dist_to_int = abs(N_z - round(N_z))
220+
diseq = 1.0 - 2.0 * dist_to_int
221+
222+
# Map to perturbation fraction
223+
diseq_frac = 0.01 + 0.19 * max(0, diseq)
224+
225+
lw = line_width_from_disequilibrium(adj, vertex=0,
226+
disequilibrium_frac=diseq_frac,
227+
n_trials=500, seed=42)
228+
229+
cascade_data.append({
230+
'z': float(z),
231+
't_lookback_gyr': float(t_look),
232+
'N': float(N_z),
233+
'disequilibrium': float(diseq),
234+
'diseq_frac': float(diseq_frac),
235+
'width_variance': float(lw['variance']),
236+
})
237+
print(f" z={z:.1f}: N={N_z:.2f}, diseq={diseq:.3f}, width={lw['variance']:.6f}")
238+
239+
widths = [d['width_variance'] for d in cascade_data]
240+
diseqs = [d['disequilibrium'] for d in cascade_data]
241+
242+
width_variation = (max(widths) - min(widths)) / np.mean(widths) if np.mean(widths) > 0 else 0
243+
varies = width_variation > 0.01
244+
245+
rho, p_val = spearmanr(diseqs, widths)
246+
correlated = abs(rho) > 0.9
247+
248+
print(f" Width variation: {width_variation:.1%} (>1%: {varies})")
249+
print(f" Spearman rho(diseq, width): {rho:.3f} (>0.9: {correlated})")
250+
251+
passed = varies and correlated
252+
print(f" -> {'PASS' if passed else 'FAIL'}")
253+
254+
return {
255+
'test': 'T3_epoch_dependent_widths',
256+
'cascade_clock': {'a': float(a_clock), 'slope': float(slope)},
257+
'cascade_data': cascade_data,
258+
'width_variation': float(width_variation),
259+
'spearman_rho': float(rho),
260+
'spearman_p': float(p_val),
261+
'PASS': passed,
262+
}
263+
264+
265+
# ============================================================
266+
# T4: Clean PAC/SEC separation
267+
# ============================================================
268+
269+
def test_T4_clean_separation(t2_result, t3_result):
270+
"""T4: Ratios don't drift (PAC), widths do (SEC)."""
271+
print("\n T4: Clean PAC/SEC separation")
272+
273+
# Ratio variability: should be zero
274+
ratio_values = [d['graph'] for d in t2_result['transition_details']]
275+
cv_ratios = np.std(ratio_values) / np.mean(ratio_values) if ratio_values else 0
276+
ratio_invariant = cv_ratios < 0.05 # some variation from graph vs hydrogen
277+
278+
# Width variability across z
279+
widths = [d['width_variance'] for d in t3_result['cascade_data']]
280+
cv_widths = np.std(widths) / np.mean(widths) if np.mean(widths) > 0 else 0
281+
width_varies = cv_widths > 0.01
282+
283+
# The key test: ratios are FIXED (no z-dependence by construction),
284+
# widths VARY with z
285+
ratio_spread_across_z = 0.0 # exactly zero — graph doesn't change
286+
width_spread_across_z = (max(widths) - min(widths)) / np.mean(widths) if widths else 0
287+
288+
print(f" Ratio spread across z: {ratio_spread_across_z:.6f} (PAC: invariant)")
289+
print(f" Width spread across z: {width_spread_across_z:.1%} (SEC: epoch-dependent)")
290+
print(f" Ratio CV: {cv_ratios:.6f}")
291+
print(f" Width CV: {cv_widths:.4f}")
292+
293+
separation = width_spread_across_z > 0.01 and ratio_spread_across_z < 1e-10
294+
295+
passed = separation
296+
print(f" -> {'PASS' if passed else 'FAIL'}")
297+
298+
return {
299+
'test': 'T4_clean_separation',
300+
'ratio_spread_across_z': float(ratio_spread_across_z),
301+
'width_spread_across_z': float(width_spread_across_z),
302+
'ratio_cv': float(cv_ratios),
303+
'width_cv': float(cv_widths),
304+
'separation': separation,
305+
'PASS': passed,
306+
}
307+
308+
309+
# ============================================================
310+
# Main
311+
# ============================================================
312+
313+
if __name__ == '__main__':
314+
print("=" * 70)
315+
print("exp_03: Photon Archaeology")
316+
print("Alpha Invariance and SEC-Encoded Line Widths")
317+
print("Midnight Initiative, Thread 1")
318+
print("=" * 70)
319+
320+
t1 = test_T1_alpha_invariance()
321+
t2 = test_T2_epoch_invariant_ratios()
322+
t3 = test_T3_epoch_dependent_widths()
323+
t4 = test_T4_clean_separation(t2, t3)
324+
325+
score = sum(1 for t in [t1, t2, t3, t4] if t['PASS'])
326+
print(f"\n{'=' * 70}")
327+
print(f" Overall: {score}/4")
328+
print(f"{'=' * 70}")
329+
330+
data = {
331+
'experiment': 'exp_03_photon_archaeology',
332+
'initiative': 'midnight',
333+
'thread': 'photon_archaeology',
334+
'test_results': {'T1': t1, 'T2': t2, 'T3': t3, 'T4': t4},
335+
'score': f"{score}/4",
336+
'n_pass': score,
337+
'n_total': 4,
338+
}
339+
340+
save_midnight_results('exp_03_photon_archaeology', _convert_numpy(data))

0 commit comments

Comments
 (0)