60 o_band: tuple[float, float],
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")
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
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})")
80 f
" LOX ΔP/Pc ∈ [{o_band[0]}, {o_band[1]}]: {'YES' if ok_o else 'NO'} ({ro:.4f})"
82 lines.append(f
" fuel ΔP/Pc ≤ {f_max}: {'YES' if ok_ff else 'NO'} ({rf:.4f})")
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)
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)
94 "--throat-span-fraction",
97 help=
"Symmetric ± fraction (default 0.125 ⇒ ±12.5%, within ~10–15%%)",
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()
105 cfg = load_config(args.config)
106 req = cfg.design_requirements.model_dump()
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)
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
119 o_band = (float(req[
"injector_dp_ratio_O_min"]), float(req[
"injector_dp_ratio_O_max"]))
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"]),
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"
138 opt_cfg, results = run_layer1_optimization(
142 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
143 tolerances={
"thrust": 0.10,
"apogee": 0.15},
144 pressure_config=pcfg,
146 layer1_max_iterations=int(args.layer1_max_iterations),
147 layer1_cma_restarts=int(args.layer1_cma_restarts),
149 perf = results.get(
"performance")
or {}
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")
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)}")
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"
178 print(
_feasibility_notes(perf, o_band=o_band, f_max=float(req[
"injector_dp_ratio_F_max"])))