1"""Numba mirror of the Python chamber solve, for the Layer 1 inner loop.
3Each function here is a transcription of the Python function named in its docstring, in the same
4order of operations, so the two paths compute the same physics and differ only by rounding and
5by the one tabulation below. tests/test_numba_ab_parity.py diffs them live at 1e-6.
7 residual(Pc) = mdot_injector(Pc) - (Pc/kappa) At / (eta_c* c*_ideal) chamber_solver.residual
8 eta_c* = eta_vap eta_mix eta_HL combustion_eff.eta_cstar
10The one thing numba cannot call is CoolProp, which the droplet heat-up reads at the chamber
11pressure (combustion_physics.liquid_heatup_properties). It is tabulated here once per fluid as
12functions of ln P -- cp at the 24 Gauss-Legendre nodes the Python integral uses, cp at the upper
13limit, the density at the mean drop temperature, and CoolProp's saturation temperature -- as
14cubic splines between the pressures where the integrand changes form (the critical pressure, where
15the Clausius-Clapeyron surface temperature meets CoolProp's saturation line, where it reaches the
16critical temperature, where the heat-up vanishes). Every interval is checked against CoolProp at
17its midpoint when the table is built; an interval that misses is refused, and a pressure there
18makes the kernel return no solution so the caller runs the Python solve.
20from __future__
import annotations
23from collections
import OrderedDict
31 P_BOIL_REF, WATSON_EXPONENT, _HEATUP_GL_W, _HEATUP_GL_X, _MARCH_STEPS, _RR_NODES,
32 _RR_WEIGHTS, _UNIT_MAD, _V_FLOOR, _march_core,
37 BARTZ_OMEGA, CYLINDER_BEAM_LENGTH_OVER_D, LECKNER_T_MAX, THROAT_RC_OVER_RT,
38 _LECKNER_CO2, _LECKNER_H2O,
44RR_W = np.ascontiguousarray(_RR_WEIGHTS, np.float64)
45GL_X = np.ascontiguousarray(_HEATUP_GL_X, np.float64)
46GL_W = np.ascontiguousarray(_HEATUP_GL_W, np.float64)
47LK_H2O = np.ascontiguousarray(_LECKNER_H2O, np.float64)
48LK_CO2 = np.ascontiguousarray(_LECKNER_CO2, np.float64)
53 "AT",
"AE",
"EPS",
"EPS_CUR",
"LSTAR",
"AC",
"CR",
"ZETA_N",
54 "EFF_MODEL",
"EFF_C",
"RR_Q",
"RR_XSCALE",
"BLOW_AS",
"EM_OPT",
"M_OPT",
"EM_CURV",
"USE_COUP",
56 "SO_OK",
"SO_RHO",
"SO_TB",
"SO_LB",
"SO_MW",
"SO_TCRIT",
"SO_T0",
"SO_CP",
"SO_HT",
57 "SF_OK",
"SF_RHO",
"SF_TB",
"SF_LB",
"SF_MW",
"SF_TCRIT",
"SF_T0",
"SF_CP",
"SF_HT",
58 "AB_ON",
"AB_TS",
"AB_EW",
"AB_E",
"AB_BELOW",
"AB_PHYS",
"AB_BC",
"AB_BMIN",
"AB_BEFF",
60 "AX_MR0",
"AX_MR1",
"AX_NMR",
"AX_PC0",
"AX_PC1",
"AX_NPC",
"AX_E0",
"AX_E1",
"AX_NE",
62_Q = {n: i
for i, n
in enumerate(_QNAMES)}
63globals().update({f
"Q_{n}": i
for n, i
in _Q.items()})
65_S_STRIDE = _Q[
"SF_OK"] - _Q[
"SO_OK"]
69 "PC",
"F",
"ISP",
"MR",
"CSTAR",
"GAMMA",
"R",
"TC_EFF",
"TC_IDEAL",
"MDOT",
"MDOT_O",
70 "MDOT_F",
"V_EXIT",
"CF",
"CF_IDEAL",
"P_EXIT",
"P_THROAT",
"T_EXIT",
"T_THROAT",
"M_EXIT",
71 "CSTAR_IDEAL",
"ETA",
"ETA_VAP",
"ETA_MIX",
"ETA_HL",
"KAPPA",
"P0",
"Q_ABL",
"F_VAP",
72 "F_VAP_O",
"F_VAP_F",
"X0",
"U_DROP0",
"EM",
"RUPE_M",
"M_MOL",
"CF_VAC",
74_R = {n: i
for i, n
in enumerate(_RNAMES)}
75globals().update({f
"R_{n}": i
for n, i
in _R.items()})
82_P_LO, _P_HI = 1.0e5, 2.5e7
87_TOL_CP, _TOL_RHO, _TOL_T = 1e-5, 2e-7, 2e-6
92 """combustion_physics.saturation_state's surface temperature (Tcrit 0 => none)."""
94 return saturation_state(P, Tb, Lb, MW, Tcrit
if Tcrit > 0
else None)[0]
98 """Per-interval cubic coefficients (highest power first) of not-a-knot splines through ys."""
99 from scipy.interpolate
import CubicSpline
100 out = np.empty((ys.shape[0], xs.size - 1, 4))
101 for k
in range(ys.shape[0]):
102 out[k] = CubicSpline(xs, ys[k]).c.T
106_REFINE, _MAX_DEPTH = 8, 4
109def _tab_range(lo, hi, flag, h_target, fn, nf, tol_rel, tol_abs, depth):
110 """Pieces (lo, hi, h, n, coef, ok, flag) covering [lo, hi]: one not-a-knot spline per run
111 of knots, each interval checked against fn at its midpoint; a run of intervals that misses
112 is tabulated again finer, and still refused after _MAX_DEPTH refinements."""
113 n =
max(3, int(math.ceil((hi - lo) / h_target)))
114 xs = np.linspace(lo, hi, n + 1)
115 ys = np.empty((nf, n + 1))
116 good = np.ones(n + 1, dtype=bool)
117 for i, x
in enumerate(xs):
119 ys[:, i] = fn(x, flag)
122 idx = np.flatnonzero(good)
125 for i
in np.flatnonzero(~good):
128 ys[:, i] = ys[:, idx[np.argmin(np.abs(idx - i))]]
131 ok = good[:-1] & good[1:]
136 want = fn(lo + (i + 0.5) * h, flag)
141 got = ((c[:, i, 0] * dx + c[:, i, 1]) * dx + c[:, i, 2]) * dx + c[:, i, 3]
142 if not np.all(np.isfinite(want))
or np.any(np.abs(got - want) > tol_rel * np.abs(want) + tol_abs):
148 while j + 1 < n
and ok[j + 1] == ok[i]:
150 a, b = lo + i * h, lo + (j + 1) * h
153 if ok[i]
or depth >= _MAX_DEPTH:
154 pieces.append((a, b, h, j - i + 1, c[:, i:j + 1, :], ok[i:j + 1].copy(), flag))
156 pieces.extend(
_tab_range(a, b, flag, h / _REFINE, fn, nf, tol_rel, tol_abs, depth + 1))
162 """Tabulate fn(x, flag) -> (nf,) on each (lo, hi, flag) segment. Returns (segs, coef, ok)."""
163 tol_rel = np.broadcast_to(np.asarray(tol_rel, dtype=float), (nf,))
165 for lo, hi, flag
in segments:
166 pieces.extend(
_tab_range(lo, hi, flag, _DX_KNOT, fn, nf, tol_rel, tol_abs, 0))
168 return np.zeros((0, 6)), np.zeros((nf, 0, 4)), np.zeros(0)
169 pieces.sort(key=
lambda p: p[0])
170 rows, coefs, oks = [], [], []
172 for a, b, h, n, c, ok, flag
in pieces:
173 rows.append((a, b, h, n, off, flag))
177 return (np.array(rows, dtype=np.float64), np.ascontiguousarray(np.concatenate(coefs, axis=1)),
178 np.concatenate(oks).astype(np.float64))
182 """Sign changes of f on [a, b], each refined by Brent."""
183 from scipy.optimize
import brentq
184 xs = np.linspace(a, b, n + 1)
193 fa, fb = vals[i], vals[i + 1]
194 if np.isfinite(fa)
and np.isfinite(fb)
and np.sign(fa) != np.sign(fb)
and fa != 0.0:
196 out.append(brentq(f, xs[i], xs[i + 1], xtol=1e-13, rtol=1e-14))
203 """(HT segs, HT coef, HT ok, TL segs, TL coef) for a fluid CoolProp knows, else None.
205 HT columns, as functions of x = ln P: cp at T_n = T0 + (T_hi - T0)(x_n + 1)/2 for each
206 Gauss-Legendre node x_n, cp at T_hi, and rho at (T0 + T_hi)/2, with T_hi = min(T_s, T_lim);
207 segment flag 1 = above the critical pressure (T_lim infinite). TL: T_lim(x) below it.
209 key = (str(name), float(T0), float(Tb), float(Lb), float(MW), float(Tcrit))
210 if key
in _HEAT_MEMO:
211 return _HEAT_MEMO[key]
213 st = _coolprop_state(name)
215 _HEAT_MEMO[key] =
None
218 x_lo, x_hi = math.log(_P_LO), math.log(_P_HI)
220 xc = math.log(float(AS.p_critical()))
226 AS.update(CP.PQ_INPUTS, math.exp(x), 0.0)
227 return float(AS.T()) - 0.05
230 Ts =
_sat_T(math.exp(x), Tb, Lb, MW, Tcrit)
231 return Ts
if sup
else min(Ts, T_lim(x))
236 Tn = 0.5 * (th - T0) * (GL_X + 1.0) + T0
237 out = np.empty(N_HEAT + 2)
238 for k, T
in enumerate(Tn):
239 AS.update(CP.PT_INPUTS, P, float(T))
241 AS.update(CP.PT_INPUTS, P, th)
242 out[N_HEAT] = AS.cpmass()
243 AS.update(CP.PT_INPUTS, P, 0.5 * (T0 + th))
244 out[N_HEAT + 1] = AS.rhomass()
249 sub_hi =
min(xc - gap, x_hi)
251 br.update({xc - gap, xc + gap})
253 x_cc = math.log(P_BOIL_REF) + (1.0 / Tb - 1.0 / Tcrit) * MW * Lb / R_UNIVERSAL_KMOL
254 if x_lo < x_cc < x_hi:
257 br.update(
_roots(
lambda x:
_sat_T(math.exp(x), Tb, Lb, MW, Tcrit) - T_lim(x), x_lo, sub_hi))
258 br.update(
_roots(
lambda x: T_hi(x,
False) - T0, x_lo, sub_hi))
260 br.update(
_roots(
lambda x: T_hi(x,
True) - T0, xc + gap, x_hi))
261 pts = sorted(b
for b
in br
if x_lo <= b <= x_hi)
264 for a, b
in zip(pts[:-1], pts[1:]):
265 if b - a < 1e-9
or (xc - gap < 0.5 * (a + b) < xc + gap):
267 sup = 0.5 * (a + b) > xc
269 if not T_hi(0.5 * (a + b), sup) > T0:
273 segs.append((a, b, 1.0
if sup
else 0.0))
276 return ht(x, flag > 0.5)
278 tol = np.full(N_HEAT + 2, _TOL_CP)
279 tol[N_HEAT + 1] = _TOL_RHO
280 HT =
_piecewise(segs, ht_rel, N_HEAT + 2, tol, 0.0)
281 TL = (
_piecewise([(x_lo, sub_hi, 0.0)],
lambda x, f: np.array([T_lim(x)]), 1, 0.0, _TOL_T)
282 if sub_hi > x_lo
else (np.zeros((0, 6)), np.zeros((1, 0, 4)), np.zeros(0)))
283 out = (HT[0], HT[1], HT[2], TL[0], TL[1], TL[2])
284 _HEAT_MEMO[key] = out
288_NO_HT = (np.zeros((0, 6)), np.zeros((N_HEAT + 2, 0, 4)), np.zeros(0),
289 np.zeros((0, 6)), np.zeros((1, 0, 4)), np.zeros(0))
294 """(interval index, dx) of x in a piecewise table, or (-1, 0) outside or on a refused one."""
295 for s
in range(segs.shape[0]):
297 if lo <= x <= segs[s, 1]:
303 j = int(segs[s, 4]) + i
306 return j, x - (lo + i*h)
312 return ((coef[k, j, 0]*dx + coef[k, j, 1])*dx + coef[k, j, 2])*dx + coef[k, j, 3]
317 for s
in range(segs.shape[0]):
318 if segs[s, 0] <= x <= segs[s, 1]:
328 """combustion_physics.saturation_state -> (ok, T_sat, h_fg)."""
329 if not (P > 0.0
and Tb > 0.0
and Lb > 0.0
and MW > 0.0):
330 return False, 0.0, 0.0
331 inv_T = 1.0/Tb - (R_UNIVERSAL_KMOL/(MW*Lb))*math.log(P/P_BOIL_REF)
334 return False, 0.0, 0.0
335 if inv_T <= 1.0/Tcrit:
336 return True, Tcrit, 0.0
338 return True, T_sat, Lb*((Tcrit - T_sat)/(Tcrit - Tb))**WATSON_EXPONENT
340 return False, 0.0, 0.0
341 return True, 1.0/inv_T, Lb
346 """regen_cooling.calculate_gas_viscosity_huzel."""
347 return 46.6e-10*(M**0.5)*((T*1.8)**0.6)*(0.45359237/0.0254)
352 return (1.0/M)*((2.0/(gamma + 1.0))*(1.0 + 0.5*(gamma - 1.0)*M*M))**(
353 (gamma + 1.0)/(2.0*(gamma - 1.0)))
358 """gas_side.mach_from_area_ratio for one station: the root of A/A*(M) = ar on the requested
359 branch. The Python bisects 64 times; this is Newton kept inside the same shrinking bracket,
360 which lands on the same root to the last bit or two in ~6 steps instead of 64."""
361 if ar <= 1.0 + 1e-12:
365 M = 1.0 + math.sqrt(2.0*(ar - 1.0)/(gamma + 1.0))
if ar < 2.0
else \
366 ((gamma + 1.0)/(gamma - 1.0))**((gamma + 1.0)/4.0)*ar**((gamma - 1.0)/2.0)
369 M = (2.0/(gamma + 1.0))**((gamma + 1.0)/(2.0*(gamma - 1.0)))/ar
371 M = 1.0 - math.sqrt(2.0*(ar - 1.0)/(gamma + 1.0))
372 if not (lo < M < hi):
378 if (f < 0.0)
if sup
else (f > 0.0):
382 dA = A*(M*M - 1.0)/(M*(1.0 + 0.5*(gamma - 1.0)*M*M))
383 Mn = M - f/dA
if dA != 0.0
else 0.5*(lo + hi)
384 if not (lo < Mn < hi):
386 if abs(Mn - M) <= 1e-16*M
or hi - lo <= 1e-16*M:
394 """nozzle.nozzle_stagnation_loss (CR <= 1: bore not declared)."""
395 if not (CR > 1.0)
or not np.isfinite(CR):
397 M =
_mach(CR, gamma,
False)
399 return (1.0 + g*M*M)/(1.0 + 0.5*(g - 1.0)*M*M)**(g/(g - 1.0))
404 """cea_cache._aux_axis on a uniform (or log-uniform) grid of n points."""
407 f = (math.log(xc) - math.log(lo))/(math.log(hi) - math.log(lo))*(n - 1)
409 f = (xc - lo)/(hi - lo)*(n - 1)
418 """CEAAuxTables._bilinear."""
419 i, wi =
_axis(Q[Q_AX_MR0], Q[Q_AX_MR1], int(Q[Q_AX_NMR]), MR,
False)
420 j, wj =
_axis(Q[Q_AX_PC0], Q[Q_AX_PC1], int(Q[Q_AX_NPC]), Pc,
True)
421 return ((1 - wi)*(1 - wj)*tab[k, i, j] + wi*(1 - wj)*tab[k, i + 1, j]
422 + (1 - wi)*wj*tab[k, i, j + 1] + wi*wj*tab[k, i + 1, j + 1])
427 """CEAAuxTables._exit."""
428 i, wi =
_axis(Q[Q_AX_MR0], Q[Q_AX_MR1], int(Q[Q_AX_NMR]), MR,
False)
429 j, wj =
_axis(Q[Q_AX_PC0], Q[Q_AX_PC1], int(Q[Q_AX_NPC]), Pc,
True)
430 kk, wk =
_axis(Q[Q_AX_E0], Q[Q_AX_E1], int(Q[Q_AX_NE]), eps,
True)
433 fi = (1 - wi)
if di == 0
else wi
435 fj = (1 - wj)
if dj == 0
else wj
437 fk = (1 - wk)
if dk == 0
else wk
438 v += fi*fj*fk*tab[k, i + di, j + dj, kk + dk]
447 """CstarWideTable.row into ``row``."""
453 j = np.searchsorted(lnPc, x) - 1
456 elif j > lnPc.size - 2:
458 f = (x - lnPc[j])/(lnPc[j + 1] - lnPc[j])
459 for i
in range(row.size):
460 row[i] = (1.0 - f)*cs[i, j] + f*cs[i, j + 1]
465 """np.interp for one point."""
471 j = np.searchsorted(xp, x, side=
"right") - 1
472 slope = (fp[j + 1] - fp[j])/(xp[j + 1] - xp[j])
473 return slope*(x - xp[j]) + fp[j]
477def _cs_of_r(r, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi):
478 """CstarOfMR.of_fraction for one oxidizer mass fraction -> (c*, outside the table)."""
479 r = _clip(r, 0.0, 1.0)
481 return c_lo*math.sqrt(r/r_lo),
True
483 return c_hi*math.sqrt((1.0 - r)/(1.0 - r_hi)),
True
484 mr = _clip(r/(1.0 - r), MR_lo, MR_hi)
485 return _interp(math.log(mr), lnMR, row),
False
489def _cs_of_MR(MR, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi):
490 """CstarOfMR.__call__."""
491 r = MR/(1.0 + MR)
if np.isfinite(MR)
else 1.0
492 return _cs_of_r(r, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)[0]
497 """combustion_physics.rupe_Em_at_M (NaN where the Python raises)."""
498 if not (np.isfinite(M)
and M > 0.0
and np.isfinite(M_opt)
and M_opt > 0.0):
500 if not (Em_opt > 0.0
and Em_opt <= 1.0)
or not (np.isfinite(curv)
and curv >= 0.0):
503 N0 = M_opt/(1.0 + M_opt)
504 return max(Em_opt*(1.0 - curv*(N - N0)**2), 0.0)
511def _heatup(Q, b, HTs, HTc, HTo, TLs, TLc, TLo, Pc, T0, Ts, Tc):
512 """liquid_heatup_properties from the tables -> (status, I, rho_l).
514 status 1: CoolProp values; 0: Python returns None here (configured props);
515 -1: the tables cannot answer (a refused interval / outside them) -- no solution."""
525 T_hi = Ts
if Ts < T_lim
else T_lim
532 for k
in range(N_HEAT):
533 Tn = 0.5*(T_hi - T0)*(GL_X[k] + 1.0) + T0
534 acc += GL_W[k]*
_pw_val(HTc, k, j, dx)/(Tc - Tn)
535 I = 0.5*(T_hi - T0)*acc
537 I +=
_pw_val(HTc, N_HEAT, j, dx)*math.log((Tc - T_hi)/(Tc - Ts))
538 rho =
_pw_val(HTc, N_HEAT + 1, j, dx)
539 cp_mean = I/math.log((Tc - T0)/(Tc - Ts))
540 if not (np.isfinite(I)
and I > 0.0
and np.isfinite(rho)
and rho > 0.0
541 and np.isfinite(cp_mean)
and cp_mean > 0.0):
547def _eta_vap(Q, CH, Pc, Tc, gamma, R, MR, mdot, D32_O, D32_F, u_O, u_F, u_ax, L_imp, L_b,
548 lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi, out):
549 """calculate_vaporization_efficiency -> eta_vap (NaN where the Python raises)."""
552 w[1] = 1.0/(1.0 + MR)
553 D32 = np.empty(2); D32[0] = D32_O; D32[1] = D32_F
554 uu = np.empty(2); uu[0] = u_O; uu[1] = u_F
555 instant = np.zeros(2, dtype=np.bool_)
556 Ts = np.zeros(2); hfg = np.zeros(2); rho_l = np.zeros(2); I_h = np.zeros(2); T0s = np.zeros(2)
557 has_I = np.zeros(2, dtype=np.bool_)
560 b = Q_SO_OK + s*_S_STRIDE
563 ok_d = np.isfinite(d)
and d > 0.0
and uu[s] > 0.0
566 if not ok_d
or Q[b] < 0.5:
573 Ts[s] = T_s; hfg[s] = h_fg; T0s[s] = T0
576 if T_s > T0
and Q[b + 8] > 0.5:
578 st, I, rho =
_heatup(Q, b, CH[12], CH[13], CH[14], CH[15], CH[16], CH[17], Pc, T0, T_s, Tc)
580 st, I, rho =
_heatup(Q, b, CH[18], CH[19], CH[20], CH[21], CH[22], CH[23], Pc, T0, T_s, Tc)
584 rho_l[s] = rho; I_h[s] = I; has_I[s] =
True
587 I_h[s] = Q[b + 7]*
max(math.log((Tc - T0)/(Tc - T_s)), 0.0)
591 L_ch = Q[Q_LSTAR]*Q[Q_AT]/Q[Q_AC]
593 if np.isfinite(L_imp)
and L_imp >= 0.0:
595 if np.isfinite(L_b)
and L_b >= 0.0:
598 marched_any = (
not instant[0])
or (
not instant[1])
604 if L_march > 0.0
or not marched_any:
605 L_c = L_march
if L_march > 1e-9
else 1e-9
606 if not (Q[Q_RR_Q] > 1.0
and np.isfinite(Q[Q_RR_Q])):
608 if not (Pc > 0.0
and Tc > 0.0
and R > 0.0
and mdot > 0.0
and Q[Q_AC] > 0.0
609 and u_ax > 0.0
and np.isfinite(u_ax))
or not (gamma > 1.0):
611 cp_g = gamma*R/(gamma - 1.0)
613 U_c = mdot/(rho_c*Q[Q_AC])
614 M_gas = R_UNIVERSAL_KMOL/R
618 nm = (0
if instant[0]
else 1) + (0
if instant[1]
else 1)
621 D2_0 = np.empty(n); w_cls = np.empty(n); rl = np.empty(n); mu = np.empty(n)
622 rf = np.empty(n); kf = np.empty(n); lnB = np.empty(n); t_h = np.empty(n)
623 inv_FB = np.empty(n); cd_blow = np.empty(n)
624 starts = np.empty(nm, dtype=np.int64)
630 T_f = T_s + (Tc - T_s)/3.0
634 B = cp_g*(Tc - T_s)/hfg[s]
if hfg[s] > 0
else np.inf
637 if Q[Q_BLOW_AS] > 0.5
and np.isfinite(B):
640 FB = 1.0
if B < 1e-8
else (1.0 + B)**0.7*math.log1p(B)/B
642 cdb = (1.0 + B)**-0.2
645 D0 = D32[s]*Q[Q_RR_XSCALE]*CH[11][k]
646 Nu0 = 2.0 + 0.6*math.sqrt(rho_f*u_ax*D0/mu_f)*pr3
649 w_cls[idx] = w[s]*RR_W[k]
650 rl[idx] = rho_l[s]; mu[idx] = mu_f; rf[idx] = rho_f; kf[idx] = k_f
652 t_h[idx] = rho_l[s]*D0**2/(6.0*Nu0*k_f)*
max(I_h[s], 0.0)
653 inv_FB[idx] = iFB; cd_blow[idx] = cdb
655 dx = L_c/_MARCH_STEPS
656 fv, xv, F = _march_core(D2_0, w_cls, rl, mu, rf, kf, lnB, t_h, inv_FB, cd_blow,
657 U_c, rho_c, pr3, cp_g, dx, _MARCH_STEPS, u_ax, F0, _V_FLOOR,
676 MR_vap = MR*fO/fF
if fF > 0.0
else np.inf
677 ratio = (
_cs_of_MR(MR_vap, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)
678 /
_cs_of_MR(MR, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi))
679 out[R_F_VAP] = F; out[R_F_VAP_O] = fO; out[R_F_VAP_F] = fF; out[R_X0] = x0; out[R_U_DROP0] = u_ax
687def _eta_mix(Q, CH, MR, rupe_M, holes_O, holes_F, lnMR, row, MR_lo, MR_hi, r_lo, r_hi,
689 Em_opt = Q[Q_EM_OPT]; M_opt = Q[Q_M_OPT]; curv = Q[Q_EM_CURV]
690 has_M = np.isfinite(rupe_M)
and rupe_M > 0.0
695 if not np.isfinite(Em):
698 split = holes_O.size > 0
and holes_F.size > 0
and holes_O.size == holes_F.size
699 ne = holes_O.size
if split
else 1
700 W = np.empty(ne); mr = np.empty(ne); Emj = np.empty(ne)
703 o = holes_O[j]; f = holes_F[j]
704 mr_j = o/f
if f > 0.0
else np.inf
706 if not (np.isfinite(mr_j)
and mr_j > 0.0
and np.isfinite(tot)
and tot >= 0.0):
708 W[j] = tot; mr[j] = mr_j
710 Emj[j] =
_rupe_Em_at_M(rupe_M*(mr_j/MR)**2, Em_opt, M_opt, curv)
711 if not np.isfinite(Emj[j]):
716 W[0] = 1.0; mr[0] = MR; Emj[0] = Em
717 if not (np.isfinite(MR)
and MR > 0.0):
732 rj[j] = mr[j]/(1.0 + mr[j])
735 z = CH[9]; wz = CH[10]
739 mad = 2.0*(1.0 - Emj[j])*r0*(1.0 - r0)
740 for k
in range(z.size):
742 r = _clip(r, 0.0, 1.0)
743 num += W[j]*wz[k]*
_cs_of_r(r, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)[0]
744 return num/
_cs_of_MR(MR, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)
752 x = math.log10(
max(paL, 1e-12))
753 a0 = 0.0; a1 = 0.0; a2 = 0.0
754 for j
in range(c.shape[1]):
759 return math.exp(a0 + a1*x + a2*x*x)
765 tc =
min(T, LECKNER_T_MAX)/1000.0
769 PE = P_bar + 2.56*pa_bar/math.sqrt(tc)
770 a = 1.888 - 2.053*math.log10(tc)
if tc > 0.75
else 2.144
776 PE = P_bar + 0.28*pa_bar
777 a = 1.0 + 0.1/tc**1.45
780 paL_m = 0.054/(tc*tc)
if tc < 0.7
else 0.225*tc*tc
781 ratio = 1.0 - (a - 1.0)*(1.0 - PE)/(a + b - 1.0 + PE)*math.exp(
782 -cc*math.log10(paL_m/
max(paL, 1e-12))**2)
783 return e0*ratio
if paL > 0.0
else 0.0
788 tot =
max(pH + pC, 1e-30)
790 x = math.log10(
max(tot*L_cm, 1e-30))
791 d = (zeta/(10.7 + 101.0*zeta) - 0.0089*zeta**10.4)*
max(x, 0.0)**2.76
792 return d
if (pH > 0
and pC > 0)
else 0.0
796def _ablative_Q(Q, CH, T0, P0, gamma, G, mu, cp, Pr, xH, xC, gas_mdot):
797 """liner_response's heat_removed [W]: the lined stations' flux at the ablation temperature,
798 through the pyrolysis-blowing fixed point."""
799 xs = CH[6]; rs = CH[7]; dA = CH[8]
801 Tw = Q[Q_AB_TS]; ew = Q[Q_AB_EW]; Rt = Q[Q_AB_RT]; beam = Q[Q_AB_LB]
803 rc = THROAT_RC_OVER_RT*Rt
805 qc = np.empty(n); qr = np.empty(n)
809 if k > 0
and r == rs[k - 1]
and (xs[k] > 0.0) == (xs[k - 1] > 0.0):
811 qc[k] = qc[k - 1]; qr[k] = qr[k - 1]
816 M =
_mach(ar, gamma, xs[k] > 0.0)
817 kk = 1.0 + 0.5*(gamma - 1.0)*M*M
819 tP = tT**(gamma/(gamma - 1.0))
820 k2 = 0.5*(gamma - 1.0)*M*M
821 Taw = T0*(1.0 + rr*k2)/(1.0 + k2)
822 sig = 1.0/((0.5*(Tw/T0)*kk + 0.5)**(0.8 - BARTZ_OMEGA/5.0)*kk**(BARTZ_OMEGA/5.0))
823 h = (0.026/Dt**0.2*(mu**0.2*cp/Pr**0.6)*G**0.8
824 *(Dt/rc)**0.1*(1.0/ar)**0.9*sig)
827 L =
min(beam, CYLINDER_BEAM_LENGTH_OVER_D*2.0*r)
829 pH = xH*P_bar; pC = xC*P_bar
832 Twa = Tw
if Tw < Tg
else Tg
837 alpha =
max((1.0/s)**0.45*aH + (1.0/s)**0.65*aC - dd, 0.0)
838 fe = ew/(1.0 - (1.0 - ew)*(1.0 - alpha))
839 q_rad = fe*SIGMA_SB*(eps_g*Tg**4 - alpha*Tw**4)
840 q_conv = h*(Taw - Tw)
841 qc[k] = q_conv
if q_conv > 0.0
else 0.0
842 qr[k] = q_rad
if q_rad > 0.0
else 0.0
851 if Q[Q_AB_PHYS] > 0.5:
852 if not (gas_mdot > 0.0):
855 f_new =
max(1.0/(1.0 + Q[Q_AB_BC]*B), Q[Q_AB_BMIN])
857 f_new = 1.0 - Q[Q_AB_BEFF]
858 if abs(f_new - f) < 1e-12:
862 if Q[Q_AB_BELOW] > 0.5:
877def _point(Pc, P, CH, C, P_O, P_F, eps_cea, out):
878 """supply - demand at Pc (NaN where the Python residual is NaN or raises); fills ``out``."""
879 if not (np.isfinite(Pc)
and Pc > 0.0):
882 sol = _solve_injector(P, P_O, P_F, Pc)
883 ok = sol[0]; mO = sol[1]; mF = sol[2]; uO = sol[3]; uF = sol[4]; D32O = sol[5]; D32F = sol[6]
884 if ok == 0.0
or not (np.isfinite(mO)
and np.isfinite(mF)):
886 holes_O = sol[26]; holes_F = sol[27]
891 if not np.isfinite(MR):
893 cs_id, cf_id, Tc, gm, Rg, Mg, cfv = cea_eval(C[0], C[1], C[2], C[3], C[4], C[5], C[6], C[7],
894 C[8], C[9], MR, Pc, eps_cea)
895 if not (np.isfinite(cs_id)
and cs_id > 0.0):
901 if Q[Q_AB_ON] > 0.5
and mdot > 0.0:
902 MRt = mO/mF
if mF > 0.0
else MR
904 mu =
_aux2(Q, aux2, 0, MRt, Pc)
905 cpg =
_aux2(Q, aux2, 1, MRt, Pc)
906 Prg =
_aux2(Q, aux2, 2, MRt, Pc)
907 xH =
max(
_aux2(Q, aux2, 3, MRt, Pc), 0.0)
908 xC =
max(
_aux2(Q, aux2, 4, MRt, Pc), 0.0)
909 Q_abl =
_ablative_Q(Q, CH, Tc, Pc, gm, mdot/Q[Q_AT], mu, cpg, Prg, xH, xC, mdot)
910 if not np.isfinite(Q_abl):
913 cp = gm*Rg/
max(gm - 1.0, 1e-6)
914 Tc_eff =
max(Tc - Q_abl/
max(mdot*cp, 1e-6), 1.0)
917 lnMR = CH[3]; lnPc = CH[4]; cs = CH[5]
918 row = np.empty(lnMR.size)
920 MR_lo = CH[2][0]; MR_hi = CH[2][1]
921 r_lo = MR_lo/(1.0 + MR_lo); r_hi = MR_hi/(1.0 + MR_hi)
922 c_lo =
_interp(math.log(MR_lo), lnMR, row)
923 c_hi =
_interp(math.log(MR_hi), lnMR, row)
926 u_ax = np.nan; L_imp = np.nan; L_b = np.nan; rupe_M = np.nan
927 w_O = MR/(1.0 + MR); w_F = 1.0/(1.0 + MR)
931 ua = (mO*uO*math.cos(math.radians(P[ANG_O])) + mF*uF*math.cos(math.radians(P[ANG_F])))/mdot
934 u_ax = ua
if (np.isfinite(ua)
and ua > 0.0)
else sol[20]
936 nn =
max(1.0, 0.5*(P[NO] + P[NF]))
937 dr = 0.5*abs(nn*P[SPO]/math.pi - nn*P[SPF]/math.pi)
938 tan_sum = math.tan(math.radians(P[ANG_O])) + math.tan(math.radians(P[ANG_F]))
939 if np.isfinite(tan_sum)
and tan_sum > 1e-9
and dr > 0.0:
941 rho_gas =
max(Pc/(P[SP_GASR]*P[SP_GAST]), 1e-6)
942 d_avg = 0.5*(P[DJO] + P[DJF])
943 if np.isfinite(L_imp)
and L_imp > 0
and d_avg > 0
and rho_gas > 1e-4:
944 L_b = _clip(d_avg**2/(4.0*L_imp)*math.sqrt(0.5*(P[RHO_O] + P[RHO_F])/rho_gas),
949 vO = mO/(P[RHO_O]*sol[14])
if (P[RHO_O] > 0
and sol[14] > 0)
else np.nan
950 vF = mF/(P[RHO_F]*sol[15])
if (P[RHO_F] > 0
and sol[15] > 0)
else np.nan
951 den = P[RHO_F]*vF**2*P[DJF]
952 if np.isfinite(den)
and den > 0
and np.isfinite(vO)
and P[RHO_O] > 0
and P[DJO] > 0:
953 rupe_M = P[RHO_O]*vO**2*P[DJO]/den
956 u_ax = w_O*uO + w_F*uF
959 model = Q[Q_EFF_MODEL]
961 eta_vap = 1.0 - Q[Q_EFF_C]
963 eta_vap = _clip(1.0 - Q[Q_EFF_C]*(1.0 - Q[Q_LSTAR]/1.0), 0.0, 1.0)
965 eta_vap =
_eta_vap(Q, CH, Pc, Tc, gm, Rg, MR, mdot, D32O, D32F, uO, uF, u_ax, L_imp, L_b,
966 lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi, out)
967 if not np.isfinite(eta_vap):
969 eta_mix =
_eta_mix(Q, CH, MR, rupe_M, holes_O, holes_F, lnMR, row, MR_lo, MR_hi, r_lo, r_hi,
971 if not np.isfinite(eta_mix):
976 if Q[Q_USE_COUP] > 0.5
and Q_abl > 0.0:
977 if not (mdot > 0.0
and Tc > 0.0
and Rg > 0.0
and gm > 1.0):
979 xq = Q_abl/(mdot*gm*Rg/(gm - 1.0)*Tc)
980 eta_HL = math.sqrt(
max(1.0 - xq, 0.0))
981 eta = eta_vap*eta_mix*eta_HL
982 if not (np.isfinite(eta)
and eta > 0.0):
985 kappa =
_kappa(Q[Q_CR], gm)
986 num = Pc/kappa*Q[Q_AT]
987 if not (np.isfinite(num)
and np.isfinite(cstar))
or abs(cstar) < 1e-12:
990 if not np.isfinite(demand):
993 out[R_MR] = MR; out[R_MDOT] = mdot; out[R_MDOT_O] = mO; out[R_MDOT_F] = mF
994 out[R_CSTAR_IDEAL] = cs_id; out[R_CSTAR] = cstar; out[R_ETA] = eta; out[R_ETA_VAP] = eta_vap
995 out[R_ETA_MIX] = eta_mix; out[R_ETA_HL] = eta_HL; out[R_GAMMA] = gm; out[R_R] = Rg
996 out[R_TC_IDEAL] = Tc; out[R_TC_EFF] = Tc_eff; out[R_KAPPA] = kappa; out[R_Q_ABL] = Q_abl
997 out[R_RUPE_M] = rupe_M; out[R_M_MOL] = Mg
998 return res
if np.isfinite(res)
else np.nan
1002def _brentq(P, CH, C, P_O, P_F, a, b, xtol, rtol, maxit, out):
1003 """scipy.optimize.brentq (the C original), on _point."""
1005 fa =
_point(a, P, CH, C, P_O, P_F, eps, out)
1006 fb =
_point(b, P, CH, C, P_O, P_F, eps, out)
1007 if not np.isfinite(fa)
or not np.isfinite(fb):
1013 if _sign(fa) == _sign(fb):
1015 c = a; fc = fa; d = b - a; e = d
1016 for _
in range(maxit):
1017 if _sign(fb) == _sign(fc):
1018 c = a; fc = fa; d = b - a; e = d
1019 if abs(fc) < abs(fb):
1020 a = b; b = c; c = a; fa = fb; fb = fc; fc = fa
1021 tol = 2.0*rtol*abs(b) + 0.5*xtol
1023 if fb == 0.0
or abs(m) <= tol:
1025 if abs(e) < tol
or abs(fa) <= abs(fb):
1030 p = 2.0*m*s; q = 1.0 - s
1032 qa = fa/fc; r = fb/fc
1033 p = s*(2.0*m*qa*(qa - r) - (b - a)*(r - 1.0))
1034 q = (qa - 1.0)*(r - 1.0)*(s - 1.0)
1039 if 2.0*p <
min(3.0*m*q - abs(tol*q), abs(e*q)):
1047 b += tol
if m > 0.0
else -tol
1048 fb =
_point(b, P, CH, C, P_O, P_F, eps, out)
1049 if not np.isfinite(fb):
1056 """Pc root, converged state and delivered thrust -> (ok, result vector).
1058 The window and the top-down bracket scan are chamber_solver.solve's (PC_CHOKE_FLOOR_PA,
1059 PC_MIN_TOTAL_DROP_FRAC, _highest_sign_change); the thrust is nozzle.calculate_thrust."""
1061 res = np.full(NR, np.nan)
1062 scratch = np.full(NR, np.nan)
1063 Pc_min = 2.0*101325.0
1064 Pc_max =
min(P_O, P_F)*(1.0 - 0.02)
1065 Pc_min =
max(Pc_min, P[SV_PCMIN]); Pc_max =
min(Pc_max, P[SV_PCMAX])
1066 if Pc_max <= Pc_min:
1068 xtol = P[SV_TOL]; rtol = P[SV_TOL]*1e-3
1069 maxit = int(P[SV_MAXIT])
if P[SV_MAXIT] > 0
else 100
1071 rmin =
_point(Pc_min, P, CH, C, P_O, P_F, eps, scratch)
1072 rmax =
_point(Pc_max, P, CH, C, P_O, P_F, eps, scratch)
1073 if not np.isfinite(rmin)
or not np.isfinite(rmax):
1076 lo = 0.0; hi = 0.0; found =
False
1077 pb = Pc_max; rb = rmax
1078 for i
in range(n - 1, -1, -1):
1079 pa = Pc_min + (Pc_max - Pc_min)*(i/n)
1080 ra = rmin
if i == 0
else _point(pa, P, CH, C, P_O, P_F, eps, scratch)
1081 if np.isfinite(ra)
and np.isfinite(rb)
and _sign(ra) != _sign(rb):
1082 lo = pa; hi = pb; found =
True
1087 Pc =
_brentq(P, CH, C, P_O, P_F, lo, hi, xtol, rtol, maxit, scratch)
1088 if not np.isfinite(Pc):
1091 r =
_point(Pc, P, CH, C, P_O, P_F, Q[Q_EPS_CUR], res)
1092 if not np.isfinite(r):
1097 g1 = cea_eval(C[0], C[1], C[2], C[3], C[4], C[5], C[6], C[7], C[8], C[9], MR, Pc, eps)[3]
1098 kappa =
_kappa(Q[Q_CR], g1)
1100 cs2, cf2, tc2, gm2, Rg2, Mg2, cfv2 = cea_eval(C[0], C[1], C[2], C[3], C[4], C[5], C[6], C[7],
1101 C[8], C[9], MR, P0, eps)
1102 if not np.isfinite(cfv2):
1105 ln_r =
_aux3(Q, aux3, 0, MR, P0, eps)
1106 Te =
_aux3(Q, aux3, 1, MR, P0, eps)
1107 Me =
_aux3(Q, aux3, 2, MR, P0, eps)
1108 P_exit = P0/math.exp(ln_r)
1109 v_exit = cs2*(cfv2 - eps*P_exit/P0)
1110 if not (np.isfinite(P_exit)
and P_exit > 0
and np.isfinite(Te)
and Te > 0
1111 and Me > 1.0
and v_exit > 0):
1113 F = Q[Q_ZETA_N]*cfv2*P0*Q[Q_AT] - Pa*Q[Q_AE]
1114 if not np.isfinite(F):
1117 thr = 2.0/(gm2 + 1.0)
1119 res[R_ISP] = F/(mdot*G0)
1120 res[R_CF] = F/(P0*Q[Q_AT])
1121 res[R_CF_IDEAL] = cfv2 - Pa*eps/P0
if P0 > 0
else cf2
1122 res[R_CF_VAC] = cfv2
1123 res[R_P_EXIT] = P_exit; res[R_T_EXIT] = Te; res[R_M_EXIT] = Me; res[R_V_EXIT] = v_exit
1124 res[R_T_THROAT] = tc2*thr
1125 res[R_P_THROAT] = P0*thr**(gm2/(gm2 - 1.0))
1127 if not (res[R_ISP] >= 0.0):
1135_CONTOUR_MEMO:
"OrderedDict" = OrderedDict()
1140 key = (cg.A_throat, cg.chamber_diameter, cg.volume, cg.A_exit)
1141 c = _CONTOUR_MEMO.get(key)
1144 _CONTOUR_MEMO[key] = c
1145 if len(_CONTOUR_MEMO) > 64:
1146 _CONTOUR_MEMO.popitem(last=
False)
1152 arr = getattr(aux,
"_numba_arrays",
None)
1155 a2 = np.ascontiguousarray(np.stack([t[
"mu_c"], t[
"cp_c"], t[
"Pr_c"], t[
"x_H2O_c"],
1156 t[
"x_CO2_c"]]), np.float64)
1157 a3 = np.ascontiguousarray(np.stack([t[
"lnPcPe"], t[
"Te"], t[
"Me"]]), np.float64)
1158 arr = (a2, a3, (float(aux.MR[0]), float(aux.MR[-1]), len(aux.MR),
1159 float(aux.Pc[0]), float(aux.Pc[-1]), len(aux.Pc),
1160 float(aux.eps[0]), float(aux.eps[-1]), len(aux.eps)))
1162 aux._numba_arrays = arr
1170 cfg = getattr(cache,
"config",
None)
1171 if cfg
is None or not getattr(cfg,
"ox_name",
None)
or not getattr(cfg,
"fuel_name",
None):
1173 wide = get_cstar_wide_table(cfg.ox_name, cfg.fuel_name)
1176 arr = getattr(wide,
"_numba_arrays",
None)
1178 arr = (np.ascontiguousarray(wide._lnMR, np.float64), np.ascontiguousarray(wide._lnPc, np.float64),
1179 np.ascontiguousarray(wide.cstar, np.float64),
1180 np.array([float(wide.MR_min), float(wide.MR_max)]))
1182 wide._numba_arrays = arr
1189 """The chamber tuple for evaluate_core, or None when a piece of the Python chamber solve is
1190 not mirrored here (the caller then runs Python)."""
1195 cg = ensure_chamber_geometry(config)
1196 eff = config.combustion.efficiency
1200 dist = str(eff.mixing_distribution)
1201 blowing = str(getattr(eff,
"droplet_blowing_model",
"abramzon_sirignano"))
1202 if dist
not in _UNIT_MAD
or blowing
not in (
"abramzon_sirignano",
"none"):
1204 z, wz = _UNIT_MAD[dist]
1205 q = float(eff.spray_size_spread_q)
1207 Q[_Q[
"AT"]] = float(cg.A_throat)
1208 Q[_Q[
"AE"]] = float(cg.A_exit)
1209 Q[_Q[
"EPS"]] = float(cg.expansion_ratio)
1210 Q[_Q[
"EPS_CUR"]] = (float(cg.A_exit / cg.A_throat)
if cg.A_throat
and cg.A_exit
and cg.A_throat > 0
1211 else float(cg.expansion_ratio))
1212 Q[_Q[
"LSTAR"]] = float(cg.Lstar)
if cg.Lstar
is not None else float(cg.volume / cg.A_throat)
1213 D = cg.chamber_diameter
1214 if (D
is None or D <= 0)
and config.regen_cooling
is not None and config.regen_cooling.chamber_inner_diameter:
1215 D = config.regen_cooling.chamber_inner_diameter
1216 if D
is None or D <= 0:
1219 Q[_Q[
"AC"]] = float(np.pi * (D / 2.0) ** 2)
1220 CR = contraction_ratio_of(cg)
1221 Q[_Q[
"CR"]] = float(CR)
if CR
is not None else 0.0
1222 Q[_Q[
"ZETA_N"]] = float(cg.nozzle_efficiency)
1223 Q[_Q[
"EFF_MODEL"]] = {
"constant": 0.0,
"linear": 1.0}.get(str(eff.model), 2.0)
1224 Q[_Q[
"EFF_C"]] = float(eff.C)
1226 Q[_Q[
"RR_XSCALE"]] = math.gamma(1.0 - 1.0 / q)
if q > 1.0
else np.nan
1227 Q[_Q[
"BLOW_AS"]] = 1.0
if blowing ==
"abramzon_sirignano" else 0.0
1228 Q[_Q[
"EM_OPT"]] = float(eff.rupe_Em_opt)
1229 Q[_Q[
"M_OPT"]] = float(eff.rupe_M_opt)
1230 Q[_Q[
"EM_CURV"]] = float(eff.rupe_Em_curvature)
1231 Q[_Q[
"USE_COUP"]] = 1.0
if eff.use_cooling_coupling
else 0.0
1232 Q[_Q[
"IMP"]] = 1.0
if config.injector.type ==
"impinging" else 0.0
1235 for pre, side
in ((
"SO",
"oxidizer"), (
"SF",
"fuel")):
1236 fl = config.fluids[side]
1237 g =
lambda k: getattr(fl, k,
None)
1238 dens, Tb, Lb, MW =
g(
"density"),
g(
"boiling_point"),
g(
"latent_heat"),
g(
"molecular_weight")
1239 ok = bool(dens
and Tb
and Lb
and MW)
1240 Q[_Q[f
"{pre}_OK"]] = 1.0
if ok
else 0.0
1241 Q[_Q[f
"{pre}_RHO"]] = float(dens
or 0.0)
1242 Q[_Q[f
"{pre}_TB"]] = float(Tb
or 0.0)
1243 Q[_Q[f
"{pre}_LB"]] = float(Lb
or 0.0)
1244 Q[_Q[f
"{pre}_MW"]] = float(MW
or 0.0)
1245 Tcrit =
g(
"critical_temperature")
or 0.0
1246 Q[_Q[f
"{pre}_TCRIT"]] = float(Tcrit)
1247 T0 =
g(
"temperature")
1248 T0 = 293.0
if T0
is None else float(T0)
1249 Q[_Q[f
"{pre}_T0"]] = T0
1250 cp =
g(
"specific_heat")
1251 Q[_Q[f
"{pre}_CP"]] = 2000.0
if cp
is None else float(cp)
1253 if ok
and g(
"name"):
1254 ht =
heatup_tables(str(fl.name), T0, float(Tb), float(Lb), float(MW), float(Tcrit))
1255 Q[_Q[f
"{pre}_HT"]] = 1.0
if ht
is not None else 0.0
1256 tables.append(ht
if ht
is not None else _NO_HT)
1258 ab = config.ablative_cooling
1259 xs = rs = dA = np.zeros(0)
1260 if ab
is not None and ab.enabled:
1262 gr = getattr(config,
"graphite_insert",
None)
1263 if gr
is None or not gr.enabled:
1266 x_end = -float(gr.axial_half_length
or gr.axial_half_length_ratio * 2.0 * contour.R_t)
1267 lined = (contour.x >= contour.x_face - 1e-12) & (contour.x <= x_end + 1e-12)
1268 xs = np.ascontiguousarray(contour.x[lined], np.float64)
1269 rs = np.ascontiguousarray(contour.r[lined], np.float64)
1270 dA = np.ascontiguousarray(contour.area_elements()[lined]
1271 * float(np.clip(ab.coverage_fraction, 0.0, 1.0)), np.float64)
1272 Ts = float(ab.ablation_surface_temperature)
1273 Q[_Q[
"AB_ON"]] = 1.0
1275 Q[_Q[
"AB_EW"]] = float(ab.surface_emissivity)
1276 Q[_Q[
"AB_E"]] = float(ablation_energy_per_mass(ab, Ts))
1277 Q[_Q[
"AB_BELOW"]] = 1.0
if Ts < ab.pyrolysis_temperature
else 0.0
1278 Q[_Q[
"AB_PHYS"]] = 1.0
if ab.use_physics_based_blowing
else 0.0
1279 Q[_Q[
"AB_BC"]] = float(ab.blowing_coefficient)
1280 Q[_Q[
"AB_BMIN"]] = float(ab.blowing_min_reduction_factor)
1281 Q[_Q[
"AB_BEFF"]] = float(np.clip(ab.blowing_efficiency, 0.0, 1.0))
1282 Q[_Q[
"AB_RT"]] = float(contour.R_t)
1283 Q[_Q[
"AB_LB"]] = float(contour.beam_length)
1286 for k, v
in zip((
"AX_MR0",
"AX_MR1",
"AX_NMR",
"AX_PC0",
"AX_PC1",
"AX_NPC",
"AX_E0",
"AX_E1",
1290 lnMR, lnPc, cs, mrlim = wide
1291 ypow = np.ascontiguousarray(_RR_NODES ** (1.0 / q)
if q > 1.0
else _RR_NODES, np.float64)
1292 (hOs, hOc, hOo, tOs, tOc, tOo), (hFs, hFc, hFo, tFs, tFc, tFo) = tables
1293 return (Q, a2, mrlim, lnMR, lnPc, cs, xs, rs, dA,
1294 np.ascontiguousarray(z, np.float64), np.ascontiguousarray(wz, np.float64), ypow,
1295 hOs, hOc, hOo, tOs, tOc, tOo, hFs, hFc, hFo, tFs, tFc, tFo, a3)
1299 """Load (or compile) evaluate_core for the argument types every config produces, so a worker
1300 pool does not pay it inside its first generation. Never raises."""
1304 CH = (np.zeros(NQ), np.zeros((5, 2, 2)), z1, z1, z1, np.zeros((2, 2)), z1, z1, z1, z1, z1,
1305 z1, _NO_HT[0], _NO_HT[1], _NO_HT[2], _NO_HT[3], _NO_HT[4], _NO_HT[5],
1306 _NO_HT[0], _NO_HT[1], _NO_HT[2], _NO_HT[3], _NO_HT[4], _NO_HT[5],
1307 np.zeros((3, 2, 2, 2)))
1308 t3 = np.ones((2, 2, 2))
1309 C = (z1, z1, z1, t3, t3, t3, t3, t3, t3, t3)
_piecewise(segments, fn, nf, tol_rel, tol_abs)
_lk_overlap(pH, pC, L_cm)
_eta_mix(Q, CH, MR, rupe_M, holes_O, holes_F, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi, out)
_sat_T(P, Tb, Lb, MW, Tcrit)
_brentq(P, CH, C, P_O, P_F, a, b, xtol, rtol, maxit, out)
_cs_of_r(r, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)
_aux3(Q, tab, k, MR, Pc, eps)
_eta_vap(Q, CH, Pc, Tc, gamma, R, MR, mdot, D32_O, D32_F, u_O, u_F, u_ax, L_imp, L_b, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi, out)
_lk_species(h2o, T, P_bar, pa_bar, L_cm)
evaluate_core(P, CH, C, P_O, P_F, Pa)
_ablative_Q(Q, CH, T0, P0, gamma, G, mu, cp, Pr, xH, xC, gas_mdot)
_wide_row(lnPc, cs, Pc, row)
heatup_tables(name, T0, Tb, Lb, MW, Tcrit)
_saturation_state(P, Tb, Lb, MW, Tcrit)
_tab_range(lo, hi, flag, h_target, fn, nf, tol_rel, tol_abs, depth)
_cs_of_MR(MR, lnMR, row, MR_lo, MR_hi, r_lo, r_hi, c_lo, c_hi)
_rupe_Em_at_M(M, Em_opt, M_opt, curv)
_heatup(Q, b, HTs, HTc, HTo, TLs, TLc, TLo, Pc, T0, Ts, Tc)
_point(Pc, P, CH, C, P_O, P_F, eps_cea, out)
chamber_inputs(config, cache)