52 ) -> Tuple[float, float, Dict[str, Any]]:
56 discharge_O = config.discharge[
"oxidizer"]
57 discharge_F = config.discharge[
"fuel"]
58 feed_O = config.feed_system[
"oxidizer"]
59 feed_F = config.feed_system[
"fuel"]
60 spray_cfg = config.spray
61 fluids = config.fluids
63 rho_O = fluids[
"oxidizer"].density
64 mu_O = fluids[
"oxidizer"].viscosity
65 sigma_O = fluids[
"oxidizer"].surface_tension
67 rho_F = fluids[
"fuel"].density
68 mu_F = fluids[
"fuel"].viscosity
69 sigma_F = fluids[
"fuel"].surface_tension
71 A_LOX, A_fuel = get_effective_areas(injector_geom)
72 d_hyd_O, d_hyd_F = get_hydraulic_diameters(injector_geom)
77 max_iter = config.solver.closure.max_iterations
78 Cd_reduction = config.solver.closure.Cd_reduction_factor
80 Cd_O_eff = discharge_O.Cd_inf
81 Cd_F_eff = discharge_F.Cd_inf
85 "constraints_satisfied":
False,
95 "injector_type":
"pintle",
96 "turbulence_intensity_O":
None,
97 "turbulence_intensity_F":
None,
98 "turbulence_length_O":
None,
99 "turbulence_length_F":
None,
100 "turbulence_intensity_mix":
None,
103 mdot_O = mdot_O_guess
104 mdot_F = mdot_F_guess
106 for iteration
in range(max_iter):
108 mdot_O = mdot_O_guess
109 mdot_F = mdot_F_guess
115 for feed_iter
in range(3):
118 delta_p_feed_O = delta_p_feed(mdot_O, rho_O, feed_O, P_tank_O, mu=mu_O)
119 delta_p_feed_F_base = delta_p_feed(mdot_F, rho_F, feed_F, P_tank_F, mu=mu_F)
122 if delta_p_feed_O == 0.0
and mdot_O > 0.01:
124 K0_val = feed_O.get(
'K0',
'N/A')
if isinstance(feed_O, dict)
else getattr(feed_O,
'K0',
'N/A')
125 warnings.warn(f
"[WARNING] LOX feed loss is 0.0 with mdot_O={mdot_O:.4f} kg/s. Check feed system config (K0={K0_val}, K_eff should be > 0).")
128 delta_p_feed_O = delta_p_feed(mdot_O, rho_O, feed_O, P_tank_O, mu=mu_O)
129 delta_p_feed_F_base = delta_p_feed(mdot_F, rho_F, feed_F, P_tank_F, mu=mu_F)
130 if config.regen_cooling
is not None and config.regen_cooling.enabled:
131 delta_p_regen = delta_p_regen_channels(
135 config.regen_cooling,
138 delta_p_feed_F = delta_p_feed_F_base + delta_p_regen
140 delta_p_feed_F = delta_p_feed_F_base
143 P_inj_O = P_tank_O - delta_p_feed_O
144 P_inj_F = P_tank_F - delta_p_feed_F
147 delta_p_inj_O =
max(0.0, P_inj_O - Pc)
148 delta_p_inj_F =
max(0.0, P_inj_F - Pc)
150 u_O_quick = np.sqrt(2 * delta_p_inj_O / rho_O)
if delta_p_inj_O > 0
else 0.0
151 u_F_quick = np.sqrt(2 * delta_p_inj_F / rho_F)
if delta_p_inj_F > 0
else 0.0
153 Re_O_quick = calculate_reynolds_number(rho_O, u_O_quick, d_hyd_O, mu_O)
154 Re_F_quick = calculate_reynolds_number(rho_F, u_F_quick, d_hyd_F, mu_F)
159 T_tank_O = getattr(fluids[
"oxidizer"],
'temperature', 90.0)
160 T_tank_F = getattr(fluids[
"fuel"],
'temperature', 300.0)
161 Cd_O_quick_base = cd_from_re(Re_O_quick, discharge_O, P_inlet=P_inj_O, T_inlet=T_tank_O)
162 Cd_F_quick_base = cd_from_re(Re_F_quick, discharge_F, P_inlet=P_inj_F, T_inlet=T_tank_F)
163 Cd_O_quick =
min(Cd_O_quick_base, Cd_O_eff)
164 Cd_F_quick =
min(Cd_F_quick_base, Cd_F_eff)
166 mdot_O = Cd_O_quick * A_LOX * np.sqrt(2 * rho_O * delta_p_inj_O)
167 mdot_F = Cd_F_quick * A_fuel * np.sqrt(2 * rho_F * delta_p_inj_F)
169 delta_p_inj_O =
max(0.0, P_inj_O - Pc)
170 delta_p_inj_F =
max(0.0, P_inj_F - Pc)
177 u_O = mdot_O / (rho_O * A_LOX)
if A_LOX > 0
else 0.0
178 u_F = mdot_F / (rho_F * A_fuel)
if A_fuel > 0
else 0.0
180 Re_O = calculate_reynolds_number(rho_O, u_O, d_hyd_O, mu_O)
181 Re_F = calculate_reynolds_number(rho_F, u_F, d_hyd_F, mu_F)
184 T_tank_O = getattr(fluids[
"oxidizer"],
'temperature', 90.0)
185 T_tank_F = getattr(fluids[
"fuel"],
'temperature', 300.0)
186 Cd_O_base = cd_from_re(Re_O, discharge_O, P_inlet=P_inj_O, T_inlet=T_tank_O)
187 Cd_F_base = cd_from_re(Re_F, discharge_F, P_inlet=P_inj_F, T_inlet=T_tank_F)
188 Cd_O =
min(Cd_O_base, Cd_O_eff)
189 Cd_F =
min(Cd_F_base, Cd_F_eff)
191 if delta_p_inj_O > 0:
192 mdot_O = Cd_O * A_LOX * np.sqrt(2 * rho_O * delta_p_inj_O)
196 if delta_p_inj_F > 0:
197 mdot_F = Cd_F * A_fuel * np.sqrt(2 * rho_F * delta_p_inj_F)
201 u_O = mdot_O / (rho_O * A_LOX)
if A_LOX > 0
else 0.0
202 u_F = mdot_F / (rho_F * A_fuel)
if A_fuel > 0
else 0.0
204 J = momentum_flux_ratio(rho_O, u_O, rho_F, u_F)
205 MR = mdot_O / mdot_F
if mdot_F > 0
else np.inf
206 TMR = thrust_momentum_ratio(J, MR)
208 if spray_cfg.spray_angle.model ==
"J":
209 theta = spray_angle_from_J(J, spray_cfg.spray_angle.k, spray_cfg.spray_angle.n)
211 theta = spray_angle_from_TMR(TMR)
213 We_O = weber_number(rho_O, u_O, injector_geom.lox.d_orifice, sigma_O)
214 We_F = weber_number(rho_F, u_F, d_hyd_F, sigma_F)
216 Oh_O = ohnesorge_number(mu_O, rho_O, sigma_O, injector_geom.lox.d_orifice)
217 Oh_F = ohnesorge_number(mu_F, rho_F, sigma_F, d_hyd_F)
219 turbulence_intensity_O = 0.16 * (Re_O ** -0.125)
if Re_O > 0
else 0.1
220 turbulence_intensity_F = 0.16 * (Re_F ** -0.125)
if Re_F > 0
else 0.1
221 turbulence_intensity_O = float(np.clip(turbulence_intensity_O, 0.02, 0.3))
222 turbulence_intensity_F = float(np.clip(turbulence_intensity_F, 0.02, 0.3))
223 turbulence_length_O = 0.07 * d_hyd_O
224 turbulence_length_F = 0.07 * d_hyd_F
225 velocity_total =
max(u_O + u_F, 1e-6)
226 turbulence_intensity_mix = (
227 (turbulence_intensity_O * u_O + turbulence_intensity_F * u_F) / velocity_total
229 turbulence_intensity_mix = float(np.clip(turbulence_intensity_mix, 0.02, 0.35))
235 V_rel = float(np.sqrt(u_O**2 + u_F**2))
236 L_open = injector_geom.fuel.h_gap
256 if spray_cfg.use_turbulence_corrections:
261 breakup_multiplier = 1.0
263 breakup_multiplier = 1.0
265 U_rel = np.sqrt(u_O ** 2 + u_F ** 2)
266 tau_evap_O = tau_evap(D32_O, spray_cfg.evaporation.K)
267 x_star_O = xstar(U_rel, tau_evap_O)
268 x_star_combined =
max(x_star_O, xstar(U_rel, tau_evap(D32_F, spray_cfg.evaporation.K)))
270 if spray_cfg.use_turbulence_corrections:
271 penetration_multiplier = 1.0 / (1.0 + spray_cfg.turbulence_penetration_gain * turbulence_intensity_mix)
272 penetration_multiplier = float(np.clip(penetration_multiplier, 0.3, 1.0))
273 x_star_combined *= penetration_multiplier
275 penetration_multiplier = 1.0
277 constraints_ok, violations = check_spray_constraints(We_O, We_F, x_star_combined, spray_cfg)
281 delta_p_feed_O_final = delta_p_feed(mdot_O, rho_O, feed_O, P_tank_O, mu=mu_O)
282 delta_p_feed_F_base_final = delta_p_feed(mdot_F, rho_F, feed_F, P_tank_F, mu=mu_F)
283 if config.regen_cooling
is not None and config.regen_cooling.enabled:
284 delta_p_regen_final = delta_p_regen_channels(
288 config.regen_cooling,
291 delta_p_feed_F_final = delta_p_feed_F_base_final + delta_p_regen_final
293 delta_p_feed_F_final = delta_p_feed_F_base_final
297 "iterations": iteration + 1,
298 "constraints_satisfied": constraints_ok,
299 "violations": violations,
309 "x_star": x_star_combined,
310 "turbulence_intensity_O": turbulence_intensity_O,
311 "turbulence_intensity_F": turbulence_intensity_F,
312 "turbulence_length_O": turbulence_length_O,
313 "turbulence_length_F": turbulence_length_F,
314 "turbulence_intensity_mix": turbulence_intensity_mix,
315 "breakup_multiplier": breakup_multiplier,
316 "penetration_multiplier": penetration_multiplier,
321 "P_injector_O": float(P_inj_O),
322 "P_injector_F": float(P_inj_F),
323 "delta_p_injector_O": float(delta_p_inj_O),
324 "delta_p_injector_F": float(delta_p_inj_F),
326 "delta_p_feed_O": float(delta_p_feed_O_final),
327 "delta_p_feed_F": float(delta_p_feed_F_final),
337 Cd_O_eff *= Cd_reduction
338 Cd_F_eff *= Cd_reduction
339 Cd_O_eff =
max(Cd_O_eff, discharge_O.Cd_min)
340 Cd_F_eff =
max(Cd_F_eff, discharge_F.Cd_min)
342 A_eff_O, A_eff_F, eff_warns = effective_flow_areas_from_cd(diagnostics, A_LOX, A_fuel)
343 diagnostics[
"A_geom_O"] = float(A_LOX)
344 diagnostics[
"A_geom_F"] = float(A_fuel)
345 diagnostics[
"A_eff_O"] = float(A_eff_O)
346 diagnostics[
"A_eff_F"] = float(A_eff_F)
348 merge_effective_area_warnings(diagnostics, eff_warns)
350 return mdot_O, mdot_F, diagnostics