STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
combustion_eff.py
Go to the documentation of this file.
1"""c* efficiency: vaporization and mixing (combustion_physics) and heat lost to the wall."""
2
3import numpy as np
4import logging
5from typing import Optional, Dict, Any, Tuple
6from .config_schemas import CombustionEfficiencyConfig
7from .constants import (
8 DEFAULT_CHAMBER_PRESS_PA,
9 DEFAULT_CHAMBER_TEMP_K,
10 DEFAULT_CSTAR_IDEAL_M_S,
11 DEFAULT_GAMMA_ND,
12 DEFAULT_GAS_CONST_J_KG_K,
13 DEFAULT_MIXTURE_RATIO_ND,
14)
15
16
18 V_chamber: float,
19 A_throat: float,
20 Lstar_override: Optional[float] = None
21) -> float:
22 """
23 Calculate characteristic length L*.
24
25 L* = V_chamber / A_throat
26
27 Parameters:
28 -----------
29 V_chamber : float
30 Chamber volume [m³]
31 A_throat : float
32 Throat area [m²]
33 Lstar_override : float, optional
34 Override value if provided in config
35
36 Returns:
37 --------
38 Lstar : float [m]
39 """
40 if Lstar_override is not None:
41 return float(Lstar_override)
42
43 if A_throat <= 0:
44 raise ValueError("A_throat must be positive")
45
46 Lstar = V_chamber / A_throat
47 return float(Lstar)
48
49
50# Heat that returns to the propellant is not a c* loss. Regenerative heat goes back to the
51# injector with the coolant (Huzel & Huang ch. 4); film coolant, and the enthalpy it picks up, stay
52# in the chamber flow. Every other source -- the ablative liner, a heat sink -- leaves for good.
53_HEAT_RETURNED_TO_PROPELLANT = ("regen", "film")
54
55
56def wall_heat_lost_W(cooling_results: Optional[Dict[str, Any]]) -> Tuple[float, Dict[str, float]]:
57 """Heat [W] that leaves the propellant through the chamber wall, and its split by source."""
58 per: Dict[str, float] = {}
59 for name, src in (cooling_results or {}).items():
60 if name in _HEAT_RETURNED_TO_PROPELLANT or not isinstance(src, dict):
61 continue
62 q = float(src.get("heat_removed", 0.0) or 0.0)
63 if q > 0.0:
64 per[name] = q
65 return float(sum(per.values())), per
66
67
69 Q_lost: float,
70 m_dot_total: float,
71 Tc: float,
72 gamma: float,
73 R: float,
74) -> float:
75 """c* retained when Q_lost [W] leaves the gas upstream of the throat.
76
77 c* = sqrt(gamma R T0) / Gamma(gamma) (Sutton & Biblarz eq. 3-32), so at fixed composition
78 c* ~ sqrt(T0). Removing Q lowers the stagnation enthalpy by Q/mdot and T0 by Q/(mdot cp):
79
80 eta_HL = sqrt(1 - Q/(mdot cp Tc)), cp = gamma R/(gamma - 1), Tc the adiabatic CEA value.
81
82 Against rocketcea with the propellant enthalpy lowered by Q/mdot (LOX/ethanol, 200-800 psia,
83 O/F 1.0-2.2, 188 kJ/kg) the loss is 0.94-1.34x CEA's; the excess near stoichiometric is heat
84 that recombination returns. The linear 1 - Q/(mdot cp T_eff) it replaces was 1.9-2.7x.
85 """
86 if Q_lost <= 0.0:
87 return 1.0
88 for name, v in (("m_dot_total", m_dot_total), ("Tc", Tc), ("R", R)):
89 if not (np.isfinite(v) and v > 0):
90 raise ValueError(f"heat_loss_cstar_efficiency: invalid {name}={v}")
91 if not (np.isfinite(gamma) and gamma > 1.0):
92 raise ValueError(f"heat_loss_cstar_efficiency: invalid gamma={gamma}")
93 x = Q_lost / (m_dot_total * gamma * R / (gamma - 1.0) * Tc)
94 # All of the stagnation enthalpy gone is the physical end of the scale.
95 return float(np.sqrt(max(1.0 - x, 0.0)))
96
97
99 Lstar: float,
100 config: CombustionEfficiencyConfig,
101 cooling_efficiency: float,
102 advanced_params: Dict[str, Any],
103 debug: bool = False,
104) -> float:
105 """c* efficiency, eta_vap * eta_mix * eta_HL.
106
107 eta_vap and eta_mix come from combustion_physics. eta_HL is the heat lost through the wall,
108 taken from the cooling results the chamber solver leaves in the spray diagnostics
109 (``spray_diagnostics["cooling"]``); ``cooling_efficiency`` is used only when the call carries
110 none. The breakdown, with its inputs and assumptions, is written back to
111 ``spray_diagnostics["cstar_efficiency"]`` so it reaches the reported diagnostics.
112
113 advanced_params: Pc, Tc (CEA, adiabatic), cstar_ideal, gamma, R, MR, Ac, At, m_dot_total,
114 u_fuel, u_lox, spray_diagnostics, fuel_props, and optionally ox_props; cea_cache (the design's
115 CEACache, whose propellant pair selects the wide c*(O/F) table) or cstar_fn for the mixing and
116 vaporized-gas c* ratios.
117 """
118 from .combustion_physics import calculate_combustion_efficiency_advanced
119
120 Pc = advanced_params.get("Pc", DEFAULT_CHAMBER_PRESS_PA)
121 Tc = advanced_params.get("Tc", DEFAULT_CHAMBER_TEMP_K)
122 cstar_ideal = advanced_params.get("cstar_ideal", DEFAULT_CSTAR_IDEAL_M_S)
123 gamma = advanced_params.get("gamma", DEFAULT_GAMMA_ND)
124 R = advanced_params.get("R", DEFAULT_GAS_CONST_J_KG_K)
125 MR = advanced_params.get("MR", DEFAULT_MIXTURE_RATIO_ND)
126 Ac = advanced_params.get("Ac", None)
127 At = advanced_params.get("At", None)
128 m_dot_total = advanced_params.get("m_dot_total", None)
129 spray_diagnostics = advanced_params.get("spray_diagnostics", None)
130 if Ac is None or m_dot_total is None:
131 raise ValueError("Ac and m_dot_total are required for combustion efficiency calculation")
132 if At is None:
133 raise ValueError("At is required for combustion efficiency calculation")
134
135 results = calculate_combustion_efficiency_advanced(
136 Lstar, Pc, Tc, cstar_ideal, gamma, R, MR, config, Ac, At, m_dot_total,
137 u_fuel=advanced_params.get("u_fuel", None),
138 u_lox=advanced_params.get("u_lox", None),
139 spray_diagnostics=spray_diagnostics,
140 fuel_props=advanced_params.get("fuel_props", None),
141 ox_props=advanced_params.get("ox_props", None),
142 debug=debug,
143 cea_cache=advanced_params.get("cea_cache", None),
144 cstar_fn=advanced_params.get("cstar_fn", None),
145 )
146
147 cooling_results = spray_diagnostics.get("cooling") if isinstance(spray_diagnostics, dict) else None
148 Q_lost, Q_by_source = wall_heat_lost_W(cooling_results)
149 if not config.use_cooling_coupling:
150 eta_HL = 1.0
151 elif isinstance(cooling_results, dict):
152 eta_HL = heat_loss_cstar_efficiency(Q_lost, float(m_dot_total), float(Tc), float(gamma), float(R))
153 else:
154 if not (np.isfinite(cooling_efficiency) and 0.0 <= cooling_efficiency <= 1.0):
155 raise ValueError(f"Invalid cooling_efficiency: {cooling_efficiency}. Must be in [0, 1].")
156 eta_HL = float(cooling_efficiency)
157 if isinstance(cooling_results, dict) and cooling_results.get("film", {}).get("enabled"):
158 results["assumptions"].append({
159 "name": "combustion.film_cooling_cstar_loss", "value": None, "unit": "",
160 "reason": "film coolant stays in the flow; its c* cost (a fuel-rich wall layer) is not modelled"})
161
162 eta = results["eta_total"] * eta_HL
163 # Not bounded by 1: c*_ideal is at the bulk O/F, and off the c* peak the stream tubes' spread
164 # or a vaporized gas nearer the peak can exceed it (combustion_physics).
165 if not (np.isfinite(eta) and eta > 0.0):
166 raise ValueError(
167 f"Invalid combustion efficiency {eta}: L*={Lstar:.4f} m, eta_vap={results['eta_vaporization']:.4f}, "
168 f"eta_mix={results['eta_mixing']:.4f}, eta_HL={eta_HL:.4f}."
169 )
170
171 if isinstance(spray_diagnostics, dict):
172 spray_diagnostics["cstar_efficiency"] = {
173 **results,
174 "eta_heat_loss": float(eta_HL),
175 "heat_lost_W": float(Q_lost),
176 "heat_lost_by_source_W": Q_by_source,
177 "eta_cstar": float(eta),
178 }
179 if debug:
180 logging.getLogger("evaluate").info(
181 f"[ETA] eta_c*={eta:.4f} = vap {results['eta_vaporization']:.4f} x mix "
182 f"{results['eta_mixing']:.4f} x HL {eta_HL:.4f} (Q_lost {Q_lost/1e3:.1f} kW)"
183 )
184 return float(eta)
185
186
188 Tc_ideal: float,
189 eta: float,
190 gamma: float
191) -> float:
192 """
193 Calculate actual chamber temperature accounting for combustion efficiency.
194
195 T_c,actual = T_c,ideal × [η / (1 - (1-η) × (γ-1)/γ)]
196
197 Parameters:
198 -----------
199 Tc_ideal : float
200 Ideal chamber temperature from CEA [K]
201 eta : float
202 Combustion efficiency
203 gamma : float
204 Specific heat ratio
205
206 Returns:
207 --------
208 Tc_actual : float [K]
209 """
210 if gamma <= 1:
211 return Tc_ideal
212
213 denominator = 1.0 - (1.0 - eta) * (gamma - 1.0) / gamma
214 if denominator <= 0:
215 return Tc_ideal
216
217 Tc_actual = Tc_ideal * (eta / denominator)
218 return float(Tc_actual)
219
220
222 Lstar: float,
223 gamma_ideal: float,
224 alpha: float = 0.1
225) -> float:
226 """
227 Calculate frozen flow correction factor for gamma.
228
229 γ_actual = γ_ideal × [1 - α × (1 - η_c*)]
230
231 This accounts for incomplete chemical reactions in the nozzle.
232
233 Parameters:
234 -----------
235 Lstar : float
236 Characteristic length [m]
237 gamma_ideal : float
238 Ideal gamma from CEA
239 alpha : float
240 Frozen flow parameter (default 0.1)
241
242 Returns:
243 --------
244 correction_factor : float
245 Factor to multiply gamma_ideal by
246 """
247 # Validate inputs
248 if Lstar <= 0:
249 raise ValueError(f"Invalid Lstar: {Lstar}. Must be positive.")
250 if gamma_ideal <= 1.0:
251 raise ValueError(f"Invalid gamma_ideal: {gamma_ideal}. Must be > 1.0 for physical gas.")
252 if alpha < 0 or alpha > 1:
253 raise ValueError(f"Invalid alpha: {alpha}. Must be in [0, 1].")
254
255 # Estimate efficiency from L* (simplified)
256 # Using default C=0.3, K=0.15
257 eta_est = 1.0 - 0.3 * np.exp(-0.15 * Lstar)
258
259 correction = 1.0 - alpha * (1.0 - eta_est)
260
261 # Validate correction factor - no clipping, raise error if invalid
262 if not np.isfinite(correction):
263 raise ValueError(f"Invalid frozen flow correction: {correction}. Check Lstar={Lstar}, alpha={alpha}.")
264 if correction < 0.5 or correction > 1.1:
265 raise ValueError(
266 f"Frozen flow correction out of reasonable range: {correction:.4f}. "
267 f"Expected [0.5, 1.1] for typical rocket engines. "
268 f"Lstar={Lstar:.4f} m, gamma_ideal={gamma_ideal:.4f}, alpha={alpha:.4f}. "
269 f"This suggests a fundamental issue with the model or inputs."
270 )
271
272 return float(correction)
#define max(a, b)
Definition Arduino.h:43
static board_sleep_info_t info
Definition board_sleep.c:14
float calculate_Lstar(float V_chamber, float A_throat, Optional[float] Lstar_override=None)
float calculate_frozen_flow_correction(float Lstar, float gamma_ideal, float alpha=0.1)
float calculate_actual_chamber_temp(float Tc_ideal, float eta, float gamma)
float eta_cstar(float Lstar, CombustionEfficiencyConfig config, float cooling_efficiency, Dict[str, Any] advanced_params, bool debug=False)
float heat_loss_cstar_efficiency(float Q_lost, float m_dot_total, float Tc, float gamma, float R)
Tuple[float, Dict[str, float]] wall_heat_lost_W(Optional[Dict[str, Any]] cooling_results)