22 ap = argparse.ArgumentParser(description=__doc__)
25 default=str(ROOT /
"configs" /
"impinging_lox_ch4_8000N.yaml"),
28 ap.add_argument(
"--layer1-max-iterations", type=int, default=90)
29 ap.add_argument(
"--layer1-cma-restarts", type=int, default=3)
33 help=
"Short single-restart smoke (overrides iterations if used with scripts)",
39 help=
"After optimization, write optimized PintleEngineConfig YAML (JSON-safe dump)",
41 args = ap.parse_args()
43 cfg = load_config(args.config)
44 req = cfg.design_requirements.model_dump()
48 "mode":
"optimizer_controlled",
49 "max_lox_pressure_psi": float(req[
"max_lox_tank_pressure_psi"]),
50 "max_fuel_pressure_psi": float(req[
"max_fuel_tank_pressure_psi"]),
53 smoke = bool(args.layer1_smoke)
54 max_it =
max(1, int(args.layer1_max_iterations))
55 restarts = 1
if smoke
else max(1, int(args.layer1_cma_restarts))
58 _thr = req.get(
"layer1_thrust_validation_rel_tol")
59 thrust_tol = float(_thr)
if _thr
is not None else 0.10
61 opt_cfg, results = run_layer1_optimization(
65 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
66 tolerances={
"thrust": thrust_tol,
"apogee": 0.15},
69 layer1_max_iterations=max_it
if not smoke
else min(max_it, 24),
70 layer1_cma_restarts=restarts
if not smoke
else 1,
73 perf = results.get(
"performance")
or {}
74 diag = perf.get(
"diagnostics")
if isinstance(perf.get(
"diagnostics"), dict)
else {}
78 return v
if v
is not None else diag.get(key)
80 ox = opt_cfg.injector.geometry.oxidizer
81 fu = opt_cfg.injector.geometry.fuel
83 a_go = float(gv(
"A_geom_O"))
84 a_gf = float(gv(
"A_geom_F"))
85 a_eo = float(gv(
"A_eff_O"))
86 a_ef = float(gv(
"A_eff_F"))
88 print(
"=== Layer 1 optimization ===")
89 print(f
"config: {args.config}")
90 print(f
"layer1_smoke={smoke} max_iterations={max_it if not smoke else min(max_it, 24)} cma_restarts={restarts}")
91 print(f
"requirements: thrust={req.get('target_thrust')} N MR={req.get('optimal_of_ratio')}")
92 imp_deg = float(ox.impingement_angle) + float(fu.impingement_angle)
93 print(f
"impingement: θ_total = θ_O+θ_F = {imp_deg:.2f} deg (solver uses this)")
95 print(f
"thrust F [N]: {perf.get('F')}")
96 print(f
"MR (O/F): {perf.get('MR')}")
97 print(f
"momentum_ratio_R: {perf.get('momentum_ratio_R')}")
98 print(f
"ΔP_O/Pc: {perf.get('injector_dp_ratio_O')}")
99 print(f
"ΔP_F/Pc: {perf.get('injector_dp_ratio_F')}")
100 print(f
"P_O / P_F [psi]: {perf.get('P_O_start_psi')} / {perf.get('P_F_start_psi')}")
101 if getattr(opt_cfg.injector,
"type",
None) ==
"impinging" and isinstance(diag, dict):
103 "injector inlet P_inj [Pa]: "
104 f
"O={diag.get('P_injector_O')} F={diag.get('P_injector_F')}"
107 "Δp_feed tank→inj [Pa]: "
108 f
"O={diag.get('delta_p_feed_O')} F={diag.get('delta_p_feed_F')}"
111 "Δp_inj jet→Pc [Pa]: "
112 f
"O={diag.get('delta_p_injector_O')} F={diag.get('delta_p_injector_F')} "
113 "(ΔP/Pc ratios use these ÷ Pc; tank−Pc would wrongly include feed losses)"
115 print(f
"Cd_O / Cd_F: {gv('Cd_O')} / {gv('Cd_F')}")
116 print(f
"A_geom_O/A_geom_F: {a_go / a_gf:.6g}")
117 print(f
"A_eff_O/A_eff_F: {a_eo / a_ef:.6g}")
118 n_d = int(
min(int(ox.n_elements), int(fu.n_elements)))
119 print(f
"n_doublets (min O/F): {n_d}")
121 f
"n_O / n_F: {int(ox.n_elements)} / {int(fu.n_elements)}\n"
122 f
"d_jet_O / d_jet_F [mm]: {float(ox.d_jet) * 1000:.5f} / {float(fu.d_jet) * 1000:.5f}\n"
123 f
"spacing_O / spacing_F [mm]: "
124 f
"{float(ox.spacing) * 1000:.5f} / {float(fu.spacing) * 1000:.5f}\n"
125 f
"θ_O / θ_F [deg]: {float(ox.impingement_angle):.2f} / {float(fu.impingement_angle):.2f}"
127 foci = diag.get(
"feed_orifice_coupling_iterations")
128 print(f
"feed_orifice_coupling_iterations: {foci}")
129 mdo, mdf = perf.get(
"mdot_O"), perf.get(
"mdot_F")
130 mbo, mbf = diag.get(
"mdot_from_bernoulli_O"), diag.get(
"mdot_from_bernoulli_F")
131 print(f
"mdot_O / mdot_from_Bern_O: {mdo} / {mbo}")
132 print(f
"mdot_F / mdot_from_Bern_F: {mdf} / {mbf}")
133 print(f
"pressure_candidate_valid: {perf.get('pressure_candidate_valid')}")
134 fr = perf.get(
"failure_reasons")
136 print(f
"failure_reasons: {fr}")
137 cg = opt_cfg.chamber_geometry
139 print(f
"A_throat [m²] (optimized): {getattr(cg, 'A_throat', None)}")
140 if args.write_config:
141 out = Path(args.write_config)
142 out.parent.mkdir(parents=
True, exist_ok=
True)
143 data = opt_cfg.model_dump(mode=
"json")
144 out.write_text(yaml.safe_dump(data, sort_keys=
False))
145 print(f
"wrote optimized config: {out.resolve()}")