23from typing
import Any, Dict, Optional, Tuple
49def mission_metrics(result: Dict[str, Any]) -> Tuple[float, float, float, float, float, Optional[float]]:
50 """Returns F, MR, R_mom, r_dp_o, r_dp_f, Pc."""
51 F = float(result.get(
"F", float(
"nan")))
52 MR = float(result.get(
"MR", float(
"nan")))
53 diag = result.get(
"diagnostics")
or {}
54 Rm = diag.get(
"momentum_ratio_R")
55 R_mom = float(Rm)
if Rm
is not None and np.isfinite(float(Rm))
else float(
"nan")
56 pc = float(result.get(
"Pc", float(
"nan")))
57 ro, rf = injector_dp_ratios_from_eval_result(pc, result)
58 return F, MR, R_mom, float(ro
or np.nan), float(rf
or np.nan), pc
95 print(
"\n" +
"=" * 70)
96 print(
"TASK 1 — Tank pressure sweep (evaluate-only; ceilings not binding here)")
97 print(
" Nominal YAML max LOX ~700 psi caps *optimizer* bounds, not physics evaluate().")
98 print(
" Sweeping P_O,P_F directly up to high psi tests whether ΔP/Pc can enter [0.20,0.35]")
99 print(
" while staying near F≈8000, MR≈3.5, R≈1 (same frozen throat geometry).")
102 cfg = copy.deepcopy(cfg_base)
105 n_o, d_o = 25, 0.00244
107 ao_af = TARGET_MR / math.sqrt(cfg.fluids[
"oxidizer"].density / cfg.fluids[
"fuel"].density)
108 a_o = n_o * math.pi * (d_o / 2.0) ** 2
111 cfg.injector.geometry.oxidizer.n_elements = n_o
112 cfg.injector.geometry.oxidizer.d_jet = d_o
113 cfg.injector.geometry.fuel.n_elements = n_f
114 cfg.injector.geometry.fuel.d_jet = d_f
116 po_list = np.linspace(p_lo, p_hi, int(n_steps))
117 pf_list = np.linspace(p_lo, p_hi, int(n_steps))
127 res = runner.evaluate(float(po) * PSI_TO_PA, float(pf) * PSI_TO_PA, P_ambient=pa, silent=
True)
133 hits_full.append((po, pf, F, MR, Rm, ro, rf))
135 thrust_ok = np.isfinite(F)
and abs(F / TARGET_F - 1.0) <= 0.05
136 mr_ok = np.isfinite(MR)
and abs(MR / TARGET_MR - 1.0) <= 0.05
137 r_ok = np.isfinite(Rm)
and abs(Rm - 1.0) <= 0.10
138 if thrust_ok
and mr_ok
and r_ok
and np.isfinite(rf)
and np.isfinite(ro):
139 mission_band.append((rf, ro, po, pf, F, MR, Rm))
141 print(f
"Grid {len(po_list)}×{len(pf_list)} = {len(po_list)*len(pf_list)} points, successful evaluates: {n_ok}")
143 "Full-mission hits (|F|,|MR|,|R| gates + both ΔP/Pc ∈ [0.20,0.35]): "
147 for row
in hits_full[:15]:
151 f
"\nAmong points with |F-8000|/8000≤5%, |MR-3.5|/3.5≤5%, |R-1|≤10% (ΔP unconstrained): "
152 f
"N={len(mission_band)}"
155 rfs = [t[0]
for t
in mission_band]
156 ros = [t[1]
for t
in mission_band]
157 in_dp_band = sum(1
for t
in mission_band
if 0.20 <= t[0] <= 0.35
and 0.20 <= t[1] <= 0.35)
158 print(f
" ΔP_F/Pc min/med/max: {min(rfs):.4f} / {float(np.median(rfs)):.4f} / {max(rfs):.4f}")
159 print(f
" ΔP_O/Pc min/med/max: {min(ros):.4f} / {float(np.median(ros)):.4f} / {max(ros):.4f}")
160 print(f
" Count with BOTH streams in [0.20, 0.35]: {in_dp_band}/{len(mission_band)}")
161 mission_band.sort(key=
lambda t: t[0])
162 rf, ro, po, pf, F, MR, Rm = mission_band[0]
164 " Lowest fuel ΔP/Pc within mission kinematic band: "
165 f
"ΔP_F/Pc={rf:.4f}, ΔP_O/Pc={ro:.4f}, P_O={po:.1f} psi, P_F={pf:.1f} psi, "
166 f
"F={F:.1f}, MR={MR:.4f}, R={Rm:.4f}"
171 cfg.injector.geometry.oxidizer.n_elements = 25
172 cfg.injector.geometry.oxidizer.d_jet = 0.00244
173 cfg.injector.geometry.oxidizer.impingement_angle = 45.0
174 cfg.injector.geometry.oxidizer.spacing = 0.008
175 cfg.injector.geometry.fuel.n_elements = 63
176 cfg.injector.geometry.fuel.d_jet = 0.00105
177 cfg.injector.geometry.fuel.impingement_angle = 45.0
178 cfg.injector.geometry.fuel.spacing = 0.008
190 perf = results.get(
"performance")
or {}
191 pc = float(perf.get(
"Pc", float(
"nan")))
192 F = float(perf.get(
"F", float(
"nan")))
193 MR = float(perf.get(
"MR", float(
"nan")))
194 Rm = perf.get(
"momentum_ratio_R")
195 R_mom = float(Rm)
if Rm
is not None and np.isfinite(float(Rm))
else float(
"nan")
196 ro = perf.get(
"injector_dp_ratio_O")
197 rf = perf.get(
"injector_dp_ratio_F")
198 if ro
is None or rf
is None or (
not np.isfinite(float(ro)))
or (
not np.isfinite(float(rf))):
199 _ro, _rf = injector_dp_ratios_from_eval_result(pc, perf)
201 ro_f = float(ro)
if ro
is not None and np.isfinite(float(ro))
else float(
"nan")
202 rf_f = float(rf)
if rf
is not None and np.isfinite(float(rf))
else float(
"nan")
208 f
" F={F:.2f} N MR={MR:.4f} R={R_mom:.4f} "
209 f
"ΔP_O/Pc={ro_f} ΔP_F/Pc={rf_f} full-mission-feasible={ok}"
211 print(f
" P_O_opt_psi≈{perf.get('P_O_start_psi')} P_F_opt_psi≈{perf.get('P_F_start_psi')}")
212 failure_reasons = perf.get(
"failure_reasons")
or []
214 print(f
" failure_reasons: {failure_reasons[:5]}")
221 requirements: Dict[str, Any],
222 pressure_config: Dict[str, Any],
225 cfg = copy.deepcopy(cfg_seed)
228 _, results = run_layer1_optimization(
231 requirements=requirements,
232 target_burn_time=float(requirements.get(
"target_burn_time", 6.0)),
233 tolerances={
"thrust": 0.10,
"apogee": 0.15},
234 pressure_config=pressure_config,
236 layer1_max_iterations=int(layer1_iters),
237 layer1_cma_restarts=1,
243 ap = argparse.ArgumentParser()
244 ap.add_argument(
"--layer1-iters", type=int, default=12, help=
"CMA generations per Layer-1 run (smoke)")
245 ap.add_argument(
"--skip-layer1", action=
"store_true")
246 args = ap.parse_args()
248 cfg_base = load_config(str(ROOT /
"configs/impinging_lox_ch4_8000N.yaml"))
254 print(
"\n(--skip-layer1: W_MOM and frozen-n experiments not run)")
261 print(
"\n" +
"=" * 70)
262 print(
"TASK 2 — W_MOM sensitivity (short smoke Layer 1)")
263 print(f
" Tank caps in pressure_config: LOX/Fuel max = {max_p:.0f} psi")
264 print(f
" layer1_max_iterations = {args.layer1_iters}, restarts = 1")
267 for w_mom
in (75.0, 300.0, 1000.0, 3000.0):
268 cfg_seed = copy.deepcopy(cfg_base)
270 req = cfg_seed.design_requirements.model_dump()
271 req[
"W_MOM"] = float(w_mom)
272 req[
"max_lox_tank_pressure_psi"] = max_p
273 req[
"max_fuel_tank_pressure_psi"] = max_p
275 tag=f
"W_MOM={w_mom:g}",
278 pressure_config=pressure_hi,
279 layer1_iters=args.layer1_iters,
282 print(
"\n" +
"=" * 70)
283 print(
"TASK 3 — Frozen n_elements_O=25, n_elements_F=63 (other DOFs free)")
285 cfg_seed = copy.deepcopy(cfg_base)
287 req = cfg_seed.design_requirements.model_dump()
288 req[
"max_lox_tank_pressure_psi"] = max_p
289 req[
"max_fuel_tank_pressure_psi"] = max_p
290 fp = dict(req.get(
"frozen_parameters")
or {})
291 fp[
"n_elements_O"] = 25
292 fp[
"n_elements_F"] = 63
293 req[
"frozen_parameters"] = fp
298 pressure_config=pressure_hi,
299 layer1_iters=args.layer1_iters,
302 print(
"\n" +
"=" * 70)
303 print(
"TASK 4 — Interpretation printed inline per run (full-mission feasible?)")
304 print(
" Full-mission gate: |F-8000|/8000≤5%, |MR-3.5|/3.5≤5%, |R-1|≤10%,")
305 print(
" both ΔP_inj/Pc ∈ [0.20, 0.35].")
None run_layer1_case(*, str tag, cfg_seed, Dict[str, Any] requirements, Dict[str, Any] pressure_config, int layer1_iters)
bool full_mission_ok(float F, float MR, float R_mom, float r_o, float r_f, *, float thrust_band=0.05, float mr_band=0.05, float r_band=0.10, float dp_lo=0.20, float dp_hi=0.35)