STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
kernels.py
Go to the documentation of this file.
1"""Numba mirror of the Python injector solve (impinging and pintle) and the CEA lookup.
2
3All state arrives as one flat float64 vector built by params.extract_params, plus the CEA tables
4as plain arrays -- @njit sees no Python objects.
5
6injector_solve / injector_solve_pintle / _dpf mirror the Python injector exactly (one closure
7pass with Cd untouched, the K_exit exit dump, A_hydraulic, pintle's fixed-K x*, the back-channel
8ring network hole by hole), and that is what accel.solve serves on the default path. The chamber
9(c* efficiency, ablative liner, nozzle, Pc root) is engine/accel/chamber.py.
10"""
11from __future__ import annotations
12
13import numpy as np
14from numba import njit
15
16from engine.accel.cea import cea_arrays, _cea_arrays_cached
17from engine.accel.params import _IDX, NP, extract_params
18
19globals().update(_IDX) # param indices as module-level int constants
20
21# Lichtarowicz (1965) Re law + counterbore entrance K: the Python model's own constants, so the
22# kernel cannot drift from engine.core.discharge.
23from engine.core.discharge import (
24 LICHTAROWICZ_RE_A as LICH_RE_A, LICHTAROWICZ_RE_B as LICH_RE_B,
25 LICHTAROWICZ_RE_C as LICH_RE_C, LICHTAROWICZ_RE_D as LICH_RE_D,
26 LICHTAROWICZ_RE_E as LICH_RE_E, COUNTERBORE_ENTRANCE_K as COUNTERBORE_K,
27)
28
29# physical constants (ed_phys_const.h)
30# physical constants (ed_phys_const.h)
31PI = np.pi
32G0 = 9.80665
33P_SEA = 101325.0
34# ed_cooling.c / ed_phys_const.h
35RANKINE_PER_K = 1.8; HUZEL_COEFF = 46.6e-10; LBM_PER_IN_S_TO_PA_S = 0.45359237 / 0.0254 # Huzel fit is lbm/(in s)
36STEFAN = 5.670374419e-8; MIN_DENS = 0.01; EPS_SMALL = 1e-6; EPS_TINY = 1e-8
37NU_LAMINAR = 4.36; NU_TURB_COEF = 0.023; NU_TURB_RE_EXP = 0.8; NU_TURB_PR_EXP = 0.4
38PRANDTL = 0.8
39D_M_REF = 5e-5; D_M_TREF = 1500.0; D_M_PREF = 2.5e6; U_SLIP_CAP = 50.0; D_MIN_GAS = 1e-6
40CS_C_L = 0.1; CS_C_U = 0.5; CS_U_RMS_CAP = 200.0
41GAS_MU = 7e-5; GAS_RHO_L = 800.0; GAS_CP_L = 2000.0; GAS_T_INJ = 293.0; GAS_CP_G = 2200.0
42SMD_INGEBO = 1; SPRAYANG_J = 1; PHI_NONE = 0; PHI_SQRTP = 1; PHI_LOGP = 2
43EFF_CONSTANT = 0; EFF_LINEAR = 1
44
45
46# ------------------------- njit kernels --------------------------------------
47@njit(cache=True)
48def _clip(x, lo, hi):
49 return lo if x < lo else (hi if x > hi else x)
50
51@njit(cache=True)
52def _tri(table, ip, im, ie, w0, w1, w2, w3, w4, w5, w6, w7):
53 f0 = table[ip-1, im-1, ie-1]; f1 = table[ip, im-1, ie-1]
54 f2 = table[ip-1, im, ie-1]; f3 = table[ip, im, ie-1]
55 f4 = table[ip-1, im-1, ie]; f5 = table[ip, im-1, ie]
56 f6 = table[ip-1, im, ie]; f7 = table[ip, im, ie]
57 if (np.isnan(f0) or np.isnan(f1) or np.isnan(f2) or np.isnan(f3) or
58 np.isnan(f4) or np.isnan(f5) or np.isnan(f6) or np.isnan(f7)):
59 return f0
60 return f0*w0 + f1*w1 + f2*w2 + f3*w3 + f4*w4 + f5*w5 + f6*w6 + f7*w7
61
62@njit(cache=True)
63def cea_eval(Pcg, MRg, epsg, cstar, Cf, Tc, gam, Rt, Mt, Cfvac, MR, Pc, eps):
64 Pc_c = _clip(Pc, Pcg[0], Pcg[-1]); MR_c = _clip(MR, MRg[0], MRg[-1]); eps_c = _clip(eps, epsg[0], epsg[-1])
65 npc = Pcg.shape[0]; nmr = MRg.shape[0]; nep = epsg.shape[0]
66 ip = np.searchsorted(Pcg, Pc_c, side="left"); ip = 1 if ip < 1 else (npc-1 if ip > npc-1 else ip)
67 im = np.searchsorted(MRg, MR_c, side="left"); im = 1 if im < 1 else (nmr-1 if im > nmr-1 else im)
68 ie = np.searchsorted(epsg, eps_c, side="left"); ie = 1 if ie < 1 else (nep-1 if ie > nep-1 else ie)
69 Pc0, Pc1 = Pcg[ip-1], Pcg[ip]; MR0, MR1 = MRg[im-1], MRg[im]; e0, e1 = epsg[ie-1], epsg[ie]
70 wx = (Pc_c-Pc0)/(Pc1-Pc0) if Pc1 != Pc0 else 0.0
71 wy = (MR_c-MR0)/(MR1-MR0) if MR1 != MR0 else 0.0
72 wz = (eps_c-e0)/(e1-e0) if e1 != e0 else 0.0
73 w0=(1-wx)*(1-wy)*(1-wz); w1=wx*(1-wy)*(1-wz); w2=(1-wx)*wy*(1-wz); w3=wx*wy*(1-wz)
74 w4=(1-wx)*(1-wy)*wz; w5=wx*(1-wy)*wz; w6=(1-wx)*wy*wz; w7=wx*wy*wz
75 return (_tri(cstar,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
76 _tri(Cf,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
77 _tri(Tc,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
78 _tri(gam,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
79 _tri(Rt,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
80 _tri(Mt,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7),
81 _tri(Cfvac,ip,im,ie,w0,w1,w2,w3,w4,w5,w6,w7))
82
83@njit(cache=True)
84def _reynolds(rho, u, d, mu):
85 if mu <= 0.0:
86 return 1e6
87 return rho*u*d/mu
88
89@njit(cache=True)
90def _cd_inf_orifice(d_hyd, cdinf, geom, dref, dmin, exps, logg, cdmax, cdfloor):
91 if geom == 0.0:
92 return cdinf
93 if not np.isfinite(d_hyd) or d_hyd <= 0.0:
94 return cdinf
95 d = dmin if dmin > d_hyd else d_hyd
96 if dref <= 0.0:
97 return _clip(cdinf, cdfloor, cdmax)
98 ratio = d/dref
99 if ratio < 1.0:
100 cd = cdinf * ratio**(exps if exps > 0.0 else 0.0)
101 else:
102 cd = cdinf + logg*np.log(ratio)
103 return _clip(cd, cdfloor, cdmax)
104
105@njit(cache=True)
106def _cd_lichtarowicz_re(cd_u, Re, x):
107 """engine.core.discharge.cd_lichtarowicz_re (Lichtarowicz, Duggins & Markland 1965)."""
108 lg = np.log10(LICH_RE_E*Re)
109 inv = (1.0/cd_u
110 + LICH_RE_A*(1.0 + LICH_RE_B*x)/Re
111 - LICH_RE_C*x/(1.0 + LICH_RE_D*lg*lg))
112 return 1.0/inv
113
114@njit(cache=True)
115def _cd_from_re(Re, P_in, T_in, d_hyd, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg,
116 cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu):
117 cd_inf_eff = _cd_inf_orifice(d_hyd, cdinf, geom, dref, dmin, exps, logg, cdmax, cdfloor)
118 if Re <= 0.0:
119 return cdmin
120 if lod > 0.0:
121 cd = _cd_lichtarowicz_re(cdu, Re, lod)
122 if beta > 0.0:
123 b4 = beta**4
124 cd = 1.0/np.sqrt((1.0 - b4)/cd**2 + b4*(1.0 + COUNTERBORE_K))
125 if cd > 0.98:
126 cd = 0.98
127 else:
128 cd = cd_inf_eff - aRe/np.sqrt(Re if Re > 1e-6 else 1e-6)
129 if upc != 0.0 and np.isfinite(P_in) and Pref > 0.0:
130 cd *= 1.0 + aP*(P_in/Pref - 1.0)
131 if utc != 0.0 and np.isfinite(T_in) and Tref > 0.0:
132 cd *= 1.0 + aT*(T_in/Tref - 1.0)
133 return _clip(cd, cdmin, cd_inf_eff)
134
135@njit(cache=True)
136def _dpf(mdot, rho, ah, k0, k1, phi, P_tank, kx, dex):
137 """feed_loss.delta_p_feed: K_eff(P) velocity head on A_hydraulic plus the exit dump.
138
139 dp = K_eff (rho/2) (mdot/(rho A_hyd))^2 + K_exit (rho/2) (mdot/(rho A_exit))^2, with
140 A_exit the d_exit bore when one is declared and A_hyd otherwise. The passage is ALWAYS
141 A_hydraulic (the schema derives it from d_inlet), never pi d_inlet^2/4: a twin line
142 declares the summed area, and the bore-derived area overstated its loss 4x.
143
144 k0 arrives with the itemised fittings already summed in (params._feed). The Colebrook
145 friction path (feed_system.<side>.roughness_m) is NOT mirrored -- it needs mu per call --
146 and accel.can_handle hands those configs to Python.
147 """
148 if phi == PHI_NONE:
149 keff = k0
150 elif phi == PHI_SQRTP:
151 keff = k0 + k1*np.sqrt(P_tank if P_tank > 0 else 0.0)
152 elif phi == PHI_LOGP:
153 keff = k0 + k1*np.log(P_tank)
154 else:
155 return np.nan
156 A = ah
157 if not (A > 0.0) or not (rho > 0.0) or mdot < 0.0:
158 return np.nan
159 Ax = PI*(dex*0.5)**2 if dex > 0.0 else A
160 v = mdot/(rho*A)
161 vx = mdot/(rho*Ax)
162 dp = keff*(rho*0.5)*v**2 + kx*(rho*0.5)*vx**2
163 return 0.0 if dp < 0.0 else dp
164
165@njit(cache=True)
166def _bern(mdot_seed, dP, Pi, rho, area, dhyd, mu, Tin, cd_cap,
167 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu):
168 if dP <= 0.0:
169 c0 = _cd_from_re(0.0, Pi, Tin, dhyd, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
170 return 0.0, c0 if c0 < cd_cap else cd_cap
171 cdlo = _cd_from_re(0.0, Pi, Tin, dhyd, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
172 cdlo = cdlo if cdlo < cd_cap else cd_cap
173 m = mdot_seed if mdot_seed > 1e-18 else cdlo*area*np.sqrt(2.0*rho*dP)
174 cd = cdlo
175 for _ in range(120):
176 m_was = m
177 u = m/(rho*area) if area > 0.0 else 0.0
178 Re = _reynolds(rho, u, dhyd, mu)
179 cd = _cd_from_re(Re, Pi, Tin, dhyd, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
180 cd = cd if cd < cd_cap else cd_cap
181 m = cd*area*np.sqrt(2.0*rho*dP)
182 denom = np.abs(m_was) if np.abs(m_was) > 1e-18 else 1e-18
183 if np.abs(m - m_was)/denom < 1e-12:
184 break
185 return m, cd
186
187
188@njit(cache=True)
189def _stream_state(m, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
190 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc):
191 dpf = _dpf(m, rho, AH, K0, K1, PHI, P_tank, KX, DEX)
192 Pi = P_tank - dpf
193 u = m/(rho*A) if A > 0.0 else 0.0
194 Re = _reynolds(rho, u, dh, mu)
195 cd = _cd_from_re(Re, Pi, Tin, dh, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
196 cd = cd if cd < cd_cap else cd_cap
197 if cc > 0.0 and pv == pv and Pi > Pc:
198 # Cavitating orifice (Nurick 1976), mirrors impinging._stream_flow.cd_of
199 K = (Pi - pv)/(Pi - Pc)
200 cdc = cc*np.sqrt(K if K > 0.0 else 0.0)
201 cd = cd if cd < cdc else cdc
202 dpi = Pi - Pc
203 mb = cd*A*np.sqrt(2.0*rho*dpi) if dpi > 0.0 else 0.0
204 return m - mb, cd, Pi, dpf, (dpi if dpi > 0.0 else 0.0)
205
206
207@njit(cache=True)
208def _stream_flow(Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
209 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc):
210 """Mirrors impinging._stream_flow: Illinois false position on [0, Cd_cap A sqrt(2 rho (P_tank - Pc))]."""
211 if not (A > 0.0) or not (P_tank > Pc):
212 g, cd, Pi, dpf, dpi = _stream_state(0.0, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
213 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)
214 return 0.0, cd, Pi, dpf, dpi
215 hi = cd_cap*A*np.sqrt(2.0*rho*(P_tank - Pc))
216 a = 0.0; b = hi
217 ga = _stream_state(a, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
218 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)[0]
219 gb = _stream_state(b, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
220 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)[0]
221 tol = 1e-13*hi
222 if gb <= 0.0:
223 m = b
224 elif ga >= 0.0:
225 m = a
226 else:
227 side = 0
228 m = b
229 for _ in range(200):
230 m = (a*gb - b*ga)/(gb - ga)
231 gm = _stream_state(m, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
232 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)[0]
233 if abs(gm) <= tol or b - a <= tol:
234 break
235 if gm > 0.0:
236 b = m; gb = gm
237 if side == -1:
238 ga *= 0.5
239 side = -1
240 else:
241 a = m; ga = gm
242 if side == 1:
243 gb *= 0.5
244 side = 1
245 g, cd, Pi, dpf, dpi = _stream_state(m, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX,
246 cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)
247 return m, cd, Pi, dpf, dpi
248
249
250@njit(cache=True)
251def _churchill_f(Re, rel_rough):
252 """impinging._churchill_f: Darcy friction, Churchill (1977)."""
253 if not (Re > 0.0):
254 return 0.0
255 A = (2.457*np.log(1.0/((7.0/Re)**0.9 + 0.27*rel_rough)))**16
256 B = (37530.0/Re)**16
257 return 8.0*((8.0/Re)**12 + 1.0/(A + B)**1.5)**(1.0/12.0)
258
259
260@njit(cache=True)
261def _cd_side(P, side, Re, P_in, Pc, cd_cap):
262 """impinging._stream_flow.cd_of for one stream: Cd(Re, P_in) capped, then the Nurick limit."""
263 if side == 0:
264 cd = _cd_from_re(Re, P_in, P[T_O], P[DJO], P[DO_CDINF], P[DO_ARE], P[DO_CDMIN], P[DO_GEOM],
265 P[DO_DREF], P[DO_DMIN], P[DO_EXPS], P[DO_LOGG], P[DO_CDMAX], P[DO_CDFLOOR],
266 P[DO_UPC], P[DO_PREF], P[DO_AP], P[DO_UTC], P[DO_TREF], P[DO_AT], P[DO_LOD],
267 P[DO_BETA], P[DO_CDU])
268 pv = P[VP_O]; cc = P[CC_O]
269 else:
270 cd = _cd_from_re(Re, P_in, P[T_F], P[DJF], P[DF_CDINF], P[DF_ARE], P[DF_CDMIN], P[DF_GEOM],
271 P[DF_DREF], P[DF_DMIN], P[DF_EXPS], P[DF_LOGG], P[DF_CDMAX], P[DF_CDFLOOR],
272 P[DF_UPC], P[DF_PREF], P[DF_AP], P[DF_UTC], P[DF_TREF], P[DF_AT], P[DF_LOD],
273 P[DF_BETA], P[DF_CDU])
274 pv = P[VP_F]; cc = P[CC_F]
275 cd = cd if cd < cd_cap else cd_cap
276 if cc > 0.0 and pv == pv and P_in > Pc:
277 K = (P_in - pv)/(P_in - Pc)
278 cdc = cc*np.sqrt(K if K > 0.0 else 0.0)
279 cd = cd if cd < cdc else cdc
280 return cd
281
282
283@njit(cache=True)
284def _net_march(P, side, m_total, P_port, Pc, cd_cap, holes):
285 """impinging._RingManifold.march: one branch of the dividing-flow ring, hole by hole.
286
287 Fills ``holes`` with each hole's flow (port first); returns (stream total, flow-weighted Cd)."""
288 if side == 0:
289 b = NET_O; rho = P[RHO_O]; mu = P[MU_O]
290 else:
291 b = NET_F; rho = P[RHO_F]; mu = P[MU_F]
292 n_ports = P[b + 1]; h = int(P[b + 2]); A_ch = P[b + 3]; D_h = P[b + 4]; s = P[b + 5]
293 eps = P[b + 6]; scale = P[b + 7]; A_hole = P[b + 8]; d_hole = P[b + 9]
294 K_ent = P[b + 10]; C_R = P[b + 11]
295 mb = m_total/(2.0*n_ports)
296 u = mb/(rho*A_ch)
297 p = P_port - (1.0 + K_ent)*0.5*rho*u*u
298 s_q = 0.0; s_cq = 0.0; cd0 = 0.0
299 for j in range(h):
300 seg = 0.5*s if j == 0 else s
301 Re_ch = rho*abs(u)*D_h/mu
302 p -= _churchill_f(Re_ch, eps)*(seg/D_h)*0.5*rho*u*abs(u)
303 dp = p - Pc
304 q = 0.0
305 if dp > 0.0:
306 q = _cd_side(P, side, 0.0, p, Pc, cd_cap)*A_hole*np.sqrt(2.0*rho*dp)
307 for _ in range(3): # the hole's Cd at its own Reynolds number
308 Re_h = rho*(q/(rho*A_hole))*d_hole/mu
309 q = _cd_side(P, side, Re_h, p, Pc, cd_cap)*A_hole*np.sqrt(2.0*rho*dp)
310 if dp > 0.0 and q > 0.0:
311 c = q/(A_hole*np.sqrt(2.0*rho*dp))
312 else:
313 c = _cd_side(P, side, 0.0, p, Pc, cd_cap)
314 if j == 0:
315 cd0 = c
316 mb -= q
317 u_new = mb/(rho*A_ch)
318 p += C_R*0.5*rho*(u*u - u_new*u_new)
319 u = u_new
320 holes[j] = q
321 s_q += q
322 s_cq += c*q
323 total = 2.0*n_ports*scale*s_q
324 return total, (s_cq/s_q if s_q > 0.0 else cd0)
325
326
327@njit(cache=True)
328def _stream_state_net(m, Pc, P_tank, side, A, cd_cap, P, holes):
329 """impinging._stream_flow.state with a ring network."""
330 if side == 0:
331 dpf = _dpf(m, P[RHO_O], P[FO_AH], P[FO_K0], P[FO_K1], P[FO_PHI], P_tank, P[FO_KX], P[FO_DEX])
332 else:
333 dpf = _dpf(m, P[RHO_F], P[FF_AH], P[FF_K0], P[FF_K1], P[FF_PHI], P_tank, P[FF_KX], P[FF_DEX])
334 Pi = P_tank - dpf
335 dpi = Pi - Pc
336 mb, cd = _net_march(P, side, m, Pi, Pc, cd_cap, holes)
337 return m - mb, cd, Pi, dpf, (dpi if dpi > 0.0 else 0.0)
338
339
340@njit(cache=True)
341def _stream_flow_net(Pc, P_tank, side, A, cd_cap, P, holes):
342 """impinging._stream_flow with ``network``: the same Illinois root on the stream total; the
343 last state evaluated is the root's, so ``holes`` ends holding the solved per-hole flows."""
344 rho = P[RHO_O] if side == 0 else P[RHO_F]
345 if not (A > 0.0) or not (P_tank > Pc):
346 g, cd, Pi, dpf, dpi = _stream_state_net(0.0, Pc, P_tank, side, A, cd_cap, P, holes)
347 return 0.0, cd, Pi, dpf, dpi
348 hi = cd_cap*A*np.sqrt(2.0*rho*(P_tank - Pc))
349 a = 0.0; b = hi
350 ga = _stream_state_net(a, Pc, P_tank, side, A, cd_cap, P, holes)[0]
351 gb = _stream_state_net(b, Pc, P_tank, side, A, cd_cap, P, holes)[0]
352 tol = 1e-13*hi
353 if gb <= 0.0:
354 m = b
355 elif ga >= 0.0:
356 m = a
357 else:
358 side_ = 0
359 m = b
360 for _ in range(200):
361 m = (a*gb - b*ga)/(gb - ga)
362 gm = _stream_state_net(m, Pc, P_tank, side, A, cd_cap, P, holes)[0]
363 if abs(gm) <= tol or b - a <= tol:
364 break
365 if gm > 0.0:
366 b = m; gb = gm
367 if side_ == -1:
368 ga *= 0.5
369 side_ = -1
370 else:
371 a = m; ga = gm
372 if side_ == 1:
373 gb *= 0.5
374 side_ = 1
375 g, cd, Pi, dpf, dpi = _stream_state_net(m, Pc, P_tank, side, A, cd_cap, P, holes)
376 return m, cd, Pi, dpf, dpi
377
378
379@njit(cache=True)
380def _tn4222(d_jet, v_jet, rho_l, mu_l, sigma, rho_g, scale, prop):
381 """Mirrors spray.smd_impinging_tn4222 (NACA TN 4222, dV = Vj) times smd_scale."""
382 if d_jet <= 0 or v_jet <= 0 or sigma <= 0 or rho_g <= 0 or rho_l <= 0 or mu_l <= 0:
383 return d_jet
384 dj = d_jet/0.0254
385 vj = v_jet/0.3048
386 d30 = dj/(2.64*np.sqrt(dj*vj) + 0.97*dj*vj)*0.0254
387 d32 = (5.0/3.915)*d30
388 if prop != 0.0:
389 rho_air = 29.3*3386.389/(287.05*((82.0 - 32.0)*5.0/9.0 + 273.15))
390 ratio = (sigma*mu_l/rho_l)/(0.0197*3.8e-4/684.0)
391 d32 = d32*(rho_g/rho_air)**(-0.25)*ratio**0.25
392 return scale*d32
393
394@njit(cache=True)
395def _lefebvre(d_or, We, Oh, C, m, p):
396 if We <= 0 or d_or <= 0:
397 return d_or
398 return C*d_or*We**(-m)*(1.0+Oh)**p
399
400@njit(cache=True)
401def _tau_evap(D32, rho_l, Lv, Tb, Tc, Pc, rho_ch, cp_g, C_ev, ev_model, K_legacy):
402 """Droplet evaporation time [s]. ONE definition, shared by every kernel site.
403
404 The kernel used to hardcode the legacy fixed-K law (tau = K*D32^2) at the impinging and
405 pintle injector-solve sites while the Python reference
406 (engine/core/injectors/impinging.py) used the DERIVED Spalding constant, which is the
407 schema default (spray.evaporation.model = "derived"). x_star gates the Cd-reduction
408 loop, so the two paths disagreed on mass flow: measured 9.7% on thrust and 8.7% on Pc
409 for a shipped config. The A/B parity suite missed it because it sweeps tank pressure at
410 fixed geometry and never diffs x_star.
411
412 Derived branch (matches _evap_k_derived on the Python side):
413 B_M = cp_g (Tc - Tb) / Lv Spalding mass-transfer number
414 D_v = 2e-5 (Tc/300)^1.75 (101325/Pc) binary diffusivity, same scaling law
415 k_ev = C_ev (8 rho_ch D_v / rho_l) ln(1 + B_M)
416 tau = D32^2 / k_ev
417 Falls back to the legacy law when the model is off or an input is missing, so a config
418 that does not declare the derived model is bit-identical to before.
419 """
420 if D32 <= 0.0:
421 return 0.0
422 if ev_model > 0.5 and Lv > 0.0 and Tb > 0.0 and rho_l > 0.0 and Pc > 0.0 and Tc > 0.0:
423 D_v = 2.0e-5*(Tc/300.0)**1.75*(101325.0/Pc)
424 if D_v > 0.0:
425 B_M = cp_g*max(0.0, Tc - Tb)/Lv
426 k_ev = C_ev*(8.0*rho_ch*D_v/rho_l)*np.log1p(B_M)
427 if k_ev > 0.0:
428 return (D32*D32)/k_ev
429 return K_legacy*D32*D32
430
431
432@njit(cache=True)
433def _ohnesorge(mu, rho, sigma, d):
434 if rho <= 0 or sigma <= 0 or d <= 0:
435 return 0.0
436 arg = rho*sigma*d
437 return mu/np.sqrt(arg if arg > 1e-12 else 1e-12) if arg > 0 else 0.0
438
439@njit(cache=True)
440def injector_solve(P, P_tank_O, P_tank_F, Pc):
441 """Returns (ok, mdot_O, mdot_F, u_O, u_F, D32_O, D32_F, mom_R, Cd_O, Cd_F,
442 Pi_O, Pi_F, dpi_O, dpi_F, A_geom_O, A_geom_F,
443 dpf_O, dpf_F, We_O, We_F, u_rel, x_star, constraints_ok, n_iter).
444 ok=0 => NaN/invalid.
445
446 The trailing eight are already computed by the solve; they are returned so the
447 diagnostics dict can be assembled here instead of by a second solve in C.
448 """
449 rho_O = P[RHO_O]; mu_O = P[MU_O]; sig_O = P[SIG_O]; tO = P[T_O]
450 rho_F = P[RHO_F]; mu_F = P[MU_F]; sig_F = P[SIG_F]; tF = P[T_F]
451 djo = P[DJO]; djf = P[DJF]; nO = int(P[NO]); nF = int(P[NF])
452 A_O = nO*PI*(djo*0.5)**2; A_F = nF*PI*(djf*0.5)**2
453 Cd_O_eff = _cd_inf_orifice(djo, P[DO_CDINF], P[DO_GEOM], P[DO_DREF], P[DO_DMIN], P[DO_EXPS], P[DO_LOGG], P[DO_CDMAX], P[DO_CDFLOOR])
454 Cd_F_eff = _cd_inf_orifice(djf, P[DF_CDINF], P[DF_GEOM], P[DF_DREF], P[DF_DMIN], P[DF_EXPS], P[DF_LOGG], P[DF_CDMAX], P[DF_CDFLOOR])
455 imp_sep = _clip(P[ANG_O] + P[ANG_F], 1.0, 179.0)
456 imp_angle = imp_sep*PI/180.0
457
458 mdot_O = 0.1; mdot_F = 0.1
459 Cd_O = 0.0; Cd_F = 0.0; Pi_O = P_tank_O; Pi_F = P_tank_F
460 dpi_O = 0.0; dpi_F = 0.0
461 We_O = 0.0; We_F = 0.0; D32_O = 0.0; D32_F = 0.0; u_rel = 0.0
462 dpf_O = 0.0; dpf_F = 0.0; x_star = 0.0; n_iter = 0
463 ti_O = 0.1; ti_F = 0.1
464 u_O = 0.0; u_F = 0.0
465 constraints_ok = 0
466 # Per-hole flows along one branch of each back-channel ring (empty for a plenum).
467 holes_O = np.zeros(int(P[NET_O_H]) if P[NET_O] > 0.0 else 0)
468 holes_F = np.zeros(int(P[NET_F_H]) if P[NET_F] > 0.0 else 0)
469
470 # ONE pass (impinging.py): Cd is orifice geometry and Reynolds number, not a lever for the
471 # spray constraints. A We/x* violation is reported (constraints_ok = 0), never traded for a
472 # smaller Cd -- the old loop multiplied Cd by closure.Cd_reduction_factor per violation.
473 # The loop shape is kept so the body below reads as before; it runs exactly once.
474 for iteration in range(1):
475 n_iter = iteration + 1
476 if P[NET_O] > 0.0:
477 mo, Cd_O, Pi_O, dpf_O, dpi_O = _stream_flow_net(Pc, P_tank_O, 0, A_O, Cd_O_eff, P, holes_O)
478 else:
479 mo, Cd_O, Pi_O, dpf_O, dpi_O = _stream_flow(
480 Pc, P_tank_O, rho_O, A_O, djo, mu_O, tO, Cd_O_eff,
481 P[FO_AH], P[FO_K0], P[FO_K1], P[FO_PHI], P[FO_KX], P[FO_DEX],
482 P[DO_CDINF], P[DO_ARE], P[DO_CDMIN], P[DO_GEOM], P[DO_DREF], P[DO_DMIN], P[DO_EXPS], P[DO_LOGG], P[DO_CDMAX], P[DO_CDFLOOR], P[DO_UPC], P[DO_PREF], P[DO_AP], P[DO_UTC], P[DO_TREF], P[DO_AT], P[DO_LOD], P[DO_BETA], P[DO_CDU], P[VP_O], P[CC_O])
483 if P[NET_F] > 0.0:
484 mf, Cd_F, Pi_F, dpf_F, dpi_F = _stream_flow_net(Pc, P_tank_F, 1, A_F, Cd_F_eff, P, holes_F)
485 else:
486 mf, Cd_F, Pi_F, dpf_F, dpi_F = _stream_flow(
487 Pc, P_tank_F, rho_F, A_F, djf, mu_F, tF, Cd_F_eff,
488 P[FF_AH], P[FF_K0], P[FF_K1], P[FF_PHI], P[FF_KX], P[FF_DEX],
489 P[DF_CDINF], P[DF_ARE], P[DF_CDMIN], P[DF_GEOM], P[DF_DREF], P[DF_DMIN], P[DF_EXPS], P[DF_LOGG], P[DF_CDMAX], P[DF_CDFLOOR], P[DF_UPC], P[DF_PREF], P[DF_AP], P[DF_UTC], P[DF_TREF], P[DF_AT], P[DF_LOD], P[DF_BETA], P[DF_CDU], P[VP_F], P[CC_F])
490 mdot_O = mo; mdot_F = mf
491 u_O = mdot_O/(rho_O*A_O) if A_O > 0 else 0.0
492 u_F = mdot_F/(rho_F*A_F) if A_F > 0 else 0.0
493 u_rel = np.sqrt(u_O*u_O + u_F*u_F - 2.0*u_O*u_F*np.cos(imp_angle))
494 rho_gas = Pc/(P[SP_GASR]*P[SP_GAST]); rho_gas = rho_gas if rho_gas > 1e-6 else 1e-6
495 if P[SP_SMDMODEL] == SMD_INGEBO:
496 D32_O = _tn4222(djo, u_O, rho_O, mu_O, sig_O, rho_gas, P[SP_SMDSCALE], P[SP_SMDPROP])
497 D32_F = _tn4222(djf, u_F, rho_F, mu_F, sig_F, rho_gas, P[SP_SMDSCALE], P[SP_SMDPROP])
498 # Measured D32 replaces the correlation where it was measured (impinging.py).
499 if P[D32M_O] > 0.0 and u_O > 0.0:
500 D32_O = P[D32M_O]
501 if P[D32M_F] > 0.0 and u_F > 0.0:
502 D32_F = P[D32M_F]
503 We_O = rho_gas*u_rel*u_rel*djo/sig_O if sig_O > 0 else np.inf
504 We_F = rho_gas*u_rel*u_rel*djf/sig_F if sig_F > 0 else np.inf
505 else:
506 alpha = 0.35
507 uo_ = max(u_O, 0.0); uf_ = max(u_F, 0.0); ur_ = max(u_rel, 0.0)
508 ue_O = np.sqrt(uo_*uo_ + (alpha*ur_)**2); ue_F = np.sqrt(uf_*uf_ + (alpha*ur_)**2)
509 We_O = rho_O*ue_O*ue_O*djo/sig_O if sig_O > 0 else np.inf
510 We_F = rho_F*ue_F*ue_F*djf/sig_F if sig_F > 0 else np.inf
511 weO = We_O; weF = We_F
512 if P[SP_SMDWECORR] > 0 and np.isfinite(P[SP_SMDWECORR]):
513 weO = min(We_O, P[SP_SMDWECORR]); weF = min(We_F, P[SP_SMDWECORR])
514 Oh_O = _ohnesorge(mu_O, rho_O, sig_O, djo); Oh_F = _ohnesorge(mu_F, rho_F, sig_F, djf)
515 D32_O = _lefebvre(djo, weO, Oh_O, P[SP_SMDC], P[SP_SMDM], P[SP_SMDP])
516 D32_F = _lefebvre(djf, weF, Oh_F, P[SP_SMDC], P[SP_SMDM], P[SP_SMDP])
517 # Shared with the Python reference via _tau_evap -- see its docstring. rho_gas is
518 # the chamber gas density computed above from P[SP_GASR]*P[SP_GAST].
519 _Tc_ev = P[SP_GAST]
520 te_O = _tau_evap(D32_O, rho_O, P[LAT_O], P[RHO_O_BOIL], _Tc_ev, Pc, rho_gas,
521 P[EV_CPGAS], P[EV_CEVAP], P[EV_MODEL], P[SP_EVAPK])
522 te_F = _tau_evap(D32_F, rho_F, P[LAT_F], P[RHO_F_BOIL], _Tc_ev, Pc, rho_gas,
523 P[EV_CPGAS], P[EV_CEVAP], P[EV_MODEL], P[SP_EVAPK])
524 # x* is a TRANSPORT length, so it takes the momentum-weighted axial velocity of the
525 # collided pair, not u_rel (the jet-to-jet closing speed, which belongs in the Ingebo
526 # Weber number). Mirrors spray.spray_axial_velocity() on the Python path; using u_rel
527 # here over-predicted x* by ~2x and left this constraint disagreeing with Python.
528 _mt = mdot_O + mdot_F
529 if _mt > 0.0:
530 _u_ax = (mdot_O*u_O*np.cos(np.deg2rad(P[ANG_O]))
531 + mdot_F*u_F*np.cos(np.deg2rad(P[ANG_F])))/_mt
532 else:
533 _u_ax = 0.0
534 if not np.isfinite(_u_ax) or _u_ax <= 0.0:
535 _u_ax = u_rel
536 x_star = max(_u_ax*te_O, _u_ax*te_F)
537 constraints_ok = 1
538 if We_O < P[SP_WEMIN] or We_F < P[SP_WEMIN]:
539 constraints_ok = 0
540 if P[SP_EVAPUSE] != 0 and x_star >= P[SP_EVAPXLIM]:
541 constraints_ok = 0
542
543 # Shear-layer turbulence. impinging.py calls _injector_turbulence_fields with
544 # the FINAL velocities (:612), so Re and the ti_mix weighting are consistent
545 # here -- unlike pintle, which weights pre-update Re with post-update u.
546 _reO = _reynolds(rho_O, u_O, djo, mu_O); _reF = _reynolds(rho_F, u_F, djf, mu_F)
547 ti_O = _clip(0.16*(_reO**(-0.125)) if _reO > 0 else 0.1, 0.02, 0.3)
548 ti_F = _clip(0.16*(_reF**(-0.125)) if _reF > 0 else 0.1, 0.02, 0.3)
549
550 # momentum ratio (bulk jet velocities)
551 A_jet_O = PI*(djo*0.5)**2; A_jet_F = PI*(djf*0.5)**2
552 n_O = nO if nO >= 1 else 1; n_F = nF if nF >= 1 else 1
553 den_O = rho_O*n_O*A_jet_O; den_F = rho_F*n_F*A_jet_F
554 v_O = mdot_O/den_O if den_O > 0 else np.nan
555 v_F = mdot_F/den_F if den_F > 0 else np.nan
556 mom_R = np.nan
557 if rho_O > 0 and rho_F > 0 and np.isfinite(v_O) and np.isfinite(v_F) and v_F != 0.0:
558 num = rho_O*v_O*v_O; den = rho_F*v_F*v_F
559 if den > 0 and num >= 0:
560 mom_R = np.sqrt(num/den)
561 if not (np.isfinite(mdot_O) and np.isfinite(mdot_F)) or mdot_F <= 0.0:
562 return (0.0, mdot_O, mdot_F, u_O, u_F, D32_O, D32_F, mom_R, Cd_O, Cd_F, Pi_O, Pi_F, dpi_O, dpi_F, A_O, A_F, dpf_O, dpf_F, We_O, We_F, u_rel, x_star, float(constraints_ok), float(n_iter), ti_O, ti_F, holes_O, holes_F)
563 return (1.0, mdot_O, mdot_F, u_O, u_F, D32_O, D32_F, mom_R, Cd_O, Cd_F, Pi_O, Pi_F, dpi_O, dpi_F, A_O, A_F, dpf_O, dpf_F, We_O, We_F, u_rel, x_star, float(constraints_ok), float(n_iter), ti_O, ti_F, holes_O, holes_F)
564
565
566@njit(cache=True)
567def _smd_pintle(L_open, V_rel, rho_f, mu_f, sigma_f, C, B, n, p):
568 """spray.smd_pintle: SMD = C * L_open * We_rel^(-n) * (1 + B*Oh_f)^p."""
569 We_rel = (rho_f*V_rel*V_rel*L_open)/sigma_f
570 denom = np.sqrt(rho_f*sigma_f*L_open)
571 Oh_f = mu_f/denom if denom > 0 else 0.0
572 factor_we = We_rel**(-n) if We_rel > 0 else 1.0
573 factor_oh = (1.0 + B*Oh_f)**p
574 return C*L_open*factor_we*factor_oh
575
576
577@njit(cache=True)
578def injector_solve_pintle(P, P_tank_O, P_tank_F, Pc):
579 """Pintle branch flows. Same 24-tuple shape as injector_solve.
580
581 Ports what PintleInjector.solve actually EXECUTES. Two things in that
582 function do not run and are deliberately not reproduced:
583 * the `if feed_iter < 2:` quick-update block is dead -- `feed_iter` is 2
584 once the `for feed_iter in range(3)` loop exits, so the condition is
585 never true (confirmed by counting cd_from_re calls: 2/iteration, not 4);
586 * that 3-iteration feed loop recomputes delta_p_feed from an unchanged
587 mdot, so its 6 calls all return the same two values.
588 Mass flow therefore converges through the outer Cd-relaxation loop alone.
589
590 mom_R is left NaN: pintle has no momentum_ratio_R (see _eta_advanced).
591 """
592 rho_O = P[RHO_O]; mu_O = P[MU_O]; sig_O = P[SIG_O]; tO = P[T_O]
593 rho_F = P[RHO_F]; mu_F = P[MU_F]; sig_F = P[SIG_F]; tF = P[T_F]
594 A_O = P[PIN_AO]; A_F = P[PIN_AF]
595 dh_O = P[PIN_DHO]; dh_F = P[PIN_DHF]
596 d_orif = P[PIN_DORIF]; h_gap = P[PIN_HGAP]
597 max_iter = int(P[SV_CLMAX]); Cd_red = P[SV_CLCDRED]
598 Cd_O_eff = P[DO_CDINF]; Cd_F_eff = P[DF_CDINF]
599
600 mdot_O = 0.1; mdot_F = 0.1
601 Cd_O = 0.0; Cd_F = 0.0; Pi_O = P_tank_O; Pi_F = P_tank_F
602 dpi_O = 0.0; dpi_F = 0.0; dpf_O = 0.0; dpf_F = 0.0
603 We_O = 0.0; We_F = 0.0; D32 = 0.0; u_O = 0.0; u_F = 0.0
604 V_rel = 0.0; x_star = 0.0; constraints_ok = 0; n_iter = 0
605 ti_O = 0.1; ti_F = 0.1
606
607 for iteration in range(max_iter):
608 n_iter = iteration + 1
609 dpf_bal_O = _dpf(mdot_O, rho_O, P[FO_AH], P[FO_K0], P[FO_K1], P[FO_PHI], P_tank_O, P[FO_KX], P[FO_DEX])
610 dpf_bal_F = _dpf(mdot_F, rho_F, P[FF_AH], P[FF_K0], P[FF_K1], P[FF_PHI], P_tank_F, P[FF_KX], P[FF_DEX])
611 Pi_O = P_tank_O - dpf_bal_O
612 Pi_F = P_tank_F - dpf_bal_F
613 dpi_O = Pi_O - Pc if Pi_O - Pc > 0.0 else 0.0
614 dpi_F = Pi_F - Pc if Pi_F - Pc > 0.0 else 0.0
615 if Pi_F < Pc:
616 mdot_F = 0.0
617 if Pi_O < Pc:
618 mdot_O = 0.0
619
620 u_O = mdot_O/(rho_O*A_O) if A_O > 0 else 0.0
621 u_F = mdot_F/(rho_F*A_F) if A_F > 0 else 0.0
622 Re_O = _reynolds(rho_O, u_O, dh_O, mu_O)
623 Re_F = _reynolds(rho_F, u_F, dh_F, mu_F)
624 cO = _cd_from_re(Re_O, Pi_O, tO, dh_O, P[DO_CDINF], P[DO_ARE], P[DO_CDMIN], P[DO_GEOM],
625 P[DO_DREF], P[DO_DMIN], P[DO_EXPS], P[DO_LOGG], P[DO_CDMAX], P[DO_CDFLOOR],
626 P[DO_UPC], P[DO_PREF], P[DO_AP], P[DO_UTC], P[DO_TREF], P[DO_AT], P[DO_LOD], P[DO_BETA], P[DO_CDU])
627 cF = _cd_from_re(Re_F, Pi_F, tF, dh_F, P[DF_CDINF], P[DF_ARE], P[DF_CDMIN], P[DF_GEOM],
628 P[DF_DREF], P[DF_DMIN], P[DF_EXPS], P[DF_LOGG], P[DF_CDMAX], P[DF_CDFLOOR],
629 P[DF_UPC], P[DF_PREF], P[DF_AP], P[DF_UTC], P[DF_TREF], P[DF_AT], P[DF_LOD], P[DF_BETA], P[DF_CDU])
630 Cd_O = cO if cO < Cd_O_eff else Cd_O_eff
631 Cd_F = cF if cF < Cd_F_eff else Cd_F_eff
632
633 mdot_O = Cd_O*A_O*np.sqrt(2.0*rho_O*dpi_O) if dpi_O > 0 else 0.0
634 mdot_F = Cd_F*A_F*np.sqrt(2.0*rho_F*dpi_F) if dpi_F > 0 else 0.0
635 u_O = mdot_O/(rho_O*A_O) if A_O > 0 else 0.0
636 u_F = mdot_F/(rho_F*A_F) if A_F > 0 else 0.0
637
638 We_O = rho_O*u_O*u_O*d_orif/sig_O if sig_O > 0 else np.inf
639 We_F = rho_F*u_F*u_F*dh_F/sig_F if sig_F > 0 else np.inf
640
641 V_rel = np.sqrt(u_O*u_O + u_F*u_F)
642 D32 = _smd_pintle(h_gap, V_rel, rho_F, mu_F, sig_F,
643 P[PIN_SMDC], P[PIN_SMDB], P[PIN_SMDN], P[PIN_SMDP])
644
645 # ti uses the PRE-UPDATE Reynolds numbers, while the ti_mix weighting
646 # below uses the POST-update velocities. That asymmetry is pintle.py's
647 # (Re_O/Re_F are computed before mdot is refreshed, ti at :218 after);
648 # impinging is self-consistent instead, recomputing Re from the same u.
649 ti_O = 0.16*(Re_O**(-0.125)) if Re_O > 0 else 0.1
650 ti_F = 0.16*(Re_F**(-0.125)) if Re_F > 0 else 0.1
651 ti_O = _clip(ti_O, 0.02, 0.3); ti_F = _clip(ti_F, 0.02, 0.3)
652
653 # pintle.py prices evaporation with the legacy fixed-K law, tau = K*D32^2, on both
654 # streams (spray.tau_evap), NOT the derived Spalding constant the impinging solve uses.
655 # This site used _tau_evap's derived branch, so x* -- which gates pintle's Cd-reduction
656 # loop -- read 0.09% off Python on canonical/pintle.yaml. Both streams share D32.
657 te = P[SP_EVAPK]*(D32**2)
658 x_star = V_rel*te
659 if P[SP_USETURB] != 0.0:
660 v_tot = u_O + u_F if u_O + u_F > 1e-6 else 1e-6
661 ti_mix = _clip((ti_O*u_O + ti_F*u_F)/v_tot, 0.02, 0.35)
662 x_star *= _clip(1.0/(1.0 + P[SP_PENGAIN]*ti_mix), 0.3, 1.0)
663
664 # Reported feed loss is recomputed from the CONVERGED mdot, matching
665 # pintle.py's "recalculate one final time ... so diagnostics have the
666 # correct final values". This deliberately makes the reported
667 # delta_p_feed inconsistent with the P_inj used in the balance above,
668 # which came from the previous iteration's mdot. Faithful, not tidy.
669 dpf_O = _dpf(mdot_O, rho_O, P[FO_AH], P[FO_K0], P[FO_K1], P[FO_PHI], P_tank_O, P[FO_KX], P[FO_DEX])
670 dpf_F = _dpf(mdot_F, rho_F, P[FF_AH], P[FF_K0], P[FF_K1], P[FF_PHI], P_tank_F, P[FF_KX], P[FF_DEX])
671
672 constraints_ok = 1
673 if We_O < P[SP_WEMIN] or We_F < P[SP_WEMIN]:
674 constraints_ok = 0
675 if P[SP_EVAPUSE] != 0 and x_star >= P[SP_EVAPXLIM]:
676 constraints_ok = 0
677 if constraints_ok:
678 break
679 Cd_O_eff *= Cd_red; Cd_F_eff *= Cd_red
680 if Cd_O_eff < P[DO_CDMIN]: Cd_O_eff = P[DO_CDMIN]
681 if Cd_F_eff < P[DF_CDMIN]: Cd_F_eff = P[DF_CDMIN]
682
683 if not (np.isfinite(mdot_O) and np.isfinite(mdot_F) and mdot_F > 0.0):
684 return (0.0, mdot_O, mdot_F, u_O, u_F, D32, D32, np.nan, Cd_O, Cd_F, Pi_O, Pi_F,
685 dpi_O, dpi_F, A_O, A_F, dpf_O, dpf_F, We_O, We_F, V_rel, x_star,
686 float(constraints_ok), float(n_iter), ti_O, ti_F, np.zeros(0), np.zeros(0))
687 return (1.0, mdot_O, mdot_F, u_O, u_F, D32, D32, np.nan, Cd_O, Cd_F, Pi_O, Pi_F,
688 dpi_O, dpi_F, A_O, A_F, dpf_O, dpf_F, We_O, We_F, V_rel, x_star,
689 float(constraints_ok), float(n_iter), ti_O, ti_F, np.zeros(0), np.zeros(0))
690
691
692@njit(cache=True)
693def _solve_injector(P, P_O, P_F, Pc):
694 """Dispatch on injector type. Both branches return the same tuple type."""
695 if P[INJ_TYPE] == 0.0:
696 return injector_solve_pintle(P, P_O, P_F, Pc)
697 return injector_solve(P, P_O, P_F, Pc)
698
699
700@njit(cache=True)
701def _sign(x):
702 return -1.0 if x < 0 else (1.0 if x > 0 else 0.0)
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
injector_solve(P, P_tank_O, P_tank_F, Pc)
Definition kernels.py:440
_lefebvre(d_or, We, Oh, C, m, p)
Definition kernels.py:395
_stream_flow_net(Pc, P_tank, side, A, cd_cap, P, holes)
Definition kernels.py:341
_tn4222(d_jet, v_jet, rho_l, mu_l, sigma, rho_g, scale, prop)
Definition kernels.py:380
_tau_evap(D32, rho_l, Lv, Tb, Tc, Pc, rho_ch, cp_g, C_ev, ev_model, K_legacy)
Definition kernels.py:401
_stream_state_net(m, Pc, P_tank, side, A, cd_cap, P, holes)
Definition kernels.py:328
_stream_state(m, Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)
Definition kernels.py:190
_cd_lichtarowicz_re(cd_u, Re, x)
Definition kernels.py:106
_bern(mdot_seed, dP, Pi, rho, area, dhyd, mu, Tin, cd_cap, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
Definition kernels.py:167
_solve_injector(P, P_O, P_F, Pc)
Definition kernels.py:693
injector_solve_pintle(P, P_tank_O, P_tank_F, Pc)
Definition kernels.py:578
_cd_side(P, side, Re, P_in, Pc, cd_cap)
Definition kernels.py:261
cea_eval(Pcg, MRg, epsg, cstar, Cf, Tc, gam, Rt, Mt, Cfvac, MR, Pc, eps)
Definition kernels.py:63
_smd_pintle(L_open, V_rel, rho_f, mu_f, sigma_f, C, B, n, p)
Definition kernels.py:567
_reynolds(rho, u, d, mu)
Definition kernels.py:84
_churchill_f(Re, rel_rough)
Definition kernels.py:251
_cd_inf_orifice(d_hyd, cdinf, geom, dref, dmin, exps, logg, cdmax, cdfloor)
Definition kernels.py:90
_clip(x, lo, hi)
Definition kernels.py:48
_net_march(P, side, m_total, P_port, Pc, cd_cap, holes)
Definition kernels.py:284
_dpf(mdot, rho, ah, k0, k1, phi, P_tank, kx, dex)
Definition kernels.py:136
_tri(table, ip, im, ie, w0, w1, w2, w3, w4, w5, w6, w7)
Definition kernels.py:52
_ohnesorge(mu, rho, sigma, d)
Definition kernels.py:433
_cd_from_re(Re, P_in, T_in, d_hyd, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu)
Definition kernels.py:116
_stream_flow(Pc, P_tank, rho, A, dh, mu, Tin, cd_cap, AH, K0, K1, PHI, KX, DEX, cdinf, aRe, cdmin, geom, dref, dmin, exps, logg, cdmax, cdfloor, upc, Pref, aP, utc, Tref, aT, lod, beta, cdu, pv, cc)
Definition kernels.py:209