31def carbon_oxidation(T_s: float, P_static: float, composition: Dict[str, float], MW_mix: float,
32 g0: float, cfg: GraphiteInsertConfig) -> Dict[str, object]:
33 """Carbon mass flux [kg/(m^2 s)] off a graphite surface at T_s and the heat it absorbs.
35 Each oxidiser attacks at 1/(1/m_kin + 1/m_diff). Diffusion limit (unit Lewis number,
36 film theory): m_diff = g * MW_C nu X / MW_mix with g the mass-transfer conductance h/cp,
37 reduced by blowing g = g0 ln(1+B)/B, B = m_C/g0. Kinetics (Bradley et al. 1984, via
38 Thakre & Yang, J. Propulsion Power 24(4), 2008): A T^b exp(-E/RT) p^n, p in atm;
39 O2 and O are diffusion-limited.
41 rates = {
"H2O": cfg.oxidation_H2O,
"CO2": cfg.oxidation_CO2,
"OH": cfg.oxidation_OH}
43 for sp
in CARBON_OXIDISERS:
44 x =
max(float(composition.get(sp, 0.0)), 0.0)
45 if sp
in rates
and x > 0:
47 p_atm = x * P_static / ATM
48 m_kin[sp] = r.A * T_s ** r.T_exponent * np.exp(-r.E / (R_GAS * T_s)) * p_atm ** r.n
52 species: Dict[str, float] = {}
54 B = m_C / g0
if g0 > 0
else 0.0
55 g = g0 * (np.log1p(B) / B
if B > 1e-12
else 1.0)
57 for sp, (nu, _dH)
in CARBON_OXIDISERS.items():
58 x =
max(float(composition.get(sp, 0.0)), 0.0)
59 m_diff = g * MW_C * nu * x / MW_mix
61 species[sp] = 0.0
if m_diff <= 0
or mk <= 0
else 1.0 / (1.0 / mk + 1.0 / m_diff)
62 m_new = sum(species.values())
63 if abs(m_new - m_C) <= 1e-12 + 1e-10 * m_new:
67 B = m_C / g0
if g0 > 0
else 0.0
68 q_chem = sum(species[sp] * CARBON_OXIDISERS[sp][1]
for sp
in species)
69 return {
"mass_flux": float(m_C),
"species": species,
"q_chem": float(q_chem),
"B": float(B),
70 "blowing_factor": float(np.log1p(B) / B)
if B > 1e-12
else 1.0}
74 cfg: GraphiteInsertConfig) -> Dict[str, object]:
75 """Gas-side balance at the throat for a graphite surface at T_s: Bartz convection (blown),
76 gas radiation, and the heat the surface chemistry absorbs. q_net goes into the insert."""
79 eps_w = cfg.emissivity
if cfg.emissivity
is not None else 0.8
80 st = station_flux(gas, contour, 0.0, T_s, eps_w)
82 ox =
carbon_oxidation(T_s, st[
"P_static"], composition, composition[
"MW"], g0, cfg)
83 q_conv = ox[
"blowing_factor"] * st[
"q_conv"]
84 q_net = q_conv + st[
"q_rad"] - ox[
"q_chem"]
85 return {
"q_conv": q_conv,
"q_conv_unblown": st[
"q_conv"],
"q_rad": st[
"q_rad"],
86 "q_chem": ox[
"q_chem"],
"q_net": q_net,
"mass_flux": ox[
"mass_flux"],
87 "species": ox[
"species"],
"B": ox[
"B"],
"h": st[
"h"],
"Taw": st[
"Taw"],
88 "P_static": st[
"P_static"]}
92 chamber_pressure: float,
93 chamber_velocity: float,
94 throat_velocity: float,
95 chamber_heat_flux: float,
99 Calculate throat recession multiplier based on local flow conditions using a heuristic scaling.
101 Throat recession is typically 1.2-2.5x higher than chamber due to:
102 1. Higher velocity → Higher convective heat transfer
103 2. Sonic conditions → Maximum heat flux
104 3. Pressure gradient → Enhanced mass transfer
105 4. Turbulence amplification near throat
107 Heuristic scaling for heat flux ratio:
108 q_throat / q_chamber ∝ (V_throat / V_chamber)^0.8 × (P_throat / P_chamber)^0.2
110 WARNING: This is not a formal Bartz correlation. Real throat heat transfer depends on
111 geometry (D_t, curvature), viscosity/Pr, and boundary layer state. If
112 heat_transfer_coefficient at the throat is already available from a CFD or
113 boundary-layer code, this heuristic multiplier should not be used.
117 chamber_pressure : float
118 Chamber pressure [Pa]
119 chamber_velocity : float
120 Chamber gas velocity [m/s]
121 throat_velocity : float
122 Throat gas velocity (sonic) [m/s]
123 chamber_heat_flux : float
124 Chamber wall heat flux [W/m²] (used for validation, not in calculation)
131 Throat recession multiplier (typically 1.2-2.5)
133 if chamber_velocity <= 0
or throat_velocity <= 0:
137 velocity_ratio = throat_velocity / chamber_velocity
138 velocity_factor = velocity_ratio ** 0.8
142 pressure_ratio = (2.0 / (gamma + 1.0)) ** (gamma / (gamma - 1.0))
143 pressure_factor = pressure_ratio ** 0.2
146 heat_flux_ratio = velocity_factor * pressure_factor
150 turbulence_enhancement = 1.1
152 multiplier = heat_flux_ratio * turbulence_enhancement
155 multiplier = float(np.clip(multiplier, 1.2, 2.5))
161 net_heat_flux: float,
162 throat_temperature: float,
163 gas_temperature: float,
164 graphite_config: GraphiteInsertConfig,
167 gas_density: Optional[float] =
None,
168 gas_viscosity: Optional[float] =
None,
169 oxygen_mass_fraction: Optional[float] =
None,
170 characteristic_length: Optional[float] =
None,
171 gas_velocity: Optional[float] =
None,
172 heat_transfer_coefficient: Optional[float] =
None,
173 backside_temperature: Optional[float] =
None,
174 effective_thickness: Optional[float] =
None,
177 throat_composition: Optional[Dict[str, float]] =
None,
178) -> Dict[str, float]:
179 """Carbon recession of the graphite throat at the surface temperature ``throat_temperature``.
181 The surface temperature is an INPUT: it comes from the insert's transient conduction
182 (engine.pipeline.thermal.wall_conduction), not from a guess. Chemistry is
183 graphite_surface_state: H2O, CO2, OH, O2 and O from the CEA throat composition, each at
184 the lesser of its kinetic and diffusion-limited rate. ``gas_state`` is a
185 gas_side.HotGasState, ``contour`` a gas_side.WallContour and ``throat_composition`` the
186 CEA throat mole fractions with the throat 'MW'. The older arguments (net_heat_flux,
187 gas_density, oxygen_mass_fraction, ...) are accepted and unused. With
188 ``simplified_graphite_oxidation`` the configured constant rate is returned instead.
190 if not graphite_config.enabled
or throat_area <= 0:
193 "recession_rate": 0.0,
195 "surface_temperature": throat_temperature,
197 "oxidation_rate": 0.0,
198 "oxidation_mass_flux": 0.0,
199 "thermal_mass_flux": 0.0,
200 "feedback_fraction": 0.0,
209 sizing_only_mode = getattr(graphite_config,
"sizing_only_mode",
False)
214 simplified_mode = getattr(graphite_config,
"simplified_graphite_oxidation",
False)
217 _simp_rate = float(getattr(graphite_config,
"simplified_oxidation_rate", 1.0e-5))
218 m_dot_ox_simple = _simp_rate * graphite_config.material_density
222 recession_rate_report = 0.0
if sizing_only_mode
else _simp_rate
225 "recession_rate": float(recession_rate_report),
226 "recession_rate_calculated": _simp_rate,
227 "mass_flux": float(0.0
if sizing_only_mode
else m_dot_ox_simple),
228 "mass_flux_calculated": float(m_dot_ox_simple),
229 "surface_temperature": float(throat_temperature),
230 "effective_heat_flux": float(net_heat_flux),
231 "radiative_relief": 0.0,
232 "conduction_loss": 0.0,
234 "oxidation_rate": 1e-5,
235 "oxidation_mass_flux": float(m_dot_ox_simple),
236 "thermal_mass_flux": 0.0,
237 "recession_rate_thermal": 0.0,
238 "mass_flux_thermal": 0.0,
239 "coverage_area": float(throat_area * graphite_config.coverage_fraction),
240 "feedback_fraction": 0.0,
244 "q_convective": float(net_heat_flux),
245 "damkohler_number": 0.0,
246 "blowing_parameter": 0.0,
247 "sizing_only_mode": sizing_only_mode,
248 "simplified_mode":
True,
251 if gas_state
is None or contour
is None or throat_composition
is None:
253 "compute_graphite_recession needs gas_state, contour and throat_composition (the CEA "
254 "throat mole fractions); set 'simplified_graphite_oxidation: true' for a constant rate")
257 rate = st[
"mass_flux"] / graphite_config.material_density
260 "recession_rate": 0.0
if sizing_only_mode
else float(rate),
261 "recession_rate_calculated": float(rate),
262 "mass_flux": 0.0
if sizing_only_mode
else float(st[
"mass_flux"]),
263 "surface_temperature": float(throat_temperature),
264 "oxidation_rate": float(rate),
265 "oxidation_mass_flux": float(st[
"mass_flux"]),
266 "recession_rate_thermal": 0.0,
267 "thermal_mass_flux": 0.0,
268 "q_convective": float(st[
"q_conv"]),
269 "q_radiation": float(st[
"q_rad"]),
270 "q_chemical": float(st[
"q_chem"]),
271 "q_net": float(st[
"q_net"]),
272 "blowing_parameter": float(st[
"B"]),
273 "species_mass_flux": st[
"species"],
274 "heat_transfer_coefficient": float(st[
"h"]),
275 "coverage_area": float(throat_area * graphite_config.coverage_fraction),
276 "sizing_only_mode": sizing_only_mode,
277 "simplified_mode":
False,
Dict[str, float] compute_graphite_recession(float net_heat_flux, float throat_temperature, float gas_temperature, GraphiteInsertConfig graphite_config, float throat_area, float pressure, Optional[float] gas_density=None, Optional[float] gas_viscosity=None, Optional[float] oxygen_mass_fraction=None, Optional[float] characteristic_length=None, Optional[float] gas_velocity=None, Optional[float] heat_transfer_coefficient=None, Optional[float] backside_temperature=None, Optional[float] effective_thickness=None, gas_state=None, contour=None, Optional[Dict[str, float]] throat_composition=None)
Dict[str, object] carbon_oxidation(float T_s, float P_static, Dict[str, float] composition, float MW_mix, float g0, GraphiteInsertConfig cfg)