34 """A smooth peaked c*(O/F), Pc-independent: 1700 exp(-0.3 ln(MR/1.55)^2) m/s."""
35 MR = np.geomspace(0.1, 20.0, 400)
36 Pcs = np.geomspace(1e5, 1.2e7, 4)
37 c = 1700.0 * np.exp(-0.3 * np.log(MR / 1.55) ** 2)
38 tab = cp.CstarWideTable(
"X",
"Y", MR, Pcs, np.repeat(c[:,
None], Pcs.size, axis=1))
39 return cp.CstarOfMR(Pc, wide=tab)
51 """Four tubes about R = 0.6, by hand: lean (0.5, 0.2) and (0.55, 0.3), rich (0.7, 0.4), (0.8, 0.1)."""
52 r = np.array([0.5, 0.55, 0.7, 0.8])
53 w = np.array([0.2, 0.3, 0.4, 0.1])
55 hand = 1.0 - (0.2 * 0.1 + 0.3 * 0.05) / 0.6 - (0.4 * 0.1 + 0.1 * 0.2) / 0.4
56 assert cp.rupe_Em_of_distribution(r, w, R) == pytest.approx(hand, rel=1e-12)
59@pytest.mark.parametrize("dist", ["gaussian", "two_tube"])
60@pytest.mark.parametrize("R,Em", [(0.45, 0.8), (0.56, 0.79), (0.7, 0.9)])
77FIG1_MEAN_LINE = [(0.26, 44.3), (0.28, 48.5), (0.30, 53.8), (0.35, 62.3), (0.40, 70.3), (0.45, 75.7),
78 (0.50, 77.7), (0.55, 77.7), (0.60, 74.4), (0.65, 68.5), (0.70, 60.5), (0.75, 53.7),
83 """N_R = M/(1+M) (TR 32-1546 eq. 2 with 1 = fuel). With the figure's own peak (77.2 at N_R 0.525)
84 the default curvature reproduces the digitized mean line within 3 points (data scatter +-5)."""
87 for N, E
in FIG1_MEAN_LINE:
88 got = 100.0 * cp.rupe_Em_at_M(N / (1 - N), 0.772, M0, cfg.rupe_Em_curvature)
89 assert abs(got - E) < 3.0, (N, E, got)
90 assert cp.rupe_Em_at_M(1.0, 0.8, 1.0) == 0.8
92 assert cp.rupe_Em_at_M(1 / 3, 0.8, 1.0) == pytest.approx(cp.rupe_Em_at_M(3.0, 0.8, 1.0), rel=1e-12)
98CEA_MIX = {(1.277, 0.85): (0.95140, 0.96101), (1.5, 0.85): (0.95227, 0.96213)}
105 fn = cp.CstarOfMR(316.4 * PSI, wide=wide)
106 g, two = CEA_MIX[key]
107 assert cp.stream_tube_mixing_efficiency(MR, fn, [(1, MR, Em)],
"gaussian")[
"eta_mix"] == pytest.approx(g, abs=3e-4)
108 assert cp.stream_tube_mixing_efficiency(MR, fn, [(1, MR, Em)],
"two_tube")[
"eta_mix"] == pytest.approx(two, abs=3e-4)
114 assert cp.stream_tube_mixing_efficiency(MR, fn, [(1.0, MR, 1.0)])[
"eta_mix"] == pytest.approx(1.0, abs=1e-14)
116 r1, r2 = 0.5, 1.6 / 2.6
117 w1 = (r2 - MR / (1 + MR)) / (r2 - r1)
118 out = cp.stream_tube_mixing_efficiency(MR, fn, [(w1, 1.0, 1.0), (1 - w1, 1.6, 1.0)])
119 hand = (w1 * fn(1.0) + (1 - w1) * fn(1.6)) / fn(MR)
120 assert out[
"eta_mix"] == pytest.approx(hand, rel=1e-12)
121 assert abs(out[
"element_centre_shift"]) < 1e-12
124def _advanced(diag_extra=None, cfg=None, cstar_fn=None, fuel_props=None, ox_props=None):
125 diag = {
"D32_O": 90e-6,
"D32_F": 160e-6,
"u_axial_spray": 22.0,
"rupe_M": 0.83,
126 "L_imp": 5.75e-3,
"L_sheet_breakup": 2.4e-3, **(diag_extra
or {})}
127 return cp.calculate_combustion_efficiency_advanced(
129 0.0127, 0.00131, 2.4, u_fuel=35.8, u_lox=25.7, spray_diagnostics=diag,
130 fuel_props=fuel_props
if fuel_props
is not None else {
131 "name":
"Ethanol",
"density": 789.0,
"boiling_point": 351.4,
"latent_heat": 838e3,
132 "molecular_weight": 46.07,
"specific_heat": 2440.0,
"temperature": 293.0,
133 "critical_temperature": 514.71},
134 ox_props=ox_props
if ox_props
is not None else {
135 "name":
"LOX",
"density": 1140.0,
"boiling_point": 90.2,
"latent_heat": 213e3,
136 "molecular_weight": 32.0,
"specific_heat": 2300.0,
"temperature": 90.0,
137 "critical_temperature": 154.6},
143 Em = cp.rupe_Em_at_M(0.83, 0.80, 1.0, 6.07)
145 want = cp.stream_tube_mixing_efficiency(1.277, fn, [(1.0, 1.277, Em)],
"gaussian")[
"eta_mix"]
146 assert r[
"rupe_Em"] == pytest.approx(Em, rel=1e-12)
147 assert r[
"eta_mixing"] == pytest.approx(want, rel=1e-12)
148 assert r[
"eta_mixing"] < r[
"eta_mixing_two_tube"] < 1.0
159 mrs = [1.02, 1.15, 1.28, 1.42]
160 flows = [0.55, 0.58, 0.6, 0.62]
161 s =
_advanced({
"element_mixture_ratios": mrs,
"element_mass_flows": flows})
162 assert s[
"eta_mixing"] < base[
"eta_mixing"] - 1e-3
163 assert s[
"rupe_Em_total"] < base[
"rupe_Em_total"]
166 W = np.array(flows) / sum(flows)
167 els = [(w, m, cp.rupe_Em_at_M(0.83 * (m / 1.277) ** 2, 0.8, 1.0, 6.07))
for w, m
in zip(W, mrs)]
168 assert s[
"eta_mixing"] == pytest.approx(
169 cp.stream_tube_mixing_efficiency(1.277, fn, els)[
"eta_mix"], rel=1e-12)
173 """A CEA cache spanning O/F 1.0-2.5 used to clamp: every lean tail tube got c*(1.0)."""
175 MR_min, MR_max = 1.0, 2.5
177 def eval(self, MR, Pc):
178 return {
"cstar_ideal": 1700.0 * math.exp(-0.3 * math.log(
min(
max(MR, 1.0), 2.5) / 1.55) ** 2)}
179 fn = cp.CstarOfMR(2.2e6, cea_cache=Cache())
180 c, out = fn.of_fraction(np.array([0.25, 0.6, 0.85]))
181 assert list(out) == [
True,
False,
True]
182 assert c[0] == pytest.approx(fn(1.0) * math.sqrt(0.25 / 0.5), rel=1e-12)
183 assert c[2] == pytest.approx(fn(2.5) * math.sqrt(0.15 / (1 - 2.5 / 3.5)), rel=1e-12)
185 names = {a[
"name"]
for a
in r[
"assumptions"]}
186 assert "combustion.mixing.cstar_outside_table" in names
and r[
"mixing_mass_outside_cstar_table"] > 0.1
215def _vap(diag_extra=None, cstar_fn=None, **kw):
217 diag = {
"D32_O": 90e-6,
"D32_F": 160e-6,
"u_axial_spray": 22.0, **(diag_extra
or {})}
218 fuel = {
"density": 789.0,
"boiling_point": 351.4,
"latent_heat": 838e3,
"molecular_weight": 46.07,
219 "specific_heat": 2440.0,
"temperature": 293.0,
"critical_temperature": 514.71}
220 ox = {
"density": 1140.0,
"boiling_point": 90.2,
"latent_heat": 213e3,
"molecular_weight": 32.0,
221 "specific_heat": 2300.0,
"temperature": 90.0,
"critical_temperature": 154.6}
222 kw.setdefault(
"fuel_props", fuel)
223 kw.setdefault(
"ox_props", ox)
224 eta, out = cp.calculate_vaporization_efficiency(
225 Pc=2.18e6, Tc=3150.0, gamma=1.14, R=370.0, MR=1.277, m_dot_total=2.4, Ac=0.0127, At=0.00131,
226 Lstar=0.95, spray_diagnostics=diag, u_fuel=35.8, u_lox=25.7, rr_q=3.0, assumptions=notes,
227 cstar_fn=cstar_fn, **kw)
228 return eta, out, notes
241 eta, out, _ =
_vap(cstar_fn=fn)
242 F, fO, fF = out[
"fraction_vaporized"], out[
"frac_vaporized_O"], out[
"frac_vaporized_F"]
244 MRv = 1.277 * fO / fF
245 assert out[
"MR_vaporized"] == pytest.approx(MRv, rel=1e-12)
246 assert eta == pytest.approx(F * fn(MRv) / fn(1.277), rel=1e-12)
249 MR = np.geomspace(0.1, 20.0, 400)
250 c = 1700.0 * np.exp(-0.3 * np.log(MR / 1.0) ** 2)
251 tab = cp.CstarWideTable(
"X",
"Y", MR, np.geomspace(1e5, 1.2e7, 4), np.repeat(c[:,
None], 4, axis=1))
252 eta2, out2, _ =
_vap(cstar_fn=cp.CstarOfMR(2.18e6, wide=tab))
253 assert eta2 < out2[
"fraction_vaporized"]
257 assert cp.abramzon_sirignano_F(1.0) == pytest.approx(2 ** 0.7 * math.log(2.0), rel=1e-12)
258 assert cp.abramzon_sirignano_F(0.0) == 1.0
259 assert cp.abramzon_sirignano_F(16.2) == pytest.approx(17.2 ** 0.7 * math.log(17.2) / 16.2, rel=1e-12)
260 assert cp.yuen_chen_drag_factor(1.0) == pytest.approx(2 ** -0.2, rel=1e-12)
262 assert cfg.droplet_blowing_model ==
"abramzon_sirignano"
265_GAS = dict(Pc=2.92e6, Tc=3226.0, gamma=1.1375, R=373.57, m_dot_total=2.718, Ac=math.pi * 0.0635 ** 2)
269 """The march's documented equations for a trace stream in burned gas (U_g = U_c), integrated by
270 scipy for 96 size classes, with the Stefan-flow corrections applied while the drop evaporates."""
271 from scipy.integrate
import solve_ivp
274 Pc, Tc, gam, R = g[
"Pc"], g[
"Tc"], g[
"gamma"], g[
"R"]
275 TsF, hF = cp.saturation_state(Pc, 351.4, 838e3, 46.07, 514.71)
276 cpg = gam * R / (gam - 1.0)
277 rho_c = Pc / (R * Tc)
278 U = g[
"m_dot_total"] / (rho_c * g[
"Ac"])
279 Tf = TsF + (Tc - TsF) / 3.0
280 mu = calculate_gas_viscosity_huzel(Tf, R_U / R)
283 rho_f = Pc / (R * Tf)
284 B = cpg * (Tc - TsF) / hF
285 FB = (1 + B) ** 0.7 * math.log1p(B) / B
if blowing
else 1.0
286 cd = (1 + B) ** -0.2
if blowing
else 1.0
287 L, u0, q = 0.05, 24.0, 3.0
288 I = 2440.0 * math.log((Tc - 293.0) / (Tc - TsF))
if I_heat
is None else I_heat
289 X = 85e-6 * math.gamma(1.0 - 1.0 / q)
290 xs, ws = np.polynomial.legendre.leggauss(96)
292 for Vk, wk
in zip(0.5 * (xs + 1.0), 0.5 * ws):
293 D0 = X * (-math.log1p(-Vk)) ** (1.0 / q)
294 Nu0 = 2.0 + 0.6 * math.sqrt(rho_f * u0 * D0 / mu) * Pr ** (1 / 3)
295 t_h = 789.0 * D0 ** 2 / (6 * Nu0 * k) * I
299 D = math.sqrt(
max(D2, 0.0))
301 Re_d = rho_c * dU * D / mu
303 Nu = 2.0 + 0.6 * math.sqrt(rho_f * dU * D / mu) * Pr ** (1 / 3) / FB
304 K = 4.0 * Nu * k * math.log1p(B) / (789.0 * cpg)
if ev
else 0.0
305 phi = 1.0 + Re_d ** (2 / 3) / 6.0
if Re_d <= 1000
else 0.424 * Re_d / 24.0
308 dv = (U - v) * 18.0 * mu * phi / (789.0 *
max(D2, 1e-30))
309 return [v, dv, -K
if D2 > 0
else 0.0]
311 hit_wall =
lambda t, s: s[0] - L
312 hit_wall.terminal =
True
313 gone =
lambda t, s: s[2]
315 sol = solve_ivp(rhs, (0.0, 1.0), [0.0, u0, D0 ** 2], events=(hit_wall, gone),
316 rtol=1e-9, atol=[1e-12, 1e-9, 1e-18], max_step=2e-5)
317 remaining += wk * (
max(sol.y[2, -1], 0.0) / D0 ** 2) ** 1.5
318 fuel = dict(name=
"F", mass_fraction=1e-9, D32=85e-6, rho_l=789.0, cp_l=2440.0, T0=293.0,
320 if I_heat
is not None:
321 fuel[
"heatup_integral"] = I_heat
322 return 1.0 - remaining, fuel, L, u0, q
325@pytest.mark.parametrize("blowing", [True, False])
328 got = cp.spray_vaporization_march(
329 [dict(name=
"O", mass_fraction=1.0 - 1e-9, instant=
True), fuel], **_GAS, L_chamber=L,
330 u_drop0=u0, rr_q=q, blowing=
"abramzon_sirignano" if blowing
else "none")[
"frac_vaporized"][
"F"]
331 assert got == pytest.approx(ref, abs=3e-4)
336 kw = dict(**_GAS, L_chamber=L, u_drop0=u0, rr_q=q)
337 streams = [dict(name=
"O", mass_fraction=1.0 - 1e-9, instant=
True), fuel]
338 a = cp.spray_vaporization_march(streams, **kw, blowing=
"none")[
"frac_vaporized"][
"F"]
339 b = cp.spray_vaporization_march(streams, **kw)[
"frac_vaporized"][
"F"]
340 assert abs(a - b) > 1e-3
344 CP = pytest.importorskip(
"CoolProp.CoolProp")
345 from scipy.integrate
import quad
346 P, T0, Ts, Tc = 2.18e6, 293.0, 455.0, 3150.0
347 liq = cp.liquid_heatup_properties(
"Ethanol", P, T0, Ts, Tc)
348 ref = quad(
lambda T: CP.PropsSI(
"C",
"T", T,
"P", P,
"Ethanol") / (Tc - T), T0, Ts)[0]
349 assert liq[
"heatup_integral"] == pytest.approx(ref, rel=1e-4)
350 assert liq[
"rho_l"] == pytest.approx(CP.PropsSI(
"D",
"T", 0.5 * (T0 + Ts),
"P", P,
"Ethanol"), rel=1e-9)
352 assert liq[
"cp_l_effective"] > 1.25 * 2440.0
353 assert cp.liquid_heatup_properties(
"RP-1", P, T0, Ts, Tc)
is None
355 lox = cp.liquid_heatup_properties(
"LOX", P, 90.0, 138.0, Tc)
356 assert lox[
"T_liquid_limit"] < 138.0
and lox[
"heatup_integral"] > 0
361 kw = dict(**_GAS, L_chamber=L, u_drop0=u0, rr_q=q, blowing=
"none")
362 I = 2440.0 * math.log((_GAS[
"Tc"] - 293.0) / (_GAS[
"Tc"] - fuel[
"T_s"]))
363 a = cp.spray_vaporization_march([dict(name=
"O", mass_fraction=0.5, instant=
True), fuel], **kw)
364 b = cp.spray_vaporization_march([dict(name=
"O", mass_fraction=0.5, instant=
True),
365 dict(fuel, heatup_integral=I)], **kw)
366 assert a[
"F_throat"] == pytest.approx(b[
"F_throat"], rel=1e-12)
368 got = cp.spray_vaporization_march([dict(name=
"O", mass_fraction=1 - 1e-9, instant=
True), fuel2],
369 **kw)[
"frac_vaporized"][
"F"]
370 assert got == pytest.approx(ref, abs=3e-4)