Skip to main content

u_analytics/spc/
chart.rs

1//! Core control chart types and trait.
2//!
3//! Defines the fundamental building blocks for all control charts: control limits,
4//! chart points with violation annotations, and the [`ControlChart`] trait that
5//! all variables charts implement.
6//!
7//! # References
8//!
9//! - Montgomery, D.C. (2019). *Introduction to Statistical Quality Control*, 8th ed.
10//! - ASTM E2587 — Standard Practice for Use of Control Charts
11
12/// Control limits for a chart.
13///
14/// Represents the upper control limit (UCL), center line (CL), and lower
15/// control limit (LCL) computed from the process data.
16///
17/// # Invariants
18///
19/// - `lcl <= cl <= ucl`
20/// - All values are finite
21#[derive(Debug, Clone, PartialEq)]
22pub struct ControlLimits {
23    /// Upper control limit (UCL = CL + 3 sigma).
24    pub ucl: f64,
25    /// Center line (process mean or target).
26    pub cl: f64,
27    /// Lower control limit (LCL = CL - 3 sigma).
28    pub lcl: f64,
29}
30
31/// A single point on a control chart.
32///
33/// Each point corresponds to a statistic computed from one subgroup or
34/// individual observation, along with any run-rule violations detected
35/// at that point.
36#[derive(Debug, Clone)]
37pub struct ChartPoint {
38    /// The computed statistic value (e.g., subgroup mean, range, proportion).
39    pub value: f64,
40    /// The zero-based index of this point in the sequence.
41    pub index: usize,
42    /// List of violations detected at this point.
43    pub violations: Vec<ViolationType>,
44}
45
46/// Types of control chart violations based on Nelson's eight rules.
47///
48/// Each variant corresponds to one of Nelson's tests for special causes
49/// of variation.
50///
51/// # Reference
52///
53/// Nelson, L.S. (1984). "The Shewhart Control Chart — Tests for Special Causes",
54/// *Journal of Quality Technology* 16(4), pp. 237-239.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum ViolationType {
57    /// Point beyond control limits (Nelson Rule 1).
58    ///
59    /// A single point falls outside the 3-sigma control limits.
60    BeyondLimits,
61
62    /// 9 points in a row on same side of center line (Nelson Rule 2).
63    ///
64    /// Indicates a sustained shift in the process mean.
65    NineOneSide,
66
67    /// 6 points in a row steadily increasing or decreasing (Nelson Rule 3).
68    ///
69    /// Indicates a trend in the process.
70    SixTrend,
71
72    /// 14 points in a row alternating up and down (Nelson Rule 4).
73    ///
74    /// Indicates systematic variation (e.g., two alternating streams).
75    FourteenAlternating,
76
77    /// 2 out of 3 points beyond 2 sigma on same side (Nelson Rule 5).
78    ///
79    /// An early warning of a potential shift.
80    TwoOfThreeBeyond2Sigma,
81
82    /// 4 out of 5 points beyond 1 sigma on same side (Nelson Rule 6).
83    ///
84    /// Indicates a small sustained shift.
85    FourOfFiveBeyond1Sigma,
86
87    /// 15 points in a row within 1 sigma of center line (Nelson Rule 7).
88    ///
89    /// Indicates stratification — reduced variation suggesting mixed streams.
90    FifteenWithin1Sigma,
91
92    /// 8 points in a row beyond 1 sigma on either side (Nelson Rule 8).
93    ///
94    /// Indicates a mixture pattern — points avoid the center zone.
95    EightBeyond1Sigma,
96}
97
98/// A violation detected on the chart.
99///
100/// Associates a specific point index with the type of violation observed.
101#[derive(Debug, Clone)]
102pub struct Violation {
103    /// The index of the point where the violation was detected.
104    pub point_index: usize,
105    /// The type of violation.
106    pub violation_type: ViolationType,
107}
108
109/// Trait for control charts that process subgroup or individual data.
110///
111/// Implementors accumulate sample data, compute control limits from the
112/// accumulated data, and detect violations using run rules.
113pub trait ControlChart {
114    /// Add a sample (subgroup) to the chart.
115    ///
116    /// For subgroup charts (X-bar-R, X-bar-S), the slice contains the
117    /// individual measurements within one subgroup.
118    ///
119    /// For individual charts (I-MR), the slice should contain exactly
120    /// one element.
121    fn add_sample(&mut self, sample: &[f64]);
122
123    /// Get the computed control limits, or `None` if insufficient data.
124    fn control_limits(&self) -> Option<ControlLimits>;
125
126    /// Check if the process is in statistical control.
127    ///
128    /// Returns `true` if no violations have been detected across all points.
129    fn is_in_control(&self) -> bool;
130
131    /// Get all violations detected across all chart points.
132    fn violations(&self) -> Vec<Violation>;
133
134    /// Get all chart points.
135    fn points(&self) -> &[ChartPoint];
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_control_limits_construction() {
144        let limits = ControlLimits {
145            ucl: 30.0,
146            cl: 25.0,
147            lcl: 20.0,
148        };
149        assert!((limits.ucl - 30.0).abs() < f64::EPSILON);
150        assert!((limits.cl - 25.0).abs() < f64::EPSILON);
151        assert!((limits.lcl - 20.0).abs() < f64::EPSILON);
152    }
153
154    #[test]
155    fn test_chart_point_construction() {
156        let point = ChartPoint {
157            value: 25.5,
158            index: 0,
159            violations: vec![ViolationType::BeyondLimits],
160        };
161        assert!((point.value - 25.5).abs() < f64::EPSILON);
162        assert_eq!(point.index, 0);
163        assert_eq!(point.violations.len(), 1);
164        assert_eq!(point.violations[0], ViolationType::BeyondLimits);
165    }
166
167    #[test]
168    fn test_violation_type_equality() {
169        assert_eq!(ViolationType::BeyondLimits, ViolationType::BeyondLimits);
170        assert_ne!(ViolationType::BeyondLimits, ViolationType::NineOneSide);
171    }
172
173    #[test]
174    fn test_violation_construction() {
175        let v = Violation {
176            point_index: 5,
177            violation_type: ViolationType::SixTrend,
178        };
179        assert_eq!(v.point_index, 5);
180        assert_eq!(v.violation_type, ViolationType::SixTrend);
181    }
182
183    #[test]
184    fn test_control_limits_clone() {
185        let limits = ControlLimits {
186            ucl: 30.0,
187            cl: 25.0,
188            lcl: 20.0,
189        };
190        let cloned = limits.clone();
191        assert_eq!(limits, cloned);
192    }
193}