use crate::core::config::DsfbConfig;
use crate::core::regime::OperatingRegime;
#[derive(Debug, Clone, Copy)]
pub struct AdmissibilityEnvelope {
pub lower: f64,
pub upper: f64,
pub regime: OperatingRegime,
pub baseline_mean: f64,
pub baseline_std: f64,
}
impl AdmissibilityEnvelope {
#[must_use]
pub fn from_baseline(
mean: f64,
std: f64,
regime: OperatingRegime,
config: &DsfbConfig,
) -> Self {
let half_width = config.envelope_sigma * std;
Self {
lower: mean - half_width,
upper: mean + half_width,
regime,
baseline_mean: mean,
baseline_std: std,
}
}
#[must_use]
pub fn is_admissible(&self, residual: f64) -> bool {
let half_width = self.upper - self.baseline_mean;
residual.abs() <= half_width
}
#[must_use]
pub fn gap(&self, residual: f64) -> f64 {
let half_width = self.upper - self.baseline_mean;
half_width - residual.abs()
}
#[must_use]
pub fn normalized_position(&self, residual: f64) -> f64 {
let half_width = self.upper - self.baseline_mean;
if half_width < 1e-15 {
return if residual.abs() < 1e-15 { 0.0 } else { f64::MAX };
}
residual.abs() / half_width
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnvelopeStatus {
Interior,
Approaching,
Exceeded,
}
impl AdmissibilityEnvelope {
#[must_use]
pub fn classify_position(&self, residual: f64) -> EnvelopeStatus {
let pos = self.normalized_position(residual);
if pos < 0.7 {
EnvelopeStatus::Interior
} else if pos < 1.0 {
EnvelopeStatus::Approaching
} else {
EnvelopeStatus::Exceeded
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_envelope_construction() {
let config = DsfbConfig::default();
let env = AdmissibilityEnvelope::from_baseline(
100.0, 2.0, OperatingRegime::SeaLevelStatic, &config,
);
assert!(env.is_admissible(0.0)); assert!(env.is_admissible(4.0)); assert!(!env.is_admissible(6.0)); }
#[test]
fn test_gap_positive_inside() {
let config = DsfbConfig::default();
let env = AdmissibilityEnvelope::from_baseline(
0.0, 1.0, OperatingRegime::SeaLevelStatic, &config,
);
assert!(env.gap(0.0) > 0.0);
assert!(env.gap(2.5) < 1e-10); }
#[test]
fn test_classification() {
let config = DsfbConfig::default();
let env = AdmissibilityEnvelope::from_baseline(
0.0, 1.0, OperatingRegime::SeaLevelStatic, &config,
);
assert_eq!(env.classify_position(0.0), EnvelopeStatus::Interior);
assert_eq!(env.classify_position(2.0), EnvelopeStatus::Approaching);
assert_eq!(env.classify_position(3.0), EnvelopeStatus::Exceeded);
}
}