70 o_band: Tuple[float, float],
71 f_band: Tuple[float, float],
73 F =
_sf(perf.get(
"F"))
74 MR =
_sf(perf.get(
"MR"))
75 R =
_sf(perf.get(
"momentum_ratio_R"))
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
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')}")
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}"
94 print(f
"\n** Joint target satisfaction (all gates): {'YES' if joint else 'NO'} **")
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()
106 cfg_path = Path(args.config)
107 center = float(args.a_throat_center_mm2)
108 frac = float(args.throat_span_fraction)
112 "A: LOX & CH4 [0.15, 0.40]",
117 "B: LOX [0.15, 0.40], CH4 [0.20, 0.50]",
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"
130 for scen_name, o_band, f_band
in scenarios:
131 cfg = load_config(str(cfg_path))
132 req = cfg.design_requirements.model_dump()
135 req[
"layer1_A_throat_mm2_min"] = center * (1.0 - frac)
136 req[
"layer1_A_throat_mm2_max"] = center * (1.0 + frac)
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]
144 req[
"W_geom_ao_af_momentum"] = 2800.0
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"]),
152 _, results = run_layer1_optimization(
156 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
157 tolerances={
"thrust": 0.10,
"apogee": 0.15},
158 pressure_config=pcfg,
160 layer1_max_iterations=int(args.layer1_max_iterations),
161 layer1_cma_restarts=int(args.layer1_cma_restarts),
163 perf = results.get(
"performance")
or {}
164 _report(scen_name, perf, o_band=o_band, f_band=f_band)