1"""The chamber wall's gas-side load: Bartz convection, H2O/CO2 gas radiation, the real bore.
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.
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).
20from scipy.optimize
import brentq
22sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
27ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
28CFG = os.path.join(ROOT,
"configs",
"ethalox_6500N.yaml")
29PSI = 6894.757293168361
32CEA_MU, CEA_CP, CEA_PR = 1.041e-4, 2236.0, 0.664
35@pytest.fixture(autouse=True)
38 monkeypatch.setenv(
"ED_ACCEL",
"off")
42 cfg = load_config(CFG)
43 if mutate
is not None:
49 return res[
"diagnostics"][
"cooling"][
"ablative"]
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)
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"]
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)
72 Taw = T0 * (1 + r * (k - 1)) / k
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)
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)
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
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)
103 cfg.regen_cooling =
None
104 assert _abl(
_evaluate(no_regen))[
"heat_removed"] == pytest.approx(base, rel=1e-9)
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
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."""
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)
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
129 assert 0.12 < a[
"gas_emissivity"] < 0.35
130 assert 0.4e6 < a[
"heat_flux_from_gas_radiative"] < 1.5e6
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)
139 for D
in (0.10, 0.20):
140 c = wall_contour(At, D, 1.2 * D * math.pi / 4 * D * D)
141 p = profile(gas, c, 1200.0, 0.85)
142 i = int(len(p[
"x"]) * 0.2)
143 q.append(p[
"q_rad"][i])
148 """Zero-pressure H2O emissivity at 1000 K and 1 bar cm is 0.034 (Hottel/Leckner)."""
150 assert 0.03 < float(_leckner_eps0(_LECKNER_H2O, 1.0, 1.0)) < 0.04
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."""
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)
167 cfg.ablative_cooling.ablation_surface_temperature = 2300.0
168 assert _abl(
_evaluate(hotter))[
"recession_rate"] < a[
"recession_rate"]
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)
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"]
183 assert qc[it] == pytest.approx(h_t * (Taw_t - Tw), rel=0.05)
185 barrel = [q
for xi, q
in zip(x, qc)
if xi < -0.06]
186 assert max(barrel) /
min(barrel) < 1.02
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:]))
192 assert mach_from_area_ratio(1.511, 1.1375,
True) == pytest.approx(1.754, abs=1e-3)
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)."""
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
205 q85 =
_evaluate(regen(0.85))[
"diagnostics"][
"cooling"][
"regen"][
"heat_flux_radiative"]
206 assert 0.5e6 < q85 < 2.0e6
static void sink(void *ctx, uint8_t type, const void *data, uint8_t len)
_python_physics(monkeypatch)
test_leckner_anchors_hottel()
test_displayed_profile_is_bartz_along_the_contour()
test_radiation_grows_with_bore()
test_regen_march_radiates_the_gas_not_a_blackbody()
test_gas_radiation_uses_a_gas_emissivity()
test_disabled_regen_block_does_not_reach_the_liner()
test_no_turbulence_multiplier()
_evaluate(mutate=None, silent=True)
test_surface_runs_at_the_ablation_temperature_without_a_sink()
test_barrel_convection_is_bartz_on_the_real_bore()
_hand_bartz(res, Tw, area_ratio)