STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_cstar_efficiency_physics.py
Go to the documentation of this file.
1"""The c* efficiency chain against references outside the code: CEA, CoolProp, textbook algebra and an
2independent ODE integration of the spray model.
3
4 eta_c* = eta_vap * eta_mix * eta_HL (engine/pipeline/combustion_physics.py, combustion_eff.py)
5
6Reference numbers were generated once and are pinned here, because CI has neither rocketcea nor the
7time to rebuild them:
8 * CEA: rocketcea 1.2.3, LOX / Ethanol cards, the ethanol card's h,cal lowered by dh (1+O/F) M_EtOH
9 so the mixture loses dh = 188 kJ/kg (or Q/mdot at the design point).
10 * CoolProp 7.2.0 saturation states.
11"""
12from __future__ import annotations
13
14import copy
15import math
16import re
17from pathlib import Path
18
19import numpy as np
20import pytest
21
22from engine.pipeline import combustion_physics as cp
23from engine.pipeline.combustion_eff import eta_cstar, heat_loss_cstar_efficiency, wall_heat_lost_W
24from engine.pipeline.config_schemas import CombustionEfficiencyConfig
25
26ROOT = Path(__file__).resolve().parents[1]
27PSI = 6894.757293168
28R_U = 8314.462618
29
30# (Pc psia, O/F, Tc K, M kg/kmol, gamma, c*(h - 188 kJ/kg) / c*(h)) -- rocketcea, see module doc.
31CEA_HEAT_LOSS = [
32 (200.0, 1.0, 2350.93, 18.4072, 1.22426, 0.982815),
33 (200.0, 1.3, 2964.33, 20.8488, 1.15791, 0.988492),
34 (200.0, 1.5, 3153.81, 22.0964, 1.13183, 0.991723),
35 (200.0, 1.8, 3244.53, 23.4859, 1.12024, 0.993283),
36 (200.0, 2.2, 3244.40, 24.8609, 1.11787, 0.993158),
37 (430.0, 1.0, 2354.56, 18.4134, 1.22718, 0.982799),
38 (430.0, 1.3, 3004.18, 20.9271, 1.16603, 0.988021),
39 (430.0, 1.5, 3225.45, 22.2483, 1.13757, 0.991223),
40 (430.0, 1.8, 3336.15, 23.6965, 1.12375, 0.993102),
41 (430.0, 2.2, 3337.74, 25.0891, 1.12107, 0.993014),
42 (800.0, 1.0, 2356.70, 18.4171, 1.22894, 0.982792),
43 (800.0, 1.3, 3031.56, 20.9812, 1.17232, 0.987711),
44 (800.0, 1.5, 3280.31, 22.3655, 1.14253, 0.990795),
45 (800.0, 1.8, 3410.87, 23.8707, 1.12661, 0.992943),
46 (800.0, 2.2, 3414.46, 25.2795, 1.12363, 0.992894),
47]
48# 6500 N design point: 431.75 psia, O/F 1.5013, mdot 2.8152 kg/s; CEA c* ratio at Q = 529.3 kW.
49DESIGN = dict(Pc=2976827.09, MR=1.5013044, mdot=2.8151867, Tc=3226.756, M=22.256595, gamma=1.137481)
50DESIGN_Q_W, DESIGN_CEA_RATIO = 529.3e3, 0.991236
51
52
53def _old_linear_factor(Q, mdot, Tc, gamma, R):
54 """The formula the chamber solver used: 1 - Q/(mdot cp T_eff), T_eff = Tc - Q/(mdot cp)."""
55 cp_ = gamma * R / (gamma - 1.0)
56 T_eff = Tc - Q / (mdot * cp_)
57 return 1.0 - Q / (mdot * cp_ * T_eff)
58
59
60# --------------------------------------------------------------------------- heat loss (TH-1, CN-2, CE-3)
62 d = DESIGN
63 R = R_U / d["M"]
64 eta = heat_loss_cstar_efficiency(DESIGN_Q_W, d["mdot"], d["Tc"], d["gamma"], R)
65 loss, loss_cea = 1.0 - eta, 1.0 - DESIGN_CEA_RATIO
66 assert abs(loss / loss_cea - 1.0) < 0.10, (eta, DESIGN_CEA_RATIO)
67
68
69@pytest.mark.parametrize("Pc_psia,MR,Tc,M,gamma,cea_ratio", CEA_HEAT_LOSS)
70def test_heat_loss_factor_tracks_cea_across_the_envelope(Pc_psia, MR, Tc, M, gamma, cea_ratio):
71 """sqrt(1 - x) is 0.94-1.34x CEA's loss over the ethalox envelope; the linear form was 1.9-2.7x."""
72 R = R_U / M
73 mdot = 1.0
74 Q = 188.0e3 * mdot
75 ratio = (1.0 - heat_loss_cstar_efficiency(Q, mdot, Tc, gamma, R)) / (1.0 - cea_ratio)
76 assert 0.90 < ratio < 1.40, ratio
77
78
80 """Smooth peaked c*(O/F) (1725 exp(-0.3 ln(MR/1.5)^2) m/s): the heat-loss tests need only a c*
81 source for the mixing and vaporized-gas ratios, which cancel between the hot and cold calls."""
82 MR = np.geomspace(0.1, 20.0, 400)
83 Pcs = np.geomspace(1e5, 1.2e7, 4)
84 c = 1725.0 * np.exp(-0.3 * np.log(MR / 1.5) ** 2)
85 return cp.CstarOfMR(Pc, wide=cp.CstarWideTable("X", "Y", MR, Pcs, np.repeat(c[:, None], 4, axis=1)))
86
87
88def _advanced_params(Q_by_source, cooling_eff):
89 """A minimal eta_cstar call around the design point with a synthetic cooling result."""
90 d = DESIGN
91 R = R_U / d["M"]
92 diag = {
93 "D32_O": 44.2e-6, "D32_F": 85.4e-6, "u_O": 32.8, "u_F": 37.5, "u_axial_spray": 24.8,
94 "rho_O_momentum": 1140.0, "rho_F_momentum": 789.0,
95 "v_O_bulk": 32.8, "v_F_bulk": 37.5, "d_jet_O": 1.548e-3, "d_jet_F": 1.421e-3,
96 "cooling": {name: {"heat_removed": q} for name, q in Q_by_source.items()},
97 }
98 return {
99 "Pc": d["Pc"], "Tc": d["Tc"], "cstar_ideal": 1725.3, "gamma": d["gamma"], "R": R,
100 "MR": d["MR"], "Ac": math.pi * 0.0635 ** 2, "At": 1.5337e-3, "m_dot_total": d["mdot"],
101 "chamber_length": 0.131,
102 "u_fuel": 37.5, "u_lox": 32.8, "spray_diagnostics": diag, "cstar_fn": _peaked_cstar(d["Pc"]),
103 "fuel_props": {"boiling_point": 351.4, "latent_heat": 838e3, "molecular_weight": 46.07,
104 "specific_heat": 2440.0, "temperature": 293.0, "critical_temperature": 514.71,
105 "density": 789.0},
106 }
107
108
110 """End to end: the factor eta_cstar applies is CEA's c* ratio, not the caller's linear factor."""
111 d = DESIGN
112 R = R_U / d["M"]
114 old = _old_linear_factor(DESIGN_Q_W, d["mdot"], d["Tc"], d["gamma"], R)
115 eta_hot = eta_cstar(1.0, cfg, old, _advanced_params({"ablative": DESIGN_Q_W}, old))
116 eta_cold = eta_cstar(1.0, cfg, 1.0, _advanced_params({}, 1.0))
117 assert abs((1.0 - eta_hot / eta_cold) / (1.0 - DESIGN_CEA_RATIO) - 1.0) < 0.10
118
119
121 """Regenerative heat rides back to the injector with the coolant; film coolant stays in the flow."""
122 Q, per = wall_heat_lost_W({"regen": {"heat_removed": 4e5}, "film": {"heat_removed": 1e5},
123 "ablative": {"heat_removed": 2e5}, "metadata": {"x": 1}})
124 assert Q == pytest.approx(2e5) and per == {"ablative": 2e5}
125
126
127# --------------------------------------------------------------------------- vaporization (CE-1, CE-8)
128@pytest.mark.parametrize("P,Tb,Lb,M,Tcrit,T_ref,h_ref", [
129 (1.0e6, 351.4, 838e3, 46.07, 514.71, 423.845, 686.61e3), # CoolProp ethanol
130 (2.9768e6, 351.4, 838e3, 46.07, 514.71, 473.085, 506.02e3),
131 (5.0e6, 351.4, 838e3, 46.07, 514.71, 501.504, 332.76e3),
132 (1.0e6, 90.2, 213e3, 32.0, 154.6, 119.621, 174.34e3), # CoolProp oxygen
133 (2.9768e6, 90.2, 213e3, 32.0, 154.6, 141.514, 119.89e3),
134])
135def test_drops_evaporate_at_their_saturation_state_at_pc(P, Tb, Lb, M, Tcrit, T_ref, h_ref):
136 """Clausius-Clapeyron + Watson against CoolProp: T within 1.5 %, h_fg within 10 %."""
137 T, h = cp.saturation_state(P, Tb, Lb, M, Tcrit)
138 assert abs(T / T_ref - 1.0) < 0.015
139 assert abs(h / h_ref - 1.0) < 0.10
140
141
143 T, h = cp.saturation_state(8.0e6, 90.2, 213e3, 32.0, 154.6)
144 assert (T, h) == (154.6, 0.0)
145
146
148 """Heating a drop and then gasifying it are successive stages; the lifetime is their sum
149 (Law 1982). They were combined as parallel rates, making the lifetime shorter than either."""
150 _, dg = cp.calculate_gasification_efficiency(
151 Tc=3227.0, Pc=2.99e6, tau_res=1.36e-3, SMD=85.7e-6, rho_l=789.0, cp_l=2440.0, L_eff=838e3,
152 T_inj=293.0, cp_g=3090.0, rho_g=2.47, mu_g=7e-5, U_slip=50.0, fuel_props={"T_star_fuel_cap_K": 473.0})
153 assert dg["tau_vap"] >= max(dg["tau_heat"], dg["tau_gasify"])
154 assert dg["tau_vap"] == pytest.approx(dg["tau_heat"] + dg["tau_gasify"], rel=1e-12)
155
156
157def _design_streams(Pc, k=1.0, MR=1.47):
158 TsO, hO = cp.saturation_state(Pc, 90.2, 213e3, 32.0, 154.6)
159 TsF, hF = cp.saturation_state(Pc, 351.4, 838e3, 46.07, 514.71)
160 wO, wF = MR / (1 + MR), 1 / (1 + MR)
161 return [dict(name="O", mass_fraction=wO, D32=44e-6 * k, rho_l=1140.0, cp_l=2300.0, T0=90.0, T_s=TsO, h_fg=hO),
162 dict(name="F", mass_fraction=wF, D32=85e-6 * k, rho_l=789.0, cp_l=2440.0, T0=293.0, T_s=TsF, h_fg=hF)]
163
164
165_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)
166_AT = 1.5337e-3
167
168
169def _eta_vap(Lstar, k=1.0):
170 return cp.spray_vaporization_march(_design_streams(_GAS["Pc"], k), **_GAS,
171 L_chamber=Lstar * _AT / _GAS["Ac"], u_drop0=24.0,
172 rr_q=3.0)["F_throat"]
173
174
176 Ls = [0.5, 0.75, 1.0, 1.5]
177 e = [_eta_vap(L) for L in Ls]
178 assert all(b > a for a, b in zip(e, e[1:])), e
179 assert e[0] < 0.995 # a short chamber leaves fuel unvaporized
180 sizes = [_eta_vap(1.0, k) for k in (1.0, 1.5, 2.0)]
181 assert all(b < a for a, b in zip(sizes, sizes[1:])), sizes
182
183
185 """With the marched stream a trace in an already-burned flow, U_g = U_c and each size class is an
186 isolated drop. Integrate the documented equations with scipy for fine classes and compare."""
187 from scipy.integrate import solve_ivp
188 from engine.pipeline.thermal.regen_cooling import calculate_gas_viscosity_huzel
189
190 g = _GAS
191 eps = 1e-9
192 Pc, Tc, gam, R = g["Pc"], g["Tc"], g["gamma"], g["R"]
193 TsF, hF = cp.saturation_state(Pc, 351.4, 838e3, 46.07, 514.71)
194 fuel = dict(name="F", mass_fraction=eps, D32=85e-6, rho_l=789.0, cp_l=2440.0, T0=293.0, T_s=TsF, h_fg=hF)
195 L, u0, q = 0.05, 24.0, 3.0
196 got = cp.spray_vaporization_march([dict(name="O", mass_fraction=1.0 - eps, instant=True), fuel],
197 **g, L_chamber=L, u_drop0=u0, rr_q=q,
198 blowing="none")["frac_vaporized"]["F"]
199
200 cpg = gam * R / (gam - 1.0)
201 rho_c = Pc / (R * Tc)
202 U = g["m_dot_total"] / (rho_c * g["Ac"])
203 Tf = TsF + (Tc - TsF) / 3.0
204 mu = calculate_gas_viscosity_huzel(Tf, R_U / R)
205 Pr = 0.8
206 k = mu * cpg / Pr
207 rho_f = Pc / (R * Tf)
208 lnB = math.log(1.0 + cpg * (Tc - TsF) / hF)
209 X = 85e-6 * math.gamma(1.0 - 1.0 / q)
210 xs, ws = np.polynomial.legendre.leggauss(96)
211 V, w = 0.5 * (xs + 1.0), 0.5 * ws
212 remaining = 0.0
213 for Vk, wk in zip(V, w):
214 D0 = X * (-math.log1p(-Vk)) ** (1.0 / q)
215 Nu0 = 2.0 + 0.6 * math.sqrt(rho_f * u0 * D0 / mu) * Pr ** (1 / 3)
216 t_h = 789.0 * 2440.0 * D0 ** 2 / (6 * Nu0 * k) * math.log((Tc - 293.0) / (Tc - TsF))
217
218 def rhs(t, s):
219 x, v, D2 = s
220 D = math.sqrt(max(D2, 0.0))
221 dU = abs(U - v)
222 Re_d = rho_c * dU * D / mu
223 Nu = 2.0 + 0.6 * math.sqrt(rho_f * dU * D / mu) * Pr ** (1 / 3)
224 K = 4.0 * Nu * k * lnB / (789.0 * cpg) if t > t_h else 0.0
225 phi = 1.0 + Re_d ** (2 / 3) / 6.0 if Re_d <= 1000 else 0.424 * Re_d / 24.0
226 dv = (U - v) * 18.0 * mu * phi / (789.0 * max(D2, 1e-30))
227 return [v, dv, -K if D2 > 0 else 0.0]
228
229 hit_wall = lambda t, s: s[0] - L
230 hit_wall.terminal = True
231 gone = lambda t, s: s[2]
232 gone.terminal = True
233 sol = solve_ivp(rhs, (0.0, 1.0), [0.0, u0, D0 ** 2], events=(hit_wall, gone),
234 rtol=1e-9, atol=[1e-12, 1e-9, 1e-18], max_step=2e-5)
235 remaining += wk * (max(sol.y[2, -1], 0.0) / D0 ** 2) ** 1.5
236 assert got == pytest.approx(1.0 - remaining, abs=3e-4), (got, 1.0 - remaining)
237
238
240 """A stream without properties is taken as vaporized at the face and says so; without the
241 injector's u_axial_spray the drops start with the pair's axial momentum per unit mass."""
242 d = DESIGN
243 diag = {"D32_O": 44e-6, "D32_F": 85e-6, "impingement_angle_deg": 89.0}
244 fuel = {"boiling_point": 351.4, "latent_heat": 838e3, "molecular_weight": 46.07, "density": 789.0,
245 "specific_heat": 2440.0, "temperature": 293.0, "critical_temperature": 514.71}
246 notes = []
247 eta, out = cp.calculate_vaporization_efficiency(
248 Pc=d["Pc"], Tc=d["Tc"], gamma=d["gamma"], R=R_U / d["M"], MR=1.5, m_dot_total=2.8,
249 Ac=0.012668, At=1.5337e-3, Lstar=1.0, spray_diagnostics=diag, fuel_props=fuel, ox_props=None,
250 u_fuel=37.5, u_lox=32.8, rr_q=3.0, assumptions=notes)
251 names = {a["name"] for a in notes}
252 assert "combustion.vaporization.oxidizer" in names
253 assert out["frac_vaporized_O"] == 1.0 and 0.0 < out["frac_vaporized_F"] < 1.0
254 assert eta == pytest.approx(0.6 + 0.4 * out["frac_vaporized_F"], rel=1e-12)
255 assert out["u_drop0"] == pytest.approx((0.6 * 32.8 + 0.4 * 37.5) * math.cos(math.radians(44.5)), rel=1e-12)
256
257
258# --------------------------------------------------------------------------- mixing (CE-4, CE-5)
260 M = cp.rupe_mixing_parameter(1140.0, 32.815, 1.54797e-3, 789.0, 37.457, 1.42140e-3)
261 assert M == pytest.approx(1140.0 * 32.815 ** 2 * 1.54797e-3 / (789.0 * 37.457 ** 2 * 1.42140e-3), rel=1e-12)
262
263
265 """E_m is best at M = rupe_M_opt and falls off symmetrically in N_R = M/(1+M) (JPL TR 32-1546
266 Fig. 1); M comes from the jet state and the impingement angles do not enter."""
268 at_opt = cp.rupe_Em_at_M(1.0, cfg.rupe_Em_opt, cfg.rupe_M_opt, cfg.rupe_Em_curvature)
269 assert at_opt == pytest.approx(cfg.rupe_Em_opt, rel=1e-15)
270 lo = cp.rupe_Em_at_M(0.5, cfg.rupe_Em_opt, 1.0, cfg.rupe_Em_curvature)
271 hi = cp.rupe_Em_at_M(2.0, cfg.rupe_Em_opt, 1.0, cfg.rupe_Em_curvature)
272 assert lo == pytest.approx(hi, rel=1e-12) and lo < at_opt
273 base = {"rho_O_momentum": 1140.0, "v_O_bulk": 30.0, "d_jet_O": 1.5e-3,
274 "rho_F_momentum": 789.0, "v_F_bulk": 30.0 * math.sqrt(1140.0 / 789.0), "d_jet_F": 1.5e-3}
275 assert cp.rupe_M_from_diagnostics(base) == pytest.approx(1.0, rel=1e-12)
276 assert cp.rupe_M_from_diagnostics({**base, "impingement_angle_deg": 120.0}) == pytest.approx(1.0, rel=1e-12)
277 assert cp.rupe_M_from_diagnostics({**base, "rupe_M": 1.3}) == 1.3
278
279
281 """mdot_O u_O sin(th_O) = mdot_F u_F sin(th_F) => R = (d_F/d_O) sqrt(sin th_F / sin th_O)."""
282 R = cp.rupe_R_opt_from_angles(43.0, 46.0, d_O=1.548e-3, d_F=1.4214e-3)
283 assert R == pytest.approx(0.943, abs=1e-3)
284
285
286# --------------------------------------------------------------------------- kinetics (CE-7)
288 """Products relax in ~1 us against a ~1 ms stay time. With the vaporization term pinned (model
289 constant) and a Pc-independent c* curve, eta must not move with Pc and Tc at all, and must move
290 smoothly with O/F (only through the c* curve) across the old 1.5 plateau edge."""
291 cfg = CombustionEfficiencyConfig(model="constant", C=0.0)
292 diag = {"rupe_M": 1.0, "D32_O": 44e-6, "D32_F": 85e-6}
293
294 def eta(MR, Pc, Tc):
295 return cp.calculate_combustion_efficiency_advanced(
296 1.0, Pc, Tc, 1700.0, 1.14, 373.0, MR, cfg, 0.0127, 0.00153, 2.8,
297 u_fuel=37.5, u_lox=32.8, spray_diagnostics=diag, fuel_props={},
298 cstar_fn=_peaked_cstar(Pc))["eta_total"]
299
300 assert eta(1.5, 2.9e6, 3200.0) == pytest.approx(eta(1.5, 1.2e6, 2400.0), rel=1e-12)
301 e = [eta(m, 2.9e6, 3200.0) for m in (1.46, 1.48, 1.50, 1.52, 1.54)]
302 steps = np.diff(e)
303 assert np.max(np.abs(np.diff(steps))) < 1e-3
304 assert not hasattr(cp, "_ea_norm_from_mr")
305
306
307# --------------------------------------------------------------------------- the design, end to end
308def _evaluate(cfg):
309 from engine.core.runner import PintleEngineRunner
310 run = PintleEngineRunner(copy.deepcopy(cfg))
311 P = cfg.lox_tank.initial_pressure_psi * PSI
312 return run.evaluate(P, cfg.fuel_tank.initial_pressure_psi * PSI, silent=True)
313
314
315@pytest.fixture(scope="module")
317 from engine.pipeline.io import load_config
318 return load_config(str(ROOT / "configs" / "ethalox_6500N.yaml"))
319
320
321def test_coarser_spray_costs_cstar(ethalox, monkeypatch):
322 """CE-1: vaporization was saturated at 1.0, so atomization never reached c*. Doubling the Ingebo
323 prefactor doubles D32 and must cost at least a point of eta_c*."""
324 monkeypatch.setenv("ED_ACCEL", "off")
325 base = _evaluate(ethalox)["diagnostics"]["eta_cstar"]
326 coarse = copy.deepcopy(ethalox)
327 coarse.spray.smd.smd_scale *= 2.0
328 assert base - _evaluate(coarse)["diagnostics"]["eta_cstar"] > 0.01
329
330
331def _with_lstar(cfg, L):
332 cfg = copy.deepcopy(cfg)
333 cg = cfg.chamber_geometry
334 dV = (L - cg.Lstar) * cg.A_throat
335 Ac = math.pi * (cg.chamber_diameter / 2.0) ** 2
336 cg.volume, cg.Lstar = L * cg.A_throat, L
337 cg.length_cylindrical += dV / Ac
338 cg.length += dV / Ac
339 return cfg
340
341
343 """CE-2: eta_c* must rise from a short chamber, with an interior optimum -- not fall from the
344 minimum L* as the kinetics surrogate and the 2x heat-loss term made it."""
345 monkeypatch.setenv("ED_ACCEL", "off")
346 out = {L: _evaluate(_with_lstar(ethalox, L))["diagnostics"] for L in (0.5, 1.0, 1.5)}
347 assert out[1.0]["eta_cstar"] > out[0.5]["eta_cstar"] + 0.005
348 vap = [out[L]["cstar_efficiency"]["eta_vaporization"] for L in (0.5, 1.0, 1.5)]
349 assert vap[0] < vap[1] < vap[2] and vap[0] < 0.995, vap
350
351
353 """The breakdown is reported with the answer, with the Rupe E_m that set the mixing term."""
354 monkeypatch.setenv("ED_ACCEL", "off")
355 d = _evaluate(ethalox)["diagnostics"]
356 b = d["cstar_efficiency"]
357 assert b["eta_cstar"] == pytest.approx(d["eta_cstar"], rel=1e-12)
358 assert b["eta_cstar"] == pytest.approx(b["eta_vaporization"] * b["eta_mixing"] * b["eta_heat_loss"], rel=1e-12)
359 assert b["rupe_Em_opt"] == ethalox.combustion.efficiency.rupe_Em_opt and b["rupe_M"] > 0
360 assert 0.0 < b["rupe_Em"] <= b["rupe_Em_opt"]
361 # The fuel evaporates at its saturation temperature at Pc, not at a configured cap.
362 T_sat, _ = cp.saturation_state(d["Pc"], 351.4, 838e3, 46.07, None)
363 assert b["T_surface_F"] == pytest.approx(T_sat, rel=1e-9)
364
365
366@pytest.mark.xfail(strict=True, reason=(
367 "SP-8089 / Sutton put well-designed unlike doublets at eta_c* 0.90-0.97. The stream-tube "
368 "integral of a cold-flow Rupe E_m of 0.80 with no gas-phase mixing downstream, plus this "
369 "injector's element-to-element O/F striation, lands the design at ~0.885: below the band. Either "
370 "the element's cold-flow E_m is better than the 0.75-0.85 literature band (measure it), or "
371 "gas-phase mixing recovers several points of what the unmixed stream tubes charge. Strict: "
372 "when the model or the design moves back into the band, this XPASS fails and must be revisited."))
374 monkeypatch.setenv("ED_ACCEL", "off")
375 d = _evaluate(ethalox)["diagnostics"]
376 assert 0.90 <= d["eta_cstar"] <= 0.97
377
378
379# --------------------------------------------------------------------------- dead keys (CE-12)
381 """A key nothing reads must say so, or the Parameters workspace offers a knob that does nothing."""
382 sources = []
383 for base in ("engine", "backend"):
384 for p in (ROOT / base).rglob("*.py"):
385 if p.name != "config_schemas.py" and "archive" not in p.parts:
386 sources.append(p.read_text(errors="ignore"))
387 blob = "\n".join(sources)
388 silent = []
389 for name, fi in CombustionEfficiencyConfig.model_fields.items():
390 if (fi.description or "").startswith("[DEPRECATED"):
391 continue
392 if not re.search(r"""(\.|["'])%s\b""" % re.escape(name), blob):
393 silent.append(name)
394 assert not silent, f"unread and unmarked: {silent}"
#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_lstar_buys_vaporization_until_heat_loss_wins(ethalox, monkeypatch)
test_design_cstar_efficiency_is_in_the_unlike_doublet_band(ethalox, monkeypatch)
test_design_cstar_breakdown_is_reported(ethalox, monkeypatch)
test_heat_loss_factor_tracks_cea_across_the_envelope(Pc_psia, MR, Tc, M, gamma, cea_ratio)
test_drops_evaporate_at_their_saturation_state_at_pc(P, Tb, Lb, M, Tcrit, T_ref, h_ref)