use crate::core::grammar::GrammarState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineReasonCode {
SustainedCompressorFouling,
TurbineErosionOnset,
AcceleratingEgtMarginLoss,
SealClearanceGrowth,
ForeignObjectDamageSignature,
CompressorStallPrecursor,
BearingWearCoupling,
ThermalBarrierCoatingSpallation,
WashRecoveryIncomplete,
TransientExcursionNotPersistent,
UnclassifiedStructuralAnomaly,
NoAnomaly,
MultiFaultSuperposition,
FanDegradationOnset,
CombinedHpcFanDegradation,
}
impl EngineReasonCode {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::SustainedCompressorFouling => "SustainedCompressorFouling",
Self::TurbineErosionOnset => "TurbineErosionOnset",
Self::AcceleratingEgtMarginLoss => "AcceleratingEGTMarginLoss",
Self::SealClearanceGrowth => "SealClearanceGrowth",
Self::ForeignObjectDamageSignature => "ForeignObjectDamageSignature",
Self::CompressorStallPrecursor => "CompressorStallPrecursor",
Self::BearingWearCoupling => "BearingWearCoupling",
Self::ThermalBarrierCoatingSpallation => "ThermalBarrierCoatingSpallation",
Self::WashRecoveryIncomplete => "WashRecoveryIncomplete",
Self::TransientExcursionNotPersistent => "TransientExcursionNotPersistent",
Self::UnclassifiedStructuralAnomaly => "UnclassifiedStructuralAnomaly",
Self::NoAnomaly => "NoAnomaly",
Self::MultiFaultSuperposition => "MultiFaultSuperposition",
Self::FanDegradationOnset => "FanDegradationOnset",
Self::CombinedHpcFanDegradation => "CombinedHPCFanDegradation",
}
}
#[must_use]
pub const fn is_anomalous(self) -> bool {
!matches!(self, Self::NoAnomaly | Self::TransientExcursionNotPersistent)
}
}
#[derive(Debug, Clone, Copy)]
pub struct HeuristicEntry {
pub code: EngineReasonCode,
pub min_drift: f64,
pub drift_sign: f64,
pub min_slew: f64,
pub slew_sign: f64,
pub requires_envelope_stress: bool,
pub min_grammar_state: GrammarState,
}
pub struct HeuristicsBank {
entries: [HeuristicEntry; 16],
count: usize,
}
impl HeuristicsBank {
#[must_use]
pub const fn default_gas_turbine() -> Self {
Self {
entries: [
HeuristicEntry {
code: EngineReasonCode::SustainedCompressorFouling,
min_drift: 0.001,
drift_sign: -1.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::TurbineErosionOnset,
min_drift: 0.002,
drift_sign: -1.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::AcceleratingEgtMarginLoss,
min_drift: 0.001,
drift_sign: 0.0,
min_slew: 0.0005,
slew_sign: 1.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::ForeignObjectDamageSignature,
min_drift: 0.01,
drift_sign: 0.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: true,
min_grammar_state: GrammarState::Violation,
},
HeuristicEntry {
code: EngineReasonCode::FanDegradationOnset,
min_drift: 0.001,
drift_sign: -1.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::CombinedHpcFanDegradation,
min_drift: 0.001,
drift_sign: -1.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::SealClearanceGrowth,
min_drift: 0.001,
drift_sign: 0.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::ThermalBarrierCoatingSpallation,
min_drift: 0.005,
drift_sign: -1.0,
min_slew: 0.001,
slew_sign: 1.0,
requires_envelope_stress: true,
min_grammar_state: GrammarState::Violation,
},
HeuristicEntry {
code: EngineReasonCode::TransientExcursionNotPersistent,
min_drift: 0.0,
drift_sign: 0.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: true,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry {
code: EngineReasonCode::MultiFaultSuperposition,
min_drift: 0.001,
drift_sign: 0.0,
min_slew: 0.0,
slew_sign: 0.0,
requires_envelope_stress: false,
min_grammar_state: GrammarState::Boundary,
},
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
HeuristicEntry { code: EngineReasonCode::NoAnomaly, min_drift: 0.0, drift_sign: 0.0, min_slew: 0.0, slew_sign: 0.0, requires_envelope_stress: false, min_grammar_state: GrammarState::Admissible },
],
count: 10,
}
}
#[must_use]
pub fn match_motif(
&self,
drift: f64,
slew: f64,
grammar_state: GrammarState,
envelope_stressed: bool,
) -> EngineReasonCode {
let mut best_code = EngineReasonCode::NoAnomaly;
let mut best_severity: u8 = 0;
let mut i = 0;
while i < self.count {
let entry = &self.entries[i];
if grammar_state.severity() < entry.min_grammar_state.severity() {
i += 1;
continue;
}
if entry.requires_envelope_stress && !envelope_stressed {
i += 1;
continue;
}
if drift.abs() < entry.min_drift {
i += 1;
continue;
}
if entry.drift_sign < 0.0 && drift >= 0.0 {
i += 1;
continue;
}
if entry.drift_sign > 0.0 && drift <= 0.0 {
i += 1;
continue;
}
if slew.abs() < entry.min_slew {
i += 1;
continue;
}
if entry.slew_sign < 0.0 && slew >= 0.0 {
i += 1;
continue;
}
if entry.slew_sign > 0.0 && slew <= 0.0 {
i += 1;
continue;
}
let sev = entry.min_grammar_state.severity();
if sev >= best_severity {
best_severity = sev;
best_code = entry.code;
}
i += 1;
}
if best_code == EngineReasonCode::NoAnomaly
&& grammar_state.severity() >= GrammarState::Boundary.severity()
{
return EngineReasonCode::UnclassifiedStructuralAnomaly;
}
best_code
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_anomaly_in_admissible() {
let bank = HeuristicsBank::default_gas_turbine();
let code = bank.match_motif(0.0, 0.0, GrammarState::Admissible, false);
assert_eq!(code, EngineReasonCode::NoAnomaly);
}
#[test]
fn test_fouling_match() {
let bank = HeuristicsBank::default_gas_turbine();
let code = bank.match_motif(-0.005, 0.0, GrammarState::Boundary, false);
assert!(code.is_anomalous());
}
#[test]
fn test_unclassified_in_boundary() {
let bank = HeuristicsBank::default_gas_turbine();
let code = bank.match_motif(0.0001, 0.0, GrammarState::Boundary, false);
assert_eq!(code, EngineReasonCode::UnclassifiedStructuralAnomaly);
}
}