STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
system_diagnostics.py
Go to the documentation of this file.
1"""Comprehensive system diagnostics for validating all dynamics and identifying issues."""
2
3import numpy as np
4from typing import Dict, Any, Optional, List
5from engine.pipeline.config_schemas import PintleEngineConfig
6from engine.core.runner import PintleEngineRunner
7
8
10 """Comprehensive diagnostics for engine system dynamics."""
11
12 def __init__(self, config: PintleEngineConfig):
13 self.config = config
15
16 def diagnose_all(self, P_tank_O: float, P_tank_F: float) -> Dict[str, Any]:
17 """
18 Run comprehensive diagnostics on all system components.
19
20 Returns:
21 --------
22 diagnostics : dict
23 Comprehensive diagnostic results with issues and recommendations
24 """
25 results = {}
26
27 # Run engine evaluation
28 try:
29 engine_results = self.runner.evaluate(P_tank_O, P_tank_F)
30 results["engine_evaluation"] = engine_results
31 except Exception as e:
32 results["engine_evaluation"] = {"error": str(e)}
33 return results
34
35 # Extract key parameters
36 Pc = engine_results.get("Pc", 0.0)
37 MR = engine_results.get("MR", 0.0)
38 mdot_total = engine_results.get("mdot_total", 0.0)
39 F = engine_results.get("F", 0.0)
40 Isp = engine_results.get("Isp", 0.0)
41 Cf_actual = engine_results.get("Cf_actual", 0.0)
42 Cf_ideal = engine_results.get("Cf_ideal", 0.0)
43 Cf_theoretical = engine_results.get("Cf_theoretical", 0.0)
44 v_exit = engine_results.get("v_exit", 0.0)
45 T_exit = engine_results.get("T_exit", 0.0)
46 P_exit = engine_results.get("P_exit", 0.0)
47 M_exit = engine_results.get("M_exit", 0.0)
48
49 # Diagnose each component
50 from engine.pipeline.config_schemas import ensure_chamber_geometry
51 cg = ensure_chamber_geometry(self.config)
52 results["cf_analysis"] = self._diagnose_cf(Cf_actual, Cf_ideal, Cf_theoretical, F, Pc, cg.A_throat)
53 results["velocity_analysis"] = self._diagnose_velocity(v_exit, T_exit, P_exit, M_exit, engine_results)
54 results["feed_system_analysis"] = self._diagnose_feed_system(P_tank_O, P_tank_F, engine_results)
55 results["chamber_dynamics"] = self._diagnose_chamber_dynamics(engine_results)
56 results["stability_analysis"] = self._diagnose_stability(engine_results)
57
58 # Overall health check
59 results["health_status"] = self._overall_health(results)
60
61 return results
62
63 def _diagnose_cf(self, Cf_actual: float, Cf_ideal: float, Cf_theoretical: float,
64 F: float, Pc: float, A_throat: float) -> Dict[str, Any]:
65 """Diagnose thrust coefficient calculations."""
66 issues = []
67 recommendations = []
68
69 # Check if Cf_actual is reasonable
70 if Cf_actual <= 0:
71 issues.append("Cf_actual is zero or negative - thrust calculation may be wrong")
72 elif Cf_actual < 1.0:
73 issues.append(f"Cf_actual ({Cf_actual:.4f}) is unusually low (< 1.0)")
74 recommendations.append("Check exit velocity and pressure calculations")
75 elif Cf_actual > 2.5:
76 issues.append(f"Cf_actual ({Cf_actual:.4f}) is unusually high (> 2.5)")
77 recommendations.append("Check for calculation errors or underexpanded nozzle")
78
79 # Check relationship between Cf values
80 if Cf_actual > Cf_ideal * 1.2:
81 issues.append(f"Cf_actual ({Cf_actual:.4f}) > Cf_ideal ({Cf_ideal:.4f}) by >20%")
82 recommendations.append("May indicate underexpanded nozzle or calculation error")
83
84 if abs(Cf_actual - Cf_theoretical) / max(Cf_theoretical, 0.01) > 0.1:
85 issues.append(f"Cf_actual ({Cf_actual:.4f}) differs significantly from Cf_theoretical ({Cf_theoretical:.4f})")
86 recommendations.append("Check momentum and pressure thrust components")
87
88 # Verify thrust equation: F = Cf_actual * Pc * A_throat
89 F_calculated = Cf_actual * Pc * A_throat
90 if abs(F - F_calculated) / max(F, 1.0) > 0.01:
91 issues.append(f"Thrust equation mismatch: F={F:.1f} N vs Cf*Pc*At={F_calculated:.1f} N")
92 recommendations.append("Verify Cf_actual calculation")
93
94 return {
95 "Cf_actual": Cf_actual,
96 "Cf_ideal": Cf_ideal,
97 "Cf_theoretical": Cf_theoretical,
98 "F": F,
99 "F_expected": F_calculated,
100 "issues": issues,
101 "recommendations": recommendations,
102 "status": "OK" if len(issues) == 0 else "ISSUES"
103 }
104
105 def _diagnose_velocity(self, v_exit: float, T_exit: float, P_exit: float,
106 M_exit: float, engine_results: Dict) -> Dict[str, Any]:
107 """Diagnose velocity and Mach number calculations."""
108 issues = []
109 recommendations = []
110
111 # Check exit velocity
112 if v_exit <= 0:
113 issues.append("Exit velocity is zero or negative")
114 elif v_exit < 1000:
115 issues.append(f"Exit velocity ({v_exit:.1f} m/s) is unusually low")
116 recommendations.append("Check exit temperature and Mach number calculations")
117 elif v_exit > 5000:
118 issues.append(f"Exit velocity ({v_exit:.1f} m/s) is unusually high")
119 recommendations.append("Verify exit temperature and Mach number")
120
121 # Check exit Mach number
122 if M_exit <= 0:
123 issues.append("Exit Mach number is zero or negative")
124 elif M_exit < 1.0:
125 issues.append(f"Exit Mach number ({M_exit:.3f}) is subsonic - should be > 1.0 for supersonic nozzle")
126 recommendations.append("Check area-Mach number solver convergence")
127 elif M_exit > 10.0:
128 issues.append(f"Exit Mach number ({M_exit:.2f}) is unusually high (> 10)")
129 recommendations.append("Verify expansion ratio and area-Mach solver")
130
131 # Check exit temperature
132 Tc = engine_results.get("Tc", 0.0)
133 if T_exit <= 0:
134 issues.append("Exit temperature is zero or negative")
135 elif T_exit > Tc * 0.95:
136 issues.append(f"Exit temperature ({T_exit:.1f} K) is too close to chamber temperature ({Tc:.1f} K)")
137 recommendations.append("Check isentropic expansion calculation")
138 elif T_exit < Tc * 0.2:
139 issues.append(f"Exit temperature ({T_exit:.1f} K) is unusually low compared to chamber ({Tc:.1f} K)")
140 recommendations.append("Verify Mach number and isentropic relations")
141
142 # Check chamber Mach number
143 chamber_intrinsics = engine_results.get("chamber_intrinsics", {})
144 M_chamber = chamber_intrinsics.get("mach_number", 0.0)
145 if M_chamber <= 0:
146 issues.append("Chamber Mach number is zero or negative")
147 elif M_chamber > 0.5:
148 issues.append(f"Chamber Mach number ({M_chamber:.3f}) is unusually high (> 0.5)")
149 recommendations.append("Check mean velocity calculation in chamber")
150
151 # Verify velocity-Mach relationship: v = M * sqrt(gamma * R * T)
152 gamma = engine_results.get("gamma", 1.2)
153 R = engine_results.get("R", 300.0)
154 sound_speed = np.sqrt(gamma * R * T_exit)
155 v_expected = M_exit * sound_speed
156 if abs(v_exit - v_expected) / max(v_exit, 1.0) > 0.01:
157 issues.append(f"Velocity-Mach mismatch: v_exit={v_exit:.1f} m/s vs M*a={v_expected:.1f} m/s")
158 recommendations.append("Verify exit velocity calculation consistency")
159
160 return {
161 "v_exit": v_exit,
162 "M_exit": M_exit,
163 "T_exit": T_exit,
164 "P_exit": P_exit,
165 "M_chamber": M_chamber,
166 "issues": issues,
167 "recommendations": recommendations,
168 "status": "OK" if len(issues) == 0 else "ISSUES"
169 }
170
171 def _diagnose_feed_system(self, P_tank_O: float, P_tank_F: float,
172 engine_results: Dict) -> Dict[str, Any]:
173 """Diagnose feed system pressure losses."""
174 issues = []
175 recommendations = []
176
177 diagnostics = engine_results.get("diagnostics", {})
178 delta_p_feed_O = diagnostics.get("delta_p_feed_O", 0.0)
179 delta_p_feed_F = diagnostics.get("delta_p_feed_F", 0.0)
180 mdot_O = diagnostics.get("mdot_O", 0.0)
181 mdot_F = diagnostics.get("mdot_F", 0.0)
182
183 # Check LOX feed pressure loss
184 if delta_p_feed_O == 0.0 and mdot_O > 0.01:
185 issues.append(f"LOX feed pressure loss is zero with mdot_O={mdot_O:.4f} kg/s")
186 recommendations.append("Check feed system configuration (d_inlet, A_hydraulic, K_eff)")
187 recommendations.append("Verify delta_p_feed function is being called correctly")
188 elif delta_p_feed_O < 0:
189 issues.append(f"LOX feed pressure loss is negative: {delta_p_feed_O:.1f} Pa")
190 elif delta_p_feed_O > P_tank_O * 0.5:
191 issues.append(f"LOX feed pressure loss ({delta_p_feed_O/1e6:.2f} MPa) is >50% of tank pressure")
192 recommendations.append("Check feed system geometry - may be too restrictive")
193
194 # Check fuel feed pressure loss
195 if delta_p_feed_F == 0.0 and mdot_F > 0.01:
196 issues.append(f"Fuel feed pressure loss is zero with mdot_F={mdot_F:.4f} kg/s")
197 recommendations.append("Check feed system configuration")
198 elif delta_p_feed_F < 0:
199 issues.append(f"Fuel feed pressure loss is negative: {delta_p_feed_F:.1f} Pa")
200 elif delta_p_feed_F > P_tank_F * 0.5:
201 issues.append(f"Fuel feed pressure loss ({delta_p_feed_F/1e6:.2f} MPa) is >50% of tank pressure")
202 recommendations.append("Check feed system geometry")
203
204 # Check injector pressures
205 P_injector_O = diagnostics.get("P_injector_O", 0.0)
206 P_injector_F = diagnostics.get("P_injector_F", 0.0)
207 Pc = engine_results.get("Pc", 0.0)
208
209 if P_injector_O <= Pc:
210 issues.append(f"LOX injector pressure ({P_injector_O/1e6:.2f} MPa) <= chamber pressure ({Pc/1e6:.2f} MPa)")
211 recommendations.append("Increase tank pressure or reduce feed losses")
212
213 if P_injector_F <= Pc:
214 issues.append(f"Fuel injector pressure ({P_injector_F/1e6:.2f} MPa) <= chamber pressure ({Pc/1e6:.2f} MPa)")
215 recommendations.append("Increase tank pressure or reduce feed losses")
216
217 return {
218 "delta_p_feed_O": delta_p_feed_O,
219 "delta_p_feed_F": delta_p_feed_F,
220 "P_injector_O": P_injector_O,
221 "P_injector_F": P_injector_F,
222 "P_tank_O": P_tank_O,
223 "P_tank_F": P_tank_F,
224 "mdot_O": mdot_O,
225 "mdot_F": mdot_F,
226 "issues": issues,
227 "recommendations": recommendations,
228 "status": "OK" if len(issues) == 0 else "ISSUES"
229 }
230
231 def _diagnose_chamber_dynamics(self, engine_results: Dict) -> Dict[str, Any]:
232 """Diagnose chamber dynamics (L*, residence time, velocities)."""
233 issues = []
234 recommendations = []
235
236 chamber_intrinsics = engine_results.get("chamber_intrinsics", {})
237 Lstar = chamber_intrinsics.get("Lstar", 0.0)
238 residence_time = chamber_intrinsics.get("residence_time", 0.0)
239 velocity_mean = chamber_intrinsics.get("velocity_mean", 0.0)
240 velocity_throat = chamber_intrinsics.get("velocity_throat", 0.0)
241 mach_number = chamber_intrinsics.get("mach_number", 0.0)
242
243 # Check L*
244 if Lstar <= 0:
245 issues.append("L* is zero or negative")
246 elif Lstar < 0.5:
247 issues.append(f"L* ({Lstar:.3f} m) is unusually short (< 0.5 m)")
248 recommendations.append("May cause incomplete combustion - check chamber volume")
249 elif Lstar > 3.0:
250 issues.append(f"L* ({Lstar:.3f} m) is unusually long (> 3.0 m)")
251 recommendations.append("May cause excessive weight - optimize chamber geometry")
252
253 # Check residence time
254 if residence_time <= 0:
255 issues.append("Residence time is zero or negative")
256 elif residence_time < 1e-4:
257 issues.append(f"Residence time ({residence_time*1e3:.3f} ms) is unusually short")
258 recommendations.append("May cause incomplete combustion")
259 elif residence_time > 0.1:
260 issues.append(f"Residence time ({residence_time*1e3:.1f} ms) is unusually long")
261 recommendations.append("Check mass flow and chamber volume")
262
263 # Check velocities
264 if velocity_mean <= 0:
265 issues.append("Mean chamber velocity is zero or negative")
266 elif velocity_mean > 500:
267 issues.append(f"Mean chamber velocity ({velocity_mean:.1f} m/s) is unusually high")
268 recommendations.append("Check mean velocity calculation")
269
270 if velocity_throat <= 0:
271 issues.append("Throat velocity is zero or negative")
272 elif abs(velocity_throat - velocity_mean * 10) < velocity_mean: # Rough check
273 issues.append(f"Throat velocity ({velocity_throat:.1f} m/s) seems inconsistent with mean ({velocity_mean:.1f} m/s)")
274
275 return {
276 "Lstar": Lstar,
277 "residence_time": residence_time,
278 "velocity_mean": velocity_mean,
279 "velocity_throat": velocity_throat,
280 "mach_number": mach_number,
281 "issues": issues,
282 "recommendations": recommendations,
283 "status": "OK" if len(issues) == 0 else "ISSUES"
284 }
285
286 def _diagnose_stability(self, engine_results: Dict) -> Dict[str, Any]:
287 """Diagnose stability analysis results."""
288 issues = []
289 recommendations = []
290
291 stability = engine_results.get("stability_results", {})
292 is_stable = stability.get("is_stable", True)
293
294 if not is_stable:
295 issues.append("Engine is predicted to be unstable")
296 recommendations.append("Review stability analysis results")
297
298 chugging = stability.get("chugging", {})
299 chugging_freq = chugging.get("frequency", 0.0)
300 if chugging_freq <= 0:
301 issues.append("Chugging frequency is zero or negative")
302 elif chugging_freq < 1.0:
303 issues.append(f"Chugging frequency ({chugging_freq:.2f} Hz) is unusually low")
304
305 return {
306 "is_stable": is_stable,
307 "chugging_frequency": chugging_freq,
308 "issues": issues,
309 "recommendations": recommendations,
310 "status": "OK" if is_stable and len(issues) == 0 else "ISSUES"
311 }
312
313 def _overall_health(self, results: Dict) -> Dict[str, Any]:
314 """Overall system health assessment."""
315 all_issues = []
316 all_recommendations = []
317
318 for component, data in results.items():
319 if isinstance(data, dict) and "issues" in data:
320 all_issues.extend([f"{component}: {issue}" for issue in data["issues"]])
321 all_recommendations.extend([f"{component}: {rec}" for rec in data.get("recommendations", [])])
322
323 status = "HEALTHY" if len(all_issues) == 0 else "NEEDS_ATTENTION"
324 if len(all_issues) > 5:
325 status = "CRITICAL"
326
327 return {
328 "status": status,
329 "total_issues": len(all_issues),
330 "issues": all_issues,
331 "recommendations": all_recommendations
332 }
333
#define max(a, b)
Definition Arduino.h:43
Dict[str, Any] _diagnose_cf(self, float Cf_actual, float Cf_ideal, float Cf_theoretical, float F, float Pc, float A_throat)
Dict[str, Any] _diagnose_feed_system(self, float P_tank_O, float P_tank_F, Dict engine_results)
Dict[str, Any] _diagnose_stability(self, Dict engine_results)
Dict[str, Any] _diagnose_velocity(self, float v_exit, float T_exit, float P_exit, float M_exit, Dict engine_results)
Dict[str, Any] diagnose_all(self, float P_tank_O, float P_tank_F)
Dict[str, Any] _diagnose_chamber_dynamics(self, Dict engine_results)