use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct ControlLimits {
pub ucl: f64,
pub cl: f64,
pub lcl: f64,
}
#[derive(Debug, Clone)]
pub struct ChartPoint {
pub value: f64,
pub index: usize,
pub violations: Vec<ViolationType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViolationType {
BeyondLimits,
NineOneSide,
SixTrend,
FourteenAlternating,
TwoOfThreeBeyond2Sigma,
FourOfFiveBeyond1Sigma,
FifteenWithin1Sigma,
EightBeyond1Sigma,
}
#[derive(Debug, Clone)]
pub struct Violation {
pub point_index: usize,
pub violation_type: ViolationType,
}
pub trait ControlChart {
fn add_sample(&mut self, sample: &[f64]) -> Result<(), ControlChartError>;
fn control_limits(&self) -> Option<ControlLimits>;
fn is_in_control(&self) -> bool;
fn violations(&self) -> Vec<Violation>;
fn points(&self) -> &[ChartPoint];
}
pub const MIN_SUBGROUP_SIZE: usize = 2;
pub const MAX_SUBGROUP_SIZE: usize = 25;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ControlChartError {
SampleLengthMismatch {
expected: usize,
got: usize,
},
NonFiniteValue,
DefectivesExceedSampleSize {
defectives: u64,
sample_size: u64,
},
NonPositiveUnits,
SubgroupSizeOutOfRange {
got: usize,
min: usize,
max: usize,
},
ZeroSampleSize,
}
impl fmt::Display for ControlChartError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ControlChartError::SubgroupSizeOutOfRange { got, min, max } => {
write!(f, "subgroup size must be {min}..={max}, got {got}")
}
ControlChartError::ZeroSampleSize => write!(f, "sample size must be at least 1"),
ControlChartError::SampleLengthMismatch { expected, got } => {
write!(f, "sample has {got} values, expected {expected}")
}
ControlChartError::NonFiniteValue => {
write!(f, "sample contains a value that is not a finite number")
}
ControlChartError::DefectivesExceedSampleSize {
defectives,
sample_size,
} => write!(
f,
"{defectives} defectives out of a sample of {sample_size}"
),
ControlChartError::NonPositiveUnits => {
write!(f, "units inspected must be a positive, finite number")
}
}
}
}
impl std::error::Error for ControlChartError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_limits_construction() {
let limits = ControlLimits {
ucl: 30.0,
cl: 25.0,
lcl: 20.0,
};
assert!((limits.ucl - 30.0).abs() < f64::EPSILON);
assert!((limits.cl - 25.0).abs() < f64::EPSILON);
assert!((limits.lcl - 20.0).abs() < f64::EPSILON);
}
#[test]
fn test_chart_point_construction() {
let point = ChartPoint {
value: 25.5,
index: 0,
violations: vec![ViolationType::BeyondLimits],
};
assert!((point.value - 25.5).abs() < f64::EPSILON);
assert_eq!(point.index, 0);
assert_eq!(point.violations.len(), 1);
assert_eq!(point.violations[0], ViolationType::BeyondLimits);
}
#[test]
fn test_violation_type_equality() {
assert_eq!(ViolationType::BeyondLimits, ViolationType::BeyondLimits);
assert_ne!(ViolationType::BeyondLimits, ViolationType::NineOneSide);
}
#[test]
fn test_violation_construction() {
let v = Violation {
point_index: 5,
violation_type: ViolationType::SixTrend,
};
assert_eq!(v.point_index, 5);
assert_eq!(v.violation_type, ViolationType::SixTrend);
}
#[test]
fn test_control_limits_clone() {
let limits = ControlLimits {
ucl: 30.0,
cl: 25.0,
lcl: 20.0,
};
let cloned = limits.clone();
assert_eq!(limits, cloned);
}
}