12from typing
import Dict, List, Tuple, Optional, Any
61 residence_time: float,
62 reaction_time_scale: float,
63 initial_progress: float = 0.0,
66 Calculate reaction progress based on residence time and reaction kinetics.
68 Models chemical reaction progress as first-order kinetics:
69 dX/dt = (1 - X) / τ_chem
71 Solution: X(t) = 1 - (1 - X0) × exp(-t / τ)
75 residence_time : float
76 Time spent in chamber [s] (must be >= 0)
77 reaction_time_scale : float
78 Chemical reaction time scale [s] (must be > 0)
79 initial_progress : float
80 Initial reaction progress (0-1)
85 Reaction progress (0-1), where 1 = complete equilibrium
90 If inputs are invalid (negative time, non-positive time scale, etc.)
93 if not np.isfinite(residence_time):
94 raise ValueError(f
"Non-finite residence_time: {residence_time}")
95 if residence_time < 0:
96 raise ValueError(f
"Negative residence_time: {residence_time}")
98 if not np.isfinite(reaction_time_scale):
99 raise ValueError(f
"Non-finite reaction_time_scale: {reaction_time_scale}")
100 if reaction_time_scale <= 0:
101 raise ValueError(f
"Non-positive reaction_time_scale: {reaction_time_scale}")
103 if not np.isfinite(initial_progress):
104 raise ValueError(f
"Non-finite initial_progress: {initial_progress}")
105 if initial_progress < 0
or initial_progress > 1:
106 raise ValueError(f
"Initial_progress out of bounds [0,1]: {initial_progress}")
110 time_ratio = residence_time / reaction_time_scale
112 if not np.isfinite(time_ratio):
113 raise ValueError(f
"Non-finite time_ratio: {residence_time} / {reaction_time_scale}")
116 exp_term = np.exp(-time_ratio)
117 if not np.isfinite(exp_term):
123 progress = 1.0 - (1.0 - initial_progress) * exp_term
125 if not np.isfinite(progress):
126 raise ValueError(f
"Non-finite progress calculated: residence_time={residence_time}, reaction_time_scale={reaction_time_scale}")
130 if progress < 0
or progress > 1:
131 raise ValueError(f
"Progress outside [0,1]: {progress} (this indicates a physics error)")
133 return float(progress)
154 fuel_type: str =
"RP-1",
155 config: Optional[PintleEngineConfig] =
None,
158 Calculate chemical reaction rate constant using Arrhenius kinetics.
160 Rate constant: k = A × P^n × exp(-Ea / (R_gas × T))
161 Reaction time: τ = 1 / k
163 Uses actual Arrhenius parameters for hydrocarbon/LOX combustion:
164 - Pre-exponential factor A depends on fuel type and mixture ratio
165 - Pressure exponent n: typically 0.7-1.0 for gas-phase reactions
166 - Activation energy Ea: depends on fuel complexity
171 Chamber pressure [Pa]
173 Chamber temperature [K]
177 Fuel type ("RP-1", "Ethanol", "H2", etc.) - affects activation energy
178 config : PintleEngineConfig, optional
179 Engine configuration for kinetics parameters. If None, uses built-in defaults.
184 Reaction rate constant [1/s]
189 If inputs are invalid or result is non-physical
192 if not np.isfinite(Pc)
or Pc <= 0:
193 raise ValueError(f
"Invalid pressure: {Pc} Pa")
194 if not np.isfinite(Tc)
or Tc <= 0:
195 raise ValueError(f
"Invalid temperature: {Tc} K")
196 if not np.isfinite(MR)
or MR <= 0:
197 raise ValueError(f
"Invalid mixture ratio: {MR}")
200 if config
is not None:
201 eff_cfg = config.combustion.efficiency
203 if fuel_type.upper()
in [
"RP-1",
"KEROSENE",
"JP"]:
204 A0 = eff_cfg.A0_hydrocarbon
205 n_pre = eff_cfg.n_pre_hydrocarbon
206 Ea = eff_cfg.Ea_hydrocarbon
208 elif fuel_type.upper()
in [
"ETHANOL",
"C2H5OH"]:
209 A0 = eff_cfg.A0_ethanol
210 n_pre = eff_cfg.n_pre_ethanol
211 Ea = eff_cfg.Ea_ethanol
213 elif fuel_type.upper()
in [
"H2",
"HYDROGEN"]:
214 A0 = eff_cfg.A0_hydrogen
215 n_pre = eff_cfg.n_pre_hydrogen
216 Ea = eff_cfg.Ea_hydrogen
218 elif fuel_type.upper()
in [
"CH4",
"METHANE"]:
220 A0 = getattr(eff_cfg,
"A0_methane", eff_cfg.A0_hydrocarbon)
221 n_pre = getattr(eff_cfg,
"n_pre_methane", eff_cfg.n_pre_hydrocarbon)
222 Ea = getattr(eff_cfg,
"Ea_methane", eff_cfg.Ea_hydrocarbon)
226 A0 = eff_cfg.A0_hydrocarbon
227 n_pre = eff_cfg.n_pre_hydrocarbon
228 Ea = eff_cfg.Ea_hydrocarbon
232 if fuel_type.upper()
in [
"RP-1",
"KEROSENE",
"JP"]:
237 elif fuel_type.upper()
in [
"ETHANOL",
"C2H5OH"]:
242 elif fuel_type.upper()
in [
"H2",
"HYDROGEN"]:
247 elif fuel_type.upper()
in [
"CH4",
"METHANE"]:
269 pressure_prefactor = (Pc / P0_ref) ** n_pre
271 A = A0 * pressure_prefactor
273 if not np.isfinite(A)
or A <= 0:
274 raise ValueError(f
"Non-physical pre-exponential: A={A}, Pc={Pc}")
277 pressure_term = (Pc / P0_ref) ** n_pressure
279 if not np.isfinite(pressure_term)
or pressure_term <= 0:
280 raise ValueError(f
"Non-physical pressure term: (Pc/P0)^n={pressure_term}, Pc={Pc}")
283 arrhenius_exponent = -Ea / (R_gas * Tc)
285 if not np.isfinite(arrhenius_exponent):
286 raise ValueError(f
"Non-finite Arrhenius exponent: Ea={Ea}, R_gas={R_gas}, Tc={Tc}")
290 arrhenius_exponent = np.clip(arrhenius_exponent, -50.0, 50.0)
292 arrhenius_term = np.exp(arrhenius_exponent)
294 if not np.isfinite(arrhenius_term)
or arrhenius_term <= 0:
295 raise ValueError(f
"Non-physical Arrhenius term: exp({arrhenius_exponent}) = {arrhenius_term}")
298 k = A * pressure_term * arrhenius_term
300 if not np.isfinite(k)
or k <= 0:
301 raise ValueError(f
"Non-physical rate constant: k={k}")
349 droplet_diameter: float,
350 fuel_props: Optional[Dict] =
None,
352 R_gas: float = 300.0,
355 Calculate droplet evaporation time scale from physics.
357 Uses D² law: τ_evap = d² / (8 × D × ln(1 + B))
358 where D is mass diffusivity and B is Spalding number.
363 Chamber pressure [Pa]
365 Chamber temperature [K]
366 droplet_diameter : float
367 Droplet diameter [m] (SMD or characteristic size)
368 fuel_props : dict, optional
369 Fuel properties (density, latent heat, etc.)
371 Specific heat ratio from CEA
373 Gas constant [J/(kg·K)] from CEA
378 Evaporation time scale [s]
383 if not np.isfinite(Pc)
or Pc <= 0:
384 raise ValueError(f
"Invalid pressure: {Pc} Pa")
385 if not np.isfinite(Tc)
or Tc <= 0:
386 raise ValueError(f
"Invalid temperature: {Tc} K")
387 if not np.isfinite(droplet_diameter)
or droplet_diameter <= 0:
388 raise ValueError(f
"Invalid droplet diameter: {droplet_diameter} m")
391 if fuel_props
is None:
395 rho_l = fuel_props.get(
"density", 810.0)
396 L_eff = fuel_props.get(
"L_vap")
or fuel_props.get(
"latent_heat", 300e3)
397 cp_l = fuel_props.get(
"specific_heat", 2000.0)
398 T_inj = fuel_props.get(
"temperature", 293.0)
402 rho_g = Pc / (R_gas * Tc)
406 cp_g = gamma * R_gas / (gamma - 1.0)
416 _, diagnostics = calculate_gasification_efficiency(
420 SMD=droplet_diameter,
431 tau_evap = diagnostics[
"tau_vap"]
432 except Exception
as e:
434 warnings.warn(f
"Gasification model failed in reaction_chemistry: {e}. Using fallback.")
437 return float(tau_evap)
443 turbulence_intensity: float = 0.1,
446 Calculate turbulent mixing time scale from actual flow conditions.
448 τ_mix = L / u_turbulent, where u_turbulent = turbulence_intensity × u_mean
453 Characteristic mixing length [m] (typically L*)
455 Mean flow velocity [m/s]
456 turbulence_intensity : float
457 Turbulence intensity (typically 0.05-0.2 for rocket chambers)
462 Mixing time scale [s]
464 if not np.isfinite(L_chamber)
or L_chamber <= 0:
465 raise ValueError(f
"Invalid chamber length: {L_chamber} m")
466 if not np.isfinite(u_mean)
or u_mean <= 0:
467 raise ValueError(f
"Invalid mean velocity: {u_mean} m/s")
468 if not np.isfinite(turbulence_intensity)
or turbulence_intensity <= 0:
469 raise ValueError(f
"Invalid turbulence intensity: {turbulence_intensity}")
471 u_turbulent = turbulence_intensity * u_mean
473 if not np.isfinite(u_turbulent)
or u_turbulent <= 0:
474 raise ValueError(f
"Non-physical turbulent velocity: u_turb={u_turbulent}")
476 tau_mix = L_chamber / u_turbulent
478 if not np.isfinite(tau_mix)
or tau_mix <= 0:
479 raise ValueError(f
"Non-physical mixing time: τ={tau_mix}")
481 return float(tau_mix)
492 config: PintleEngineConfig,
493 spray_diagnostics: Optional[Dict] =
None,
494 Tc_kinetics: Optional[float] =
None,
495) -> Dict[str, float]:
497 Calculate reaction progress through chamber using actual physics.
499 No reference conditions - all calculations based on actual engine state.
504 Characteristic length [m]
506 Chamber pressure [Pa]
508 Chamber temperature [K] (Used for residence time - Ideal is conservative)
510 Characteristic velocity [m/s]
514 Gas constant [J/(kg·K)]
517 config : PintleEngineConfig
519 spray_diagnostics : dict, optional
520 Spray diagnostics (SMD, velocities, etc.) for evaporation/mixing
521 Tc_kinetics : float, optional
522 Temperature to use for reaction kinetics [K]. If None, uses Tc.
523 (Actual/Effective is conservative)
528 Reaction progress and time scales
531 if not all(np.isfinite([Lstar, Pc, Tc, cstar, gamma, R, MR])):
532 raise ValueError(
"Non-finite input parameters to calculate_chamber_reaction_progress")
535 T_react = Tc_kinetics
if Tc_kinetics
is not None else Tc
537 if Lstar <= 0
or Pc <= 0
or Tc <= 0
or cstar <= 0
or gamma <= 1.0
or R <= 0
or MR <= 0
or T_react <= 0:
538 raise ValueError(f
"Invalid physical parameters: L*={Lstar}, Pc={Pc}, Tc={Tc}, T_react={T_react}, c*={cstar}, γ={gamma}, R={R}, MR={MR}")
544 tau_residence, tau_valid = NumericalStability.safe_divide(Lstar * cstar, R * Tc,
None,
"tau_residence")
545 if not tau_valid.passed:
546 raise ValueError(f
"Residence time calculation failed: L*={Lstar}, c*={cstar}, R={R}, Tc={Tc}")
555 if spray_diagnostics
and "SMD" in spray_diagnostics:
556 smd = spray_diagnostics[
"SMD"]
568 u_mean = cstar / 15.0
571 turbulence_intensity = 0.1
572 if spray_diagnostics
and "turbulence_intensity_mix" in spray_diagnostics:
573 turbulence_intensity = spray_diagnostics[
"turbulence_intensity_mix"]
579 mixing_squared = tau_mixing ** 2
580 reaction_squared = tau_reaction ** 2
582 if not (np.isfinite(mixing_squared)
and np.isfinite(reaction_squared)):
583 raise ValueError(f
"Non-finite time scales: τ_mix={tau_mixing}, τ_reac={tau_reaction}")
585 sum_squared = mixing_squared + reaction_squared
586 tau_combined, tau_combined_valid = NumericalStability.safe_sqrt(sum_squared,
"sqrt(tau_mix^2 + tau_reac^2)")
587 if not tau_combined_valid.passed:
589 tau_combined = tau_mixing + tau_reaction
591 tau_effective = tau_evaporation + tau_combined
593 if not np.isfinite(tau_effective)
or tau_effective <= 0:
594 raise ValueError(f
"Non-physical effective time: τ_eff={tau_effective}")
602 "progress_injection": float(progress_injection),
603 "progress_mid": float(progress_mid),
604 "progress_throat": float(progress_throat),
605 "tau_residence": float(tau_residence),
606 "tau_evaporation": float(tau_evaporation),
607 "tau_mixing": float(tau_mixing),
608 "tau_reaction": float(tau_reaction),
609 "tau_effective": float(tau_effective),
624 species_equilibrium: Dict[str, float],
625 species_initial: Optional[Dict[str, float]] =
None,
626) -> Dict[str, float]:
628 Calculate species concentrations based on reaction progress.
630 Linear interpolation between initial and equilibrium:
631 X_species = X_initial + progress × (X_equilibrium - X_initial)
636 Reaction progress (0-1)
637 species_equilibrium : dict
638 Equilibrium species mole fractions {species_name: fraction}
639 species_initial : dict, optional
640 Initial species mole fractions (default: pure reactants)
644 species_current : dict
645 Current species mole fractions
647 if not np.isfinite(progress)
or progress < 0
or progress > 1:
648 raise ValueError(f
"Invalid progress: {progress}")
650 if species_initial
is None:
656 for species, X_eq
in species_equilibrium.items():
657 if not np.isfinite(X_eq)
or X_eq < 0
or X_eq > 1:
658 raise ValueError(f
"Invalid equilibrium fraction for {species}: {X_eq}")
660 X_init = species_initial.get(species, 0.0)
661 if not np.isfinite(X_init)
or X_init < 0
or X_init > 1:
662 raise ValueError(f
"Invalid initial fraction for {species}: {X_init}")
664 X_current = X_init + progress * (X_eq - X_init)
667 if not np.isfinite(X_current)
or X_current < 0
or X_current > 1:
668 raise ValueError(f
"Non-physical species fraction for {species}: X={X_current}")
670 species_current[species] = float(X_current)
672 return species_current
Dict[str, float] calculate_chamber_reaction_progress(float Lstar, float Pc, float Tc, float cstar, float gamma, float R, float MR, PintleEngineConfig config, Optional[Dict] spray_diagnostics=None, Optional[float] Tc_kinetics=None)