mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-08-06 07:35:46 +00:00
271 lines
11 KiB
Python
271 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Map historical stellar-gas law families onto current observation support."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
REPO = Path(__file__).resolve().parents[2]
|
|
DATA_DIR = REPO / "shared-data/data/stellar_gas_observation"
|
|
LADDER = DATA_DIR / "historical_stellar_gas_model_ladder.json"
|
|
FIT = DATA_DIR / "stellar_gas_shock_eigen_fit.json"
|
|
LINE_DIAGNOSTICS = DATA_DIR / "stellar_gas_line_ratio_diagnostics.json"
|
|
OUT = DATA_DIR / "historical_stellar_gas_prior_map.json"
|
|
DOC = REPO / "6-Documentation/docs/historical_stellar_gas_model_ladder_2026-05-09.md"
|
|
DESTINATION = "Gdrive:topological_storage/research-stack/stellar-gas-observation/seed-2026-05-09"
|
|
|
|
|
|
def now_iso() -> str:
|
|
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
|
|
|
|
|
def run(cmd: list[str]) -> subprocess.CompletedProcess:
|
|
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
|
|
|
|
|
|
def rclone_copyto(local: Path, remote: str) -> tuple[bool, str]:
|
|
proc = run(["rclone", "copyto", str(local), remote, "--checksum"])
|
|
message = (proc.stderr or proc.stdout).decode(errors="replace").strip()
|
|
return proc.returncode == 0, message
|
|
|
|
|
|
def load(path: Path) -> dict:
|
|
return json.loads(path.read_text())
|
|
|
|
|
|
def hook_support(model: dict, fit: dict, line_diag: dict | None) -> tuple[float, list[str], list[str]]:
|
|
agg = fit["aggregate_observables"]
|
|
available = []
|
|
missing = []
|
|
line_ratio_support = 0.0
|
|
shock_line_support = 0.0
|
|
balmer_support = 0.0
|
|
if line_diag:
|
|
line_ratio_support = min(
|
|
1.0,
|
|
line_diag.get("valid_ratio_rows", 0) / max(1, line_diag.get("rows_seen", 1)),
|
|
)
|
|
shock_line_support = float(
|
|
line_diag.get("shock_lier_support", {}).get("fractional_proxy_support", 0.0)
|
|
)
|
|
balmer_count = line_diag.get("aggregate_ratios", {}).get("balmer_decrement", {}).get("count", 0)
|
|
balmer_support = min(1.0, balmer_count / max(1, line_diag.get("rows_seen", 1)))
|
|
hook_to_observable = {
|
|
"redshift_or_distance": ["snr_med_mean"],
|
|
"position_context": ["snr_med_mean"],
|
|
"gas_velocity_span": ["gas_velocity_span_kms"],
|
|
"velocity_gradient_proxy": ["gas_velocity_span_kms", "velocity_contrast"],
|
|
"velocity_dispersion": ["gas_sigma_1re_kms"],
|
|
"gas_sigma": ["gas_sigma_1re_kms"],
|
|
"line_width": ["gas_sigma_1re_kms"],
|
|
"emission_line_flux": ["shock_proxy_score"],
|
|
"line_ratio": ["__line_ratio_support__"],
|
|
"equivalent_width": ["shock_proxy_score"],
|
|
"surface_brightness": ["shock_proxy_score"],
|
|
"attenuation": ["__balmer_decrement_support__"],
|
|
"density_or_pressure_if_available": [],
|
|
"pre_post_state_if_available": [],
|
|
"temperature_or_electron_density": [],
|
|
"stellar_context": ["stellar_sigma_1re_kms"],
|
|
"mass_radius_temperature_if_available": [],
|
|
"density": [],
|
|
"shock_front_radius_time": [],
|
|
"ambient_density": [],
|
|
"magnetic_field": [],
|
|
"outflow_velocity": ["gas_velocity_span_kms", "velocity_contrast"],
|
|
"line_asymmetry": [],
|
|
"optical_depth": [],
|
|
"diffusion_time": [],
|
|
"dynamical_time": [],
|
|
"shock_velocity": ["gas_velocity_span_kms"],
|
|
}
|
|
scores = []
|
|
for hook in model.get("observable_hooks", []):
|
|
observables = hook_to_observable.get(hook, [])
|
|
if not observables:
|
|
missing.append(hook)
|
|
continue
|
|
present = False
|
|
for obs in observables:
|
|
if obs == "__line_ratio_support__":
|
|
if line_ratio_support > 0:
|
|
available.append(f"{hook}->line_ratio_diagnostics")
|
|
present = True
|
|
# Blend general line-ratio coverage with shock-sensitive line-ratio support.
|
|
scores.append((line_ratio_support + shock_line_support) / 2)
|
|
continue
|
|
if obs == "__balmer_decrement_support__":
|
|
if balmer_support > 0:
|
|
available.append(f"{hook}->balmer_decrement")
|
|
present = True
|
|
scores.append(balmer_support)
|
|
continue
|
|
summary = agg.get(obs, {})
|
|
count = summary.get("count", 0)
|
|
mean = summary.get("mean", 0.0)
|
|
if count:
|
|
available.append(f"{hook}->{obs}")
|
|
present = True
|
|
if obs == "shock_proxy_score":
|
|
scores.append(float(mean))
|
|
else:
|
|
scores.append(min(1.0, float(count) / max(1, fit["admitted_proxy_rows"])))
|
|
if not present:
|
|
missing.append(hook)
|
|
if not scores:
|
|
return 0.0, available, missing
|
|
return round(sum(scores) / len(scores), 6), available, missing
|
|
|
|
|
|
def decision_for(score: float, missing: list[str], gate: str) -> str:
|
|
if score <= 0:
|
|
return "HOLD_NO_CURRENT_OBSERVABLE"
|
|
if missing or gate.startswith("HOLD"):
|
|
return "HOLD_PARTIAL_PRIOR_SUPPORT"
|
|
return "ADMIT_PRIOR_SUPPORT"
|
|
|
|
|
|
def build_map(ladder: dict, fit: dict, line_diag: dict | None) -> dict:
|
|
mapped = []
|
|
for model in ladder["models"]:
|
|
score, available, missing = hook_support(model, fit, line_diag)
|
|
mapped.append(
|
|
{
|
|
"id": model["id"],
|
|
"period": model["period"],
|
|
"names": model["names"],
|
|
"law_family": model["law_family"],
|
|
"equation_shape": model["equation_shape"],
|
|
"local_axis": model["local_axis"],
|
|
"current_gate": model["gate"],
|
|
"current_observation_support": score,
|
|
"available_hooks": available,
|
|
"missing_hooks": missing,
|
|
"decision": decision_for(score, missing, model["gate"]),
|
|
}
|
|
)
|
|
admitted = [m for m in mapped if m["decision"] == "ADMIT_PRIOR_SUPPORT"]
|
|
partial = [m for m in mapped if m["decision"] == "HOLD_PARTIAL_PRIOR_SUPPORT"]
|
|
return {
|
|
"schema": "historical_stellar_gas_prior_map_v0",
|
|
"created": now_iso(),
|
|
"claim_boundary": "Maps historical stellar-gas law families onto current MaNGA observation proxies. It ranks available support and missing gates; it does not validate the physical laws or infer causal shock events.",
|
|
"source_ladder": str(LADDER.relative_to(REPO)),
|
|
"source_fit": str(FIT.relative_to(REPO)),
|
|
"source_line_diagnostics": str(LINE_DIAGNOSTICS.relative_to(REPO)) if line_diag else None,
|
|
"fit_decision": fit["decision"],
|
|
"fit_refinement": fit["physical_shock_axis_refinement"],
|
|
"line_ratio_refinement": line_diag.get("shock_lier_support") if line_diag else None,
|
|
"summary": {
|
|
"model_count": len(mapped),
|
|
"admitted_prior_support": len(admitted),
|
|
"partial_prior_support": len(partial),
|
|
"no_current_observable": len(mapped) - len(admitted) - len(partial),
|
|
},
|
|
"models": mapped,
|
|
"decision": "ADMIT_HISTORICAL_PRIOR_SURFACE",
|
|
}
|
|
|
|
|
|
def write_doc(result: dict, ladder: dict) -> None:
|
|
lines = [
|
|
"# Historical Stellar Gas Model Ladder",
|
|
"",
|
|
"**Date:** 2026-05-09",
|
|
"",
|
|
f"**Decision:** `{result['decision']}`",
|
|
"",
|
|
"**Claim boundary:** this is a historical prior and routing surface. It",
|
|
"does not claim that the current MaNGA proxy fit validates any physical",
|
|
"law or detects a specific shock event.",
|
|
"",
|
|
"## Why This Helps",
|
|
"",
|
|
"The stack now has a wide historical basis for stellar gas modeling rather",
|
|
"than a single modern blob. Each law family gets a receipt role:",
|
|
"",
|
|
"```text",
|
|
"historical law -> observable hook -> current column/proxy -> gate",
|
|
"```",
|
|
"",
|
|
"## Current Support",
|
|
"",
|
|
f"- Models mapped: {result['summary']['model_count']}",
|
|
f"- Admitted prior support: {result['summary']['admitted_prior_support']}",
|
|
f"- Partial prior support: {result['summary']['partial_prior_support']}",
|
|
f"- No current observable: {result['summary']['no_current_observable']}",
|
|
"",
|
|
"## Ladder",
|
|
"",
|
|
"| Period | Law family | Names | Support | Decision |",
|
|
"|---|---|---|---:|---|",
|
|
]
|
|
for model in result["models"]:
|
|
lines.append(
|
|
f"| {model['period']} | `{model['law_family']}` | "
|
|
f"{', '.join(model['names'])} | {model['current_observation_support']:.6f} | "
|
|
f"`{model['decision']}` |"
|
|
)
|
|
lines += [
|
|
"",
|
|
"## Source Notes",
|
|
"",
|
|
]
|
|
for source in ladder.get("source_notes", []):
|
|
lines.append(f"- {source['title']}: `{source['url']}`")
|
|
lines += [
|
|
"",
|
|
"## Next Gate",
|
|
"",
|
|
"The current support is strongest for velocity, dispersion, and named",
|
|
"line-ratio proxy lanes. The next refinement is adding uncertainty,",
|
|
"electron-density, temperature, and attenuation gates so Saha, radiative",
|
|
"transfer, and shock-excitation support can move beyond proxy status.",
|
|
"",
|
|
]
|
|
DOC.write_text("\n".join(lines))
|
|
|
|
|
|
def main() -> int:
|
|
ladder = load(LADDER)
|
|
fit = load(FIT)
|
|
line_diag = load(LINE_DIAGNOSTICS) if LINE_DIAGNOSTICS.exists() else None
|
|
result = build_map(ladder, fit, line_diag)
|
|
OUT.write_text(json.dumps(result, indent=2) + "\n")
|
|
write_doc(result, ladder)
|
|
receipt_path = DATA_DIR / f"historical_stellar_gas_prior_map_receipt_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
|
receipt = {
|
|
"schema": "historical_stellar_gas_prior_map_receipt_v0",
|
|
"created": now_iso(),
|
|
"claim_boundary": result["claim_boundary"],
|
|
"ladder_file": str(LADDER.relative_to(REPO)),
|
|
"prior_map_file": str(OUT.relative_to(REPO)),
|
|
"doc_file": str(DOC.relative_to(REPO)),
|
|
"summary": result["summary"],
|
|
"decision": result["decision"],
|
|
"uploads": {},
|
|
}
|
|
receipt_path.write_text(json.dumps(receipt, indent=2) + "\n")
|
|
uploads = {
|
|
"ladder": (LADDER, f"{DESTINATION}/derived/{LADDER.name}"),
|
|
"prior_map": (OUT, f"{DESTINATION}/derived/{OUT.name}"),
|
|
"doc": (DOC, f"{DESTINATION}/docs/{DOC.name}"),
|
|
"receipt": (receipt_path, f"{DESTINATION}/receipts/{receipt_path.name}"),
|
|
}
|
|
for key, (local, remote) in uploads.items():
|
|
ok, message = rclone_copyto(local, remote)
|
|
receipt["uploads"][key] = {"drive_path": remote, "ok": ok, "message": message}
|
|
receipt_path.write_text(json.dumps(receipt, indent=2) + "\n")
|
|
if receipt["uploads"]["receipt"]["ok"]:
|
|
rclone_copyto(receipt_path, receipt["uploads"]["receipt"]["drive_path"])
|
|
print(json.dumps(receipt, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|