STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
ablative_cooling.py
Go to the documentation of this file.
1"""Ablative liner: quasi-steady surface ablation along the real contour.
2
3The char surface sits at the material's ablation temperature while it recedes (the
4reinforcement's melt/decomposition bound; ``ablation_surface_temperature``). The gas-side
5load at that temperature comes from engine.pipeline.thermal.gas_side (Bartz convection,
6H2O/CO2 gas radiation), station by station. The mass consumed follows the quasi-steady
7(Landau) balance
8
9 q_net = m'' * [H_abl + cp * (T_s - T_init)]
10
11the heat of ablation plus the sensible heat to bring virgin liner from its initial
12temperature to the surface. Pyrolysis gas thickens the boundary layer: convection is
13reduced by f(B) = 1/(1 + c B), B the pyrolysis flow over the chamber flow, or by the
14legacy constant (1 - blowing_efficiency) when physics blowing is off.
15
16The hot face exchanges radiation with the gas only; the gas term is net of the wall's own
17emission, so nothing more is subtracted (an internal liner does not see the room).
18"""
19
20from __future__ import annotations
21
22from typing import Dict, Optional
23
24import numpy as np
25
26from engine.pipeline.config_schemas import AblativeCoolingConfig
27from engine.pipeline.constants import EPSILON_SMALL
28
29
30def ablation_energy_per_mass(cfg: AblativeCoolingConfig, surface_temperature: float) -> float:
31 """J per kg of liner consumed at a surface held at ``surface_temperature``."""
32 return cfg.heat_of_ablation + cfg.specific_heat * max(surface_temperature - cfg.ambient_temperature, 0.0)
33
34
35def blowing_reduction(cfg: AblativeCoolingConfig, pyrolysis_mdot: float, gas_mdot: Optional[float]) -> float:
36 """Fraction of the convective flux left after pyrolysis-gas blowing."""
37 if cfg.use_physics_based_blowing:
38 if gas_mdot is None or gas_mdot <= 0:
39 raise ValueError("physics-based blowing needs the chamber mass flow")
40 B = pyrolysis_mdot / gas_mdot
41 return max(1.0 / (1.0 + cfg.blowing_coefficient * B), cfg.blowing_min_reduction_factor)
42 return 1.0 - float(np.clip(cfg.blowing_efficiency, 0.0, 1.0))
43
44
45def liner_response(gas, contour, cfg: AblativeCoolingConfig, x_liner_end: float,
46 gas_mdot: float, surface_temperature: Optional[float] = None,
47 with_profile: bool = False) -> Dict[str, object]:
48 """Quasi-steady ablation of the liner from the face to ``x_liner_end`` (throat frame).
49
50 ``gas`` is a gas_side.HotGasState and ``contour`` a gas_side.WallContour. Returns the
51 barrel-station numbers under the old keys (recession_rate, heat_flux_from_gas_*), the
52 peak along the liner, the integrated heat to the liner, and with ``with_profile`` the
53 whole-contour display arrays (segment_*).
54 """
55 from engine.pipeline.thermal.gas_side import profile
56
57 Ts = float(cfg.ablation_surface_temperature if surface_temperature is None else surface_temperature)
58 lined = (contour.x >= contour.x_face - 1e-12) & (contour.x <= x_liner_end + 1e-12)
59 p = profile(gas, contour, Ts, cfg.surface_emissivity, mask=lined)
60 dA = contour.area_elements()[lined] * float(np.clip(cfg.coverage_fraction, 0.0, 1.0))
61 below_pyrolysis = Ts < cfg.pyrolysis_temperature
62 E = ablation_energy_per_mass(cfg, Ts)
63 q_conv, q_rad = np.maximum(p["q_conv"], 0.0), np.maximum(p["q_rad"], 0.0)
64 f = 1.0
65 for _ in range(50):
66 q_net = np.maximum(f * q_conv + q_rad, 0.0)
67 m_pyro = float(np.sum(q_net / E * dA))
68 f_new = blowing_reduction(cfg, m_pyro, gas_mdot)
69 if abs(f_new - f) < 1e-12:
70 f = f_new
71 break
72 f = f_new
73 q_net = np.maximum(f * q_conv + q_rad, 0.0) if not below_pyrolysis else np.zeros_like(q_conv)
74 mass_flux = q_net / max(E, EPSILON_SMALL)
75 rate = mass_flux / cfg.material_density
76 Q = float(np.sum(q_net * dA))
77 A = float(np.sum(dA))
78
79 # Barrel reference station: mid-barrel (the old single-flux number, and the rate the
80 # chamber diameter grows at).
81 x_b = 0.5 * (contour.x_face + contour.x_cone_start)
82 ib = int(np.argmin(np.abs(p["x"] - x_b)))
83 ipk = int(np.argmax(rate))
84 out: Dict[str, object] = {
85 "enabled": True,
86 "surface_temperature": Ts,
87 "recession_rate": float(rate[ib]),
88 "mass_flux": float(mass_flux[ib]),
89 "effective_heat_flux": float(q_net[ib]),
90 "heat_flux_from_gas_convective": float(q_conv[ib]),
91 "heat_flux_from_gas_radiative": float(q_rad[ib]),
92 "h_gas": float(p["h"][ib]),
93 "adiabatic_wall_temperature": float(p["Taw"][ib]),
94 "gas_emissivity": float(p["eps_gas"][ib]),
95 "beam_length": float(p["beam_length"][ib]),
96 "recession_rate_peak": float(rate[ipk]),
97 "x_recession_peak": float(p["x"][ipk]),
98 "heat_flux_from_gas_convective_peak": float(q_conv[ipk]),
99 "heat_flux_from_gas_radiative_peak": float(q_rad[ipk]),
100 "recession_rate_mean": float(np.sum(rate * dA) / A) if A > 0 else 0.0,
101 "blowing_reduction": float(f),
102 "energy_per_mass": float(E),
103 "below_pyrolysis": bool(below_pyrolysis),
104 "pyrolysis_temperature": float(cfg.pyrolysis_temperature),
105 "cooling_power": Q,
106 "heat_removed": Q,
107 "coverage_area": A,
108 "mass_flow": float(np.sum(mass_flux * dA)),
109 "x_liner_end": float(x_liner_end),
110 "leckner_pressure_correction_clamped": bool(np.any(p["leckner_clamped"])),
111 }
112 if with_profile:
113 full = profile(gas, contour, Ts, cfg.surface_emissivity)
114 qc, qr = np.maximum(full["q_conv"], 0.0), np.maximum(full["q_rad"], 0.0)
115 out.update({
116 "segment_x": full["x"].tolist(),
117 "segment_r": full["r"].tolist(),
118 "segment_q_conv": qc.tolist(),
119 "segment_q_rad": qr.tolist(),
120 "segment_q_incident": (qc + qr).tolist(),
121 "segment_q_net": np.maximum(f * qc + qr, 0.0).tolist(),
122 "segment_h": full["h"].tolist(),
123 "segment_M": full["M"].tolist(),
124 "throat_index": int(np.argmin(np.abs(full["x"]))),
125 "profile_wall_temperature": Ts,
126 })
127 return out
#define max(a, b)
Definition Arduino.h:43
float blowing_reduction(AblativeCoolingConfig cfg, float pyrolysis_mdot, Optional[float] gas_mdot)
float ablation_energy_per_mass(AblativeCoolingConfig cfg, float surface_temperature)
Dict[str, object] liner_response(gas, contour, AblativeCoolingConfig cfg, float x_liner_end, float gas_mdot, Optional[float] surface_temperature=None, bool with_profile=False)