STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
run_full_pipeline.py
Go to the documentation of this file.
1"""Run full pipeline and display all performance metrics"""
2
3import numpy as np
4import sys
5from pathlib import Path
6
7sys.path.insert(0, str(Path(__file__).parent.parent))
8
9from engine.pipeline.io import load_config
10from engine.core.runner import PintleEngineRunner
11from ui.flight_sim import setup_flight
12
13print("=" * 80)
14print("FULL PIPELINE PERFORMANCE ANALYSIS")
15print("=" * 80)
16
17# Load configuration
18config_path = Path(__file__).parent.parent / "configs" / "default.yaml"
19config = load_config(str(config_path))
20
21# Initialize runner
22runner = PintleEngineRunner(config)
23
24# Test at target operating point from engine specs
25P_tank_O = 400 * 6894.76 # psi to Pa (from previous analysis)
26P_tank_F = 300 * 6894.76 # psi to Pa (from previous analysis)
27
28print(f"\nINPUT CONDITIONS:")
29print(f" P_tank_O = {P_tank_O/6894.76:.0f} psi ({P_tank_O/1e6:.2f} MPa)")
30print(f" P_tank_F = {P_tank_F/6894.76:.0f} psi ({P_tank_F/1e6:.2f} MPa)")
31
32# Run pipeline
33print(f"\n{'='*80}")
34print("SOLVING FOR CHAMBER PRESSURE AND CALCULATING PERFORMANCE...")
35print(f"{'='*80}")
36
37results = runner.evaluate(P_tank_O, P_tank_F)
38
39# Extract diagnostics if available
40diagnostics = results.get('diagnostics', {})
41if isinstance(diagnostics, list):
42 diagnostics = diagnostics[0] if diagnostics else {}
43
44print(f"\n{'='*80}")
45print("CHAMBER PERFORMANCE")
46print(f"{'='*80}")
47
48Pc = results['Pc']
49print(f" Solved Pc = {Pc/6894.76:.1f} psi ({Pc/1e6:.3f} MPa)")
50
51# Mass flow rates
52mdot_O = results['mdot_O']
53mdot_F = results['mdot_F']
54mdot_total = mdot_O + mdot_F
55MR = results['MR']
56
57print(f"\n Mass Flow Rates:")
58print(f" Oxidizer: {mdot_O:.4f} kg/s")
59print(f" Fuel: {mdot_F:.4f} kg/s")
60print(f" Total: {mdot_total:.4f} kg/s")
61print(f" Mixture Ratio (O/F): {MR:.3f}")
62
63# Combustion properties
64cstar_actual = results['cstar_actual']
65eta_cstar = diagnostics.get('eta_cstar', np.nan)
66
67print(f"\n Combustion Properties:")
68print(f" c*_actual = {cstar_actual:.1f} m/s ({cstar_actual*3.28084:.0f} ft/s)")
69if not np.isnan(eta_cstar):
70 print(f" η_c* (combustion efficiency) = {eta_cstar:.4f}")
71
72# Chamber intrinsics
73Tc = diagnostics.get('Tc', np.nan)
74gamma = diagnostics.get('gamma', np.nan)
75R = diagnostics.get('R', np.nan)
76M = diagnostics.get('M', np.nan)
77
78if not np.isnan(Tc):
79 print(f" Chamber Temperature: {Tc:.1f} K ({Tc*9/5-459.67:.0f} °F)")
80if not np.isnan(gamma):
81 print(f" Gamma: {gamma:.4f}")
82if not np.isnan(R):
83 print(f" Gas Constant R: {R:.2f} J/(kg·K)")
84if np.isfinite(M):
85 print(f" Molecular Weight: {M:.2f} kg/kmol ({M:.2f} lb/lbmol)")
86else:
87 print(" Molecular Weight: unavailable from CEA cache")
88
89# Performance metrics
90F = results['F']
91Isp = results['Isp']
92F_momentum = results.get('F_momentum', np.nan)
93F_pressure = results.get('F_pressure', np.nan)
94P_exit = results.get('P_exit', np.nan)
95v_exit = results.get('v_exit', np.nan)
96
97print(f"\n Performance Metrics:")
98print(f" Thrust: {F/1000:.3f} kN ({F:.1f} N)")
99if not np.isnan(F_momentum):
100 print(f" Momentum component: {F_momentum/1000:.3f} kN")
101if not np.isnan(F_pressure):
102 print(f" Pressure component: {F_pressure/1000:.3f} kN")
103print(f" Specific Impulse: {Isp:.2f} s")
104if not np.isnan(v_exit):
105 print(f" Exit Velocity: {v_exit:.1f} m/s")
106if not np.isnan(P_exit):
107 print(f" Exit Pressure: {P_exit/6894.76:.2f} psi ({P_exit/1e6:.3f} MPa)")
108
109print(f"\n{'='*80}")
110print("INJECTOR PERFORMANCE")
111print(f"{'='*80}")
112
113# Calculate injector pressures
114from engine.pipeline.feed_loss import delta_p_feed
115
116delta_p_feed_O = delta_p_feed(mdot_O, config.fluids["oxidizer"].density,
117 config.feed_system["oxidizer"], P_tank_O)
118
119# For fuel: add regenerative cooling pressure drop if enabled
120delta_p_feed_F_base = delta_p_feed(mdot_F, config.fluids["fuel"].density,
121 config.feed_system["fuel"], P_tank_F)
122
123delta_p_regen = 0.0
124if config.regen_cooling is not None and config.regen_cooling.enabled:
125 from engine.pipeline.thermal.regen_cooling import delta_p_regen_channels
126 mu_F = config.fluids["fuel"].viscosity
127 delta_p_regen = delta_p_regen_channels(
128 mdot_F, config.fluids["fuel"].density, mu_F, config.regen_cooling, P_tank_F
129 )
130 delta_p_feed_F = delta_p_feed_F_base + delta_p_regen
131else:
132 delta_p_feed_F = delta_p_feed_F_base
133
134P_inj_O = P_tank_O - delta_p_feed_O
135P_inj_F = P_tank_F - delta_p_feed_F
136
137delta_p_inj_O = P_inj_O - Pc
138delta_p_inj_F = P_inj_F - Pc
139
140print(f"\n Pressure Drops:")
141print(f" Feed System (LOX):")
142print(f" P_tank = {P_tank_O/6894.76:.1f} psi")
143print(f" Δp_feed = {delta_p_feed_O/6894.76:.2f} psi")
144print(f" P_injector = {P_inj_O/6894.76:.1f} psi")
145print(f" Δp_injector = {delta_p_inj_O/6894.76:.1f} psi")
146print(f" Feed System (Fuel):")
147print(f" P_tank = {P_tank_F/6894.76:.1f} psi")
148print(f" Δp_feed_base = {delta_p_feed_F_base/6894.76:.2f} psi")
149if config.regen_cooling is not None and config.regen_cooling.enabled:
150 print(f" Δp_regen_cooling = {delta_p_regen/6894.76:.2f} psi")
151 print(f" Δp_feed_total = {delta_p_feed_F/6894.76:.2f} psi")
152print(f" P_injector = {P_inj_F/6894.76:.1f} psi")
153print(f" Δp_injector = {delta_p_inj_F/6894.76:.1f} psi")
154
155# Injector geometry
156from engine.core.geometry import get_effective_areas, get_hydraulic_diameters
157
158A_LOX, A_fuel = get_effective_areas(config.injector.geometry)
159d_hyd_O, d_hyd_F = get_hydraulic_diameters(config.injector.geometry)
160
161print(f"\n Injector Geometry:")
162print(f" LOX:")
163print(f" Number of orifices: {config.injector.geometry.lox.n_orifices}")
164print(f" Orifice diameter: {config.injector.geometry.lox.d_orifice*1000:.2f} mm")
165print(f" Total area: {A_LOX*1e6:.4f} mm²")
166print(f" Hydraulic diameter: {d_hyd_O*1000:.2f} mm")
167print(f" Fuel:")
168print(f" Pintle tip diameter: {config.injector.geometry.fuel.d_pintle_tip*1000:.2f} mm")
169print(f" Gap height: {config.injector.geometry.fuel.h_gap*1000:.2f} mm")
170print(f" Total area: {A_fuel*1e6:.4f} mm²")
171print(f" Hydraulic diameter: {d_hyd_F*1000:.2f} mm")
172
173# Flow velocities
174rho_O = config.fluids["oxidizer"].density
175rho_F = config.fluids["fuel"].density
176
177u_O = mdot_O / (rho_O * A_LOX)
178u_F = mdot_F / (rho_F * A_fuel)
179
180print(f"\n Flow Velocities:")
181print(f" LOX: {u_O:.2f} m/s")
182print(f" Fuel: {u_F:.2f} m/s")
183
184# Discharge coefficients
185from engine.core.discharge import cd_from_re, calculate_reynolds_number
186
187mu_O = config.fluids["oxidizer"].viscosity
188mu_F = config.fluids["fuel"].viscosity
189
190Re_O = calculate_reynolds_number(rho_O, u_O, d_hyd_O, mu_O)
191Re_F = calculate_reynolds_number(rho_F, u_F, d_hyd_F, mu_F)
192
193Cd_O = cd_from_re(Re_O, config.discharge["oxidizer"])
194Cd_F = cd_from_re(Re_F, config.discharge["fuel"])
195
196print(f"\n Discharge Coefficients:")
197print(f" LOX: Cd = {Cd_O:.4f} (Re = {Re_O:.0f})")
198print(f" Fuel: Cd = {Cd_F:.4f} (Re = {Re_F:.0f})")
199
200# Spray diagnostics
201J = diagnostics.get('J', np.nan)
202TMR = diagnostics.get('TMR', np.nan)
203theta = diagnostics.get('theta', np.nan)
204We_O = diagnostics.get('We_O', np.nan)
205We_F = diagnostics.get('We_F', np.nan)
206D32_O = diagnostics.get('D32_O', np.nan)
207D32_F = diagnostics.get('D32_F', np.nan)
208x_star = diagnostics.get('x_star', np.nan)
209
210print(f"\n{'='*80}")
211print("SPRAY DIAGNOSTICS")
212print(f"{'='*80}")
213
214if not np.isnan(J):
215 print(f" Momentum Flux Ratio (J): {J:.3f}")
216if not np.isnan(TMR):
217 print(f" Thrust/Momentum Ratio (TMR): {TMR:.3f}")
218if not np.isnan(theta):
219 print(f" Spray Angle (θ): {theta*180/np.pi:.1f}°")
220if not np.isnan(We_O):
221 print(f" Weber Number (LOX): {We_O:.0f}")
222if not np.isnan(We_F):
223 print(f" Weber Number (Fuel): {We_F:.0f}")
224if not np.isnan(D32_O):
225 print(f" Sauter Mean Diameter (LOX): {D32_O*1e6:.2f} μm")
226if not np.isnan(D32_F):
227 print(f" Sauter Mean Diameter (Fuel): {D32_F*1e6:.2f} μm")
228if not np.isnan(x_star):
229 print(f" Evaporation Length (x*): {x_star*1000:.2f} mm")
230
231constraints_satisfied = diagnostics.get('constraints_satisfied', False)
232print(f" Spray Constraints Satisfied: {constraints_satisfied}")
233
234# Cooling diagnostics
235cooling = results.get("cooling", {})
236if cooling:
237 print(f"\n{'='*80}")
238 print("COOLING SUMMARY")
239 print(f"{'='*80}")
240
241 regen = cooling.get("regen")
242 if regen and regen.get("enabled", False):
243 print(" Regenerative Cooling:")
244 print(f" Coolant outlet temperature: {regen['coolant_outlet_temperature']:.1f} K")
245 print(f" Heat removed: {regen['heat_removed']/1000:.1f} kW")
246 print(f" Hot-side heat flux: {regen['overall_heat_flux']/1000:.1f} kW/m²")
247 if 'mdot_coolant' in regen:
248 print(f" Coolant flow through channels: {regen['mdot_coolant']:.3f} kg/s")
249 print(f" Wall temperature (hot/cool): {regen['wall_temperature_hot']:.1f} K / {regen['wall_temperature_coolant']:.1f} K")
250 if regen.get('film_effectiveness', 0.0) > 0:
251 print(f" Film effectiveness applied: {regen['film_effectiveness']:.2f}")
252
253 film = cooling.get("film")
254 if film and film.get("enabled", False):
255 print(" Film Cooling:")
256 print(f" Mass fraction: {film['mass_fraction']:.3f}")
257 print(f" Effectiveness: {film['effectiveness']:.2f}")
258 print(f" Film mass flow: {film['mdot_film']:.3f} kg/s")
259 print(f" Heat-flux reduction factor: {film['heat_flux_factor']:.2f}")
260
261 ablative = cooling.get("ablative")
262 if ablative and ablative.get("enabled", False):
263 print(" Ablative Cooling:")
264 print(f" Recession rate: {ablative['recession_rate']*1e6:.3f} µm/s")
265 print(f" Effective heat flux: {ablative['effective_heat_flux']/1000:.1f} kW/m²")
266
267# ==============================================================================
268# FLIGHT SIMULATION
269# ==============================================================================
270print(f"\n{'='*80}")
271print("FLIGHT SIMULATION")
272print(f"{'='*80}")
273
274try:
275 rocket = setup_flight(config, F, mdot_O, mdot_F)
276
277 # Display summary results
278 print(f"\n{'-'*80}")
279 print("FLIGHT RESULTS")
280 print(f"{'-'*80}")
281 print(f" Apogee: {rocket.apogee:.2f} m")
282 print(f" Max Velocity: {rocket.maxVelocity:.2f} m/s")
283 print(f" Flight Time: {rocket.totalTime:.2f} s")
284 print(f" Burn Time: {rocket.motor.burnOut:.2f} s")
285 print(f" Launch Rail Exit Velocity: {rocket.outOfRailVelocity:.2f} m/s")
286
287except Exception as e:
288 print(f"\n[ERROR] Flight simulation failed: {e}")
289 print("Skipping flight simulation section.")
290
291# Compare to target
292print(f"\n{'='*80}")
293print("COMPARISON TO TARGET (from engine specs)")
294print(f"{'='*80}")
295
296target_mdot = 1.81
297target_F = 5.30753
298target_MR = 2.36
299target_Isp = 299.0
300
301print(f" Mass Flow:")
302print(f" Target: {target_mdot:.3f} kg/s")
303print(f" Actual: {mdot_total:.3f} kg/s")
304print(f" Difference: {mdot_total - target_mdot:.3f} kg/s ({abs(mdot_total - target_mdot)/target_mdot*100:.1f}%)")
305
306print(f"\n Thrust:")
307print(f" Target: {target_F:.3f} kN")
308print(f" Actual: {F/1000:.3f} kN")
309print(f" Difference: {F/1000 - target_F:.3f} kN ({abs(F/1000 - target_F)/target_F*100:.1f}%)")
310
311print(f"\n Mixture Ratio:")
312print(f" Target: {target_MR:.2f}")
313print(f" Actual: {MR:.2f}")
314print(f" Difference: {MR - target_MR:.2f} ({abs(MR - target_MR)/target_MR*100:.1f}%)")
315
316print(f"\n Specific Impulse:")
317print(f" Target: {target_Isp:.1f} s")
318print(f" Actual: {Isp:.1f} s")
319print(f" Difference: {Isp - target_Isp:.1f} s ({abs(Isp - target_Isp)/target_Isp*100:.1f}%)")
320
321print(f"\n{'='*80}")
322print("[OK] FULL PIPELINE ANALYSIS COMPLETE")
323print(f"{'='*80}")
324