STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
coaxial.py
Go to the documentation of this file.
1"""Coaxial injector model implementation."""
2
3from __future__ import annotations
4
5from typing import Tuple, Dict, Any
6
7import numpy as np
8
9from engine.pipeline.config_schemas import PintleEngineConfig, CoaxialInjectorConfig
10from engine.pipeline.feed_loss import delta_p_feed
11from engine.pipeline.thermal.regen_cooling import delta_p_regen_channels
12from engine.core.discharge import cd_from_re, calculate_reynolds_number
13from engine.core.spray import (
14 momentum_flux_ratio,
15 thrust_momentum_ratio,
16 spray_angle_from_J,
17 spray_angle_from_TMR,
18 weber_number,
19 ohnesorge_number,
20 smd_lefebvre,
21 tau_evap,
22 xstar,
23 check_spray_constraints,
24)
25
26from . import InjectorModel
27
28
29def _annulus_area(inner_diameter: float, gap_thickness: float) -> float:
30 outer_diameter = inner_diameter + 2.0 * gap_thickness
31 return 0.25 * np.pi * (outer_diameter ** 2 - inner_diameter ** 2)
32
33
34def _annulus_hydraulic_diameter(inner_diameter: float, gap_thickness: float) -> float:
35 outer_diameter = inner_diameter + 2.0 * gap_thickness
36 return outer_diameter - inner_diameter
37
38
40 """Shear-coaxial injector solver (core oxidizer, annular fuel)."""
41
42 injector_config: CoaxialInjectorConfig
43
44 def __init__(self, engine_config: PintleEngineConfig):
45 super().__init__(engine_config)
46 injector_cfg = engine_config.injector
47 if not isinstance(injector_cfg, CoaxialInjectorConfig):
48 raise TypeError("CoaxialInjector requires CoaxialInjectorConfig")
49 self.injector_config = injector_cfg
50
51 def solve(
52 self,
53 P_tank_O: float,
54 P_tank_F: float,
55 Pc: float,
56 ) -> Tuple[float, float, Dict[str, Any]]:
57 config = self.engine_config
58 geometry = self.injector_config.geometry
59
60 discharge_O = config.discharge["oxidizer"]
61 discharge_F = config.discharge["fuel"]
62 feed_O = config.feed_system["oxidizer"]
63 feed_F = config.feed_system["fuel"]
64 spray_cfg = config.spray
65 fluids = config.fluids
66
67 rho_O = fluids["oxidizer"].density
68 mu_O = fluids["oxidizer"].viscosity
69 sigma_O = fluids["oxidizer"].surface_tension
70
71 rho_F = fluids["fuel"].density
72 mu_F = fluids["fuel"].viscosity
73 sigma_F = fluids["fuel"].surface_tension
74
75 # Geometrical quantities
76 A_core = geometry.core.n_ports * np.pi * (geometry.core.d_port / 2.0) ** 2
77 d_hyd_core = geometry.core.d_port # Treat each port as round orifice
78
79 A_annulus = _annulus_area(geometry.annulus.inner_diameter, geometry.annulus.gap_thickness)
80 d_hyd_annulus = _annulus_hydraulic_diameter(geometry.annulus.inner_diameter, geometry.annulus.gap_thickness)
81
82 mdot_O = 0.1
83 mdot_F = 0.1
84
85 max_iter = config.solver.closure.max_iterations
86 Cd_reduction = config.solver.closure.Cd_reduction_factor
87 Cd_O_eff = discharge_O.Cd_inf
88 Cd_F_eff = discharge_F.Cd_inf
89
90 diagnostics = {
91 "iterations": 0,
92 "constraints_satisfied": False,
93 "violations": [],
94 "J": None,
95 "TMR": None,
96 "theta": None,
97 "We_O": None,
98 "We_F": None,
99 "D32_O": None,
100 "D32_F": None,
101 "x_star": None,
102 "injector_type": "coaxial",
103 }
104
105 for iteration in range(max_iter):
106 # Feed loss fixed-point iteration
107 for feed_iter in range(3):
108 delta_p_feed_O = delta_p_feed(mdot_O, rho_O, feed_O, P_tank_O, mu=mu_O)
109 delta_p_feed_F_base = delta_p_feed(mdot_F, rho_F, feed_F, P_tank_F, mu=mu_F)
110 if config.regen_cooling is not None and config.regen_cooling.enabled:
111 delta_p_regen = delta_p_regen_channels(
112 mdot_F,
113 rho_F,
114 mu_F,
115 config.regen_cooling,
116 P_tank_F,
117 )
118 delta_p_feed_F = delta_p_feed_F_base + delta_p_regen
119 else:
120 delta_p_feed_F = delta_p_feed_F_base
121
122 # CRITICAL: Calculate injector pressures AFTER feed loss calculation (always, not just in else block)
123 P_inj_O = P_tank_O - delta_p_feed_O
124 P_inj_F = P_tank_F - delta_p_feed_F
125
126 if feed_iter < 2:
127 delta_p_inj_O = max(0.0, P_inj_O - Pc)
128 delta_p_inj_F = max(0.0, P_inj_F - Pc)
129
130 u_O_quick = np.sqrt(2 * delta_p_inj_O / rho_O) if delta_p_inj_O > 0 else 0.0
131 u_F_quick = np.sqrt(2 * delta_p_inj_F / rho_F) if delta_p_inj_F > 0 else 0.0
132
133 Re_O_quick = calculate_reynolds_number(rho_O, u_O_quick, d_hyd_core, mu_O)
134 Re_F_quick = calculate_reynolds_number(rho_F, u_F_quick, d_hyd_annulus, mu_F)
135
136 T_tank_O = 90.0
137 T_tank_F = 300.0
138 Cd_O_quick_base = cd_from_re(Re_O_quick, discharge_O, P_inlet=P_inj_O, T_inlet=T_tank_O)
139 Cd_F_quick_base = cd_from_re(Re_F_quick, discharge_F, P_inlet=P_inj_F, T_inlet=T_tank_F)
140 Cd_O_quick = min(Cd_O_quick_base, Cd_O_eff)
141 Cd_F_quick = min(Cd_F_quick_base, Cd_F_eff)
142
143 mdot_O = Cd_O_quick * A_core * np.sqrt(2 * rho_O * delta_p_inj_O)
144 mdot_F = Cd_F_quick * A_annulus * np.sqrt(2 * rho_F * delta_p_inj_F)
145
146 delta_p_inj_O = max(0.0, P_inj_O - Pc)
147 delta_p_inj_F = max(0.0, P_inj_F - Pc)
148
149 if P_inj_O < Pc:
150 mdot_O = 0.0
151 if P_inj_F < Pc:
152 mdot_F = 0.0
153
154 # Reynolds-based Cd evaluation with inlet conditions
155 u_O = mdot_O / (rho_O * A_core) if A_core > 0 else 0.0
156 u_F_axial = mdot_F / (rho_F * A_annulus) if A_annulus > 0 else 0.0
157 swirl_angle = np.deg2rad(geometry.annulus.swirl_angle)
158 u_F = u_F_axial / np.cos(swirl_angle) if np.cos(swirl_angle) > 1e-6 else u_F_axial
159
160 Re_O = calculate_reynolds_number(rho_O, u_O, d_hyd_core, mu_O)
161 Re_F = calculate_reynolds_number(rho_F, u_F, d_hyd_annulus, mu_F)
162
163 Cd_O_base = cd_from_re(Re_O, discharge_O, P_inlet=P_inj_O, T_inlet=90.0)
164 Cd_F_base = cd_from_re(Re_F, discharge_F, P_inlet=P_inj_F, T_inlet=300.0)
165 Cd_O = min(Cd_O_base, Cd_O_eff)
166 Cd_F = min(Cd_F_base, Cd_F_eff)
167
168 if delta_p_inj_O > 0:
169 mdot_O = Cd_O * A_core * np.sqrt(2 * rho_O * delta_p_inj_O)
170 else:
171 mdot_O = 0.0
172
173 if delta_p_inj_F > 0:
174 mdot_F = Cd_F * A_annulus * np.sqrt(2 * rho_F * delta_p_inj_F)
175 else:
176 mdot_F = 0.0
177
178 u_O = mdot_O / (rho_O * A_core) if A_core > 0 else 0.0
179 u_F_axial = mdot_F / (rho_F * A_annulus) if A_annulus > 0 else 0.0
180 u_F = u_F_axial / np.cos(swirl_angle) if np.cos(swirl_angle) > 1e-6 else u_F_axial
181
182 J = momentum_flux_ratio(rho_O, u_O, rho_F, u_F_axial)
183 MR = mdot_O / mdot_F if mdot_F > 0 else np.inf
184 TMR = thrust_momentum_ratio(J, MR)
185
186 if spray_cfg.spray_angle.model == "J":
187 theta = spray_angle_from_J(J, spray_cfg.spray_angle.k, spray_cfg.spray_angle.n)
188 else:
189 theta = spray_angle_from_TMR(TMR)
190
191 We_O = weber_number(rho_O, u_O, geometry.core.d_port, sigma_O)
192 We_F = weber_number(rho_F, u_F, d_hyd_annulus, sigma_F)
193
194 Oh_O = ohnesorge_number(mu_O, rho_O, sigma_O, geometry.core.d_port)
195 Oh_F = ohnesorge_number(mu_F, rho_F, sigma_F, d_hyd_annulus)
196
197 D32_O = smd_lefebvre(
198 geometry.core.d_port,
199 We_O,
200 Oh_O,
201 spray_cfg.smd.C,
202 spray_cfg.smd.m,
203 spray_cfg.smd.p,
204 )
205 D32_F = smd_lefebvre(
206 d_hyd_annulus,
207 We_F,
208 Oh_F,
209 spray_cfg.smd.C,
210 spray_cfg.smd.m,
211 spray_cfg.smd.p,
212 )
213
214 U_rel = np.sqrt(u_O ** 2 + u_F_axial ** 2)
215 tau_evap_O = tau_evap(D32_O, spray_cfg.evaporation.K)
216 x_star_O = xstar(U_rel, tau_evap_O)
217 x_star_combined = max(x_star_O, xstar(U_rel, tau_evap(D32_F, spray_cfg.evaporation.K)))
218
219 constraints_ok, violations = check_spray_constraints(We_O, We_F, x_star_combined, spray_cfg)
220
221 diagnostics.update(
222 {
223 "iterations": iteration + 1,
224 "constraints_satisfied": constraints_ok,
225 "violations": violations,
226 "J": J,
227 "TMR": TMR,
228 "theta": theta,
229 "We_O": We_O,
230 "We_F": We_F,
231 "D32_O": D32_O,
232 "D32_F": D32_F,
233 "x_star": x_star_combined,
234 "swirl_angle_deg": geometry.annulus.swirl_angle,
235 # Discharge coefficients
236 "Cd_O": float(Cd_O),
237 "Cd_F": float(Cd_F),
238 }
239 )
240
241 if constraints_ok:
242 break
243
244 Cd_O_eff *= Cd_reduction
245 Cd_O_eff = max(Cd_O_eff, discharge_O.Cd_min)
246 Cd_F_eff *= Cd_reduction
247 Cd_F_eff = max(Cd_F_eff, discharge_F.Cd_min)
248
249 return mdot_O, mdot_F, diagnostics
#define min(a, b)
Definition Arduino.h:42
#define max(a, b)
Definition Arduino.h:43
Tuple[float, float, Dict[str, Any]] solve(self, float P_tank_O, float P_tank_F, float Pc)
Definition coaxial.py:56
__init__(self, PintleEngineConfig engine_config)
Definition coaxial.py:44
float _annulus_hydraulic_diameter(float inner_diameter, float gap_thickness)
Definition coaxial.py:34
float _annulus_area(float inner_diameter, float gap_thickness)
Definition coaxial.py:29