Download eval/truth.py from psiloceyeben/oracle7-deepspace: direct link, hf CLI and curl.
- Browser
- Download file 5.9 kB
-
https://huggingface.co/psiloceyeben/oracle7-deepspace/resolve/main/eval/truth.py
- Command line
-
hf download hf://psiloceyeben/oracle7-deepspace/eval/truth.py
-
curl -L -o truth.py https://huggingface.co/psiloceyeben/oracle7-deepspace/resolve/main/eval/truth.py
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") | |