32 """Print mdot/ρ/A/v and ρv² terms consistent with impinging bulk momentum_ratio_R."""
33 diag = perf.get(
"diagnostics")
or {}
34 mdot_o = float(perf.get(
"mdot_O", diag.get(
"mdot_O")
or float(
"nan")))
35 mdot_f = float(perf.get(
"mdot_F", diag.get(
"mdot_F")
or float(
"nan")))
36 a_go = perf.get(
"A_geom_O", diag.get(
"A_geom_O"))
37 a_gf = perf.get(
"A_geom_F", diag.get(
"A_geom_F"))
38 a_go = float(a_go)
if a_go
is not None else float(
"nan")
39 a_gf = float(a_gf)
if a_gf
is not None else float(
"nan")
40 a_eo = perf.get(
"A_eff_O", diag.get(
"A_eff_O"))
41 a_ef = perf.get(
"A_eff_F", diag.get(
"A_eff_F"))
42 a_eo = float(a_eo)
if a_eo
is not None else float(
"nan")
43 a_ef = float(a_ef)
if a_ef
is not None else float(
"nan")
45 v_o = mdot_o / (rho_o * a_go)
if np.isfinite(a_go)
and a_go > 0
and rho_o > 0
else float(
"nan")
46 v_f = mdot_f / (rho_f * a_gf)
if np.isfinite(a_gf)
and a_gf > 0
and rho_f > 0
else float(
"nan")
47 flux_o = rho_o * v_o * v_o
if np.isfinite(v_o)
else float(
"nan")
48 flux_f = rho_f * v_f * v_f
if np.isfinite(v_f)
else float(
"nan")
49 r_diag = diag.get(
"momentum_ratio_R")
50 r_diag = float(r_diag)
if r_diag
is not None and np.isfinite(r_diag)
else float(
"nan")
51 r_recon = math.sqrt(flux_o / flux_f)
if flux_f > 0
and flux_o >= 0
else float(
"nan")
53 cd_o = perf.get(
"Cd_O", diag.get(
"Cd_O"))
54 cd_f = perf.get(
"Cd_F", diag.get(
"Cd_F"))
55 cd_o = float(cd_o)
if cd_o
is not None else float(
"nan")
56 cd_f = float(cd_f)
if cd_f
is not None else float(
"nan")
58 exp_af = _expected_geom_ao_af_for_unit_momentum_ratio(optimal_mr, rho_o, rho_f)
59 ao_af_geom = a_go / a_gf
if np.isfinite(a_go)
and np.isfinite(a_gf)
and a_gf > 0
else float(
"nan")
60 ao_af_eff = a_eo / a_ef
if np.isfinite(a_eo)
and np.isfinite(a_ef)
and a_ef > 0
else float(
"nan")
62 u_o_lin = diag.get(
"u_O")
63 u_f_lin = diag.get(
"u_F")
64 u_o_lin = float(u_o_lin)
if u_o_lin
is not None else float(
"nan")
65 u_f_lin = float(u_f_lin)
if u_f_lin
is not None else float(
"nan")
67 print(
"\n--- Impinging momentum decomposition (bulk v = mdot/(ρ A_geom_tot)) ---")
68 print(f
"mdot_O [kg/s]: {mdot_o:.6g}")
69 print(f
"mdot_F [kg/s]: {mdot_f:.6g}")
70 print(f
"rho_O [kg/m³]: {rho_o:.6g}")
71 print(f
"rho_F [kg/m³]: {rho_f:.6g}")
72 print(f
"A_geom_O [m²]: {a_go:.6g}")
73 print(f
"A_geom_F [m²]: {a_gf:.6g}")
74 print(f
"A_eff_O [m²]: {a_eo:.6g}")
75 print(f
"A_eff_F [m²]: {a_ef:.6g}")
76 print(f
"v_O_bulk [m/s]: {v_o:.6g} (runner linear u_O [m/s]: {u_o_lin:.6g})")
77 print(f
"v_F_bulk [m/s]: {v_f:.6g} (runner linear u_F [m/s]: {u_f_lin:.6g})")
78 print(f
"rho_O*v_O² [Pa]: {flux_o:.6g}")
79 print(f
"rho_F*v_F² [Pa]: {flux_f:.6g}")
80 print(f
"momentum_ratio_R: {r_diag:.6g} (reconstructed √(flux_O/flux_F): {r_recon:.6g})")
81 print(f
"Cd_O / Cd_F: {cd_o:.6g} / {cd_f:.6g}")
82 print(
"\n--- Geometry momentum hint (optimizer steering; MR from requirements) ---")
83 print(f
"A_geom_O/A_geom_F: {ao_af_geom:.6g}")
84 print(f
"A_eff_O/A_eff_F: {ao_af_eff:.6g}")
85 print(f
"expected_AO_AF_for_R≈1: {exp_af:.6g} (= MR/√(ρ_O/ρ_F), MR={optimal_mr:g})")
86 pg = perf.get(
"geom_ao_af")
87 pe = perf.get(
"expected_ao_af_for_R1")
88 if pg
is not None or pe
is not None:
89 print(f
"(promoted perf geom_ao_af / expected): {pg} / {pe}")
93 ap = argparse.ArgumentParser(description=__doc__)
97 default=str(ROOT /
"configs/impinging_lox_ch4_8000N.yaml"),
98 help=
"YAML config path",
100 ap.add_argument(
"--layer1-max-iterations", type=int, default=22)
101 args = ap.parse_args()
103 cfg = load_config(args.config)
104 req = cfg.design_requirements.model_dump()
107 "mode":
"optimizer_controlled",
108 "max_lox_pressure_psi": float(req[
"max_lox_tank_pressure_psi"]),
109 "max_fuel_pressure_psi": float(req[
"max_fuel_tank_pressure_psi"]),
112 opt_cfg, results = run_layer1_optimization(
116 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
117 tolerances={
"thrust": 0.10,
"apogee": 0.15},
118 pressure_config=pcfg,
120 layer1_max_iterations=int(args.layer1_max_iterations),
121 layer1_cma_restarts=1,
124 perf = results.get(
"performance")
or {}
125 rho_o = float(opt_cfg.fluids[
"oxidizer"].density)
126 rho_f = float(opt_cfg.fluids[
"fuel"].density)
127 optimal_mr = float(req.get(
"optimal_of_ratio", 3.5))
128 geom = getattr(getattr(opt_cfg,
"injector",
None),
"geometry",
None)
129 ox = getattr(geom,
"oxidizer",
None)
130 fu = getattr(geom,
"fuel",
None)
132 print(
"\n=== Layer-1 summary (8000 N impinging, asymmetric ΔP/Pc bands) ===")
134 f
"Preferred bands: O [{req.get('injector_dp_ratio_O_min')},{req.get('injector_dp_ratio_O_max')}], "
135 f
"F [{req.get('injector_dp_ratio_F_min')},{req.get('injector_dp_ratio_F_max')}]"
137 print(f
"Thrust F [N]: {perf.get('F')}")
138 print(f
"O/F (MR): {perf.get('MR')}")
139 print(f
"momentum_ratio_R: {perf.get('momentum_ratio_R')}")
140 print(f
"ΔP_inj_O / Pc: {perf.get('injector_dp_ratio_O')}")
141 print(f
"ΔP_inj_F / Pc: {perf.get('injector_dp_ratio_F')}")
142 print(f
"P_O_start_psi: {perf.get('P_O_start_psi')}")
143 print(f
"P_F_start_psi: {perf.get('P_F_start_psi')}")
144 print(f
"injector_dp_out_of_range: {perf.get('injector_dp_out_of_range')}")
146 f
"geom AO/AF diagnostics: AoAf={perf.get('geom_ao_af')} "
147 f
"expected_R1={perf.get('expected_ao_af_for_R1')} "
148 f
"rel_err={perf.get('geom_ao_af_rel_error')}"
151 perf, rho_o=rho_o, rho_f=rho_f, optimal_mr=optimal_mr
154 print(f
"n_elements_O / d_jet_O [mm]: {ox.n_elements} / {ox.d_jet * 1000:.4f}")
155 print(f
"n_elements_F / d_jet_F [mm]: {fu.n_elements} / {fu.d_jet * 1000:.4f}")
156 print(f
"spacing_O/spacing_F [mm]: {ox.spacing * 1000:.4f} / {fu.spacing * 1000:.4f}")
157 print(f
"impingement_angle_O/F [deg]: {ox.impingement_angle:.2f} / {fu.impingement_angle:.2f}")