oracle7-deepspace / eval /truth.py
psiloceyeben's picture
Upload folder using huggingface_hub
3538060 verified
Raw History Blame Contribute Delete
5.9 kB
"""TRUTH physics that the onboard twin does not model. The flight code (deepspace/) is unchanged: its twin is still the
base Spacecraft class, so every term below is STRUCTURAL mismatch, not a perturbed constant.
thermal inter-zone conduction, and a radiative (T^4) share of the heat loss instead of the twin's linear law;
heater powers differ from design by x0.8-1.25 per zone
power coulombic charge efficiency 95%, an unmodelled load that random-walks within +-4% of the craft's source
(sized on dev seeds 0-3: +-0.6 A exceeded an RTG craft's whole margin and browned out healthy craft),
array current noise
sensors quantised to 0.25 C, heavy-tailed noise (2% of readings get an extra N(0, 3 C) outlier)
attitude pointing jitter 3x the design value
dropout 2% of telemetry frames never reach the flight software (handled by the runner)
"""
from __future__ import annotations
import math
import random
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from deepspace.sim import DT, ZONES, Spacecraft # noqa: E402
VARIANTS = {"base": set(), "thermal": {"thermal"}, "power": {"power"}, "sensors": {"sensors"}, "attitude": {"attitude"},
"all": {"thermal", "power", "sensors", "attitude", "dropout"}}
class TruthCraft(Spacecraft):
def __init__(self, seed=0, profile="europa_clipper", terms=()):
super().__init__(seed=seed, profile=profile)
self.terms = set(terms)
self.trng = random.Random(seed * 31 + 7)
self.load_drift = 0.0
if "thermal" in self.terms:
self.p["heater_w"] = {z: w * self.trng.uniform(0.8, 1.25) for z, w in self.p["heater_w"].items()}
def step(self):
soc0 = self.soc
super().step()
if "thermal" in self.terms:
# conduction between zones, and a radiative share of the loss (30% of the linear law at 17 C, T^4 shaped)
mean = sum(self.temp.values()) / 3.0
for z in ZONES:
self.temp[z] += DT * 0.0006 * (mean - self.temp[z]) * 0.05
tk, ek, ref = self.temp[z] + 273.15, self.p["t_env"][z] + 273.15, 290.0
rad = 0.3 * self.p["k_env"][z] * (tk ** 4 - ek ** 4) / (4 * ref ** 3)
self.temp[z] -= DT * rad * 0.05
if "power" in self.terms:
gain = self.soc - soc0
if gain > 0:
self.soc -= 0.05 * gain # coulombic loss on charge
bound = 0.04 * self.p["source_amp"]
self.load_drift = max(-bound, min(bound, self.load_drift + self.trng.gauss(0, bound / 12)))
noise = self.trng.gauss(0, 0.08)
self.i_load += self.load_drift
self.i_source += noise
self.soc = min(1.0, max(0.0, self.soc - (self.load_drift - noise) * DT / 3600.0 / self.p["cap_ah"]))
if "attitude" in self.terms:
self.pointing_err = max(0.05, self.pointing_err + self.trng.gauss(0, 0.1))
def _read(self, z, s):
v = super()._read(z, s)
if "sensors" in self.terms:
if self.trng.random() < 0.02:
v += self.trng.gauss(0, 3.0)
v = round(v * 4) / 4.0
return v
def run_truth(params, onboard, terms, ticks=320, physical=None, magnitude_override=None):
"""the runtime.run loop against TruthCraft; frames can drop out before reaching the flight software"""
sc = TruthCraft(seed=params["seed"] * 7 + 1, profile=params["profile"], terms=terms)
drop = random.Random(params["seed"] * 101 + 3)
actions, receipts = [], []
min_soc, sci_off, over_band = 1.0, 0, 0
from deepspace.sim import BANDS
for t in range(ticks):
if t == params["onset"]:
sc.inject(params["fault"], zone=params["zone"], wheel=params["wheel"], sensor=params["sensor"],
magnitude=params["magnitude"])
if magnitude_override:
magnitude_override(sc, params)
sc.step()
min_soc = min(min_soc, sc.soc)
sci_off += 0 if sc.science_on else 1
over_band += any(not BANDS[z][0] <= sc.temp[z] <= BANDS[z][1] for z in ZONES)
tm = sc.telemetry()
if "dropout" in terms and drop.random() < 0.02:
continue
r = onboard(tm)
if r:
receipts.append(r)
if r["action"] in physical:
sc.command(r["action"], **r["arg"])
actions.append((sc.t, r["action"], r["arg"]))
return {"actions": actions, "receipts": receipts, "damage": sc.damage, "safe": sc.safe, "final_soc": sc.soc,
"min_soc": min_soc, "science_off_ticks": sci_off, "out_of_band_ticks": over_band}
# out-of-range fault intensities: WEAK (near or below the detection thresholds) and STRONG (beyond the design range)
def _set(kind):
def f(sc, p):
fault = p["fault"]
if fault == "sensor_bias":
sc.sensor_bias[(p["zone"], p["sensor"])] = 9.0 if kind == "weak" else 60.0
elif fault == "wheel_friction":
sc.wheel_friction[p["wheel"]] = 3.8 if kind == "weak" else 20.0
elif fault == "solar_degradation":
sc.source_health = 0.82 if kind == "weak" else 0.45
elif fault == "rtg_decline":
sc.source_health = 0.91 if kind == "weak" else 0.70
elif fault == "battery_short":
sc.short_amp = 3.0 if kind == "weak" else 12.0
elif fault == "thruster_degradation":
sc.thruster_eff["A"] = 0.30 if kind == "weak" else 0.03
return f
INTENSITY = {"weak": _set("weak"), "strong": _set("strong")}
SCALABLE = ("sensor_bias", "wheel_friction", "solar_degradation", "rtg_decline", "battery_short", "thruster_degradation")