STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
run_layer1_free_throat_exploration.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Layer-1 exploration with A_throat free in a ±fraction band around baseline mm² (not frozen).
3
4Removes ``A_throat_mm2`` from ``frozen_parameters`` for this run only and passes::
5
6 requirements[\"layer1_A_throat_mm2_min\"]
7 requirements[\"layer1_A_throat_mm2_max\"]
8
9which Layer 1 applies to design-vector index 0 (see ``layer1_static_optimization``).
10
11Preset matches user exploration defaults:
12 target thrust/MR from YAML, W_MOM=300, W_geom_ao_af_momentum=2800,
13 fuel ΔP/Pc band [0.50, 1.50].
14"""
15
16from __future__ import annotations
17
18import argparse
19import copy
20import math
21import sys
22from pathlib import Path
23from typing import Any, Dict
24
25ROOT = Path(__file__).resolve().parents[1]
26sys.path.insert(0, str(ROOT))
27
28from engine.core.runner import PintleEngineRunner # noqa: E402
29from engine.optimizer.layers.layer1_static_optimization import run_layer1_optimization # noqa: E402
30from engine.pipeline.io import load_config # noqa: E402
31
32
33def _clean_frozen(req: Dict[str, Any]) -> None:
34 fp = req.get("frozen_parameters")
35 if not isinstance(fp, dict):
36 fp = {}
37 else:
38 fp = {k: v for k, v in fp.items() if v is not None}
39 fp.pop("A_throat_mm2", None)
40 req["frozen_parameters"] = fp
41
42
43def _injector_dp_ratios(perf: Dict[str, Any]) -> tuple[float, float]:
44 ro = perf.get("injector_dp_ratio_O")
45 rf = perf.get("injector_dp_ratio_F")
46 if ro is not None and rf is not None:
47 return float(ro), float(rf)
48 diag = perf.get("diagnostics") or {}
49 pc = float(perf.get("Pc", float("nan")))
50 dpo = diag.get("delta_p_injector_O")
51 dpf = diag.get("delta_p_injector_F")
52 ro_v = float(dpo) / pc if dpo is not None and pc > 0 else float("nan")
53 rf_v = float(dpf) / pc if dpf is not None and pc > 0 else float("nan")
54 return ro_v, rf_v
55
56
58 perf: Dict[str, Any],
59 *,
60 o_band: tuple[float, float],
61 f_max: float,
62) -> str:
63 F = float(perf.get("F", float("nan")))
64 MR = float(perf.get("MR", float("nan")))
65 R = perf.get("momentum_ratio_R")
66 R = float(R) if R is not None else float("nan")
67 ro, rf = _injector_dp_ratios(perf)
68
69 lines = []
70 ok_f = abs(F - 8000.0) / 8000.0 <= 0.05 if math.isfinite(F) else False
71 ok_mr = abs(MR - 3.5) / 3.5 <= 0.05 if math.isfinite(MR) else False
72 ok_r = abs(R - 1.0) <= 0.10 if math.isfinite(R) else False
73 ok_o = o_band[0] <= ro <= o_band[1] if math.isfinite(ro) else False
74 ok_ff = rf <= f_max if math.isfinite(rf) else False
75
76 lines.append(f" thrust near 8000 N (≤5%): {'YES' if ok_f else 'NO'} (F={F:.2f})")
77 lines.append(f" MR near 3.5 (≤5%): {'YES' if ok_mr else 'NO'} (MR={MR:.4f})")
78 lines.append(f" R near 1 (|R−1|≤10%): {'YES' if ok_r else 'NO'} (R={R:.4f})")
79 lines.append(
80 f" LOX ΔP/Pc ∈ [{o_band[0]}, {o_band[1]}]: {'YES' if ok_o else 'NO'} ({ro:.4f})"
81 )
82 lines.append(f" fuel ΔP/Pc ≤ {f_max}: {'YES' if ok_ff else 'NO'} ({rf:.4f})")
83
84 all_ok = ok_f and ok_mr and ok_r and ok_o and ok_ff
85 lines.insert(0, f"\nJoint feasibility (all YES): {'YES' if all_ok else 'NO'}\n")
86 return "\n".join(lines)
87
88
89def main() -> None:
90 ap = argparse.ArgumentParser(description=__doc__)
91 ap.add_argument("--config", type=str, default=str(ROOT / "configs/impinging_lox_ch4_8000N.yaml"))
92 ap.add_argument("--a-throat-center-mm2", type=float, default=2443.263433372886)
93 ap.add_argument(
94 "--throat-span-fraction",
95 type=float,
96 default=0.125,
97 help="Symmetric ± fraction (default 0.125 ⇒ ±12.5%, within ~10–15%%)",
98 )
99 ap.add_argument("--layer1-max-iterations", type=int, default=150)
100 ap.add_argument("--layer1-cma-restarts", type=int, default=1)
101 ap.add_argument("--w-mom", type=float, default=300.0)
102 ap.add_argument("--w-geom-ao-af", type=float, default=2800.0)
103 args = ap.parse_args()
104
105 cfg = load_config(args.config)
106 req = cfg.design_requirements.model_dump()
107 _clean_frozen(req)
108
109 center = float(args.a_throat_center_mm2)
110 frac = float(args.throat_span_fraction)
111 req["layer1_A_throat_mm2_min"] = center * (1.0 - frac)
112 req["layer1_A_throat_mm2_max"] = center * (1.0 + frac)
113
114 req["W_MOM"] = float(args.w_mom)
115 req["W_geom_ao_af_momentum"] = float(args.w_geom_ao_af)
116 req["injector_dp_ratio_F_min"] = 0.5
117 req["injector_dp_ratio_F_max"] = 1.5
118
119 o_band = (float(req["injector_dp_ratio_O_min"]), float(req["injector_dp_ratio_O_max"]))
120
121 pcfg = {
122 "mode": "optimizer_controlled",
123 "max_lox_pressure_psi": float(req["max_lox_tank_pressure_psi"]),
124 "max_fuel_pressure_psi": float(req["max_fuel_tank_pressure_psi"]),
125 }
126
127 print(
128 "\n=== Free-throat Layer-1 exploration ===\n"
129 f"A_throat band [mm²]: [{req['layer1_A_throat_mm2_min']:.6f}, {req['layer1_A_throat_mm2_max']:.6f}] "
130 f"(±{100 * frac:.2f}% around {center:.6f})\n"
131 f"Frozen params: A_throat removed; other frozen_* from YAML unchanged.\n"
132 f"W_MOM={req['W_MOM']} W_geom_ao_af={req['W_geom_ao_af_momentum']}\n"
133 f"Fuel ΔP/Pc ∈ [{req['injector_dp_ratio_F_min']}, {req['injector_dp_ratio_F_max']}]\n"
134 f"LOX ΔP/Pc ∈ [{o_band[0]}, {o_band[1]}]\n"
135 f"Iterations={args.layer1_max_iterations} restarts={args.layer1_cma_restarts}\n"
136 )
137
138 opt_cfg, results = run_layer1_optimization(
139 copy.deepcopy(cfg),
140 PintleEngineRunner(copy.deepcopy(cfg)),
141 req,
142 target_burn_time=float(req.get("target_burn_time", 6.0)),
143 tolerances={"thrust": 0.10, "apogee": 0.15},
144 pressure_config=pcfg,
145 layer1_smoke=False,
146 layer1_max_iterations=int(args.layer1_max_iterations),
147 layer1_cma_restarts=int(args.layer1_cma_restarts),
148 )
149 perf = results.get("performance") or {}
150 ro, rf = _injector_dp_ratios(perf)
151
152 cg = getattr(opt_cfg, "chamber_geometry", None) or getattr(opt_cfg, "chamber", None)
153 a_throat_m2 = getattr(cg, "A_throat", None) if cg is not None else None
154 a_throat_mm2 = float(a_throat_m2) * 1e6 if a_throat_m2 is not None else float("nan")
155
156 ig = opt_cfg.injector.geometry
157 print("\n=== Results ===")
158 print(f"A_throat [mm²]: {a_throat_mm2:.6f}")
159 print(f"Thrust F [N]: {perf.get('F')}")
160 print(f"MR: {perf.get('MR')}")
161 print(f"momentum_ratio_R: {perf.get('momentum_ratio_R')}")
162 print(f"ΔP_inj_O / Pc: {ro}")
163 print(f"ΔP_inj_F / Pc: {rf}")
164 print(f"P_O_start_psi: {perf.get('P_O_start_psi')}")
165 print(f"P_F_start_psi: {perf.get('P_F_start_psi')}")
166 diagp = perf.get("diagnostics") or {}
167 ae_o = perf.get("A_eff_O", diagp.get("A_eff_O"))
168 ae_f = perf.get("A_eff_F", diagp.get("A_eff_F"))
169 print(f"A_geom_O / A_geom_F: {perf.get('geom_ao_af')}")
170 print(f"A_eff_O / A_eff_F: {ae_o} / {ae_f} → ratio {_aeff_ratio_from(ae_o, ae_f)}")
171 print(
172 f"Injector: n_O={int(ig.oxidizer.n_elements)} d_O={ig.oxidizer.d_jet*1000:.4f} mm "
173 f"sp_O={ig.oxidizer.spacing*1000:.3f} mm | "
174 f"n_F={int(ig.fuel.n_elements)} d_F={ig.fuel.d_jet*1000:.4f} mm "
175 f"sp_F={ig.fuel.spacing*1000:.3f} mm"
176 )
177
178 print(_feasibility_notes(perf, o_band=o_band, f_max=float(req["injector_dp_ratio_F_max"])))
179
180
181def _aeff_ratio_from(ae: Any, af: Any) -> float:
182 try:
183 if ae is not None and af is not None and float(af) > 0:
184 return float(ae) / float(af)
185 except (TypeError, ValueError):
186 pass
187 return float("nan")
188
189
190if __name__ == "__main__":
191 main()
str _feasibility_notes(Dict[str, Any] perf, *, tuple[float, float] o_band, float f_max)
tuple[float, float] _injector_dp_ratios(Dict[str, Any] perf)