STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
run_layer1_free_throat_lox_focus_ranked.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Follow-up free-throat Layer-1 grid: emphasize LOX ΔP/Pc band via W_DP_O ≥ W_DP_F.
3
4Runs multiple optimizations (±12.5%% / ±15%% throat span × oxidizer ΔP weight sweep),
5then ranks results by user's gates + LOX proximity preference toward 0.35.
6
7Cd / injector physics unchanged (weights only).
8"""
9
10from __future__ import annotations
11
12import argparse
13import copy
14import math
15import sys
16from pathlib import Path
17from typing import Any, Dict, List, Optional, Tuple
18
19ROOT = Path(__file__).resolve().parents[1]
20sys.path.insert(0, str(ROOT))
21
22from engine.core.runner import PintleEngineRunner # noqa: E402
23from engine.optimizer.layers.layer1_static_optimization import run_layer1_optimization # noqa: E402
24from engine.pipeline.io import load_config # noqa: E402
25
26
27def _clean_frozen(req: Dict[str, Any]) -> None:
28 fp = req.get("frozen_parameters")
29 if not isinstance(fp, dict):
30 fp = {}
31 else:
32 fp = {k: v for k, v in fp.items() if v is not None}
33 fp.pop("A_throat_mm2", None)
34 req["frozen_parameters"] = fp
35
36
37def _injector_dp_ratios(perf: Dict[str, Any]) -> Tuple[float, float]:
38 ro = perf.get("injector_dp_ratio_O")
39 rf = perf.get("injector_dp_ratio_F")
40 if ro is not None and rf is not None:
41 return float(ro), float(rf)
42 diag = perf.get("diagnostics") or {}
43 pc = float(perf.get("Pc", float("nan")))
44 dpo = diag.get("delta_p_injector_O")
45 dpf = diag.get("delta_p_injector_F")
46 ro_v = float(dpo) / pc if dpo is not None and pc > 0 else float("nan")
47 rf_v = float(dpf) / pc if dpf is not None and pc > 0 else float("nan")
48 return ro_v, rf_v
49
50
52 perf: Dict[str, Any],
53 *,
54 o_band: Tuple[float, float],
55 f_cap: float,
56) -> Tuple[bool, bool, bool, bool, bool]:
57 F = float(perf.get("F", float("nan")))
58 MR = float(perf.get("MR", float("nan")))
59 R = perf.get("momentum_ratio_R")
60 R = float(R) if R is not None else float("nan")
61 ro, rf = _injector_dp_ratios(perf)
62 ok_f = abs(F - 8000.0) / 8000.0 <= 0.05 if math.isfinite(F) else False
63 ok_mr = abs(MR - 3.5) / 3.5 <= 0.05 if math.isfinite(MR) else False
64 ok_r = abs(R - 1.0) <= 0.10 if math.isfinite(R) else False
65 ok_o = o_band[0] <= ro <= o_band[1] if math.isfinite(ro) else False
66 ok_ff = rf <= f_cap if math.isfinite(rf) else False
67 return ok_f, ok_mr, ok_r, ok_o, ok_ff
68
69
70def _sf(x: Any) -> float:
71 try:
72 if x is None:
73 return float("nan")
74 v = float(x)
75 return v if math.isfinite(v) else float("nan")
76 except (TypeError, ValueError):
77 return float("nan")
78
79
81 perf: Dict[str, Any],
82 *,
83 o_band: Tuple[float, float],
84 f_cap: float,
85) -> Tuple[int, float, float]:
86 """Sort ascending: fewer gate failures; lower LOX stress metric."""
87 ok_f, ok_mr, ok_r, ok_o, ok_ff = _gates(perf, o_band=o_band, f_cap=f_cap)
88 fails = sum(not x for x in (ok_f, ok_mr, ok_r, ok_o, ok_ff))
89
90 ro, _rf = _injector_dp_ratios(perf)
91 lo_o, hi_o = o_band
92 if not math.isfinite(ro):
93 return fails, 1e9, 1e9
94 # Prefer LOX inside band hugging hi_o (0.35); penalize violations.
95 if ro < lo_o:
96 lox_primary = 1000.0 + (lo_o - ro)
97 lox_secondary = lo_o - ro
98 elif ro > hi_o:
99 lox_primary = 500.0 + (ro - hi_o)
100 lox_secondary = ro - hi_o
101 else:
102 lox_primary = hi_o - ro
103 lox_secondary = abs(ro - hi_o)
104
105 return fails, lox_primary, lox_secondary
106
107
108def main() -> None:
109 ap = argparse.ArgumentParser(description=__doc__)
110 ap.add_argument("--config", type=str, default=str(ROOT / "configs/impinging_lox_ch4_8000N.yaml"))
111 ap.add_argument("--a-throat-center-mm2", type=float, default=2443.263433372886)
112 ap.add_argument("--layer1-max-iterations", type=int, default=110)
113 ap.add_argument("--layer1-cma-restarts", type=int, default=1)
114 ap.add_argument("--w-dp-base", type=float, default=160.0)
115 args = ap.parse_args()
116
117 cfg_path = Path(args.config)
118 a_center = float(args.a_throat_center_mm2)
119 w_base = float(args.w_dp_base)
120
121 cases: List[Tuple[str, float, float, Optional[float], Optional[float]]] = [
122 ("sym ±12.5%", 0.125, w_base, None, None),
123 ("O-heavy ±12.5% ×1.5", 0.125, w_base, w_base * 1.5, w_base),
124 ("O-heavy ±12.5% ×2", 0.125, w_base, w_base * 2.0, w_base),
125 ("O-heavy ±15% ×1.5", 0.15, w_base, w_base * 1.5, w_base),
126 ("O-heavy ±15% ×2", 0.15, w_base, w_base * 2.0, w_base),
127 ("O-heavy ±15% ×2.25", 0.15, w_base, w_base * 2.25, w_base),
128 ]
129
130 rows: List[Dict[str, Any]] = []
131
132 print("\n=== LOX-focused free-throat Layer-1 grid ===\n")
133
134 for label, throat_frac, w_dp, w_o, w_f in cases:
135 cfg = load_config(str(cfg_path))
136 req = cfg.design_requirements.model_dump()
137 _clean_frozen(req)
138
139 req["layer1_A_throat_mm2_min"] = a_center * (1.0 - throat_frac)
140 req["layer1_A_throat_mm2_max"] = a_center * (1.0 + throat_frac)
141
142 req["W_MOM"] = 300.0
143 req["W_geom_ao_af_momentum"] = 2800.0
144 req["injector_dp_ratio_F_min"] = 0.5
145 req["injector_dp_ratio_F_max"] = 1.5
146 req["W_DP"] = float(w_dp)
147 if w_o is not None:
148 req["W_DP_O"] = float(w_o)
149 else:
150 req.pop("W_DP_O", None)
151 if w_f is not None:
152 req["W_DP_F"] = float(w_f)
153 else:
154 req.pop("W_DP_F", None)
155
156 pcfg = {
157 "mode": "optimizer_controlled",
158 "max_lox_pressure_psi": float(req["max_lox_tank_pressure_psi"]),
159 "max_fuel_pressure_psi": float(req["max_fuel_tank_pressure_psi"]),
160 }
161
162 print(f"\n--- Case: {label} ---")
163
164 opt_cfg, results = run_layer1_optimization(
165 copy.deepcopy(cfg),
166 PintleEngineRunner(copy.deepcopy(cfg)),
167 req,
168 target_burn_time=float(req.get("target_burn_time", 6.0)),
169 tolerances={"thrust": 0.10, "apogee": 0.15},
170 pressure_config=pcfg,
171 layer1_smoke=False,
172 layer1_max_iterations=int(args.layer1_max_iterations),
173 layer1_cma_restarts=int(args.layer1_cma_restarts),
174 )
175 perf = results.get("performance") or {}
176 ro, rf = _injector_dp_ratios(perf)
177 cg = getattr(opt_cfg, "chamber_geometry", None)
178 atm = getattr(cg, "A_throat", None) if cg is not None else None
179 a_mm2 = float(atm) * 1e6 if atm is not None else float("nan")
180
181 ok_f, ok_mr, ok_r, ok_o, ok_ff = _gates(
182 perf,
183 o_band=(float(req["injector_dp_ratio_O_min"]), float(req["injector_dp_ratio_O_max"])),
184 f_cap=float(req["injector_dp_ratio_F_max"]),
185 )
186
187 rows.append(
188 {
189 "label": label,
190 "throat_frac": throat_frac,
191 "W_DP": w_dp,
192 "W_DP_O": w_o if w_o is not None else w_dp,
193 "W_DP_F": w_f if w_f is not None else w_dp,
194 "F": perf.get("F"),
195 "MR": perf.get("MR"),
196 "R": perf.get("momentum_ratio_R"),
197 "dP_O": ro,
198 "dP_F": rf,
199 "A_throat_mm2": a_mm2,
200 "P_O_psi": perf.get("P_O_start_psi"),
201 "P_F_psi": perf.get("P_F_start_psi"),
202 "ok_F": ok_f,
203 "ok_MR": ok_mr,
204 "ok_R": ok_r,
205 "ok_LOX": ok_o,
206 "ok_FuelCap": ok_ff,
207 "perf": perf,
208 "opt_cfg": opt_cfg,
209 }
210 )
211
212 o_band = (0.20, 0.35)
213 f_cap = 1.50
214
215 rows_sorted = sorted(rows, key=lambda r: _rank_key(r["perf"], o_band=o_band, f_cap=f_cap))
216
217 print("\n" + "=" * 110)
218 print("RANKED CANDIDATES (best first)")
219 print("Gates: |F−8000|/8000≤5%; |MR−3.5|/3.5≤5%; |R−1|≤10%; LOX ΔP/Pc∈[0.20,0.35]; fuel ΔP/Pc≤1.50")
220 print("Secondary: minimize LOX violation / maximize LOX ΔP/Pc toward 0.35 inside band")
221 print("=" * 110)
222
223 hdr = (
224 f"{'rank':>4} {'case':<26} {'At_mm2':>9} {'F':>9} {'MR':>7} {'R':>7} "
225 f"{'dP_O':>7} {'dP_F':>7} {'gF':>3}{'gMR':>4}{'gR':>4}{'gO':>4}{'gf':>4}"
226 )
227 print(hdr)
228 print("-" * len(hdr))
229
230 for i, r in enumerate(rows_sorted, start=1):
231 print(
232 f"{i:4d} {r['label']:<26} {r['A_throat_mm2']:9.1f} "
233 f"{_sf(r['F']):9.2f} {_sf(r['MR']):7.4f} {_sf(r['R']):7.4f} "
234 f"{_sf(r['dP_O']):7.4f} {_sf(r['dP_F']):7.4f} "
235 f"{'Y' if r['ok_F'] else 'N':>3}"
236 f"{'Y' if r['ok_MR'] else 'N':>4}"
237 f"{'Y' if r['ok_R'] else 'N':>4}"
238 f"{'Y' if r['ok_LOX'] else 'N':>4}"
239 f"{'Y' if r['ok_FuelCap'] else 'N':>4}"
240 )
241
242 best = rows_sorted[0]
243 print("\nBest-ranked row:", best["label"])
244 ig = best["opt_cfg"].injector.geometry
245 print(
246 f" Injector n_O={int(ig.oxidizer.n_elements)} d_O={ig.oxidizer.d_jet*1000:.4f} mm "
247 f"n_F={int(ig.fuel.n_elements)} d_F={ig.fuel.d_jet*1000:.4f} mm"
248 )
249
250
251if __name__ == "__main__":
252 main()
Tuple[bool, bool, bool, bool, bool] _gates(Dict[str, Any] perf, *, Tuple[float, float] o_band, float f_cap)
Tuple[int, float, float] _rank_key(Dict[str, Any] perf, *, Tuple[float, float] o_band, float f_cap)
Tuple[float, float] _injector_dp_ratios(Dict[str, Any] perf)