38 """The spray/doublet diagnostics impinging.py publishes that the kernel tuple does not carry.
40 Layer 1 reads D_pitch_*, element_gap_*, L_imp and vaporization_length_total out of the
41 diagnostics (its impinging geometry-fit term and final report), and on the DEFAULT path those
42 diagnostics are this dict (closure.flows -> accel.solve). Without these keys the accelerated
43 path reported None where ED_ACCEL=off reported numbers. Computed from the solved state with
44 the SAME helpers, in the same order, as ImpingingInjector.solve -- not re-derived.
50 check_spray_constraints, evaporation_constant_m2_s, momentum_flux_ratio,
51 ohnesorge_number, spray_angle_from_J, spray_angle_from_TMR, tau_evap, tau_evap_from_k,
52 thrust_momentum_ratio,
54 (_ok, mdot_O, mdot_F, u_O, u_F, D32_O, D32_F, _mom_R, _Cd_O, _Cd_F,
55 _Pi_O, _Pi_F, _dpi_O, _dpi_F, _A_O, _A_F, _dpf_O, _dpf_F, We_O, We_F, u_rel, x_star,
56 _cok, _n_iter, _ti_O, _ti_F) = sol[:26]
57 geometry = config.injector.geometry
58 spray_cfg = config.spray
59 fluids = config.fluids
60 rho_O, mu_O, sigma_O = fluids[
"oxidizer"].density, fluids[
"oxidizer"].viscosity, fluids[
"oxidizer"].surface_tension
61 rho_F, mu_F, sigma_F = fluids[
"fuel"].density, fluids[
"fuel"].viscosity, fluids[
"fuel"].surface_tension
63 rho_gas = float(
max(Pc / (spray_cfg.smd.chamber_gas_R * spray_cfg.smd.chamber_gas_T), 1e-6))
64 J = momentum_flux_ratio(rho_O, u_O, rho_F, u_F)
65 MR = mdot_O / mdot_F
if mdot_F > 0
else np.inf
66 TMR = thrust_momentum_ratio(J, MR)
67 if spray_cfg.spray_angle.model ==
"J":
68 theta = spray_angle_from_J(J, spray_cfg.spray_angle.k, spray_cfg.spray_angle.n)
70 theta = spray_angle_from_TMR(TMR)
71 Oh_O = ohnesorge_number(mu_O, rho_O, sigma_O, geometry.oxidizer.d_jet)
72 Oh_F = ohnesorge_number(mu_F, rho_F, sigma_F, geometry.fuel.d_jet)
74 _ev = spray_cfg.evaporation
75 k_evap_O = k_evap_F = float(
"nan")
76 if getattr(_ev,
"model",
"derived") ==
"derived":
77 k_evap_O = evaporation_constant_m2_s(
78 Tc=spray_cfg.smd.chamber_gas_T, Pc=Pc, rho_g=rho_gas, rho_l=rho_O,
79 L_vap=float(getattr(fluids[
"oxidizer"],
"latent_heat", 0.0)
or 0.0),
80 T_boil=float(getattr(fluids[
"oxidizer"],
"boiling_point", 0.0)
or 0.0),
81 cp_g=float(getattr(_ev,
"cp_gas", 2200.0)), C_evap=float(getattr(_ev,
"C_evap", 1.562)))
82 k_evap_F = evaporation_constant_m2_s(
83 Tc=spray_cfg.smd.chamber_gas_T, Pc=Pc, rho_g=rho_gas, rho_l=rho_F,
84 L_vap=float(getattr(fluids[
"fuel"],
"latent_heat", 0.0)
or 0.0),
85 T_boil=float(getattr(fluids[
"fuel"],
"boiling_point", 0.0)
or 0.0),
86 cp_g=float(getattr(_ev,
"cp_gas", 2200.0)), C_evap=float(getattr(_ev,
"C_evap", 1.562)))
87 if np.isfinite(k_evap_O)
and np.isfinite(k_evap_F):
88 tau_evap_O = tau_evap_from_k(D32_O, k_evap_O)
89 tau_evap_F = tau_evap_from_k(D32_F, k_evap_F)
91 tau_evap_O = tau_evap(D32_O, _ev.K)
92 tau_evap_F = tau_evap(D32_F, _ev.K)
94 s_O = float(getattr(geometry.oxidizer,
"spacing", 0.0)
or 0.0)
95 s_F = float(getattr(geometry.fuel,
"spacing", 0.0)
or 0.0)
96 L_imp = impingement_standoff_m(
97 0.5 * (geometry.oxidizer.n_elements + geometry.fuel.n_elements), s_O, s_F,
98 geometry.oxidizer.impingement_angle, geometry.fuel.impingement_angle)
99 D_pitch_O = float(
max(1, int(geometry.oxidizer.n_elements)) * s_O / np.pi)
100 D_pitch_F = float(
max(1, int(geometry.fuel.n_elements)) * s_F / np.pi)
101 _d_avg = 0.5 * (float(geometry.oxidizer.d_jet) + float(geometry.fuel.d_jet))
102 _rho_l_avg = 0.5 * (float(rho_O) + float(rho_F))
103 if np.isfinite(L_imp)
and L_imp > 0
and _d_avg > 0
and rho_gas > 1e-4:
104 L_b = float((_d_avg ** 2) / (4.0 * L_imp) * np.sqrt(_rho_l_avg / rho_gas))
105 L_b = float(np.clip(L_b, 0.0, 20.0 * _d_avg))
108 _cok2, violations = check_spray_constraints(We_O, We_F, x_star, spray_cfg)
110 "violations": violations,
"J": J,
"TMR": TMR,
"theta": theta,
111 "rho_gas_breakup": float(rho_gas),
"Oh_O": float(Oh_O),
"Oh_F": float(Oh_F),
112 "k_evap_O": float(k_evap_O),
"k_evap_F": float(k_evap_F),
113 "tau_evap_O": float(tau_evap_O),
"tau_evap_F": float(tau_evap_F),
114 "L_imp": L_imp,
"L_sheet_breakup": float(L_b),
115 "D_pitch_O": D_pitch_O,
"D_pitch_F": D_pitch_F,
"s_pair": float(0.5 * (s_O + s_F)),
116 "element_gap_O": float(s_O - geometry.oxidizer.d_jet),
117 "element_gap_F": float(s_F - geometry.fuel.d_jet),
118 "vaporization_length_total": float((L_imp
if np.isfinite(L_imp)
else 0.0) + L_b + x_star),
119 "apply_tau_res_correction": bool(getattr(_ev,
"apply_tau_res_correction",
False)),
120 "breakup_multiplier": 1.0,
"penetration_multiplier": 1.0,
125 """Mirror native_injector._result_to_diag using Numba's injector_solve tuple.
127 `sol` is the 24-tuple from kernels.injector_solve. With `config` and `Pc` (every caller in
128 engine.accel passes them), an impinging result also carries the spray/doublet keys
129 ImpingingInjector.solve publishes (see _impinging_spray_extras).
131 (_ok, mdot_O, mdot_F, u_O, u_F, D32_O, D32_F, mom_R, Cd_O, Cd_F,
132 Pi_O, Pi_F, dpi_O, dpi_F, A_geom_O, A_geom_F,
133 dpf_O, dpf_F, We_O, We_F, u_rel, x_star, constraints_ok, n_iter,
134 ti_O, ti_F) = sol[:26]
135 holes_O, holes_F = (sol[26], sol[27])
if len(sol) > 26
else ((), ())
137 djo, djf = float(P[_DJO]), float(P[_DJF])
138 rho_O, rho_F = float(P[_RHO_O]), float(P[_RHO_F])
139 n_O, n_F =
max(1, int(P[_NO])),
max(1, int(P[_NF]))
141 mdot_bn_O = Cd_O * A_geom_O * math.sqrt(2.0 * rho_O * dpi_O)
if dpi_O > 0
else 0.0
142 mdot_bn_F = Cd_F * A_geom_F * math.sqrt(2.0 * rho_F * dpi_F)
if dpi_F > 0
else 0.0
147 A_eff_O = Cd_O * A_geom_O
148 A_eff_F = Cd_F * A_geom_F
159 if float(P[_INJ_TYPE]) == 0.0:
160 dh_O, dh_F = float(P[_PIN_DHO]), float(P[_PIN_DHF])
162 dh_O, dh_F = djo, djf
163 v_tot =
max(u_O + u_F, 1e-6)
164 ti_mix =
min(
max((ti_O * u_O + ti_F * u_F) / v_tot, 0.02), 0.35)
167 "injector_type":
"impinging",
168 "A_eff_O": A_eff_O,
"A_eff_F": A_eff_F,
169 "turbulence_intensity_O": ti_O,
"turbulence_intensity_F": ti_F,
170 "turbulence_length_O": 0.07 * dh_O,
"turbulence_length_F": 0.07 * dh_F,
171 "turbulence_intensity_mix": ti_mix,
172 "iterations": int(n_iter),
173 "constraints_satisfied": bool(constraints_ok),
174 "We_O": We_O,
"We_F": We_F,
175 "D32_O": D32_O,
"D32_F": D32_F,
176 "x_star": x_star,
"u_rel": u_rel,
"V_rel": u_rel,
177 "u_O": u_O,
"u_F": u_F,
178 "Cd_O": Cd_O,
"Cd_F": Cd_F,
179 "P_injector_O": Pi_O,
"P_injector_F": Pi_F,
180 "delta_p_injector_O": dpi_O,
"delta_p_injector_F": dpi_F,
181 "delta_p_feed_O": dpf_O,
"delta_p_feed_F": dpf_F,
182 "mdot_from_bernoulli_O": mdot_bn_O,
"mdot_from_bernoulli_F": mdot_bn_F,
183 "A_geom_O": A_geom_O,
"A_geom_F": A_geom_F,
184 "A_jet_O": math.pi * (djo / 2.0) ** 2,
"A_jet_F": math.pi * (djf / 2.0) ** 2,
185 "d_jet_O": djo,
"d_jet_F": djf,
186 "momentum_ratio_n_elements_O": n_O,
"momentum_ratio_n_elements_F": n_F,
187 "rho_O_momentum": rho_O,
"rho_F_momentum": rho_F,
188 "MR": (mdot_O / mdot_F)
if mdot_F > 0
else float(
"nan"),
192 for tag, mdot, rho, area
in ((
"O", mdot_O, rho_O, A_geom_O),
193 (
"F", mdot_F, rho_F, A_geom_F)):
194 if rho > 0
and area > 0:
195 v_bulk = mdot / (rho * area)
196 if math.isfinite(v_bulk):
197 diag[f
"v_{tag}_bulk"] = v_bulk
200 if math.isfinite(mom_R)
and mom_R > 0:
201 diag[
"momentum_ratio_R"] = mom_R
202 if float(P[_INJ_TYPE]) != 0.0:
205 v_Ob = mdot_O / (rho_O * A_geom_O)
if (rho_O > 0
and A_geom_O > 0)
else float(
"nan")
206 v_Fb = mdot_F / (rho_F * A_geom_F)
if (rho_F > 0
and A_geom_F > 0)
else float(
"nan")
207 den = rho_F * v_Fb ** 2 * djf
208 if math.isfinite(den)
and den > 0
and math.isfinite(v_Ob)
and rho_O > 0
and djo > 0:
209 rupe_M = rho_O * v_Ob ** 2 * djo / den
210 if math.isfinite(rupe_M)
and rupe_M > 0:
211 diag[
"rupe_M"] = rupe_M
215 u_ax = ((mdot_O * u_O * math.cos(math.radians(float(P[_ANG_O])))
216 + mdot_F * u_F * math.cos(math.radians(float(P[_ANG_F])))) / mt)
if mt > 0
else float(
"nan")
217 diag[
"u_axial_spray"] = u_ax
if (math.isfinite(u_ax)
and u_ax > 0)
else float(u_rel)
218 if float(P[_INJ_TYPE]) != 0.0:
221 imp_sep = float(P[_ANG_O]) + float(P[_ANG_F])
222 diag[
"impingement_angle_deg"] =
max(1.0,
min(179.0, imp_sep))
224 if config
is not None and Pc
is not None and float(P[_INJ_TYPE]) != 0.0:
230 """impinging._manifold_diagnostics from the kernel's per-hole flows."""
231 if len(holes_O) == 0
and len(holes_F) == 0:
232 return {
"manifold_model":
"plenum"}
233 out = {
"manifold_model":
"ring_network"}
235 for k, holes, md, dpi, rho, d, n
in (
236 (
"O", holes_O, mdot_O, dpi_O, float(P[_RHO_O]), float(P[_DJO]), float(P[_NO])),
237 (
"F", holes_F, mdot_F, dpi_F, float(P[_RHO_F]), float(P[_DJF]), float(P[_NF]))):
241 n_ports, scale, A_ch = float(P[b + 1]), float(P[b + 7]), float(P[b + 3])
242 hs = [float(q)
for q
in holes]
243 mean = md / (2.0 * n_ports * scale * len(hs))
if md > 0
else float(
"nan")
245 out[f
"element_mass_flows_{k}"] = hs
246 out[f
"element_flow_ratio_min_{k}"] = float(
min(hs) / mean)
if mean > 0
else float(
"nan")
247 out[f
"element_flow_ratio_max_{k}"] = float(
max(hs) / mean)
if mean > 0
else float(
"nan")
248 out[f
"manifold_branch_velocity_{k}"] = float(md / (2.0 * n_ports) / (rho * A_ch))
249 out[f
"manifold_ports_{k}"] = int(n_ports)
250 A = n * math.pi * d ** 2 / 4.0
251 out[f
"Cd_eff_manifold_{k}"] = (float(md / (A * math.sqrt(2.0 * rho * dpi)))
252 if dpi > 0
and md > 0
else float(
"nan"))
253 if "O" in per
and "F" in per
and len(per[
"O"]) == len(per[
"F"]):
254 mrs = [o / f
if f > 0
else float(
"inf")
for o, f
in zip(per[
"O"], per[
"F"])]
255 out[
"element_mixture_ratios"] = mrs
256 out[
"element_mass_flows"] = [o + f
for o, f
in zip(per[
"O"], per[
"F"])]
257 out[
"element_mixture_ratio_min"] = float(
min(mrs))
258 out[
"element_mixture_ratio_max"] = float(
max(mrs))