42 ap = argparse.ArgumentParser(description=__doc__)
46 default=str(ROOT /
"configs/impinging_lox_ch4_8000N.yaml"),
47 help=
"YAML config path",
49 ap.add_argument(
"--skip-layer1", action=
"store_true", help=
"Use --po-psi/--pf-psi instead of optimizing")
55 "When used with --skip-layer1: YAML to copy injector (and fluids) from so the sweep "
56 "matches a prior optimized geometry. If omitted, injector geometry comes from --config."
59 ap.add_argument(
"--po-psi", type=float, default=
None, help=
"LOX tank pressure [psi] when skipping Layer 1")
60 ap.add_argument(
"--pf-psi", type=float, default=
None, help=
"Fuel tank pressure [psi] when skipping Layer 1")
61 ap.add_argument(
"--ratio-min", type=float, default=1.5)
62 ap.add_argument(
"--ratio-max", type=float, default=3.0)
63 ap.add_argument(
"--ratio-steps", type=int, default=16)
64 ap.add_argument(
"--layer1-max-iterations", type=int, default=22)
65 ap.add_argument(
"--silent-eval", action=
"store_true", help=
"Pass silent=True to runner.evaluate")
66 args = ap.parse_args()
68 cfg = load_config(args.config)
69 req = cfg.design_requirements.model_dump()
71 "mode":
"optimizer_controlled",
72 "max_lox_pressure_psi": float(req[
"max_lox_tank_pressure_psi"]),
73 "max_fuel_pressure_psi": float(req[
"max_fuel_tank_pressure_psi"]),
77 if args.po_psi
is None or args.pf_psi
is None:
78 raise SystemExit(
"--skip-layer1 requires --po-psi and --pf-psi")
79 geom_src = load_config(args.geometry_config)
if args.geometry_config
else cfg
80 opt_cfg = copy.deepcopy(geom_src)
81 po_pa = float(args.po_psi) * PSI_TO_PA
82 pf_pa = float(args.pf_psi) * PSI_TO_PA
85 opt_cfg, results = run_layer1_optimization(
89 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
90 tolerances={
"thrust": 0.10,
"apogee": 0.15},
93 layer1_max_iterations=int(args.layer1_max_iterations),
94 layer1_cma_restarts=1,
96 perf_hint = results.get(
"performance")
or {}
97 po_pa = float(perf_hint[
"P_O_start_psi"]) * PSI_TO_PA
98 pf_pa = float(perf_hint[
"P_F_start_psi"]) * PSI_TO_PA
100 geom = getattr(getattr(opt_cfg,
"injector",
None),
"geometry",
None)
102 raise SystemExit(
"No injector.geometry on config")
105 n_o =
max(1, int(ox.n_elements))
106 n_f =
max(1, int(fu.n_elements))
107 d_f = float(fu.d_jet)
108 d_o_base = float(ox.d_jet)
112 ratios = np.linspace(float(args.ratio_min), float(args.ratio_max), int(args.ratio_steps))
114 print(
"\n=== A_geom_O/A_geom_F sweep (evaluate-only, fixed tank pressures) ===")
115 print(f
"P_tank_O = {po_pa / PSI_TO_PA:.3f} psi, P_tank_F = {pf_pa / PSI_TO_PA:.3f} psi")
116 print(f
"Baseline: n_O={n_o} n_F={n_f} d_O={d_o_base * 1000:.4f} mm d_F={d_f * 1000:.4f} mm")
118 f
"{'AoAf_tgt':>10} {'AoAf_geom':>10} {'R':>8} {'MR':>8} {'F[N]':>10} "
119 f
"{'dP_O/Pc':>10} {'dP_F/Pc':>10} {'err':>8}"
123 cfg_i = copy.deepcopy(opt_cfg)
124 g = cfg_i.injector.geometry
125 scale = math.sqrt(
max(1e-30, float(r_tgt) * float(n_f) / float(n_o)))
126 d_o_new = d_f * scale
127 g.oxidizer.d_jet = d_o_new
131 ev = runner.evaluate(po_pa, pf_pa, P_ambient=P_ambient, silent=args.silent_eval)
132 except Exception
as e:
133 print(f
"{r_tgt:10.4f} {'FAIL':>10} — {e}")
136 a_go = float(ev.get(
"A_geom_O")
or 0.0)
137 a_gf = float(ev.get(
"A_geom_F")
or 0.0)
138 ao_af = a_go / a_gf
if a_gf > 0
else float(
"nan")
139 R = ev.get(
"diagnostics", {}).get(
"momentum_ratio_R")
140 R = float(R)
if R
is not None and np.isfinite(R)
else float(
"nan")
141 MR = float(ev.get(
"MR", float(
"nan")))
142 F = float(ev.get(
"F", float(
"nan")))
143 pc = float(ev.get(
"Pc", float(
"nan")))
145 err = abs(ao_af - r_tgt) / r_tgt
if np.isfinite(ao_af)
and r_tgt > 0
else float(
"nan")
148 f
"{r_tgt:10.4f} {ao_af:10.4f} {R:8.4f} {MR:8.4f} {F:10.2f} "
149 f
"{(ro if ro is not None else float('nan')):10.4f} "
150 f
"{(rf if rf is not None else float('nan')):10.4f} {err:8.2e}"