SM-Bello's picture
Upload 7 files
e5a9daa verified
Raw
History Blame Contribute Delete
11.7 kB
"""
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"}
]