STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
run_layer1_free_throat_dp_band_compare.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Free-throat Layer-1 with two methane ΔP/Pc band scenarios vs updated LOX band.
3
4Scenario A — matches YAML defaults after update:
5 LOX [0.15, 0.40], CH4 [0.15, 0.40]
6
7Scenario B — relaxed methane only:
8 LOX [0.15, 0.40], CH4 [0.20, 0.50]
9
10Reports whether optimum satisfies (approximate gates):
11 |F−8000|/8000 ≤ 5%, |MR−3.5|/3.5 ≤ 5%, |R−1| ≤ 10%,
12 both ΔP_inj/Pc streams inside the scenario bands.
13"""
14
15from __future__ import annotations
16
17import argparse
18import copy
19import math
20import sys
21from pathlib import Path
22from typing import Any, Dict, Tuple
23
24ROOT = Path(__file__).resolve().parents[1]
25sys.path.insert(0, str(ROOT))
26
27from engine.core.runner import PintleEngineRunner # noqa: E402
28from engine.optimizer.layers.layer1_static_optimization import run_layer1_optimization # noqa: E402
29from engine.pipeline.io import load_config # noqa: E402
30
31
32def _clean_frozen(req: Dict[str, Any]) -> None:
33 fp = req.get("frozen_parameters")
34 if not isinstance(fp, dict):
35 fp = {}
36 else:
37 fp = {k: v for k, v in fp.items() if v is not None}
38 fp.pop("A_throat_mm2", None)
39 req["frozen_parameters"] = fp
40
41
42def _dp_ratios(perf: Dict[str, Any]) -> Tuple[float, float]:
43 ro = perf.get("injector_dp_ratio_O")
44 rf = perf.get("injector_dp_ratio_F")
45 if ro is not None and rf is not None:
46 return float(ro), float(rf)
47 diag = perf.get("diagnostics") or {}
48 pc = float(perf.get("Pc", float("nan")))
49 dpo = diag.get("delta_p_injector_O")
50 dpf = diag.get("delta_p_injector_F")
51 ro_v = float(dpo) / pc if dpo is not None and pc > 0 else float("nan")
52 rf_v = float(dpf) / pc if dpf is not None and pc > 0 else float("nan")
53 return ro_v, rf_v
54
55
56def _sf(x: Any) -> float:
57 try:
58 if x is None:
59 return float("nan")
60 v = float(x)
61 return v if math.isfinite(v) else float("nan")
62 except (TypeError, ValueError):
63 return float("nan")
64
65
67 name: str,
68 perf: Dict[str, Any],
69 *,
70 o_band: Tuple[float, float],
71 f_band: Tuple[float, float],
72) -> None:
73 F = _sf(perf.get("F"))
74 MR = _sf(perf.get("MR"))
75 R = _sf(perf.get("momentum_ratio_R"))
76 ro, rf = _dp_ratios(perf)
77
78 ok_f = abs(F - 8000.0) / 8000.0 <= 0.05 if math.isfinite(F) else False
79 ok_mr = abs(MR - 3.5) / 3.5 <= 0.05 if math.isfinite(MR) else False
80 ok_r = abs(R - 1.0) <= 0.10 if math.isfinite(R) else False
81 ok_o = o_band[0] <= ro <= o_band[1] if math.isfinite(ro) else False
82 ok_ff = f_band[0] <= rf <= f_band[1] if math.isfinite(rf) else False
83 joint = ok_f and ok_mr and ok_r and ok_o and ok_ff
84
85 print(f"\n{'=' * 72}\n{name}\n{'=' * 72}")
86 print(f"F [N]: {F:.4g} MR: {MR:.6g} R: {R:.6g}")
87 print(f"ΔP_O/Pc: {ro:.6g} (band [{o_band[0]}, {o_band[1]}]) OK={ok_o}")
88 print(f"ΔP_F/Pc: {rf:.6g} (band [{f_band[0]}, {f_band[1]}]) OK={ok_ff}")
89 print(f"P_O_psi: {perf.get('P_O_start_psi')} P_F_psi: {perf.get('P_F_start_psi')}")
90 print(
91 f"Gates: thrust≤5% {ok_f} MR≤5% {ok_mr} R≤10% {ok_r} "
92 f"LOX band {ok_o} CH4 band {ok_ff}"
93 )
94 print(f"\n** Joint target satisfaction (all gates): {'YES' if joint else 'NO'} **")
95
96
97def main() -> None:
98 ap = argparse.ArgumentParser(description=__doc__)
99 ap.add_argument("--config", type=str, default=str(ROOT / "configs/impinging_lox_ch4_8000N.yaml"))
100 ap.add_argument("--a-throat-center-mm2", type=float, default=2443.263433372886)
101 ap.add_argument("--throat-span-fraction", type=float, default=0.125)
102 ap.add_argument("--layer1-max-iterations", type=int, default=130)
103 ap.add_argument("--layer1-cma-restarts", type=int, default=1)
104 args = ap.parse_args()
105
106 cfg_path = Path(args.config)
107 center = float(args.a_throat_center_mm2)
108 frac = float(args.throat_span_fraction)
109
110 scenarios = [
111 (
112 "A: LOX & CH4 [0.15, 0.40]",
113 (0.15, 0.40),
114 (0.15, 0.40),
115 ),
116 (
117 "B: LOX [0.15, 0.40], CH4 [0.20, 0.50]",
118 (0.15, 0.40),
119 (0.20, 0.50),
120 ),
121 ]
122
123 print(
124 "\nFree-throat Layer-1 ΔP band comparison\n"
125 f" throat span ±{100 * frac:.2f}% around {center:.4f} mm²\n"
126 f" iterations={args.layer1_max_iterations} restarts={args.layer1_cma_restarts}\n"
127 " W_MOM=300 W_geom_ao_af_momentum=2800\n"
128 )
129
130 for scen_name, o_band, f_band in scenarios:
131 cfg = load_config(str(cfg_path))
132 req = cfg.design_requirements.model_dump()
133 _clean_frozen(req)
134
135 req["layer1_A_throat_mm2_min"] = center * (1.0 - frac)
136 req["layer1_A_throat_mm2_max"] = center * (1.0 + frac)
137
138 req["injector_dp_ratio_O_min"] = o_band[0]
139 req["injector_dp_ratio_O_max"] = o_band[1]
140 req["injector_dp_ratio_F_min"] = f_band[0]
141 req["injector_dp_ratio_F_max"] = f_band[1]
142
143 req["W_MOM"] = 300.0
144 req["W_geom_ao_af_momentum"] = 2800.0
145
146 pcfg = {
147 "mode": "optimizer_controlled",
148 "max_lox_pressure_psi": float(req["max_lox_tank_pressure_psi"]),
149 "max_fuel_pressure_psi": float(req["max_fuel_tank_pressure_psi"]),
150 }
151
152 _, results = run_layer1_optimization(
153 copy.deepcopy(cfg),
154 PintleEngineRunner(copy.deepcopy(cfg)),
155 req,
156 target_burn_time=float(req.get("target_burn_time", 6.0)),
157 tolerances={"thrust": 0.10, "apogee": 0.15},
158 pressure_config=pcfg,
159 layer1_smoke=False,
160 layer1_max_iterations=int(args.layer1_max_iterations),
161 layer1_cma_restarts=int(args.layer1_cma_restarts),
162 )
163 perf = results.get("performance") or {}
164 _report(scen_name, perf, o_band=o_band, f_band=f_band)
165
166
167if __name__ == "__main__":
168 main()
None _report(str name, Dict[str, Any] perf, *, Tuple[float, float] o_band, Tuple[float, float] f_band)
Tuple[float, float] _dp_ratios(Dict[str, Any] perf)