#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PfdRange {
pub min: f64,
pub max: f64,
}
impl PfdRange {
#[inline]
pub fn contains(self, pfd: f64) -> bool {
pfd >= self.min && pfd < self.max
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PfhRange {
pub min: f64,
pub max: f64,
}
impl PfhRange {
#[inline]
pub fn contains(self, pfh: f64) -> bool {
pfh >= self.min && pfh < self.max
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SilLevel {
None,
Sil1,
Sil2,
Sil3,
Sil4,
}
impl SilLevel {
pub fn pfd_range(self) -> PfdRange {
match self {
SilLevel::None => PfdRange {
min: 1e-1,
max: f64::INFINITY,
},
SilLevel::Sil1 => PfdRange {
min: 1e-2,
max: 1e-1,
},
SilLevel::Sil2 => PfdRange {
min: 1e-3,
max: 1e-2,
},
SilLevel::Sil3 => PfdRange {
min: 1e-4,
max: 1e-3,
},
SilLevel::Sil4 => PfdRange {
min: 1e-5,
max: 1e-4,
},
}
}
pub fn pfh_range(self) -> PfhRange {
match self {
SilLevel::None => PfhRange {
min: 1e-5,
max: f64::INFINITY,
},
SilLevel::Sil1 => PfhRange {
min: 1e-6,
max: 1e-5,
},
SilLevel::Sil2 => PfhRange {
min: 1e-7,
max: 1e-6,
},
SilLevel::Sil3 => PfhRange {
min: 1e-8,
max: 1e-7,
},
SilLevel::Sil4 => PfhRange {
min: 1e-9,
max: 1e-8,
},
}
}
pub fn classify_from_pfd(pfd: f64) -> SilLevel {
if !pfd.is_finite() || pfd < 0.0 {
return SilLevel::None;
}
if pfd < 1e-4 {
SilLevel::Sil4
} else if pfd < 1e-3 {
SilLevel::Sil3
} else if pfd < 1e-2 {
SilLevel::Sil2
} else if pfd < 1e-1 {
SilLevel::Sil1
} else {
SilLevel::None
}
}
pub fn classify_from_pfh(pfh: f64) -> SilLevel {
if !pfh.is_finite() || pfh < 0.0 {
return SilLevel::None;
}
if pfh < 1e-8 {
SilLevel::Sil4
} else if pfh < 1e-7 {
SilLevel::Sil3
} else if pfh < 1e-6 {
SilLevel::Sil2
} else if pfh < 1e-5 {
SilLevel::Sil1
} else {
SilLevel::None
}
}
pub fn level(self) -> u8 {
match self {
SilLevel::None => 0,
SilLevel::Sil1 => 1,
SilLevel::Sil2 => 2,
SilLevel::Sil3 => 3,
SilLevel::Sil4 => 4,
}
}
pub fn label(self) -> &'static str {
match self {
SilLevel::None => "None",
SilLevel::Sil1 => "SIL 1",
SilLevel::Sil2 => "SIL 2",
SilLevel::Sil3 => "SIL 3",
SilLevel::Sil4 => "SIL 4",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SilError {
RequirementNotMet {
required: SilLevel,
achieved: SilLevel,
},
InvalidMetric,
}
impl core::fmt::Display for SilError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SilError::RequirementNotMet { required, achieved } => write!(
f,
"SIL requirement not met: required {}, achieved {}",
required.label(),
achieved.label(),
),
SilError::InvalidMetric => write!(f, "SIL metric is not a finite positive value"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SafetyRequirement {
pub required_sil: SilLevel,
pub achieved_sil: SilLevel,
}
impl SafetyRequirement {
pub fn new(required_sil: SilLevel, achieved_sil: SilLevel) -> Self {
Self {
required_sil,
achieved_sil,
}
}
pub fn from_pfd(required_sil: SilLevel, achieved_pfd: f64) -> Result<Self, SilError> {
if !achieved_pfd.is_finite() || achieved_pfd < 0.0 {
return Err(SilError::InvalidMetric);
}
let achieved_sil = SilLevel::classify_from_pfd(achieved_pfd);
Ok(Self::new(required_sil, achieved_sil))
}
pub fn from_pfh(required_sil: SilLevel, achieved_pfh: f64) -> Result<Self, SilError> {
if !achieved_pfh.is_finite() || achieved_pfh < 0.0 {
return Err(SilError::InvalidMetric);
}
let achieved_sil = SilLevel::classify_from_pfh(achieved_pfh);
Ok(Self::new(required_sil, achieved_sil))
}
pub fn is_satisfied(&self) -> bool {
self.achieved_sil >= self.required_sil
}
pub fn verify(&self) -> Result<(), SilError> {
if self.is_satisfied() {
Ok(())
} else {
Err(SilError::RequirementNotMet {
required: self.required_sil,
achieved: self.achieved_sil,
})
}
}
pub fn margin_levels(&self) -> i8 {
self.achieved_sil.level() as i8 - self.required_sil.level() as i8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pfd_ranges_contain_midpoints() {
assert!(SilLevel::Sil1.pfd_range().contains(5e-2));
assert!(SilLevel::Sil2.pfd_range().contains(5e-3));
assert!(SilLevel::Sil3.pfd_range().contains(5e-4));
assert!(SilLevel::Sil4.pfd_range().contains(5e-5));
assert!(SilLevel::None.pfd_range().contains(0.5));
}
#[test]
fn pfd_range_boundaries_are_exclusive_upper() {
assert!(!SilLevel::Sil1.pfd_range().contains(1e-1));
assert!(SilLevel::None.pfd_range().contains(1e-1));
assert!(!SilLevel::Sil2.pfd_range().contains(1e-2));
assert!(SilLevel::Sil1.pfd_range().contains(1e-2));
}
#[test]
fn pfd_range_lower_boundary_is_inclusive() {
assert!(SilLevel::Sil1.pfd_range().contains(1e-2));
assert!(SilLevel::Sil2.pfd_range().contains(1e-3));
}
#[test]
fn pfh_ranges_contain_midpoints() {
assert!(SilLevel::Sil1.pfh_range().contains(5e-6));
assert!(SilLevel::Sil2.pfh_range().contains(5e-7));
assert!(SilLevel::Sil3.pfh_range().contains(5e-8));
assert!(SilLevel::Sil4.pfh_range().contains(5e-9));
assert!(SilLevel::None.pfh_range().contains(1e-4));
}
#[test]
fn classify_pfd_roundtrip() {
let cases = [
(5e-5, SilLevel::Sil4),
(5e-4, SilLevel::Sil3),
(5e-3, SilLevel::Sil2),
(5e-2, SilLevel::Sil1),
(0.5, SilLevel::None),
];
for (pfd, expected) in cases {
let got = SilLevel::classify_from_pfd(pfd);
assert_eq!(got, expected, "PFD={pfd:.1e}");
assert!(
got.pfd_range().contains(pfd) || got == SilLevel::None,
"Range check failed for pfd={pfd:.1e}"
);
}
}
#[test]
fn classify_pfd_boundary_values() {
assert_eq!(SilLevel::classify_from_pfd(1e-3), SilLevel::Sil2);
assert_eq!(SilLevel::classify_from_pfd(1e-2), SilLevel::Sil1);
}
#[test]
fn classify_pfd_invalid_inputs() {
assert_eq!(SilLevel::classify_from_pfd(f64::NAN), SilLevel::None);
assert_eq!(SilLevel::classify_from_pfd(-1.0), SilLevel::None);
assert_eq!(SilLevel::classify_from_pfd(f64::INFINITY), SilLevel::None);
}
#[test]
fn classify_pfh_roundtrip() {
let cases = [
(5e-9, SilLevel::Sil4),
(5e-8, SilLevel::Sil3),
(5e-7, SilLevel::Sil2),
(5e-6, SilLevel::Sil1),
(1e-4, SilLevel::None),
];
for (pfh, expected) in cases {
let got = SilLevel::classify_from_pfh(pfh);
assert_eq!(got, expected, "PFH={pfh:.1e}");
}
}
#[test]
fn classify_pfh_invalid_inputs() {
assert_eq!(SilLevel::classify_from_pfh(f64::NAN), SilLevel::None);
assert_eq!(SilLevel::classify_from_pfh(-0.1), SilLevel::None);
}
#[test]
fn sil_ordering() {
assert!(SilLevel::None < SilLevel::Sil1);
assert!(SilLevel::Sil1 < SilLevel::Sil2);
assert!(SilLevel::Sil2 < SilLevel::Sil3);
assert!(SilLevel::Sil3 < SilLevel::Sil4);
}
#[test]
fn requirement_satisfied_when_achieved_meets_required() {
let req = SafetyRequirement::new(SilLevel::Sil2, SilLevel::Sil2);
assert!(req.is_satisfied());
assert!(req.verify().is_ok());
}
#[test]
fn requirement_satisfied_when_achieved_exceeds_required() {
let req = SafetyRequirement::new(SilLevel::Sil2, SilLevel::Sil3);
assert!(req.is_satisfied());
assert_eq!(req.margin_levels(), 1);
}
#[test]
fn requirement_not_satisfied_when_achieved_below_required() {
let req = SafetyRequirement::new(SilLevel::Sil3, SilLevel::Sil2);
assert!(!req.is_satisfied());
assert_eq!(req.margin_levels(), -1);
assert!(matches!(
req.verify(),
Err(SilError::RequirementNotMet { .. })
));
}
#[test]
fn requirement_from_pfd_valid() {
let req = SafetyRequirement::from_pfd(SilLevel::Sil2, 5e-4).unwrap();
assert_eq!(req.achieved_sil, SilLevel::Sil3);
assert!(req.is_satisfied());
}
#[test]
fn requirement_from_pfd_invalid_metric() {
let result = SafetyRequirement::from_pfd(SilLevel::Sil2, f64::NAN);
assert_eq!(result, Err(SilError::InvalidMetric));
}
#[test]
fn requirement_from_pfh_valid() {
let req = SafetyRequirement::from_pfh(SilLevel::Sil3, 5e-8).unwrap();
assert_eq!(req.achieved_sil, SilLevel::Sil3);
assert!(req.is_satisfied());
}
#[test]
fn requirement_from_pfh_invalid_metric() {
let result = SafetyRequirement::from_pfh(SilLevel::Sil1, -1.0);
assert_eq!(result, Err(SilError::InvalidMetric));
}
}