""" 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"} ]