""" PHI-Arc Engine PHM — Ramjet Engine Model Reference: Marquardt RJ43 / Boeing CIM-10 Bomarc class Certification: MIL-STD-810, MIL-PRF-23699 """ import numpy as np from typing import Dict, List from .base_engine import BaseEngine GAMMA = 1.40 CP = 1005.0 P_SL = 101325.0 T_ISA_SL = 288.15 LAPSE = 0.0065 LHV_JP10 = 43.50e6 class RamjetEngine(BaseEngine): def __init__(self): super().__init__( name="RJ43 Class Ramjet", engine_type="ramjet", 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", "MIL-PRF-23699 — Lubricating Oil, Aircraft Turbine Engine", "MIL-HDBK-516 — Airworthiness Certification Criteria", "AIAA S-080 — Ramjet/Scramjet Performance Standards" ] self.eta_b = 0.95 self.eta_n = 0.95 self.A_inlet = 0.5 self.A_throat = 0.15 self.A_exit = 0.6 self.EGT_REDLINE = 2200.0 self.EGT_WARN = 2000.0 def _build_fault_library(self): self.fault_library = [ { "id": "F1", "name": "F1 — Inlet Unstart / Shock Position Error", "ata": "ATA 71-00", "mechanism": "Inlet shock system moves forward of cowl lip, causing mass flow spillage and pressure recovery loss. Thrust drops dramatically, combustion instability.", "leading": "Thrust↓ + P0 recovery↓ + combustion instability (inlet buzz)", "color": "#e74c3c", "levels": [[0.99,1.00,1.00],[0.95,1.00,1.00],[0.90,1.00,1.00]], "actions": [ "Inlet spike/cowl inspection (AMM 71-00-00-200-801)", "Inlet spike actuator repair (AMM 71-00-00-400-801)", "Inlet assembly replacement (AMM 71-00-00-720-801)" ], "cert_requirements": [ "MIL-STD-810 514.8 — Inlet must survive vibration environment", "MIL-HDBK-516 5.1.1 — Inlet pressure recovery ≥ declared value", "AIAA S-080 4.2 — Inlet start margin ≥ 5% Mach" ] }, { "id": "F2", "name": "F2 — Combustor Flameout / Instability", "ata": "ATA 73-10", "mechanism": "Fuel injector blockage or flameholder erosion causes local flame extinction. Combustion efficiency drops, EGT spread increases, potential for hard start.", "leading": "EGT spread↑ + η_b↓ + thrust oscillation", "color": "#f39c12", "levels": [[1.00,0.99,1.00],[1.00,0.96,1.00],[1.00,0.92,1.00]], "actions": [ "Fuel injector flow test (AMM 73-10-00-200-801)", "Flameholder inspection/repair (AMM 73-10-00-400-801)", "Combustor module replacement (AMM 73-10-00-720-801)" ], "cert_requirements": [ "MIL-HDBK-516 5.2.1 — Combustion efficiency ≥ 90%", "MIL-STD-810 507.6 — Combustor must survive thermal cycling", "AIAA S-080 4.3 — Flame stability margin ≥ 15%" ] }, { "id": "F3", "name": "F3 — Nozzle Throat Erosion / Area Change", "ata": "ATA 75-40", "mechanism": "Thermal erosion of convergent nozzle throat increases area. Chamber pressure drops, thrust decreases, specific impulse degrades.", "leading": "Thrust↓ + chamber pressure↓ + Isp↓ (nozzle area↑)", "color": "#9b59b6", "levels": [[1.00,1.00,0.99],[1.00,1.00,0.97],[1.00,1.00,0.94]], "actions": [ "Nozzle throat dimension check (AMM 75-40-00-200-801)", "Nozzle insert replacement (AMM 75-40-00-400-801)", "Full nozzle assembly replacement (AMM 75-40-00-720-801)" ], "cert_requirements": [ "MIL-HDBK-516 5.3.1 — Nozzle area tolerance ±3%", "MIL-STD-810 516.8 — Nozzle must survive shock loading", "AIAA S-080 4.4 — Nozzle efficiency ≥ 95%" ] }, { "id": "F4", "name": "F4 — Thermal Protection Degradation", "ata": "ATA 71-00", "mechanism": "Ablative or regenerative cooling system degradation causes wall temperature rise. Structural integrity compromised, potential for burn-through.", "leading": "Wall temp↑ + cooling flow↓ + structural deformation", "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": [ "Thermal protection inspection (AMM 71-00-00-200-801)", "Cooling channel flow test + repair (AMM 71-00-00-400-801)", "Combustor liner replacement (AMM 71-00-00-720-801)" ], "cert_requirements": [ "MIL-STD-810 501.7 — High temperature survival", "MIL-HDBK-516 5.4.1 — Wall temperature must remain below material limit", "AIAA S-080 4.5 — Thermal protection life ≥ declared cycles" ] }, { "id": "F5", "name": "F5 — Fuel System Leak / Blockage", "ata": "ATA 73-10", "mechanism": "Fuel manifold leak or injector blockage causes asymmetric combustion. Thrust loss, potential for flameout on affected sector.", "leading": "Fuel pressure↓ + thrust↓ + EGT spread↑", "color": "#1abc9c", "levels": [[0.99,1.00,1.00],[0.96,1.00,1.00],[0.93,1.00,1.00]], "actions": [ "Fuel system pressure test (AMM 73-10-00-200-801)", "Fuel manifold repair/replacement (AMM 73-10-00-400-801)", "Complete fuel system overhaul (AMM 73-10-00-720-801)" ], "cert_requirements": [ "MIL-HDBK-516 5.5.1 — Fuel system leak rate ≤ declared limit", "MIL-STD-810 514.8 — Fuel system vibration integrity", "AIAA S-080 4.6 — Fuel distribution uniformity ≥ 95%" ] } ] 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", 15000) M = flight_conditions.get("mach", 2.5) T, P = self.isa_conditions(alt_m) T0 = T * (1 + 0.2 * M * M) P0 = P * (T0 / T) ** 3.5 # Ram compression T_t2 = T0 P_t2 = P0 * 0.85 # Inlet pressure recovery # Combustor T_t4 = 2200.0 mdot = self.A_inlet * P0 * M * (GAMMA / (T0 * 287))**0.5 f = mdot * CP * (T_t4 - T_t2) / (self.eta_b * LHV_JP10) # Nozzle T_t5 = T_t4 P_t5 = P_t2 * 0.95 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) 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", 15000) M = flight_conditions.get("mach", 2.5) T, P = self.isa_conditions(alt_m) T0 = T * (1 + 0.2 * M * M) P0 = P * (T0 / T) ** 3.5 eta_rec = 0.85 * 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 = 2200.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_JP10) # 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": 50000, "unit": "ft", "type": "number"}, {"name": "mach", "label": "Mach Number", "default": 2.5, "unit": "Mach", "type": "number"}, {"name": "EGT", "label": "Combustor Exit Temp", "default": 1900.0, "unit": "°C", "type": "number"}, {"name": "thrust", "label": "Thrust", "default": 50.0, "unit": "kN", "type": "number"}, {"name": "Isp", "label": "Specific Impulse", "default": 1200.0, "unit": "s", "type": "number"}, {"name": "mdot", "label": "Air Mass Flow", "default": 2.5, "unit": "kg/s", "type": "number"} ]