86 def residual(self, Pc: float, P_tank_O: float, P_tank_F: float) -> float:
88 Calculate residual: supply(Pc) - demand(Pc)
90 Residual = 0 when supply equals demand.
95 Chamber pressure guess [Pa]
97 Oxidizer tank pressure [Pa]
99 Fuel tank pressure [Pa]
103 residual : float [kg/s]
107 P_tank_O_val = float(P_tank_O)
108 P_tank_F_val = float(P_tank_F)
111 if not np.isfinite(Pc_val)
or Pc_val <= 0:
113 if not np.isfinite(P_tank_O_val)
or P_tank_O_val <= 0:
115 if not np.isfinite(P_tank_F_val)
or P_tank_F_val <= 0:
125 mdot_O, mdot_F, diagnostics = flows(
131 except Exception
as e:
134 warnings.warn(f
"flows() failed in residual at Pc={Pc_val/1e6:.2f} MPa: {e}")
138 if not (np.isfinite(mdot_O)
and np.isfinite(mdot_F)):
141 mdot_supply = mdot_O + mdot_F
147 MR, mr_valid = NumericalStability.safe_divide(mdot_O, mdot_F, 2.5,
"MR")
148 if not mr_valid.passed:
154 cg = ensure_chamber_geometry(self.
config)
155 eps_default = cg.expansion_ratio
156 cea_props = self.
cea_cache.eval(MR, Pc_val, 101325.0, eps_default)
157 except Exception
as e:
160 warnings.warn(f
"CEA cache eval failed in residual: {e}")
163 cstar_ideal = cea_props.get(
"cstar_ideal", 0.0)
164 if not np.isfinite(cstar_ideal)
or cstar_ideal <= 0:
176 cg = ensure_chamber_geometry(self.
config)
177 current_Lstar = calculate_Lstar(
180 Lstar_override=cg.Lstar
197 "Tc": cea_props.get(
"Tc", DEFAULT_CHAMBER_TEMP_K),
198 "cstar_ideal": cea_props.get(
"cstar_ideal", DEFAULT_CSTAR_IDEAL_M_S),
199 "gamma": cea_props.get(
"gamma", DEFAULT_GAMMA_ND),
200 "R": cea_props.get(
"R", DEFAULT_GAS_CONST_J_KG_K),
202 "Ac": geometry[
"area_cross"],
204 "chamber_length": geometry[
"length"],
205 "Dinj": current_injector_diameter,
206 "m_dot_total": mdot_supply,
207 "spray_diagnostics": diagnostics,
208 "turbulence_intensity": diagnostics.get(
"turbulence_intensity_mix", DEFAULT_TURBULENCE_INTENSITY_ND),
209 "momentum_ratio_R": diagnostics.get(
"momentum_ratio_R"),
217 if "u_F" in diagnostics:
218 advanced_params[
"u_fuel"] = diagnostics[
"u_F"]
219 if "u_O" in diagnostics:
220 advanced_params[
"u_lox"] = diagnostics[
"u_O"]
222 if hasattr(self,
'_debug')
and self.
_debug:
223 logging.getLogger(
"evaluate").
info(f
"[SOLVER_DEBUG] Pc Guess: {Pc_val/1e6:.4f} MPa | Supply mdot: {mdot_supply:.4f} kg/s | MR: {MR:.3f}")
228 self.
config.combustion.efficiency,
231 debug=self.
_debug if hasattr(self,
'_debug')
else False,
237 if not np.isfinite(eta)
or eta <= 0:
242 cstar_actual = eta * cstar_ideal
246 cg = ensure_chamber_geometry(self.
config)
247 kappa = nozzle_stagnation_loss(contraction_ratio_of(cg), float(cea_props.get(
"gamma", DEFAULT_GAMMA_ND)))
248 mdot_demand, demand_valid = NumericalStability.safe_divide(
249 Pc_val / kappa * cg.A_throat,
254 if not demand_valid.passed:
257 residual = mdot_supply - mdot_demand
260 if not np.isfinite(residual):
263 return float(residual)
310 """Supply > demand even at the ceiling: injector oversized / throat undersized for these
311 pressures. Raise with the supply/demand numbers at Pc_max so the message is actionable."""
313 mdot_O_test, mdot_F_test, diag_test = flows(P_tank_O, P_tank_F, Pc_max, self.
config)
314 mdot_supply_test = mdot_O_test + mdot_F_test
315 MR_test = mdot_O_test / mdot_F_test
if mdot_F_test > 0
else np.inf
316 cg = ensure_chamber_geometry(self.
config)
317 cea_props_test = self.
cea_cache.eval(MR_test, Pc_max, 101325.0, cg.expansion_ratio)
318 cstar_ideal_test = cea_props_test.get(
"cstar_ideal", 0.0)
320 advanced_params_test = {
322 "Tc": cea_props_test.get(
"Tc", DEFAULT_CHAMBER_TEMP_K),
323 "cstar_ideal": cstar_ideal_test,
324 "gamma": cea_props_test.get(
"gamma", DEFAULT_GAMMA_ND),
325 "R": cea_props_test.get(
"R", DEFAULT_GAS_CONST_J_KG_K),
327 "Ac": geometry_test[
"area_cross"],
329 "chamber_length": geometry_test[
"length"],
331 "m_dot_total": mdot_supply_test,
332 "spray_diagnostics": diag_test,
333 "turbulence_intensity": diag_test.get(
"turbulence_intensity_mix", DEFAULT_TURBULENCE_INTENSITY_ND),
338 eta_test = eta_cstar(
339 calculate_Lstar(cg.volume, cg.A_throat, Lstar_override=cg.Lstar),
340 self.
config.combustion.efficiency,
341 diag_test.get(
"cooling_efficiency", 1.0),
342 advanced_params_test,
345 cstar_actual_test = eta_test * cstar_ideal_test
346 kappa_test = nozzle_stagnation_loss(contraction_ratio_of(cg), float(cea_props_test.get(
"gamma", DEFAULT_GAMMA_ND)))
347 mdot_demand_test = (Pc_max / kappa_test * cg.A_throat) / cstar_actual_test
if cstar_actual_test > 0
else np.inf
348 if mdot_demand_test > 0
and mdot_supply_test > mdot_demand_test:
349 Pc_estimate = mdot_supply_test * cstar_actual_test / cg.A_throat * kappa_test
351 f
"No solution: Supply > Demand at all Pc. "
352 f
"Residual at Pc_min: {residual_min:.4f} kg/s, at Pc_max: {residual_max:.4f} kg/s. "
353 f
"\nDiagnostics at Pc_max ({Pc_max/1e6:.2f} MPa):"
354 f
"\n - Supply: {mdot_supply_test:.4f} kg/s (mdot_O={mdot_O_test:.4f}, mdot_F={mdot_F_test:.4f})"
355 f
"\n - Demand: {mdot_demand_test:.4f} kg/s (c*_actual={cstar_actual_test:.1f} m/s, At={cg.A_throat*1e6:.2f} mm²)"
356 f
"\n - Estimated Pc needed: {Pc_estimate/1e6:.2f} MPa (vs Pc_max={Pc_max/1e6:.2f} MPa)"
358 f
"\n 1. Reduce injector orifice areas (currently oversized)"
359 f
"\n 2. Increase throat area (currently undersized)"
360 f
"\n 3. Increase tank pressures to allow higher Pc_max"
361 f
"\n 4. Check combustion efficiency (low efficiency reduces demand)"
364 f
"No solution: Supply > Demand at all Pc. "
365 f
"Residual: [{residual_min:.4f}, {residual_max:.4f}] kg/s. "
366 f
"Could not compute detailed diagnostics."
370 except Exception
as diag_e:
372 f
"No solution: Supply > Demand at all Pc. "
373 f
"Residual at bounds: [{residual_min:.4f}, {residual_max:.4f}] kg/s. "
374 f
"Pc_max ({Pc_max/1e6:.2f} MPa) limited by tank pressure. "
375 f
"Possible causes: Injector oversized, throat undersized, or combustion efficiency too low. "
376 f
"Diagnostic error: {diag_e}"
383 Pc_guess: float =
None,
385 silent: bool =
False,
386 ) -> Tuple[float, Dict[str, Any]]:
388 Solve for chamber pressure.
393 Oxidizer tank pressure [Pa]
395 Fuel tank pressure [Pa]
396 Pc_guess : float, optional
397 Initial guess for chamber pressure [Pa]
402 Solved chamber pressure
408 Pc_min =
max(PC_CHOKE_FLOOR_PA, self.
config.solver.Pc_bounds[0])
409 Pc_max =
min(
min(P_tank_O, P_tank_F) * (1.0 - PC_MIN_TOTAL_DROP_FRAC), self.
config.solver.Pc_bounds[1])
412 raise ValueError(f
"Invalid pressure bounds: Pc_max ({Pc_max}) <= Pc_min ({Pc_min})")
416 Pc_guess = (Pc_min + Pc_max) / 2
429 def residual_func(Pc):
430 return self.
residual(Pc, P_tank_O, P_tank_F)
437 convergence_history: list = []
439 if _accel_pc
is not None:
443 residual_min = residual_func(Pc_min)
444 residual_max = residual_func(Pc_max)
446 if not np.isfinite(residual_min):
449 test_res = residual_func((Pc_min + Pc_max) / 2)
450 if not np.isfinite(test_res):
452 f
"Residual function returns non-finite values. "
453 f
"Pc_min={Pc_min/1e6:.2f} MPa, Pc_max={Pc_max/1e6:.2f} MPa. "
454 f
"Check injector geometry, feed system, or CEA cache."
456 except Exception
as e:
458 f
"Residual function evaluation failed at bounds. "
459 f
"Pc_min={Pc_min/1e6:.2f} MPa, Pc_max={Pc_max/1e6:.2f} MPa. "
462 if not np.isfinite(residual_max):
464 f
"Residual function returns non-finite at Pc_max={Pc_max/1e6:.2f} MPa. "
465 f
"Check that tank pressures are sufficient and injector geometry is valid."
474 if residual_min > 0
and residual_max > 0:
477 f
"No solution: Supply < Demand at all Pc. "
478 f
"Residual at bounds: [{residual_min:.4f}, {residual_max:.4f}] kg/s. "
479 f
"Insufficient mass flow. Check tank pressures and injector geometry."
482 def tracked_residual_func(Pc):
483 res = residual_func(Pc)
484 convergence_history.append(float(res))
488 if self.
config.solver.method ==
"brentq":
490 tracked_residual_func,
493 xtol=self.
config.solver.tolerance,
494 rtol=self.
config.solver.tolerance * 1e-3,
495 maxiter=self.
config.solver.max_iterations,
498 success = result.converged
502 tracked_residual_func,
504 tol=self.
config.solver.tolerance,
505 maxiter=self.
config.solver.max_iterations
508 conv_check = NumericalStability.check_convergence(
510 self.
config.solver.tolerance,
513 if not conv_check.passed
and conv_check.severity ==
"error":
514 raise RuntimeError(f
"Convergence validation failed: {conv_check.message}")
517 except Exception
as e:
518 raise RuntimeError(f
"Chamber pressure solver failed: {e}")
522 if not np.isfinite(Pc_val):
523 raise RuntimeError(f
"Solver returned non-finite pressure: {Pc_val}")
525 Pc_check = PhysicalConstraints.validate_pressure(Pc_val,
"Pc_solution")
526 if not Pc_check.passed
and Pc_check.severity ==
"error":
527 raise RuntimeError(f
"Solution validation failed: {Pc_check.message}")
530 mdot_O, mdot_F, closure_diag = flows(P_tank_O, P_tank_F, Pc_val, self.
config)
531 MR = mdot_O / mdot_F
if mdot_F > 0
else 0
535 cg = ensure_chamber_geometry(self.
config)
536 current_Lstar = calculate_Lstar(
539 Lstar_override=cg.Lstar
545 cg = ensure_chamber_geometry(self.
config)
546 eps_current = cg.A_exit / cg.A_throat
if cg.A_throat
and cg.A_exit
and cg.A_throat > 0
else cg.expansion_ratio
547 cea_props = self.
cea_cache.eval(MR, Pc_val, 101325.0, eps_current)
550 mdot_total = mdot_O + mdot_F
562 with_profile=
not getattr(self,
"_silent",
False),
567 eff_cfg = self.
config.combustion.efficiency
568 if getattr(eff_cfg,
'use_mixture_coupling',
False)
and isinstance(closure_diag, dict):
575 closure_diag[
'mixture_diagnostics'] = mixture_diag
583 "Tc": cea_props[
"Tc"],
584 "Tc_kinetics": effective_Tc,
585 "cstar_ideal": cea_props.get(
"cstar_ideal", DEFAULT_CSTAR_IDEAL_M_S),
586 "gamma": cea_props.get(
"gamma", DEFAULT_GAMMA_ND),
587 "R": cea_props.get(
"R", DEFAULT_GAS_CONST_J_KG_K),
589 "Ac": geometry[
"area_cross"],
591 "chamber_length": geometry[
"length"],
592 "Dinj": current_injector_diameter,
593 "m_dot_total": mdot_total,
594 "u_fuel": closure_diag.get(
"u_F"),
595 "u_lox": closure_diag.get(
"u_O"),
596 "spray_diagnostics": closure_diag,
597 "turbulence_intensity": closure_diag.get(
"turbulence_intensity_mix", DEFAULT_TURBULENCE_INTENSITY_ND),
598 "momentum_ratio_R": closure_diag.get(
"momentum_ratio_R"),
607 self.
config.combustion.efficiency,
614 cstar_actual = eta * cea_props[
"cstar_ideal"]
615 gamma = cea_props[
"gamma"]
619 logging.getLogger(
"evaluate").
info(
620 f
"[CSTAR] eta={eta:.4f} | c*_ideal={cea_props['cstar_ideal']:.1f} m/s -> c*_actual={cstar_actual:.1f} m/s | "
621 f
"ratio={cstar_actual/cea_props['cstar_ideal']:.4f}"
631 cg = ensure_chamber_geometry(self.
config)
632 kappa = nozzle_stagnation_loss(contraction_ratio_of(cg), float(cea_props[
"gamma"]))
633 F_check = (cg.nozzle_efficiency * cea_props[
"Cf_vac"] * Pc_val / kappa * cg.A_throat
634 - 101325.0 * (cg.A_exit
or cg.A_throat * cg.expansion_ratio))
635 Isp = F_check / (mdot_total * g0)
if mdot_total > 0
else 0.0
638 validation_results = validate_engine_state(
639 Pc_val, MR, mdot_total, cstar_actual, gamma, effective_Tc, Isp, 0.0
643 critical_errors = [r
for r
in validation_results
if r.severity ==
"error" and not r.passed]
645 error_msgs = [r.message
for r
in critical_errors]
646 raise RuntimeError(f
"Solution validation failed:\n" +
"\n".join(error_msgs))
652 "mdot_total": mdot_total,
654 "cstar_ideal": cea_props[
"cstar_ideal"],
655 "Tc_ideal": cea_props[
"Tc"],
656 "cstar_actual": cstar_actual,
657 "Pc_ns": Pc_val / kappa,
658 "stagnation_loss_kappa": kappa,
659 "cea_extrapolated": bool(cea_props.get(
"extrapolated",
False)),
660 "cea_clamped": tuple(cea_props.get(
"clamped", ())),
664 "cooling_efficiency": cooling_eff,
666 "Tc_ideal": cea_props[
"Tc"],
669 "M": cea_props.get(
"M"),
671 "validation_results": validation_results,
672 "convergence_history": convergence_history,
676 diagnostics[
"cooling"] = cooling_results
678 return Pc_val, diagnostics
682 closure_diag: Dict[str, Any],
684 ) -> Dict[str, float]:
686 Extract and validate mixture quality diagnostics from closure diagnostics.
688 DIAGNOSTICS-ONLY - no efficiency impact. Physics comes from:
689 - eta_Lstar: actual SMD via gasification model
690 - eta_mixing: actual turbulence/velocities via diffusion
691 - eta_kinetics: actual T/P via Damköhler number
694 - closure_diag: Injector diagnostics dict
695 - strict: If True, raise on missing critical data. If False, warn.
697 Returns: Dict with actual_smd_microns, x_star_mm, We_O, We_F, etc.
699 if not isinstance(closure_diag, dict):
701 raise ValueError(
"closure_diag must be a dictionary")
704 warnings.warn(
"closure_diag is not a dictionary, cannot extract mixture diagnostics")
710 D32_O = closure_diag.get(
"D32_O")
711 D32_F = closure_diag.get(
"D32_F")
713 if D32_O
is None and D32_F
is None:
714 msg =
"Missing SMD data: both D32_O and D32_F are None in closure diagnostics"
716 raise ValueError(msg)
722 float(D32_O)
if D32_O
is not None else 0.0,
723 float(D32_F)
if D32_F
is not None else 0.0,
725 actual_smd_microns = actual_smd_m * 1e6
727 if not np.isfinite(actual_smd_microns):
729 f
"Non-finite SMD: {actual_smd_microns:.2f} microns. "
730 f
"D32_O={D32_O}, D32_F={D32_F}. Check spray breakup calculations."
733 raise ValueError(msg)
737 elif actual_smd_microns <= 0:
739 f
"Invalid SMD: {actual_smd_microns:.2f} microns (must be > 0). "
740 f
"D32_O={D32_O}, D32_F={D32_F}. Check spray breakup calculations."
743 raise ValueError(msg)
748 diagnostics[
"actual_smd_microns"] = float(actual_smd_microns)
751 x_star_m = closure_diag.get(
"x_star")
754 msg =
"Missing x_star in closure diagnostics. Check spray impingement calculations."
756 raise ValueError(msg)
761 x_star_mm = float(x_star_m) * 1000.0
763 if not np.isfinite(x_star_mm):
764 msg = f
"Non-finite x_star: {x_star_mm} mm. Must be finite."
766 raise ValueError(msg)
772 diagnostics[
"x_star_mm"] = float(x_star_mm)
775 We_O = closure_diag.get(
"We_O")
776 We_F = closure_diag.get(
"We_F")
780 if np.isfinite(we_o)
and we_o >= 0:
781 diagnostics[
"We_O"] = we_o
783 raise ValueError(f
"Invalid We_O: {we_o}. Must be finite and non-negative.")
787 if np.isfinite(we_f)
and we_f >= 0:
788 diagnostics[
"We_F"] = we_f
790 raise ValueError(f
"Invalid We_F: {we_f}. Must be finite and non-negative.")
793 if "We_O" in diagnostics
and "We_F" in diagnostics:
794 we_o = diagnostics[
"We_O"]
795 we_f = diagnostics[
"We_F"]
798 if we_o > 0
and we_f > 0:
799 diagnostics[
"We_min"] =
min(we_o, we_f)
801 diagnostics[
"We_min"] =
max(we_o, we_f)
804 I_mix = closure_diag.get(
"turbulence_intensity_mix")
806 if I_mix
is not None:
807 I_mix_val = float(I_mix)
809 if not np.isfinite(I_mix_val):
810 msg = f
"Non-finite turbulence intensity: {I_mix_val}"
812 raise ValueError(msg)
816 elif I_mix_val <= 0
or I_mix_val > 1:
818 f
"Turbulence intensity out of range: {I_mix_val:.4f} (expected 0-1). "
819 f
"Check turbulence calculations."
822 raise ValueError(msg)
827 diagnostics[
"turbulence_intensity_mix"] = I_mix_val
893 cea_props: Dict[str, float],
894 closure_diag: Dict[str, Any],
895 with_profile: bool =
True,
896 ) -> Tuple[Dict[str, Any], float, float]:
898 mdot_total = mdot_O + mdot_F
899 cooling_results: Dict[str, Any] = {}
900 Tc = float(cea_props[
"Tc"])
903 closure_diag[
"cooling"] = cooling_results
904 return cooling_results, 1.0, Tc
906 fuel_fluid = config.fluids[
"fuel"]
909 gamma = float(cea_props[
"gamma"])
910 R = float(cea_props[
"R"])
912 rho_g =
max(Pc_val / (R *
max(Tc, 1.0)), 1e-6)
913 area_cross = geometry[
"area_cross"]
914 velocity_g = mdot_total / (rho_g * area_cross)
916 regen_cfg = config.regen_cooling
919 regen_active = regen_cfg
is not None and regen_cfg.enabled
920 MR = mdot_O / mdot_F
if mdot_F > 0
else float(cea_props.get(
"MR", 0.0)
or 0.0)
921 tr = self.
cea_cache.aux.transport(MR, Pc_val,
"chamber")
922 mu_g_config = regen_cfg.hot_gas_viscosity
if regen_active
else tr[
"mu"]
923 M = cea_props.get(
"M")
924 if regen_active
and M
is not None and M > 0
and Tc > 0:
926 mu_g_calculated = calculate_gas_viscosity_huzel(Tc, M)
928 mu_g_calculated = tr[
"mu"]
929 mu_g = mu_g_calculated
930 k_g = regen_cfg.hot_gas_thermal_conductivity
if regen_active
else tr[
"k"]
931 Pr_g = regen_cfg.hot_gas_prandtl
if (regen_active
and regen_cfg.hot_gas_prandtl > 0)
else tr[
"Pr"]
933 Re_g = rho_g * velocity_g * geometry[
"diameter"] /
max(mu_g, 1e-8)
934 Nu_g = 0.023 * (Re_g ** 0.8) * (Pr_g ** 0.4)
937 turbulence_intensity_calc = float(np.clip(0.16 * Re_g ** -0.125, 0.02, 0.25))
if Re_g > 0
else 0.05
938 turbulence_boost = 1.0
940 turbulence_boost = 1.0 +
max(regen_cfg.gas_turbulence_intensity, 0.0)
941 turbulence_intensity_calc =
max(
942 turbulence_intensity_calc,
943 float(np.clip(regen_cfg.gas_turbulence_intensity, 0.0, 0.5)),
946 h_hot_base = Nu_g * k_g / geometry[
"diameter"] * turbulence_boost
954 "velocity": velocity_g,
955 "length": geometry[
"length"],
956 "circumference": geometry[
"circumference"],
957 "area": geometry[
"area"],
958 "area_cross": area_cross,
959 "h_hot_base": h_hot_base,
960 "turbulence_intensity": turbulence_intensity_calc,
963 film_cfg = config.film_cooling
966 "mdot_available_for_regen": mdot_F,
967 "effective_gas_temperature": Tc,
971 if film_cfg
is not None and film_cfg.enabled:
972 film_results = compute_film_cooling(
979 cooling_results[
"film"] = film_results
981 effective_Tc = float(film_results.get(
"effective_gas_temperature", Tc))
982 comp = self.
cea_cache.aux.composition(MR, Pc_val,
"chamber")
988 "M": cea_props.get(
"M"),
989 "chamber_area": geometry[
"area_cross"],
990 "A_throat": ensure_chamber_geometry(config).A_throat,
991 "chamber_length": geometry[
"length"],
992 "turbulence_intensity": turbulence_intensity_calc,
993 "x_H2O": comp[
"H2O"],
994 "x_CO2": comp[
"CO2"],
998 "density": float(fuel_fluid.density),
999 "viscosity": float(fuel_fluid.viscosity),
1000 "cp": float(fuel_fluid.specific_heat),
1001 "thermal_conductivity": float(fuel_fluid.thermal_conductivity),
1002 "temperature": float(fuel_fluid.temperature),
1005 mdot_coolant = float(film_results.get(
"mdot_available_for_regen", mdot_F))
1008 regen_results = compute_regen_heat_transfer(
1015 regen_results[
"mdot_coolant"] = mdot_coolant
1016 cooling_results[
"regen"] = regen_results
1018 abl_cfg = config.ablative_cooling
1019 if abl_cfg
is not None and abl_cfg.enabled:
1021 cg_now = ensure_chamber_geometry(config)
1023 gas = HotGasState(T0=effective_Tc, P0=Pc_val, gamma=gamma,
1024 mass_flux_throat=mdot_total / cg_now.A_throat, mu=tr[
"mu"],
1025 cp=tr[
"cp"], Pr=tr[
"Pr"], x_H2O=comp[
"H2O"], x_CO2=comp[
"CO2"])
1026 ablative_results = liner_response(gas, contour, abl_cfg, self.
_liner_end(contour),
1027 mdot_total, with_profile=with_profile)
1028 ablative_results[
"incident_heat_flux"] = (ablative_results[
"heat_flux_from_gas_convective"]
1029 + ablative_results[
"heat_flux_from_gas_radiative"])
1032 abl_heat_removed = ablative_results.get(
"heat_removed", 0.0)
1033 if abl_heat_removed > 0
and mdot_total > 0:
1034 cp = gamma * R /
max(gamma - 1.0, 1e-6)
1035 delta_T_abl = abl_heat_removed /
max(mdot_total * cp, 1e-6)
1036 effective_Tc =
max(effective_Tc - delta_T_abl, 1.0)
1037 ablative_results[
"temperature_reduction"] = float(delta_T_abl)
1039 ablative_results[
"temperature_reduction"] = 0.0
1040 ablative_results[
"effective_gas_temperature"] = float(effective_Tc)
1041 cooling_results[
"ablative"] = ablative_results
1052 metadata = cooling_results.setdefault(
"metadata", {})
1053 metadata[
"gas_turbulence_intensity"] = turbulence_intensity_calc
1054 metadata[
"effective_gas_temperature"] = float(effective_Tc)
1055 metadata[
"original_gas_temperature"] = float(Tc)
1056 metadata[
"gas_viscosity"] = float(mu_g)
1057 metadata[
"gas_viscosity_config"] = float(mu_g_config)
1058 metadata[
"gas_viscosity_calculated"] = float(mu_g_calculated)
1059 closure_diag[
"cooling"] = cooling_results
1061 return cooling_results, cooling_eff, effective_Tc
1105 Extract physical chamber geometry from configuration.
1107 Returns a dictionary with:
1108 - length: Total physical length [m]
1109 - diameter: Chamber inner diameter [m]
1110 - area_cross: Cross-sectional area [m²]
1111 - circumference: Chamber circumference [m]
1112 - area: Total wetted surface area [m²] (cylindrical + contraction)
1115 cg = ensure_chamber_geometry(self.
config)
1116 regen_cfg = self.
config.regen_cooling
1119 diameter = cg.chamber_diameter
1122 if (diameter
is None or diameter <= 0)
and regen_cfg
is not None and regen_cfg.chamber_inner_diameter
is not None:
1123 diameter = regen_cfg.chamber_inner_diameter
1126 if diameter
is None or diameter <= 0:
1129 diameter =
max(diameter, 1e-6)
1130 area_cross = np.pi * (diameter / 2.0)**2
1131 circumference = np.pi * diameter
1134 length_total = cg.length
1135 length_cyl = cg.length_cylindrical
1136 length_cont = cg.length_contraction
1140 if length_cyl
is not None and length_cont
is not None:
1143 area_cyl = circumference * length_cyl
1148 A_throat = cg.A_throat
if cg.A_throat
and cg.A_throat > 0
else (area_cross / 3.0)
1149 r2 = np.sqrt(A_throat / np.pi)
1151 slant_height = np.sqrt((r1 - r2)**2 + length_cont**2)
1152 area_cont = np.pi * (r1 + r2) * slant_height
1154 area_wetted = area_cyl + area_cont
1157 area_wetted = circumference * length_total
1160 "length": float(length_total),
1161 "diameter": float(diameter),
1162 "area_cross": float(area_cross),
1163 "circumference": float(circumference),
1164 "area": float(area_wetted),