Spaces:
Sleeping
Sleeping
| """ | |
| PHI-Arc Engine PHM — Liquid Propellant Rocket Engine Model | |
| Reference: RS-25 / RL10 class expander/bleed cycle | |
| Certification: NASA-STD-5012, MIL-STD-1540, ECSS-E-ST-32-02 | |
| """ | |
| import numpy as np | |
| from typing import Dict, List | |
| from .base_engine import BaseEngine | |
| GAMMA = 1.25 | |
| CP = 2800.0 | |
| P_SL = 101325.0 | |
| T_ISA_SL = 288.15 | |
| LAPSE = 0.0065 | |
| LHV_LOX_LH2 = 119.90e6 | |
| class RocketEngine(BaseEngine): | |
| def __init__(self): | |
| super().__init__( | |
| name="RS-25 Class Rocket Engine", | |
| engine_type="rocket", | |
| ata_chapters={ | |
| "71-00": "Powerplant", | |
| "73-10": "Propellant System", | |
| "75-40": "Nozzle", | |
| "77-10": "Engine Controls" | |
| } | |
| ) | |
| self._build_fault_library() | |
| self.cert_standards = [ | |
| "NASA-STD-5012 — Structural Requirements and Factors of Safety", | |
| "MIL-STD-1540 — Product Verification Requirements", | |
| "ECSS-E-ST-32-02 — Structural Factors of Safety", | |
| "AIAA S-081 — Liquid Rocket Engine Standards" | |
| ] | |
| self.eta_c = 0.85 | |
| self.eta_b = 0.98 | |
| self.eta_n = 0.97 | |
| self.A_throat = 0.08 | |
| self.A_exit = 4.0 | |
| self.Of_ratio = 6.0 | |
| self.Pc = 20.0e6 | |
| self.EGT_REDLINE = 3500.0 | |
| self.EGT_WARN = 3300.0 | |
| def _build_fault_library(self): | |
| self.fault_library = [ | |
| { | |
| "id": "F1", "name": "F1 — Injector Face Erosion / Blockage", "ata": "ATA 73-10", | |
| "mechanism": "Combustion instability or cavitation causes injector face erosion or blockage. Propellant distribution becomes asymmetric, combustion efficiency drops, chamber pressure oscillates.", | |
| "leading": "Pc oscillation↑ + η_c↓ + thrust variation↑", "color": "#e74c3c", | |
| "levels": [[0.99,1.00,1.00],[0.95,1.00,1.00],[0.91,1.00,1.00]], | |
| "actions": [ | |
| "Injector face inspection (AMM 73-10-00-200-801)", | |
| "Injector element replacement (AMM 73-10-00-400-801)", | |
| "Full injector assembly replacement (AMM 73-10-00-720-801)" | |
| ], | |
| "cert_requirements": [ | |
| "NASA-STD-5012 4.1 — Injector must survive declared combustion cycles", | |
| "MIL-STD-1540 5.2.1 — Injector flow uniformity ≥ 95%", | |
| "AIAA S-081 3.1 — Combustion stability margin ≥ 20%" | |
| ] | |
| }, | |
| { | |
| "id": "F2", "name": "F2 — Chamber Wall Degradation / Burn-through", "ata": "ATA 71-00", | |
| "mechanism": "Regenerative cooling channel blockage or wall thinning causes hot spots. Local wall temperature exceeds material limit, potential for catastrophic burn-through.", | |
| "leading": "Wall temp↑↑ + cooling pressure↓ + chamber deformation", "color": "#f39c12", | |
| "levels": [[1.00,0.99,1.00],[1.00,0.96,1.00],[1.00,0.92,1.00]], | |
| "actions": [ | |
| "Chamber wall thickness inspection (AMM 71-00-00-200-801)", | |
| "Cooling channel flow test + repair (AMM 71-00-00-400-801)", | |
| "Chamber replacement (AMM 71-00-00-720-801)" | |
| ], | |
| "cert_requirements": [ | |
| "NASA-STD-5012 4.2 — Chamber wall life ≥ declared thermal cycles", | |
| "ECSS-E-ST-32-02 5.1 — Safety factor ≥ 1.25 for pressure vessels", | |
| "AIAA S-081 3.2 — Burn-through probability ≤ 1e-6 per mission" | |
| ] | |
| }, | |
| { | |
| "id": "F3", "name": "F3 — Nozzle Throat Erosion", "ata": "ATA 75-40", | |
| "mechanism": "Ablative or regeneratively-cooled nozzle throat erodes under high heat flux. Throat area increases, chamber pressure drops, thrust decreases, Isp degrades.", | |
| "leading": "Pc↓ + thrust↓ + Isp↓ (throat area↑)", "color": "#9b59b6", | |
| "levels": [[1.00,1.00,0.99],[1.00,1.00,0.96],[1.00,1.00,0.92]], | |
| "actions": [ | |
| "Nozzle throat dimension check (AMM 75-40-00-200-801)", | |
| "Nozzle throat insert replacement (AMM 75-40-00-400-801)", | |
| "Full nozzle assembly replacement (AMM 75-40-00-720-801)" | |
| ], | |
| "cert_requirements": [ | |
| "NASA-STD-5012 4.3 — Nozzle life ≥ declared firings", | |
| "MIL-STD-1540 5.3.1 — Throat area tolerance ±2%", | |
| "AIAA S-081 3.3 — Nozzle efficiency ≥ 96%" | |
| ] | |
| }, | |
| { | |
| "id": "F4", "name": "F4 — Turbopump Cavitation / Bearing Wear", "ata": "ATA 73-10", | |
| "mechanism": "LOX or LH2 turbopump cavitation causes flow rate drop and vibration. Bearing wear increases, seal leakage risk, potential for pump failure.", | |
| "leading": "Pump vibration↑ + flow rate↓ + NPSH↓", "color": "#3498db", | |
| "levels": [[1.00,1.00,1.00,0.02],[1.00,1.00,1.00,0.05],[1.00,1.00,1.00,0.08]], | |
| "actions": [ | |
| "Turbopump vibration analysis (AMM 73-10-00-200-801)", | |
| "Turbopump bearing/seal replacement (AMM 73-10-00-400-801)", | |
| "Turbopump assembly replacement (AMM 73-10-00-720-801)" | |
| ], | |
| "cert_requirements": [ | |
| "NASA-STD-5012 4.4 — Turbopump life ≥ declared cycles", | |
| "MIL-STD-1540 5.4.1 — Cavitation margin ≥ 10% NPSH", | |
| "AIAA S-081 3.4 — Pump efficiency ≥ 75%" | |
| ] | |
| }, | |
| { | |
| "id": "F5", "name": "F5 — O/F Ratio Drift / Control Valve Fault", "ata": "ATA 77-10", | |
| "mechanism": "Propellant control valve malfunction causes oxidizer-to-fuel ratio drift. Mixture ratio outside design envelope causes combustion temperature excursion.", | |
| "leading": "O/F ratio drift + Tc excursion + exhaust color change", "color": "#1abc9c", | |
| "levels": [[0.99,1.00,1.00],[0.96,1.00,1.00],[0.93,1.00,1.00]], | |
| "actions": [ | |
| "Control valve functional test (AMM 77-10-00-040-801)", | |
| "Control valve actuator replacement (AMM 77-10-00-400-801)", | |
| "Full propellant control system overhaul (AMM 77-10-00-720-801)" | |
| ], | |
| "cert_requirements": [ | |
| "NASA-STD-5012 4.5 — O/F control accuracy ±2%", | |
| "MIL-STD-1540 5.5.1 — Valve response time ≤ 50ms", | |
| "AIAA S-081 3.5 — Mixture ratio stability margin ≥ 10%" | |
| ] | |
| } | |
| ] | |
| def compute_healthy_baseline(self, flight_conditions: Dict) -> Dict[str, float]: | |
| Pc = flight_conditions.get("chamber_pressure_mpa", 20.0) * 1e6 | |
| Of = flight_conditions.get("of_ratio", 6.0) | |
| # Rocket chamber conditions | |
| Tc = 3500.0 | |
| mdot_ox = Pc * self.A_throat / (3000 * Of / (Of + 1)) | |
| mdot_fuel = mdot_ox / Of | |
| mdot_total = mdot_ox + mdot_fuel | |
| # Nozzle expansion | |
| Pe = P_SL | |
| Ve = (2 * CP * Tc * (1 - (Pe/Pc)**0.2))**0.5 | |
| thrust = mdot_total * Ve + (Pe - P_SL) * self.A_exit | |
| Isp = thrust / (mdot_total * 9.81) | |
| return { | |
| "EGT": Tc - 273.15, "thrust": thrust/1000, "Isp": Isp, | |
| "Pc": Pc/1e6, "mdot": mdot_total, "Of": Of | |
| } | |
| def compute_fault_signatures(self, baseline: Dict, flight_conditions: Dict) -> List[List[Dict]]: | |
| results = [] | |
| for f in self.fault_library: | |
| lvl_vals = [] | |
| for lv in range(3): | |
| m = f["levels"][lv] | |
| Pc = flight_conditions.get("chamber_pressure_mpa", 20.0) * 1e6 * m[0] | |
| Of = flight_conditions.get("of_ratio", 6.0) | |
| eta_c = self.eta_c * m[1] | |
| eta_n = self.eta_n * m[2] | |
| mech_loss = m[3] if len(m) > 3 else 0 | |
| Tc = 3500.0 * eta_c | |
| mdot_ox = Pc * self.A_throat / (3000 * Of / (Of + 1)) * (1 - mech_loss) | |
| mdot_fuel = mdot_ox / Of | |
| mdot_total = mdot_ox + mdot_fuel | |
| Pe = P_SL | |
| Ve = (2 * CP * Tc * (1 - (Pe/Pc)**0.2))**0.5 * eta_n | |
| thrust = mdot_total * Ve + (Pe - P_SL) * self.A_exit | |
| Isp = thrust / (mdot_total * 9.81) if mdot_total > 0 else 0 | |
| lvl_vals.append({ | |
| "EGT": Tc - 273.15, "thrust": thrust/1000, "Isp": Isp, | |
| "Pc": Pc/1e6, "mdot": mdot_total, "Of": Of | |
| }) | |
| results.append(lvl_vals) | |
| return results | |
| def classify_fault(self, measured: Dict, baseline: Dict, fault_sigs: List[List[Dict]]) -> Dict: | |
| params = ["EGT", "thrust", "Isp", "Pc"] | |
| user_sig = np.zeros(4) | |
| for i, p in enumerate(params): | |
| if p in measured and p in baseline: | |
| denom = baseline[p] * 0.10 if p in ["thrust", "Isp", "Pc"] else (self.EGT_REDLINE - baseline[p]) | |
| user_sig[i] = (measured[p] - baseline[p]) / denom if denom != 0 else 0 | |
| scores = [] | |
| for fi, f in enumerate(self.fault_library): | |
| best_score = -1e9 | |
| best_lvl = 0 | |
| for lv in range(3): | |
| fv = fault_sigs[fi][lv] | |
| sig = np.zeros(4) | |
| for i, p in enumerate(params): | |
| denom = baseline[p] * 0.10 if p in ["thrust", "Isp", "Pc"] else (self.EGT_REDLINE - baseline[p]) | |
| sig[i] = (fv[p] - baseline[p]) / denom if denom != 0 else 0 | |
| denom = (np.linalg.norm(sig) * np.linalg.norm(user_sig)) + 1e-9 | |
| score = np.dot(sig, user_sig) / denom | |
| if score > best_score: | |
| best_score = score | |
| best_lvl = lv | |
| sev = max(0.0, min(1.0, best_score)) | |
| scores.append({"fi": fi, "f": f, "sev": sev, "lvl": best_lvl, "sig": user_sig}) | |
| scores.sort(key=lambda x: x["sev"], reverse=True) | |
| magnitude = np.linalg.norm(user_sig) | |
| status = "HEALTHY" if magnitude < 0.12 else ("INDETERMINATE" if scores[0]["sev"] < 0.30 else "FAULT_DETECTED") | |
| return { | |
| "base": baseline, "faults": fault_sigs, "scores": scores, | |
| "user_sig": user_sig, "status": status, | |
| "thresholds": { | |
| "EGT_warn": self.EGT_WARN, "EGT_maint": self.EGT_REDLINE, | |
| "Pc_warn": baseline["Pc"] * 0.95, "Pc_maint": baseline["Pc"] * 0.90 | |
| }, | |
| "deviations": {p: user_sig[i] for i, p in enumerate(params)} | |
| } | |
| def get_parameter_labels(self) -> List[str]: | |
| return ["EGT", "Thrust", "Isp", "Chamber Pressure"] | |
| def get_parameter_units(self) -> Dict[str, str]: | |
| return {"EGT": "°C", "thrust": "kN", "Isp": "s", "Pc": "MPa"} | |
| def get_input_fields(self) -> List[Dict]: | |
| return [ | |
| {"name": "chamber_pressure", "label": "Chamber Pressure", "default": 20.0, "unit": "MPa", "type": "number"}, | |
| {"name": "of_ratio", "label": "O/F Ratio", "default": 6.0, "unit": "", "type": "number"}, | |
| {"name": "EGT", "label": "Combustor Temp", "default": 3200.0, "unit": "°C", "type": "number"}, | |
| {"name": "thrust", "label": "Thrust", "default": 2000.0, "unit": "kN", "type": "number"}, | |
| {"name": "Isp", "label": "Specific Impulse", "default": 450.0, "unit": "s", "type": "number"}, | |
| {"name": "Pc", "label": "Chamber Pressure (measured)", "default": 20.0, "unit": "MPa", "type": "number"} | |
| ] | |