STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_hot_gas_wall_flux.py
Go to the documentation of this file.
1"""The chamber wall's gas-side load: Bartz convection, H2O/CO2 gas radiation, the real bore.
2
3Findings TH-3, TH-4, TH-5, TH-10 and TH-11 of the 2026-09-25 overnight review. The old model
4took its bore, k, Pr, recovery factor, emissivity and turbulence from the DISABLED regen block,
5radiated the gas as a blackbody at the wall's emissivity 0.85, tripled the convection with an
6unsourced turbulence factor, pinned the surface at a 1200 K "limit" and then subtracted the
7wall's own emission a second time toward a 600 K sink.
8
9Expected numbers are computed here by hand: Bartz (Huzel & Huang eq. 4-13), the exact
10isentropic area-Mach relation, and CEA frozen transport for LOX/ethanol at O/F 1.50, 430 psia
11(rocketcea get_Chamber_Transport, frozen: cp 0.534 BTU/lbm R = 2236 J/(kg K), mu 1.041 mP,
12Pr 0.664; these move < 1 % over O/F 1.45-1.55 and 400-450 psia).
13"""
14import copy
15import math
16import os
17import sys
18
19import pytest
20from scipy.optimize import brentq
21
22sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
23
24from engine.core.runner import PintleEngineRunner # noqa: E402
25from engine.pipeline.io import load_config # noqa: E402
26
27ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
28CFG = os.path.join(ROOT, "configs", "ethalox_6500N.yaml")
29PSI = 6894.757293168361
30P_TANK = 584.27 * PSI
31SIGMA = 5.670374419e-8
32CEA_MU, CEA_CP, CEA_PR = 1.041e-4, 2236.0, 0.664
33
34
35@pytest.fixture(autouse=True)
36def _python_physics(monkeypatch):
37 # The numba kernel mirrors the Python physics after the fact; this checks the Python.
38 monkeypatch.setenv("ED_ACCEL", "off")
39
40
41def _evaluate(mutate=None, silent=True):
42 cfg = load_config(CFG)
43 if mutate is not None:
44 mutate(cfg)
45 return PintleEngineRunner(copy.deepcopy(cfg)).evaluate(P_TANK, P_TANK, silent=silent)
46
47
48def _abl(res):
49 return res["diagnostics"]["cooling"]["ablative"]
50
51
52def _mach_sub(ar, g):
53 f = lambda M: (1 / M) * ((2 / (g + 1)) * (1 + (g - 1) / 2 * M * M)) ** ((g + 1) / (2 * (g - 1))) - ar
54 return brentq(f, 1e-9, 1 - 1e-12)
55
56
57def _hand_bartz(res, Tw, area_ratio):
58 """H&H eq. 4-13 with the stagnation state of the solve; r_c the mean of the drawn contour's
59 1.5 Rt / 0.382 Rt throat radii."""
60 d = res["diagnostics"]
61 cfg = load_config(CFG)
62 At = cfg.chamber_geometry.A_throat
63 Dt = math.sqrt(4 * At / math.pi)
64 rc = 0.5 * (1.5 + 0.382) * Dt / 2
65 g, T0 = d["gamma"], d["Tc_ideal"]
66 M = _mach_sub(area_ratio, g)
67 k = 1 + (g - 1) / 2 * M * M
68 sigma = 1 / ((0.5 * Tw / T0 * k + 0.5) ** 0.68 * k ** 0.12)
69 h = (0.026 / Dt ** 0.2 * (CEA_MU ** 0.2 * CEA_CP / CEA_PR ** 0.6) * (d["mdot_total"] / At) ** 0.8
70 * (Dt / rc) ** 0.1 * (1 / area_ratio) ** 0.9 * sigma)
71 r = CEA_PR ** (1 / 3)
72 Taw = T0 * (1 + r * (k - 1)) / k
73 return h, Taw
74
75
77 res = _evaluate()
78 a = _abl(res)
79 cfg = load_config(CFG)
80 CR = (math.pi / 4 * cfg.chamber_geometry.chamber_diameter ** 2) / cfg.chamber_geometry.A_throat
81 h, Taw = _hand_bartz(res, a["surface_temperature"], CR)
82 assert a["h_gas"] == pytest.approx(h, rel=0.05)
83 # recovery at the barrel's Mach 0.07 is essentially total: Taw >= 0.999 T0 (old: 0.94 T0)
84 assert a["adiabatic_wall_temperature"] >= 0.999 * res["diagnostics"]["Tc_ideal"]
85 assert a["heat_flux_from_gas_convective"] == pytest.approx(h * (Taw - a["surface_temperature"]), rel=0.05)
86
87
89 base = _abl(_evaluate())["heat_removed"]
90
91 def bore(value):
92 def m(cfg):
93 cfg.regen_cooling.chamber_inner_diameter = value
94 cfg.regen_cooling.radiation_emissivity_hot = 0.3
95 cfg.regen_cooling.hot_gas_thermal_conductivity = 0.9
96 cfg.regen_cooling.gas_turbulence_intensity = 0.4
97 return m
98
99 for mutate in (bore(0.05), bore(0.127), bore(None)):
100 assert _abl(_evaluate(mutate))["heat_removed"] == pytest.approx(base, rel=1e-9)
101
102 def no_regen(cfg):
103 cfg.regen_cooling = None
104 assert _abl(_evaluate(no_regen))["heat_removed"] == pytest.approx(base, rel=1e-9)
105
106
108 base = _abl(_evaluate())
109
110 def m(cfg):
111 cfg.ablative_cooling.turbulence_sensitivity = 0.0
112 assert _abl(_evaluate(m))["heat_removed"] == pytest.approx(base["heat_removed"], rel=1e-9)
113 assert "turbulence_multiplier" not in base or base["turbulence_multiplier"] == 1.0
114
115
117 """H2O 45 % / CO2 14 % at ~29 bar over an 8-12 cm beam: Leckner eps_g 0.15-0.30, i.e.
118 0.8-2 MW/m^2 onto a 1200 K wall -- not 0.85 sigma Tc^4 = 5.1 MW/m^2."""
119 from engine.pipeline.thermal.gas_side import HotGasState, profile, wall_contour
120 gas = HotGasState(T0=3225.0, P0=2.965e6, gamma=1.1375, mass_flux_throat=1835.0, mu=CEA_MU,
121 cp=CEA_CP, Pr=CEA_PR, x_H2O=0.453, x_CO2=0.141)
122 At = 1.5337e-3
123 c = wall_contour(At, 0.127, At)
124 p = profile(gas, c, 1200.0, 0.85)
125 q_barrel = p["q_rad"][int(len(p["x"]) * 0.2)]
126 assert 0.8e6 < q_barrel < 2.0e6
127 # and in the solve, on the liner at its own surface temperature
128 a = _abl(_evaluate())
129 assert 0.12 < a["gas_emissivity"] < 0.35
130 assert 0.4e6 < a["heat_flux_from_gas_radiative"] < 1.5e6
131
132
134 from engine.pipeline.thermal.gas_side import HotGasState, profile, wall_contour
135 gas = HotGasState(T0=3225.0, P0=2.965e6, gamma=1.1375, mass_flux_throat=1835.0, mu=CEA_MU,
136 cp=CEA_CP, Pr=CEA_PR, x_H2O=0.453, x_CO2=0.141)
137 At = 1.5337e-3
138 q = []
139 for D in (0.10, 0.20):
140 c = wall_contour(At, D, 1.2 * D * math.pi / 4 * D * D) # same L/D
141 p = profile(gas, c, 1200.0, 0.85)
142 i = int(len(p["x"]) * 0.2) # a barrel station
143 q.append(p["q_rad"][i])
144 assert q[1] > q[0]
145
146
148 """Zero-pressure H2O emissivity at 1000 K and 1 bar cm is 0.034 (Hottel/Leckner)."""
149 from engine.pipeline.thermal.gas_side import _LECKNER_H2O, _leckner_eps0
150 assert 0.03 < float(_leckner_eps0(_LECKNER_H2O, 1.0, 1.0)) < 0.04
151
152
154 """The hot face sits at the material's ablation temperature (a config property), and the
155 net flux is exactly blown convection plus gas radiation: no second wall-emission term."""
156 a = _abl(_evaluate())
157 cfg = load_config(CFG)
158 assert a["surface_temperature"] == cfg.ablative_cooling.ablation_surface_temperature
159 f = a["blowing_reduction"]
160 q = f * a["heat_flux_from_gas_convective"] + a["heat_flux_from_gas_radiative"]
161 assert a["effective_heat_flux"] == pytest.approx(q, rel=1e-12)
162 E = cfg.ablative_cooling.heat_of_ablation + cfg.ablative_cooling.specific_heat * (
163 a["surface_temperature"] - cfg.ablative_cooling.ambient_temperature)
164 assert a["recession_rate"] == pytest.approx(q / E / cfg.ablative_cooling.material_density, rel=1e-9)
165
166 def hotter(cfg):
167 cfg.ablative_cooling.ablation_surface_temperature = 2300.0
168 assert _abl(_evaluate(hotter))["recession_rate"] < a["recession_rate"]
169
170 def sink(cfg):
171 cfg.ablative_cooling.radiative_sink_fallback_temperature = 1500.0
172 assert _abl(_evaluate(sink))["recession_rate"] == pytest.approx(a["recession_rate"], rel=1e-12)
173
174
176 res = _evaluate(silent=False)
177 a = _abl(res)
178 x, qc = a["segment_x"], a["segment_q_conv"]
179 it = a["throat_index"]
180 cfg = load_config(CFG)
181 Tw = a["profile_wall_temperature"]
182 h_t, Taw_t = _hand_bartz(res, Tw, 1.0 + 1e-9)
183 assert qc[it] == pytest.approx(h_t * (Taw_t - Tw), rel=0.05)
184 # flat along the barrel: no Gaussian bump, no cone drawn from the face
185 barrel = [q for xi, q in zip(x, qc) if xi < -0.06]
186 assert max(barrel) / min(barrel) < 1.02
187 # falls monotonically down the nozzle (the old Newton missed a factor 2 and zig-zagged)
188 nozzle = [q for xi, q in zip(x, qc) if xi > 1e-6]
189 assert all(b < a_ for a_, b in zip(nozzle, nozzle[1:]))
190 # the supersonic station at A/A* = 2 against Bartz with the exact Mach number
191 from engine.pipeline.thermal.gas_side import mach_from_area_ratio
192 assert mach_from_area_ratio(1.511, 1.1375, True) == pytest.approx(1.754, abs=1e-3)
193
194
196 """With regen on, radiation_emissivity_hot is the WALL's emissivity: the gas term is Leckner's,
197 1-2 MW/m^2 here, not 0.85 sigma Tc^4 (~5-6 MW/m^2)."""
198 def regen(eps_wall):
199 def m(cfg):
200 cfg.regen_cooling.enabled = True
201 cfg.regen_cooling.chamber_inner_diameter = cfg.chamber_geometry.chamber_diameter
202 cfg.regen_cooling.radiation_emissivity_hot = eps_wall
203 return m
204
205 q85 = _evaluate(regen(0.85))["diagnostics"]["cooling"]["regen"]["heat_flux_radiative"]
206 assert 0.5e6 < q85 < 2.0e6
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
static void sink(void *ctx, uint8_t type, const void *data, uint8_t len)
Definition flight_e2e.c:246
_evaluate(mutate=None, silent=True)
_hand_bartz(res, Tw, area_ratio)