""" PHI-Arc Engine PHM — High-Bypass Turbofan Engine Model Reference: CFM56/LEAP-1A class, two-spool architecture Certification: EASA CS-E, FAR Part 33 """ import numpy as np from typing import Dict, List from .base_engine import BaseEngine GAMMA_C = 1.40 CP_C = 1005.0 GAMMA_H = 1.333 CP_H = 1148.0 P_SL = 101325.0 T_ISA_SL = 288.15 LAPSE = 0.0065 LHV_JETA = 43.10e6 LHV_JP8 = 43.24e6 class TurbofanEngine(BaseEngine): def __init__(self): super().__init__( name="CFM-LEAP Class Turbofan", engine_type="turbofan", ata_chapters={ "72-00": "Engine General", "72-30": "Compressor (LPC/HPC)", "72-50": "Turbine (HPT/LPT)", "73-10": "Fuel Distribution", "75-30": "Bleed Air", "77-10": "Engine Controls (FADEC)", "79-20": "Oil System" } ) self._build_fault_library() self.cert_standards = [ "EASA CS-E — Certification Specifications for Engines", "FAR Part 33 — Airworthiness Standards: Aircraft Engines", "SAE ARP 755A — Gas Turbine Engine Performance Presentation", "AC 33-2B — Certification Guidance for Part 33" ] # Design parameters (CFM56-5B class) self.BPR = 5.5 self.OPR = 32.0 self.FPR = 1.65 self.eta_fan = 0.92 self.eta_lpc = 0.88 self.eta_hpc = 0.86 self.eta_hpt = 0.89 self.eta_lpt = 0.91 self.eta_b = 0.995 self.eta_mech = 0.99 self.mdot_core = 25.0 self.mdot_fan = self.mdot_core * self.BPR self.EGT_REDLINE = 950.0 self.EGT_WARN = 920.0 def _build_fault_library(self): self.fault_library = [ { "id": "F1", "name": "F1 — HPC Blade Erosion / FOD", "ata": "ATA 72-30", "mechanism": "Foreign object damage or erosion reduces HPC efficiency and pressure ratio. Core flow drops, requiring higher TIT to maintain thrust. N2 may increase while N1 decreases relatively.", "leading": "EGT rise with N2↑ and EPR↓ (HPC efficiency loss)", "color": "#e74c3c", "levels": [[0.99,1.00,1.00,0.00],[0.96,1.00,1.00,0.00],[0.93,1.00,1.00,0.00]], "actions": [ "Borescope inspection HPC (AMM 72-30-00-200-801)", "HPC blade blend/repair per SRM (AMM 72-30-00-400-801)", "HPC module replacement (AMM 72-30-00-720-801)" ], "cert_requirements": [ "CS-E 510 — Compressor pressure ratio must remain within declared limits", "FAR 33.76 — Bird ingestion: HPC must survive medium flocking bird", "AC 33-2B 33.76 — FOD tolerance assessment required" ] }, { "id": "F2", "name": "F2 — HPT Blade Creep / Erosion", "ata": "ATA 72-50", "mechanism": "High-pressure turbine blade creep or erosion increases tip clearance and reduces aerodynamic efficiency. EGT rises disproportionately to maintain thrust. Cooling flow degradation accelerates.", "leading": "EGT↑↑ with N1/N2 normal, EPR stable (HPT efficiency loss)", "color": "#f39c12", "levels": [[1.00,0.99,1.00,0.00],[1.00,0.97,1.00,0.00],[1.00,0.94,1.00,0.00]], "actions": [ "Hot section borescope HPT (AMM 72-50-00-200-801)", "HPT blade coating inspection/repair (AMM 72-50-00-400-801)", "HPT module replacement / life-limited parts (AMM 72-50-00-720-801)" ], "cert_requirements": [ "CS-E 800 — Turbine cooling and life limits", "FAR 33.27 — Turbine blade life: declared cyclic endurance", "AC 33-2B 33.27 — Creep-life tracking and retirement criteria" ] }, { "id": "F3", "name": "F3 — Fuel Nozzle Coking / Flow Maldistribution", "ata": "ATA 73-10", "mechanism": "Thermal coking of fuel nozzles distorts spray pattern, creating hot spots and reducing combustion efficiency. Pattern factor increases. EGT spreads widen.", "leading": "FF↑ with EGT spread increase (ΔEGT > 25°C)", "color": "#9b59b6", "levels": [[0.99,1.00,1.00,0.00],[0.97,1.00,1.00,0.00],[0.95,1.00,1.00,0.00]], "actions": [ "Fuel nozzle flow test (AMM 73-10-00-200-801)", "Fuel manifold and nozzle replacement (AMM 73-10-00-400-801)", "Combustor inspection + full fuel system overhaul (AMM 73-10-00-720-801)" ], "cert_requirements": [ "CS-E 560 — Combustor pattern factor ≤ 0.25", "FAR 33.33 — Fuel system: declared flow distribution tolerance", "SAE ARP 1536 — Fuel nozzle flow tolerance ±4%" ] }, { "id": "F4", "name": "F4 — VSV / VBV Malfunction", "ata": "ATA 75-30 / 77-10", "mechanism": "Variable stator vane or variable bleed valve actuator fault causes incorrect scheduling. Surge margin reduced. HPC stability compromised at high power.", "leading": "N2 surge + EGT spike on acceleration (VSV/VBV scheduling error)", "color": "#3498db", "levels": [[1.00,1.00,0.99,0.00],[1.00,1.00,0.97,0.00],[1.00,1.00,0.94,0.00]], "actions": [ "VSV/VBV actuator functional test (AMM 75-30-00-040-801)", "VSV/VBV actuator and linkage replacement (AMM 75-30-00-400-801)", "FADEC software update + HPC borescope (AMM 77-10-00-710-801)" ], "cert_requirements": [ "CS-E 650 — Compressor stability: surge margin ≥ 15%", "FAR 33.65 — Surge and stall characteristics", "AC 33-2B 33.65 — VSV/VBV scheduling validation" ] }, { "id": "F5", "name": "F5 — Bearing / Oil System Degradation", "ata": "ATA 79-20", "mechanism": "Main bearing wear increases oil temperature and metal particle count. Friction losses rise. Oil pressure may drop. N2 vibration increases.", "leading": "Oil temp↑ + metal particles↑ + N2 vibration↑ (SOAP confirms)", "color": "#1abc9c", "levels": [[1.00,1.00,1.00,0.015],[1.00,1.00,1.00,0.030],[1.00,1.00,1.00,0.050]], "actions": [ "Oil SOAP + spectrometric analysis (AMM 79-20-00-200-801)", "Magnetic chip detector inspection (AMM 79-20-00-200-802)", "Bearing replacement / engine removal (AMM 72-00-00-720-801)" ], "cert_requirements": [ "CS-E 840 — Oil system integrity and monitoring", "FAR 33.62 — Oil system: prevent bearing damage propagation", "MIL-PRF-23699 — Oil contamination limits" ] } ] 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", 10668) M = flight_conditions.get("mach", 0.78) thrust = flight_conditions.get("thrust_n", 120000) fuel = flight_conditions.get("fuel_type", "Jet-A") T, P = self.isa_conditions(alt_m) T_t2 = T * (1 + 0.2 * M * M) P_t2 = P * (T_t2 / T) ** 3.5 LHV = LHV_JETA if "Jet-A" in fuel else LHV_JP8 # Simplified turbofan thermodynamics T_t13 = T_t2 * (1 + (self.FPR**0.2857 - 1) / self.eta_fan) P_t13 = P_t2 * self.FPR LPC_PR = self.OPR**0.4 HPC_PR = self.OPR**0.6 T_t25 = T_t2 * (1 + (LPC_PR**0.2857 - 1) / self.eta_lpc) P_t25 = P_t2 * LPC_PR T_t3 = T_t25 * (1 + (HPC_PR**0.2857 - 1) / self.eta_hpc) P_t3 = P_t25 * HPC_PR mdot_core = self.mdot_core * (P_t2 / P_SL) * (T_ISA_SL / T_t2)**0.5 mdot_fan = mdot_core * self.BPR W_fan = mdot_fan * CP_C * (T_t13 - T_t2) W_comp = mdot_core * CP_C * (T_t3 - T_t2) W_req = W_comp + thrust / (self.eta_mech * 200) # Simplified TIT = T_t3 + W_req / (mdot_core * CP_H * 0.4) T_t45 = TIT * (1 - self.eta_hpt * 0.3) T_t5 = T_t45 * (1 - self.eta_lpt * 0.25) EGT = T_t5 - 273.15 FF = mdot_core * CP_H * (TIT - T_t3) / (self.eta_b * LHV) N1 = (mdot_fan / self.mdot_fan)**0.5 * 100 N2 = (mdot_core / self.mdot_core)**0.5 * (self.OPR/32)**0.25 * 100 EPR = P_t13 / P return { "EGT": EGT, "FF": FF, "N1": N1, "N2": N2, "EPR": EPR, "TIT": TIT, "T_t3": T_t3, "T_amb": T, "P_amb": P } 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] # Apply multipliers to baseline eta_hpc = self.eta_hpc * m[0] eta_hpt = self.eta_hpt * m[1] eta_lpt = self.eta_lpt * m[2] mech_loss = m[3] alt_m = flight_conditions.get("altitude_m", 10668) M = flight_conditions.get("mach", 0.78) thrust = flight_conditions.get("thrust_n", 120000) fuel = flight_conditions.get("fuel_type", "Jet-A") T, P = self.isa_conditions(alt_m) T_t2 = T * (1 + 0.2 * M * M) P_t2 = P * (T_t2 / T) ** 3.5 LHV = LHV_JETA if "Jet-A" in fuel else LHV_JP8 T_t13 = T_t2 * (1 + (self.FPR**0.2857 - 1) / self.eta_fan) LPC_PR = self.OPR**0.4 HPC_PR = self.OPR**0.6 T_t25 = T_t2 * (1 + (LPC_PR**0.2857 - 1) / self.eta_lpc) P_t25 = P_t2 * LPC_PR T_t3 = T_t25 * (1 + (HPC_PR**0.2857 - 1) / eta_hpc) mdot_core = self.mdot_core * (P_t2 / P_SL) * (T_ISA_SL / T_t2)**0.5 mdot_fan = mdot_core * self.BPR W_comp = mdot_core * CP_C * (T_t3 - T_t2) W_req = W_comp + thrust / ((self.eta_mech - mech_loss) * 200) TIT = T_t3 + W_req / (mdot_core * CP_H * 0.4) T_t45 = TIT * (1 - eta_hpt * 0.3) T_t5 = T_t45 * (1 - eta_lpt * 0.25) EGT = T_t5 - 273.15 FF = mdot_core * CP_H * (TIT - T_t3) / (self.eta_b * LHV) N1 = (mdot_fan / self.mdot_fan)**0.5 * 100 N2 = (mdot_core / self.mdot_core)**0.5 * (self.OPR/32)**0.25 * 100 EPR = P_t2 * self.FPR * LPC_PR * HPC_PR / P lvl_vals.append({"EGT": EGT, "FF": FF, "N1": N1, "N2": N2, "EPR": EPR}) results.append(lvl_vals) return results def classify_fault(self, measured: Dict, baseline: Dict, fault_sigs: List[List[Dict]]) -> Dict: params = ["EGT", "FF", "N1", "N2"] user_sig = np.zeros(4) for i, p in enumerate(params): if p in measured and p in baseline: denom = baseline[p] * 0.05 if p in ["N1", "N2"] else (baseline[p] * 0.10 if p == "FF" 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.05 if p in ["N1", "N2"] else (baseline[p] * 0.10 if p == "FF" 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) if magnitude < 0.12: status = "HEALTHY" elif scores[0]["sev"] < 0.30: status = "INDETERMINATE" else: status = "FAULT_DETECTED" thresh = { "EGT_warn": self.EGT_WARN, "EGT_maint": self.EGT_REDLINE, "FF_warn": baseline["FF"] * 1.03, "FF_maint": baseline["FF"] * 1.06, "N1_warn": baseline["N1"] * 0.985, "N1_maint": baseline["N1"] * 0.970, "N2_warn": baseline["N2"] * 0.985, "N2_maint": baseline["N2"] * 0.970 } return { "base": baseline, "faults": fault_sigs, "scores": scores, "user_sig": user_sig, "status": status, "thresholds": thresh, "deviations": {p: user_sig[i] for i, p in enumerate(params)} } def get_parameter_labels(self) -> List[str]: return ["EGT", "Fuel Flow", "N1", "N2"] def get_parameter_units(self) -> Dict[str, str]: return {"EGT": "°C", "FF": "kg/s", "N1": "%", "N2": "%"} def get_input_fields(self) -> List[Dict]: return [ {"name": "altitude", "label": "Altitude", "default": 35000, "unit": "ft", "type": "number"}, {"name": "mach", "label": "Mach Number", "default": 0.78, "unit": "Mach", "type": "number"}, {"name": "thrust", "label": "Thrust Setting", "default": 120, "unit": "kN", "type": "number"}, {"name": "fuel_type", "label": "Fuel Type", "default": "Jet-A", "options": ["Jet-A", "JP-8"], "type": "select"}, {"name": "EGT", "label": "EGT", "default": 580.0, "unit": "°C", "type": "number"}, {"name": "FF", "label": "Fuel Flow", "default": 2800.0, "unit": "kg/hr", "type": "number"}, {"name": "N1", "label": "N1 (Fan)", "default": 98.5, "unit": "%", "type": "number"}, {"name": "N2", "label": "N2 (Core)", "default": 96.0, "unit": "%", "type": "number"} ]