STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_spray_mixing_physics.py
Go to the documentation of this file.
1"""Mixing, vaporization and drop-size physics against references outside the code.
2
3 eta_mix = sum_i w_i c*(O/F_i) / c*(O/F) stream tubes (Pieper, Dean & Valentine, JSR 4(6) 1967)
4 E_m = 1 - sum_{r<R} w (R-r)/R - sum_{r>R} w (r-R)/(1-R) Rupe (JPL TR 32-1546 eq. 1)
5 eta_vap = F c*(O/F_vap) / c*(O/F) Priem & Heidmann (NASA TR R-67)
6
7References: rocketcea (LOX / Ethanol cards, the ones the CEA cache is built from), CoolProp, JPL TR
832-1546 Fig. 1 digitized, Abramzon & Sirignano (1989), Yuen & Chen (1976), and an independent scipy
9integration of the drop equations. Numbers that need rocketcea are pinned for CI.
10"""
11from __future__ import annotations
12
13import math
14
15import numpy as np
16import pytest
17
18from engine.pipeline import combustion_physics as cp
19from engine.pipeline.config_schemas import CombustionEfficiencyConfig
20
21PSI = 6894.757293168361
22R_U = 8314.462618
23
24
26 try:
27 from rocketcea.cea_obj import CEA_Obj
28 except Exception:
29 return None
30 return CEA_Obj(oxName="LOX", fuelName="Ethanol")
31
32
33def _synthetic_cstar(Pc=2.2e6):
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)
40
41
43 tab = cp.get_cstar_wide_table("LOX", "Ethanol")
44 if tab is None:
45 pytest.skip("no wide c* table for LOX/Ethanol (output/cache/cstar_wide_LOX_Ethanol.npz)")
46 return tab
47
48
49# --------------------------------------------------------------------------- Rupe E_m
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])
54 R = 0.6
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)
57
58
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)])
62 fn = _synthetic_cstar()
63 out = cp.stream_tube_mixing_efficiency(R / (1 - R), fn, [(1.0, R / (1 - R), Em)], dist)
64 # Gaussian tubes past r = 0 or 1 (beyond ~4 sigma, weight < 1e-4) are clipped to pure propellant.
65 assert out["Em_total"] == pytest.approx(Em, abs=1e-4 if dist == "gaussian" else 1e-12)
66
67
69 """E_m 0.7 about r = 0.4 puts the normal's lean tail 2.2 sigma from pure fuel."""
70 fn = _synthetic_cstar()
71 out = cp.stream_tube_mixing_efficiency(0.4 / 0.6, fn, [(1.0, 0.4 / 0.6, 0.7)])
72 assert 0.01 < out["mass_clipped_to_pure_propellant"] < 0.03
73 assert out["Em_total"] == pytest.approx(0.7, abs=5e-3)
74
75
76# Rupe's mean line, JPL TR 32-1546 Fig. 1 (orifice area ratios 0.26-1.0), digitized: (N_R, E_m %).
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),
79 (0.78, 48.6)]
80
81
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)."""
86 M0 = 0.525 / 0.475
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
91 # Symmetric in N_R, not in ln M: M = 1/3 (N 0.25) and M = 3 (N 0.75) fall off alike.
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)
93
94
95# --------------------------------------------------------------------------- stream-tube c* integral
96# Direct CEA (rocketcea, LOX/Ethanol, 316.4 psia): scipy quad of c* over a normal in r, and the two
97# tubes at R +- MAD. (MR, E_m) -> (gaussian, two-tube).
98CEA_MIX = {(1.277, 0.85): (0.95140, 0.96101), (1.5, 0.85): (0.95227, 0.96213)}
99
100
101@pytest.mark.parametrize("key", list(CEA_MIX))
103 wide = _wide_or_skip()
104 MR, Em = key
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)
109
110
112 fn = _synthetic_cstar()
113 MR = 1.3
114 assert cp.stream_tube_mixing_efficiency(MR, fn, [(1.0, MR, 1.0)])["eta_mix"] == pytest.approx(1.0, abs=1e-14)
115 # Two perfectly mixed elements at O/F 1.0 and 1.6 whose masses average to the bulk r:
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
122
123
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(
128 0.95, 2.18e6, 3150.0, 1650.0, 1.14, 370.0, 1.277, cfg or CombustionEfficiencyConfig(),
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},
138 cstar_fn=cstar_fn or _synthetic_cstar())
139
140
142 r = _advanced()
143 Em = cp.rupe_Em_at_M(0.83, 0.80, 1.0, 6.07)
144 fn = _synthetic_cstar()
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 # the other shape's spread, reported
149
150
152 a = _advanced(cfg=CombustionEfficiencyConfig(Em_peak=0.6, mixing_sigma=0.2))
153 b = _advanced(cfg=CombustionEfficiencyConfig(Em_peak=0.99, mixing_sigma=5.0))
154 assert a["eta_mixing"] == b["eta_mixing"]
155
156
158 base = _advanced()
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"]
164 # Each element's own M moves as (MR_j/MR)^2: same orifices, v ~ mdot.
165 fn = _synthetic_cstar()
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)
170
171
173 """A CEA cache spanning O/F 1.0-2.5 used to clamp: every lean tail tube got c*(1.0)."""
174 class Cache:
175 MR_min, MR_max = 1.0, 2.5
176 config = None
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)
184 r = _advanced(cstar_fn=fn)
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
187
188
190 C = _rocketcea()
191 if C is None:
192 pytest.skip("rocketcea not installed")
193 pc = 316.4
194 cs = lambda mr: C.get_Cstar(pc, mr) * 0.3048
195 for mr in (0.3, 0.5, 6.0, 10.0):
196 r = mr / (1 + mr)
197 edge = 1.0 if mr < 1 else 2.5
198 re = edge / (1 + edge)
199 law = cs(edge) * math.sqrt(r / re if mr < 1 else (1 - r) / (1 - re))
200 assert abs(law / cs(mr) - 1) < 0.2
201 assert abs(law - cs(mr)) < abs(cs(edge) - cs(mr))
202
203
205 wide = _wide_or_skip()
206 C = _rocketcea()
207 if C is None:
208 pytest.skip("rocketcea not installed")
209 fn = cp.CstarOfMR(2.0e6, wide=wide)
210 for mr in (0.3, 1.0, 1.4, 2.0, 5.0):
211 assert fn(mr) == pytest.approx(C.get_Cstar(2.0e6 / PSI, mr) * 0.3048, rel=4e-3)
212
213
214# --------------------------------------------------------------------------- vaporization
215def _vap(diag_extra=None, cstar_fn=None, **kw):
216 notes = []
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
229
230
232 eta0, out0, _ = _vap()
233 eta1, out1, _ = _vap({"L_imp": 5.75e-3, "L_sheet_breakup": 2.4e-3})
234 assert out1["x_drop_formation"] == pytest.approx(8.15e-3)
235 assert out1["L_march"] == pytest.approx(out0["L_chamber_equiv"] - 8.15e-3)
236 assert out1["fraction_vaporized"] < out0["fraction_vaporized"]
237
238
240 fn = _synthetic_cstar()
241 eta, out, _ = _vap(cstar_fn=fn)
242 F, fO, fF = out["fraction_vaporized"], out["frac_vaporized_O"], out["frac_vaporized_F"]
243 assert fO > fF # the fuel is the slow stream
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)
247 assert eta > F # below the c* peak the lean gas gains
248 # Past the peak the same shift costs c*: move the peak below the injected O/F.
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"]
254
255
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"
263
264
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)
266
267
268def _single_drop_reference(blowing: bool, I_heat=None):
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
272 from engine.pipeline.thermal.regen_cooling import calculate_gas_viscosity_huzel
273 g = _GAS
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)
281 Pr = 0.8
282 k = mu * cpg / Pr
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)
291 remaining = 0.0
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
296
297 def rhs(t, s):
298 x, v, D2 = s
299 D = math.sqrt(max(D2, 0.0))
300 dU = abs(U - v)
301 Re_d = rho_c * dU * D / mu
302 ev = t > t_h
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
306 if ev:
307 phi *= cd
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]
310
311 hit_wall = lambda t, s: s[0] - L
312 hit_wall.terminal = True
313 gone = lambda t, s: s[2]
314 gone.terminal = True
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,
319 T_s=TsF, h_fg=hF)
320 if I_heat is not None:
321 fuel["heatup_integral"] = I_heat
322 return 1.0 - remaining, fuel, L, u0, q
323
324
325@pytest.mark.parametrize("blowing", [True, False])
327 ref, fuel, L, u0, q = _single_drop_reference(blowing)
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)
332
333
335 _, fuel, L, u0, q = _single_drop_reference(False)
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
341
342
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)
351 # Ethanol's cp climbs from 2.4 to ~4 kJ/(kg K) on the way to T_sat: the configured 2440 is 30 % low.
352 assert liq["cp_l_effective"] > 1.25 * 2440.0
353 assert cp.liquid_heatup_properties("RP-1", P, T0, Ts, Tc) is None
354 # LOX: CoolProp's T_sat at 2.18 MPa (~134.6 K) sits below Clausius-Clapeyron's; integrated to it.
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
357
358
360 _, fuel, L, u0, q = _single_drop_reference(False)
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)
367 ref, fuel2, *_ = _single_drop_reference(False, I_heat=1.4 * I)
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)
371
372
374 pytest.importorskip("CoolProp")
375 fuel = {"name": "Ethanol", "density": 789.0, "boiling_point": 351.4, "latent_heat": 838e3,
376 "molecular_weight": 46.07, "specific_heat": 2440.0, "temperature": 293.0,
377 "critical_temperature": 514.71}
378 _, out, notes = _vap(fuel_props=fuel)
379 assert out["rho_l_F"] < 760.0 and out["cp_l_F"] > 3000.0 and "CoolProp" in out["liquid_props_F"]
380 _, out2, notes2 = _vap()
381 assert out2["rho_l_F"] == 789.0
382 assert "combustion.vaporization.fuel.liquid_properties" in {a["name"] for a in notes2}
383 fuel_rp = dict(fuel, name="RP-1")
384 _, _, notes3 = _vap(fuel_props=fuel_rp)
385 assert "combustion.vaporization.fuel.liquid_properties" in {a["name"] for a in notes3}
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
*brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer *sa const json_pointer & rhs
Definition json.hpp:13930
test_march_with_blowing_matches_an_independent_integration(blowing)
_single_drop_reference(bool blowing, I_heat=None)
test_the_stream_tubes_reproduce_the_Em_they_were_built_from(dist, R, Em)
_vap(diag_extra=None, cstar_fn=None, **kw)
_advanced(diag_extra=None, cfg=None, cstar_fn=None, fuel_props=None, ox_props=None)