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))
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).
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_*).
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
63 q_conv, q_rad = np.maximum(p[
"q_conv"], 0.0), np.maximum(p[
"q_rad"], 0.0)
66 q_net = np.maximum(f * q_conv + q_rad, 0.0)
67 m_pyro = float(np.sum(q_net / E * dA))
69 if abs(f_new - f) < 1e-12:
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))
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] = {
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),
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"])),
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)
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,
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)