STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
unify_capture_anchor.py
Go to the documentation of this file.
1"""Capture a golden anchor fixture for the unification effort (UNIFICATION_PLAN.md Phase 1).
2
3Runs deterministic forward evaluations against a target EngineDesign tree (main worktree or HEAD)
4and dumps the physics outputs as JSON. Stability fields are recorded for reference but are NOT part
5of the equivalence set (the stability model intentionally changed on this branch).
6
7Usage:
8 python scripts/unify_capture_anchor.py <target_engine_design_root> <config_yaml> <out_json>
9
10IMPORTANT: run with cwd = <target_engine_design_root> so the target tree's `engine` package is the
11one imported (sys.path[0] insertion below enforces it as well).
12"""
13
14import json
15import sys
16from pathlib import Path
17
18import numpy as np
19
20
22 if isinstance(v, (np.floating, np.integer)):
23 return float(v)
24 if isinstance(v, (int, float, str, bool)) or v is None:
25 return v
26 return None # drop arrays/objects — anchors compare scalars only
27
28
29# Physics fields that define anchor equivalence (stability excluded by design).
30ANCHOR_FIELDS = [
31 "Pc", "F", "Isp", "MR", "mdot_O", "mdot_F", "mdot_total",
32 "P_exit", "Tc", "Cf", "v_exit", "M_exit", "eps",
33]
34DIAG_FIELDS = [
35 "Cd_O", "Cd_F", "delta_p_injector_O", "delta_p_injector_F",
36 "delta_p_feed_O", "delta_p_feed_F", "D32_O", "D32_F",
37 "We_O", "We_F", "momentum_ratio_R", "TMR", "J",
38]
39
40
41def main():
42 target = Path(sys.argv[1]).resolve()
43 cfg_path = Path(sys.argv[2]).resolve()
44 out_path = Path(sys.argv[3]).resolve()
45
46 sys.path.insert(0, str(target))
47 from engine.pipeline.io import load_config # noqa: E402 (target tree)
48 from engine.core.runner import PintleEngineRunner # noqa: E402
49
50 config = load_config(str(cfg_path))
51 runner = PintleEngineRunner(config)
52
53 psi = 6894.757
54 p_lox0 = float(getattr(config.lox_tank, "initial_pressure_psi", 700.0))
55 p_fuel0 = float(getattr(config.fuel_tank, "initial_pressure_psi", 600.0))
56
57 points = []
58 for scale in (1.0, 0.9, 1.1):
59 P_O, P_F = p_lox0 * scale * psi, p_fuel0 * scale * psi
60 res = runner.evaluate(P_tank_O=P_O, P_tank_F=P_F)
61 diag = res.get("diagnostics", {}) or {}
62 points.append({
63 "P_tank_O_psi": p_lox0 * scale,
64 "P_tank_F_psi": p_fuel0 * scale,
65 "outputs": {k: _to_jsonable(res.get(k)) for k in ANCHOR_FIELDS},
66 "diagnostics": {k: _to_jsonable(diag.get(k)) for k in DIAG_FIELDS},
67 # reference-only (non-comparable): stability summary of whatever model the tree has
68 "stability_ref": {
69 "state": (res.get("stability_results") or {}).get("stability_state"),
70 "score": _to_jsonable((res.get("stability_results") or {}).get("stability_score")),
71 },
72 })
73
74 fixture = {
75 "meta": {
76 "target_tree": str(target),
77 "config": str(cfg_path),
78 "fuel": str(config.fluids["fuel"].name),
79 "oxidizer": str(config.fluids["oxidizer"].name),
80 "injector_type": str(config.injector.type),
81 "equivalence_fields": {"outputs": ANCHOR_FIELDS, "diagnostics": DIAG_FIELDS},
82 "note": "stability_ref is informational only — NOT part of anchor equivalence",
83 },
84 "points": points,
85 }
86 out_path.parent.mkdir(parents=True, exist_ok=True)
87 out_path.write_text(json.dumps(fixture, indent=2))
88 print(f"wrote {out_path}")
89 for p in points:
90 o = p["outputs"]
91 print(f" scale point: Pc={o['Pc']:.0f} Pa F={o['F']:.1f} N MR={o['MR']:.4f} Isp={o['Isp']:.2f}")
92
93
94if __name__ == "__main__":
95 main()