""" PHI-Arc Engine PHM — Scramjet Engine Model Reference: X-51A Waverider / HyShot class Certification: MIL-STD-810, NASA-TM standards """ import numpy as np from typing import Dict, List from .base_engine import BaseEngine GAMMA = 1.36 # Supersonic combustion CP = 1200.0 P_SL = 101325.0 T_ISA_SL = 288.15 LAPSE = 0.0065 LHV_H2 = 119.90e6 class ScramjetEngine(BaseEngine): def __init__(self): super().__init__( name="X-51A Class Scramjet", engine_type="scramjet", ata_chapters={ "71-00": "Powerplant", "73-10": "Fuel System", "75-40": "Exhaust Nozzle", "77-10": "Engine Controls" } ) self._build_fault_library() self.cert_standards = [ "MIL-STD-810 — Environmental Engineering Considerations", "NASA-TM-2004-213086 — Scramjet Performance Metrics", "AIAA S-080 — Hypersonic Vehicle Standards", "MIL-HDBK-516 — Airworthiness Certification Criteria" ] self.eta_b = 0.90 self.eta_n = 0.92 self.A_inlet = 0.3 self.A_exit = 0.4 self.EGT_REDLINE = 2800.0 self.EGT_WARN = 2600.0 def _build_fault_library(self): self.fault_library = [ { "id": "F1", "name": "F1 — Inlet Unstart (Thermal Choking)", "ata": "ATA 71-00", "mechanism": "Thermal choking in isolator causes shock train ejection from combustor. Mass flow drops, pressure recovery collapses, thrust loss catastrophic.", "leading": "P0 recovery↓ + thrust↓ + isolator pressure spike", "color": "#e74c3c", "levels": [[0.98,1.00,1.00],[0.93,1.00,1.00],[0.88,1.00,1.00]], "actions": [ "Inlet/isolator inspection (AMM 71-00-00-200-801)", "Isolator boundary layer bleed repair (AMM 71-00-00-400-801)", "Inlet/isolator module replacement (AMM 71-00-00-720-801)" ], "cert_requirements": [ "NASA-TM-2004-213086 3.1 — Inlet start margin ≥ 8% Mach", "AIAA S-080 5.2 — Isolator pressure rise ratio ≥ 2.0", "MIL-HDBK-516 6.1.1 — Inlet must survive thermal choking recovery" ] }, { "id": "F2", "name": "F2 — Combustion Instability / Flashback", "ata": "ATA 73-10", "mechanism": "Fuel injection maldistribution or flameholding failure causes combustion instability. Oscillatory pressure loads, potential for structural damage.", "leading": "Pressure oscillation↑ + thrust variation↑ + η_b↓", "color": "#f39c12", "levels": [[1.00,0.98,1.00],[1.00,0.94,1.00],[1.00,0.89,1.00]], "actions": [ "Fuel injector flow uniformity test (AMM 73-10-00-200-801)", "Flameholder/cavity repair (AMM 73-10-00-400-801)", "Combustor module replacement (AMM 73-10-00-720-801)" ], "cert_requirements": [ "NASA-TM-2004-213086 3.2 — Combustion efficiency ≥ 85%", "AIAA S-080 5.3 — Pressure oscillation amplitude ≤ 5% mean", "MIL-HDBK-516 6.2.1 — Combustion stability margin ≥ 20%" ] }, { "id": "F3", "name": "F3 — Nozzle Throat Erosion / Thermal Deformation", "ata": "ATA 75-40", "mechanism": "Extreme thermal loading causes nozzle throat erosion and deformation. Expansion ratio changes, thrust vector shifts, Isp degrades.", "leading": "Thrust vector shift + Isp↓ + wall temp↑", "color": "#9b59b6", "levels": [[1.00,1.00,0.98],[1.00,1.00,0.95],[1.00,1.00,0.91]], "actions": [ "Nozzle throat dimension + profile check (AMM 75-40-00-200-801)", "Nozzle insert/cooling repair (AMM 75-40-00-400-801)", "Full nozzle assembly replacement (AMM 75-40-00-720-801)" ], "cert_requirements": [ "NASA-TM-2004-213086 3.3 — Nozzle efficiency ≥ 92%", "AIAA S-080 5.4 — Thrust vector alignment ≤ 0.5°", "MIL-HDBK-516 6.3.1 — Nozzle thermal deformation tolerance" ] }, { "id": "F4", "name": "F4 — Thermal Protection System Failure", "ata": "ATA 71-00", "mechanism": "Active cooling channel blockage or ablative liner depletion causes wall temperature exceedance. Structural integrity compromised.", "leading": "Wall temp↑↑ + cooling pressure↓ + structural strain↑", "color": "#3498db", "levels": [[1.00,1.00,1.00,0.03],[1.00,1.00,1.00,0.07],[1.00,1.00,1.00,0.12]], "actions": [ "Thermal protection inspection (AMM 71-00-00-200-801)", "Cooling channel flow test + cleaning (AMM 71-00-00-400-801)", "Thermal protection system replacement (AMM 71-00-00-720-801)" ], "cert_requirements": [ "NASA-TM-2004-213086 3.4 — Wall temperature ≤ material limit", "AIAA S-080 5.5 — TPS life ≥ declared thermal cycles", "MIL-HDBK-516 6.4.1 — Active cooling redundancy required" ] }, { "id": "F5", "name": "F5 — Fuel System Blockage / Leak", "ata": "ATA 73-10", "mechanism": "Hydrogen fuel system leak or injector blockage causes combustion asymmetry. Thrust loss, potential for flameout.", "leading": "Fuel pressure↓ + thrust↓ + combustion asymmetry", "color": "#1abc9c", "levels": [[0.98,1.00,1.00],[0.94,1.00,1.00],[0.90,1.00,1.00]], "actions": [ "Fuel system leak test (AMM 73-10-00-200-801)", "Fuel injector/manifold repair (AMM 73-10-00-400-801)", "Complete fuel system overhaul (AMM 73-10-00-720-801)" ], "cert_requirements": [ "NASA-TM-2004-213086 3.5 — Fuel injection uniformity ≥ 90%", "AIAA S-080 5.6 — Fuel system leak rate ≤ 0.1% flow", "MIL-HDBK-516 6.5.1 — H2 system safety per MIL-STD-882" ] } ] def isa_conditions(self, alt_m): T = T_ISA_SL - LAPSE * alt_m P = P_SL * (T / T_ISA_SL) ** 5.2561 return T, P def compute_healthy_baseline(self, flight_conditions: Dict) -> Dict[str, float]: alt_m = flight_conditions.get("altitude_m", 30000) M = flight_conditions.get("mach", 5.5) T, P = self.isa_conditions(alt_m) T0 = T * (1 + 0.18 * M * M) P0 = P * (T0 / T) ** 4.0 T_t2 = T0 P_t2 = P0 * 0.70 T_t4 = 2700.0 mdot = self.A_inlet * P0 * M * (GAMMA / (T0 * 287))**0.5 f = mdot * CP * (T_t4 - T_t2) / (self.eta_b * LHV_H2) T_t5 = T_t4 P_t5 = P_t2 * 0.90 V_exit = (2 * CP * (T_t5 - T))**0.5 thrust = mdot * (V_exit - M * (GAMMA * 287 * T)**0.5) + (P_t5 - P) * self.A_exit Isp = thrust / (f * 9.81) if f > 0 else 0 EGT = T_t5 - 273.15 return {"EGT": EGT, "thrust": thrust/1000, "Isp": Isp, "mdot": mdot, "T_amb": T, "P_amb": P} def compute_fault_signatures(self, baseline: Dict, flight_conditions: Dict) -> List[List[Dict]]: results = [] for fault in self.fault_library: # renamed from 'f' to avoid shadowing lvl_vals = [] for lv in range(3): m = fault["levels"][lv] alt_m = flight_conditions.get("altitude_m", 30000) M = flight_conditions.get("mach", 5.5) T, P = self.isa_conditions(alt_m) T0 = T * (1 + 0.18 * M * M) P0 = P * (T0 / T) ** 4.0 eta_rec = 0.70 * m[0] eta_b = self.eta_b * m[1] eta_n = self.eta_n * m[2] T_t2 = T0 P_t2 = P0 * eta_rec T_t4 = 2700.0 * (1 - (m[3] if len(m) > 3 else 0)) mdot = self.A_inlet * P0 * M * (GAMMA / (T0 * 287))**0.5 fuel_ratio = mdot * CP * (T_t4 - T_t2) / (eta_b * LHV_H2) # renamed from 'f' T_t5 = T_t4 P_t5 = P_t2 * eta_n V_exit = (2 * CP * (T_t5 - T))**0.5 thrust = mdot * (V_exit - M * (GAMMA * 287 * T)**0.5) + (P_t5 - P) * self.A_exit Isp = thrust / (fuel_ratio * 9.81) if fuel_ratio > 0 else 0 EGT = T_t5 - 273.15 lvl_vals.append({"EGT": EGT, "thrust": thrust/1000, "Isp": Isp, "mdot": mdot}) results.append(lvl_vals) return results def classify_fault(self, measured: Dict, baseline: Dict, fault_sigs: List[List[Dict]]) -> Dict: params = ["EGT", "thrust", "Isp", "mdot"] 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", "mdot"] 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", "mdot"] 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, "thrust_warn": baseline["thrust"] * 0.95, "thrust_maint": baseline["thrust"] * 0.90 }, "deviations": {p: user_sig[i] for i, p in enumerate(params)} } def get_parameter_labels(self) -> List[str]: return ["EGT", "Thrust", "Isp", "Mass Flow"] def get_parameter_units(self) -> Dict[str, str]: return {"EGT": "°C", "thrust": "kN", "Isp": "s", "mdot": "kg/s"} def get_input_fields(self) -> List[Dict]: return [ {"name": "altitude", "label": "Altitude", "default": 100000, "unit": "ft", "type": "number"}, {"name": "mach", "label": "Mach Number", "default": 5.5, "unit": "Mach", "type": "number"}, {"name": "EGT", "label": "Combustor Exit Temp", "default": 2400.0, "unit": "°C", "type": "number"}, {"name": "thrust", "label": "Thrust", "default": 45.0, "unit": "kN", "type": "number"}, {"name": "Isp", "label": "Specific Impulse", "default": 1800.0, "unit": "s", "type": "number"}, {"name": "mdot", "label": "Air Mass Flow", "default": 1.8, "unit": "kg/s", "type": "number"} ]