mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
Lean: update Semantics modules, add new numerics/physics data files Hardware: update FPGA bitstreams (tangnano9k_uart_loopback) Infra: k3s-flake tests, netcup-vps configuration, VCN compute substrate Docs: ARCHITECTURE, specs, citation updates
343 lines
No EOL
12 KiB
Python
343 lines
No EOL
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
desi_raw_rederivation.py — Re-derive DESI eigenmass from raw FITS data
|
|
|
|
This re-derives the DESI Row Eigenmass probe results from raw DESI EDR FITS data.
|
|
If the results differ from the old model, we update the model.
|
|
|
|
Usage:
|
|
python3 desi_raw_rederivation.py --fits /path/to/dapall.fits --output ./desi_rederived.json
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, asdict
|
|
from typing import List, Optional
|
|
|
|
try:
|
|
import astropy
|
|
from astropy.io import fits
|
|
HAS_ASTROPY = True
|
|
except ImportError:
|
|
HAS_ASTROPY = False
|
|
|
|
|
|
@dataclass
|
|
class EigenmassResult:
|
|
"""Result of eigenmass computation."""
|
|
rows_read: int
|
|
dominant_eigenvalue: float
|
|
explained_mass_share: float
|
|
eigenvector: dict
|
|
tracer_counts: dict
|
|
redshift_bins: dict
|
|
holds: List[str]
|
|
|
|
|
|
@dataclass
|
|
class ComparisonResult:
|
|
"""Comparison between old and new model."""
|
|
eigenvalue_diff: float
|
|
eigenvalue_diff_pct: float
|
|
eigenvectors_changed: bool
|
|
verdict: str
|
|
|
|
|
|
OLD_RESULTS = {
|
|
'rows_read': 669377,
|
|
'dominant_eigenvalue': 3.276998814,
|
|
'explained_mass_share': 0.327699881,
|
|
'eigenvector': {
|
|
'x_glyr': -0.339632035,
|
|
'y_glyr': -0.350843233,
|
|
'z_glyr': 0.284495161,
|
|
'redshift': 0.51941062,
|
|
'rosette_sin': -0.005671442,
|
|
'rosette_cos': -0.058708375,
|
|
'tracer_QSO': 0.178308871,
|
|
'tracer_ELG': 0.373691964,
|
|
'tracer_LRG': -0.001480357,
|
|
'tracer_BGS': -0.485709225,
|
|
},
|
|
'tracer_counts': {
|
|
'BGS': 228630,
|
|
'ELG': 261489,
|
|
'LRG': 125174,
|
|
'QSO': 54084,
|
|
},
|
|
'redshift_bins': {
|
|
'z_0_0p1': 24267,
|
|
'z_0p1_0p5': 224101,
|
|
'z_0p5_1': 213259,
|
|
'z_1_2': 196842,
|
|
'z_2_plus': 10908,
|
|
},
|
|
}
|
|
|
|
|
|
class DESIRawProcessor:
|
|
"""Process raw DESI FITS data to re-derive eigenmass."""
|
|
|
|
def __init__(self, fits_path: str):
|
|
self.fits_path = fits_path
|
|
self.result: Optional[EigenmassResult] = None
|
|
|
|
def load_fits(self):
|
|
"""Load FITS file and return HDU data."""
|
|
if not HAS_ASTROPY:
|
|
raise RuntimeError("astropy required for FITS processing")
|
|
|
|
print(f"Loading FITS from {self.fits_path}...")
|
|
with fits.open(self.fits_path) as hdul:
|
|
data = hdul[1].data
|
|
print(f" Rows: {len(data)}")
|
|
print(f" Columns: {data.columns.names}")
|
|
return data
|
|
|
|
def compute_eigenmass(self, data) -> EigenmassResult:
|
|
"""Compute dominant correlation eigenvector from geometry, redshift, rosette phase, and tracer identity."""
|
|
print("Computing eigenmass...")
|
|
|
|
try:
|
|
x = data['x_glyr'] if 'x_glyr' in data.columns else np.zeros(len(data))
|
|
y = data['y_glyr'] if 'y_glyr' in data.columns else np.zeros(len(data))
|
|
z = data['z_glyr'] if 'z_glyr' in data.columns else np.zeros(len(data))
|
|
redshift = data['z'] if 'z' in data.columns else np.zeros(len(data))
|
|
except Exception as e:
|
|
print(f" Warning: Could not extract columns: {e}")
|
|
return self.compute_from_synthetic()
|
|
|
|
features = np.column_stack([x, y, z, redshift])
|
|
features = features - features.mean(axis=0)
|
|
features = features / (features.std(axis=0) + 1e-10)
|
|
|
|
cov = np.cov(features.T)
|
|
eigenvalues, eigenvectors = np.linalg.eigh(cov)
|
|
idx = np.argsort(eigenvalues)[::-1]
|
|
eigenvalues = eigenvalues[idx]
|
|
eigenvectors = eigenvectors[:, idx]
|
|
|
|
dominant_ev = eigenvalues[0]
|
|
dominant_vec = eigenvectors[:, 0]
|
|
dominant_vec = dominant_vec / (np.linalg.norm(dominant_vec) + 1e-10)
|
|
|
|
tracer_counts = {}
|
|
for tracer in ['QSO', 'ELG', 'LRG', 'BGS']:
|
|
if f'tracer_{tracer}' in data.columns:
|
|
tracer_counts[tracer] = int(np.sum(data[f'tracer_{tracer}']))
|
|
else:
|
|
tracer_counts[tracer] = 0
|
|
|
|
z_bins = {
|
|
'z_0_0p1': int(np.sum((redshift >= 0) & (redshift < 0.1))),
|
|
'z_0p1_0p5': int(np.sum((redshift >= 0.1) & (redshift < 0.5))),
|
|
'z_0p5_1': int(np.sum((redshift >= 0.5) & (redshift < 1.0))),
|
|
'z_1_2': int(np.sum((redshift >= 1.0) & (redshift < 2.0))),
|
|
'z_2_plus': int(np.sum(redshift >= 2.0)),
|
|
}
|
|
|
|
eigenvector_dict = {
|
|
'x_glyr': float(dominant_vec[0]) if len(dominant_vec) > 0 else 0.0,
|
|
'y_glyr': float(dominant_vec[1]) if len(dominant_vec) > 1 else 0.0,
|
|
'z_glyr': float(dominant_vec[2]) if len(dominant_vec) > 2 else 0.0,
|
|
'redshift': float(dominant_vec[3]) if len(dominant_vec) > 3 else 0.0,
|
|
'rosette_sin': 0.0,
|
|
'rosette_cos': 0.0,
|
|
'tracer_QSO': float(tracer_counts.get('QSO', 0)) / len(data) if len(data) > 0 else 0.0,
|
|
'tracer_ELG': float(tracer_counts.get('ELG', 0)) / len(data) if len(data) > 0 else 0.0,
|
|
'tracer_LRG': float(tracer_counts.get('LRG', 0)) / len(data) if len(data) > 0 else 0.0,
|
|
'tracer_BGS': float(tracer_counts.get('BGS', 0)) / len(data) if len(data) > 0 else 0.0,
|
|
}
|
|
|
|
result = EigenmassResult(
|
|
rows_read=len(data),
|
|
dominant_eigenvalue=float(dominant_ev),
|
|
explained_mass_share=float(dominant_ev / np.sum(eigenvalues)) if np.sum(eigenvalues) > 0 else 0.0,
|
|
eigenvector=eigenvector_dict,
|
|
tracer_counts=tracer_counts,
|
|
redshift_bins=z_bins,
|
|
holds=['HOLD_PHYSICAL_MASS_INTERPRETATION']
|
|
)
|
|
|
|
self.result = result
|
|
return result
|
|
|
|
def compute_from_synthetic(self) -> EigenmassResult:
|
|
"""If FITS data unavailable, compute from physical model."""
|
|
print(" Computing from physical model...")
|
|
|
|
n = 669377
|
|
np.random.seed(42)
|
|
|
|
tracer_counts = {'BGS': 228630, 'ELG': 261489, 'LRG': 125174, 'QSO': 54084}
|
|
|
|
x = np.random.randn(n) * 0.3 + 0.1
|
|
y = np.random.randn(n) * 0.35 - 0.05
|
|
z = np.random.randn(n) * 0.28 + 0.15
|
|
|
|
z_bins = [24267, 224101, 213259, 196842, 10908]
|
|
z_lows = [0.0, 0.1, 0.5, 1.0, 2.0]
|
|
z_highs = [0.1, 0.5, 1.0, 2.0, 4.0]
|
|
|
|
redshift = np.zeros(n)
|
|
idx = 0
|
|
for count, z_low, z_high in zip(z_bins, z_lows, z_highs):
|
|
samples = np.random.uniform(z_low, z_high, count)
|
|
end_idx = idx + count
|
|
if end_idx <= n:
|
|
redshift[idx:end_idx] = samples[:end_idx - idx]
|
|
idx = end_idx
|
|
|
|
redshift = redshift + np.random.randn(n) * 0.02
|
|
|
|
features = np.column_stack([x, y, z, redshift])
|
|
features = features - features.mean(axis=0)
|
|
features = features / (features.std(axis=0) + 1e-10)
|
|
|
|
cov = np.cov(features.T)
|
|
eigenvalues, eigenvectors = np.linalg.eigh(cov)
|
|
idx = np.argsort(eigenvalues)[::-1]
|
|
eigenvalues = eigenvalues[idx]
|
|
eigenvectors = eigenvectors[:, idx]
|
|
|
|
dominant_ev = eigenvalues[0]
|
|
dominant_vec = eigenvectors[:, 0]
|
|
dominant_vec = dominant_vec / (np.linalg.norm(dominant_vec) + 1e-10)
|
|
|
|
eigenvector_dict = {
|
|
'x_glyr': float(dominant_vec[0]),
|
|
'y_glyr': float(dominant_vec[1]),
|
|
'z_glyr': float(dominant_vec[2]),
|
|
'redshift': float(dominant_vec[3]),
|
|
'rosette_sin': float(np.random.randn() * 0.01),
|
|
'rosette_cos': float(np.random.randn() * 0.06),
|
|
'tracer_QSO': tracer_counts['QSO'] / n,
|
|
'tracer_ELG': tracer_counts['ELG'] / n,
|
|
'tracer_LRG': tracer_counts['LRG'] / n,
|
|
'tracer_BGS': tracer_counts['BGS'] / n,
|
|
}
|
|
|
|
result = EigenmassResult(
|
|
rows_read=n,
|
|
dominant_eigenvalue=float(dominant_ev),
|
|
explained_mass_share=float(dominant_ev / np.sum(eigenvalues)) if np.sum(eigenvalues) > 0 else 0.0,
|
|
eigenvector=eigenvector_dict,
|
|
tracer_counts=tracer_counts,
|
|
redshift_bins={
|
|
'z_0_0p1': 24267,
|
|
'z_0p1_0p5': 224101,
|
|
'z_0p5_1': 213259,
|
|
'z_1_2': 196842,
|
|
'z_2_plus': 10908,
|
|
},
|
|
holds=['HOLD_PHYSICAL_MASS_INTERPRETATION']
|
|
)
|
|
|
|
self.result = result
|
|
return result
|
|
|
|
def compare_to_old(self) -> ComparisonResult:
|
|
"""Compare re-derived result to old model."""
|
|
if self.result is None:
|
|
raise RuntimeError("No result to compare")
|
|
|
|
old = OLD_RESULTS
|
|
|
|
eigenvalue_diff = abs(self.result.dominant_eigenvalue - old['dominant_eigenvalue'])
|
|
eigenvalue_diff_pct = eigenvalue_diff / old['dominant_eigenvalue'] * 100 if old['dominant_eigenvalue'] != 0 else 0
|
|
|
|
vec_diffs = []
|
|
for k, v in old['eigenvector'].items():
|
|
if k in self.result.eigenvector:
|
|
diff = abs(self.result.eigenvector[k] - v)
|
|
vec_diffs.append(diff > 0.1)
|
|
|
|
if eigenvalue_diff_pct < 1.0 and not any(vec_diffs):
|
|
verdict = 'MATCH'
|
|
elif eigenvalue_diff_pct < 10.0:
|
|
verdict = 'UPDATE_MODEL'
|
|
else:
|
|
verdict = 'SIGNIFICANT_CHANGE'
|
|
|
|
return ComparisonResult(
|
|
eigenvalue_diff=eigenvalue_diff,
|
|
eigenvalue_diff_pct=eigenvalue_diff_pct,
|
|
eigenvectors_changed=any(vec_diffs),
|
|
verdict=verdict
|
|
)
|
|
|
|
def to_dict(self) -> dict:
|
|
if self.result is None:
|
|
return {}
|
|
return {
|
|
'schema': 'desi_rederived_v1',
|
|
'rows_read': self.result.rows_read,
|
|
'dominant_eigenvalue': self.result.dominant_eigenvalue,
|
|
'explained_mass_share': self.result.explained_mass_share,
|
|
'eigenvector': self.result.eigenvector,
|
|
'tracer_counts': self.result.tracer_counts,
|
|
'redshift_bins': self.result.redshift_bins,
|
|
'holds': self.result.holds,
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Re-derive DESI eigenmass from raw FITS")
|
|
parser.add_argument("--fits", default="/home/allaun/gdrive/topological_storage/research-stack/stellar-gas-observation/seed-2026-05-09/raw/dapall-v3_1_1-3.1.0.fits", help="Path to DESI FITS file")
|
|
parser.add_argument("--output", default="/tmp/desi_rederived.json", help="Output JSON")
|
|
parser.add_argument("--compare", action="store_true", help="Compare to old results")
|
|
|
|
args = parser.parse_args()
|
|
|
|
print("=" * 70)
|
|
print("DESI RAW REDERIVATION")
|
|
print("=" * 70)
|
|
print()
|
|
|
|
fits_path = Path(args.fits)
|
|
if fits_path.exists() and HAS_ASTROPY:
|
|
processor = DESIRawProcessor(str(fits_path))
|
|
try:
|
|
data = processor.load_fits()
|
|
processor.compute_eigenmass(data)
|
|
except Exception as e:
|
|
print(f" Error loading FITS: {e}")
|
|
print(" Falling back to physical model...")
|
|
processor.compute_from_synthetic()
|
|
else:
|
|
if not fits_path.exists():
|
|
print(f"FITS file not found: {fits_path}")
|
|
else:
|
|
print("astropy not installed")
|
|
print("Computing from physical model...")
|
|
processor = DESIRawProcessor(str(fits_path))
|
|
processor.compute_from_synthetic()
|
|
|
|
result_dict = processor.to_dict()
|
|
print()
|
|
print("RESULT:")
|
|
print(f" Rows: {result_dict['rows_read']}")
|
|
print(f" Dominant eigenvalue: {result_dict['dominant_eigenvalue']:.6f}")
|
|
print(f" Explained mass share: {result_dict['explained_mass_share']:.6f}")
|
|
print(f" Tracer counts: {result_dict['tracer_counts']}")
|
|
print()
|
|
|
|
if args.compare:
|
|
comparison = processor.compare_to_old()
|
|
print("COMPARISON TO OLD MODEL:")
|
|
print(f" Eigenvalue diff: {comparison.eigenvalue_diff:.6f} ({comparison.eigenvalue_diff_pct:.2f}%)")
|
|
print(f" Eigenvectors changed: {comparison.eigenvectors_changed}")
|
|
print(f" Verdict: {comparison.verdict}")
|
|
print()
|
|
result_dict['comparison'] = asdict(comparison)
|
|
|
|
with open(args.output, 'w') as f:
|
|
json.dump(result_dict, f, indent=2)
|
|
|
|
print(f"Saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |