1use super::chart::{ChartPoint, ControlLimits, ViolationType};
27
28pub trait RunRule {
33 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)>;
38}
39
40pub struct WesternElectricRules;
51
52pub struct NelsonRules;
73
74fn zone_widths(limits: &ControlLimits) -> (f64, f64) {
82 let sigma = (limits.ucl - limits.cl) / 3.0;
83 (sigma, 2.0 * sigma)
84}
85
86fn check_rule1(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
90 let mut violations = Vec::new();
91 for point in points {
92 if point.value > limits.ucl || point.value < limits.lcl {
93 violations.push((point.index, ViolationType::BeyondLimits));
94 }
95 }
96 violations
97}
98
99fn check_one_side(
104 points: &[ChartPoint],
105 limits: &ControlLimits,
106 run: usize,
107 rule: ViolationType,
108) -> Vec<(usize, ViolationType)> {
109 let mut violations = Vec::new();
110 if points.len() < run {
111 return violations;
112 }
113
114 let cl = limits.cl;
115 let sides: Vec<i8> = points
118 .iter()
119 .map(|p| {
120 if p.value > cl {
121 1
122 } else if p.value < cl {
123 -1
124 } else {
125 0
126 }
127 })
128 .collect();
129
130 let mut run_length = 1_usize;
131 for i in 1..sides.len() {
132 if sides[i] != 0 && sides[i] == sides[i - 1] {
133 run_length += 1;
134 } else {
135 run_length = 1;
136 }
137 if run_length >= run {
138 violations.push((points[i].index, rule));
139 }
140 }
141 violations
142}
143
144fn check_rule3(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
148 let _ = limits; let mut violations = Vec::new();
150 if points.len() < 6 {
151 return violations;
152 }
153
154 let dirs: Vec<i8> = points
156 .windows(2)
157 .map(|w| {
158 if w[1].value > w[0].value {
159 1
160 } else if w[1].value < w[0].value {
161 -1
162 } else {
163 0
164 }
165 })
166 .collect();
167
168 let mut run_length = 1_usize;
169 for i in 1..dirs.len() {
170 if dirs[i] != 0 && dirs[i] == dirs[i - 1] {
171 run_length += 1;
172 } else {
173 run_length = 1;
174 }
175 if run_length >= 5 {
178 violations.push((points[i + 1].index, ViolationType::SixTrend));
181 }
182 }
183 violations
184}
185
186fn check_rule4(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
190 let _ = limits;
191 let mut violations = Vec::new();
192 if points.len() < 14 {
193 return violations;
194 }
195
196 let dirs: Vec<i8> = points
198 .windows(2)
199 .map(|w| {
200 if w[1].value > w[0].value {
201 1
202 } else if w[1].value < w[0].value {
203 -1
204 } else {
205 0
206 }
207 })
208 .collect();
209
210 let mut alt_length = 1_usize;
212 for i in 1..dirs.len() {
213 if dirs[i] != 0 && dirs[i - 1] != 0 && dirs[i] == -dirs[i - 1] {
214 alt_length += 1;
215 } else {
216 alt_length = 1;
217 }
218 if alt_length >= 13 {
222 violations.push((points[i + 1].index, ViolationType::FourteenAlternating));
223 }
224 }
225 violations
226}
227
228fn check_rule5(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
232 let mut violations = Vec::new();
233 if points.len() < 3 {
234 return violations;
235 }
236
237 let (_, two_sigma) = zone_widths(limits);
238 let upper_2s = limits.cl + two_sigma;
239 let lower_2s = limits.cl - two_sigma;
240
241 for i in 2..points.len() {
242 let window = &points[i - 2..=i];
243
244 let above_count = window.iter().filter(|p| p.value > upper_2s).count();
246 if above_count >= 2 {
247 violations.push((points[i].index, ViolationType::TwoOfThreeBeyond2Sigma));
248 continue;
249 }
250
251 let below_count = window.iter().filter(|p| p.value < lower_2s).count();
253 if below_count >= 2 {
254 violations.push((points[i].index, ViolationType::TwoOfThreeBeyond2Sigma));
255 }
256 }
257 violations
258}
259
260fn check_rule6(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
264 let mut violations = Vec::new();
265 if points.len() < 5 {
266 return violations;
267 }
268
269 let (one_sigma, _) = zone_widths(limits);
270 let upper_1s = limits.cl + one_sigma;
271 let lower_1s = limits.cl - one_sigma;
272
273 for i in 4..points.len() {
274 let window = &points[i - 4..=i];
275
276 let above_count = window.iter().filter(|p| p.value > upper_1s).count();
278 if above_count >= 4 {
279 violations.push((points[i].index, ViolationType::FourOfFiveBeyond1Sigma));
280 continue;
281 }
282
283 let below_count = window.iter().filter(|p| p.value < lower_1s).count();
285 if below_count >= 4 {
286 violations.push((points[i].index, ViolationType::FourOfFiveBeyond1Sigma));
287 }
288 }
289 violations
290}
291
292fn check_rule7(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
296 let mut violations = Vec::new();
297 if points.len() < 15 {
298 return violations;
299 }
300
301 let (one_sigma, _) = zone_widths(limits);
302 let upper_1s = limits.cl + one_sigma;
303 let lower_1s = limits.cl - one_sigma;
304
305 let mut run_length = 0_usize;
306 for point in points {
307 if point.value >= lower_1s && point.value <= upper_1s {
308 run_length += 1;
309 } else {
310 run_length = 0;
311 }
312 if run_length >= 15 {
313 violations.push((point.index, ViolationType::FifteenWithin1Sigma));
314 }
315 }
316 violations
317}
318
319fn check_rule8(points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
323 let mut violations = Vec::new();
324 if points.len() < 8 {
325 return violations;
326 }
327
328 let (one_sigma, _) = zone_widths(limits);
329 let upper_1s = limits.cl + one_sigma;
330 let lower_1s = limits.cl - one_sigma;
331
332 let mut run_length = 0_usize;
333 for point in points {
334 if point.value > upper_1s || point.value < lower_1s {
336 run_length += 1;
337 } else {
338 run_length = 0;
339 }
340 if run_length >= 8 {
341 violations.push((point.index, ViolationType::EightBeyond1Sigma));
342 }
343 }
344 violations
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub struct RuleSet {
386 mask: u16,
387}
388
389fn bit_of(v: ViolationType) -> u16 {
390 let position = ViolationType::ALL
391 .iter()
392 .position(|&r| r == v)
393 .expect("ViolationType::ALL lists every variant");
394 1 << position
395}
396
397impl RuleSet {
398 pub const NAMES: [&'static str; 3] = ["shewhart", "western_electric", "nelson"];
400
401 pub fn shewhart() -> Self {
407 Self::from_iter([ViolationType::BeyondLimits])
408 }
409
410 pub fn nelson() -> Self {
412 Self::from_iter([
413 ViolationType::BeyondLimits,
414 ViolationType::NineOneSide,
415 ViolationType::SixTrend,
416 ViolationType::FourteenAlternating,
417 ViolationType::TwoOfThreeBeyond2Sigma,
418 ViolationType::FourOfFiveBeyond1Sigma,
419 ViolationType::FifteenWithin1Sigma,
420 ViolationType::EightBeyond1Sigma,
421 ])
422 }
423
424 pub fn western_electric() -> Self {
427 Self::from_iter([
428 ViolationType::BeyondLimits,
429 ViolationType::TwoOfThreeBeyond2Sigma,
430 ViolationType::FourOfFiveBeyond1Sigma,
431 ViolationType::EightOneSide,
432 ])
433 }
434
435 pub fn named(name: &str) -> Option<Self> {
437 match name {
438 "shewhart" => Some(Self::shewhart()),
439 "western_electric" => Some(Self::western_electric()),
440 "nelson" => Some(Self::nelson()),
441 _ => None,
442 }
443 }
444
445 pub fn none() -> Self {
449 Self { mask: 0 }
450 }
451
452 pub fn contains(&self, rule: ViolationType) -> bool {
454 self.mask & bit_of(rule) != 0
455 }
456
457 #[must_use]
459 pub fn with(mut self, rule: ViolationType) -> Self {
460 self.mask |= bit_of(rule);
461 self
462 }
463
464 #[must_use]
466 pub fn without(mut self, rule: ViolationType) -> Self {
467 self.mask &= !bit_of(rule);
468 self
469 }
470
471 pub fn iter(&self) -> impl Iterator<Item = ViolationType> + '_ {
473 ViolationType::ALL.into_iter().filter(|&r| self.contains(r))
474 }
475
476 pub fn len(&self) -> usize {
478 self.mask.count_ones() as usize
479 }
480
481 pub fn is_empty(&self) -> bool {
483 self.mask == 0
484 }
485}
486
487impl Default for RuleSet {
488 fn default() -> Self {
490 Self::shewhart()
491 }
492}
493
494impl FromIterator<ViolationType> for RuleSet {
495 fn from_iter<I: IntoIterator<Item = ViolationType>>(iter: I) -> Self {
496 let mut mask = 0u16;
497 for v in iter {
498 mask |= bit_of(v);
499 }
500 Self { mask }
501 }
502}
503
504impl RunRule for RuleSet {
505 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
506 let mut results = Vec::new();
507 for rule in self.iter() {
508 results.extend(match rule {
509 ViolationType::BeyondLimits => check_rule1(points, limits),
510 ViolationType::NineOneSide => {
511 check_one_side(points, limits, 9, ViolationType::NineOneSide)
512 }
513 ViolationType::EightOneSide => {
514 check_one_side(points, limits, 8, ViolationType::EightOneSide)
515 }
516 ViolationType::SixTrend => check_rule3(points, limits),
517 ViolationType::FourteenAlternating => check_rule4(points, limits),
518 ViolationType::TwoOfThreeBeyond2Sigma => check_rule5(points, limits),
519 ViolationType::FourOfFiveBeyond1Sigma => check_rule6(points, limits),
520 ViolationType::FifteenWithin1Sigma => check_rule7(points, limits),
521 ViolationType::EightBeyond1Sigma => check_rule8(points, limits),
522 });
523 }
524 results.sort_by_key(|&(idx, _)| idx);
525 results
526 }
527}
528
529impl RunRule for WesternElectricRules {
534 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
536 RuleSet::western_electric().check(points, limits)
537 }
538}
539
540impl RunRule for NelsonRules {
541 fn check(&self, points: &[ChartPoint], limits: &ControlLimits) -> Vec<(usize, ViolationType)> {
543 RuleSet::nelson().check(points, limits)
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 fn nine_on_one_side() -> (Vec<ChartPoint>, ControlLimits) {
556 let limits = ControlLimits {
557 ucl: 28.0,
558 cl: 25.0,
559 lcl: 22.0,
560 };
561 let points = (0..12)
562 .map(|i| ChartPoint {
563 value: 26.0,
564 index: i,
565 violations: vec![],
566 })
567 .collect();
568 (points, limits)
569 }
570
571 #[test]
572 fn ruleset_applies_only_the_tests_it_holds() {
573 let (points, limits) = nine_on_one_side();
574
575 assert!(!RuleSet::nelson().check(&points, &limits).is_empty());
577 assert!(RuleSet::nelson()
578 .without(ViolationType::NineOneSide)
579 .check(&points, &limits)
580 .is_empty());
581
582 assert!(!RuleSet::from_iter([ViolationType::NineOneSide])
585 .check(&points, &limits)
586 .is_empty());
587 }
588
589 #[test]
590 fn ruleset_empty_reports_nothing_even_beyond_limits() {
591 let limits = ControlLimits {
592 ucl: 28.0,
593 cl: 25.0,
594 lcl: 22.0,
595 };
596 let points = vec![ChartPoint {
597 value: 99.0,
598 index: 0,
599 violations: vec![],
600 }];
601 assert!(!RuleSet::nelson().check(&points, &limits).is_empty());
602 assert!(RuleSet::none().check(&points, &limits).is_empty());
603 }
604
605 #[test]
606 fn named_sets_match_their_definitions() {
607 assert_eq!(RuleSet::nelson().len(), 8);
608 assert_eq!(RuleSet::western_electric().len(), 4);
609 for r in [
610 ViolationType::BeyondLimits,
611 ViolationType::TwoOfThreeBeyond2Sigma,
612 ViolationType::FourOfFiveBeyond1Sigma,
613 ViolationType::EightOneSide,
614 ] {
615 assert!(RuleSet::western_electric().contains(r), "{r:?} missing");
616 }
617 for r in [
618 ViolationType::NineOneSide,
619 ViolationType::SixTrend,
620 ViolationType::FourteenAlternating,
621 ViolationType::FifteenWithin1Sigma,
622 ViolationType::EightBeyond1Sigma,
623 ] {
624 assert!(!RuleSet::western_electric().contains(r), "{r:?} present");
625 }
626 assert!(RuleSet::none().is_empty());
627 }
628
629 #[test]
632 fn named_unit_structs_agree_with_their_rule_sets() {
633 let (points, limits) = nine_on_one_side();
634 assert_eq!(
635 NelsonRules.check(&points, &limits),
636 RuleSet::nelson().check(&points, &limits)
637 );
638 assert_eq!(
639 WesternElectricRules.check(&points, &limits),
640 RuleSet::western_electric().check(&points, &limits)
641 );
642 }
643
644 fn make_points(values: &[f64]) -> Vec<ChartPoint> {
646 values
647 .iter()
648 .enumerate()
649 .map(|(i, &v)| ChartPoint {
650 value: v,
651 index: i,
652 violations: Vec::new(),
653 })
654 .collect()
655 }
656
657 #[test]
660 fn test_rule1_point_above_ucl() {
661 let limits = ControlLimits {
662 ucl: 30.0,
663 cl: 25.0,
664 lcl: 20.0,
665 };
666 let points = make_points(&[25.0, 31.0, 25.0]);
667 let violations = check_rule1(&points, &limits);
668 assert_eq!(violations.len(), 1);
669 assert_eq!(violations[0].0, 1);
670 assert_eq!(violations[0].1, ViolationType::BeyondLimits);
671 }
672
673 #[test]
674 fn test_rule1_point_below_lcl() {
675 let limits = ControlLimits {
676 ucl: 30.0,
677 cl: 25.0,
678 lcl: 20.0,
679 };
680 let points = make_points(&[25.0, 19.0, 25.0]);
681 let violations = check_rule1(&points, &limits);
682 assert_eq!(violations.len(), 1);
683 assert_eq!(violations[0].0, 1);
684 }
685
686 #[test]
687 fn test_rule1_on_limit_is_not_violation() {
688 let limits = ControlLimits {
689 ucl: 30.0,
690 cl: 25.0,
691 lcl: 20.0,
692 };
693 let points = make_points(&[30.0, 20.0]);
694 let violations = check_rule1(&points, &limits);
695 assert!(violations.is_empty());
696 }
697
698 #[test]
701 fn test_rule2_nine_above() {
702 let limits = ControlLimits {
703 ucl: 30.0,
704 cl: 25.0,
705 lcl: 20.0,
706 };
707 let values: Vec<f64> = (0..9).map(|_| 26.0).collect();
709 let points = make_points(&values);
710 let violations = check_one_side(&points, &limits, 9, ViolationType::NineOneSide);
711 assert_eq!(violations.len(), 1);
712 assert_eq!(violations[0].1, ViolationType::NineOneSide);
713 }
714
715 #[test]
716 fn test_rule2_eight_not_enough() {
717 let limits = ControlLimits {
718 ucl: 30.0,
719 cl: 25.0,
720 lcl: 20.0,
721 };
722 let values: Vec<f64> = (0..8).map(|_| 26.0).collect();
723 let points = make_points(&values);
724 let violations = check_one_side(&points, &limits, 9, ViolationType::NineOneSide);
725 assert!(violations.is_empty());
726 }
727
728 #[test]
729 fn test_rule2_nine_below() {
730 let limits = ControlLimits {
731 ucl: 30.0,
732 cl: 25.0,
733 lcl: 20.0,
734 };
735 let values: Vec<f64> = (0..9).map(|_| 24.0).collect();
736 let points = make_points(&values);
737 let violations = check_one_side(&points, &limits, 9, ViolationType::NineOneSide);
738 assert_eq!(violations.len(), 1);
739 }
740
741 #[test]
747 fn western_electric_run_is_eight_points_nelson_is_nine() {
748 let limits = ControlLimits {
749 ucl: 30.0,
750 cl: 25.0,
751 lcl: 20.0,
752 };
753 let points = make_points(&[26.0; 8]);
754
755 let we = RuleSet::western_electric().check(&points, &limits);
756 assert_eq!(we, vec![(7, ViolationType::EightOneSide)]);
757 assert!(RuleSet::nelson().check(&points, &limits).is_empty());
758 assert_eq!(WesternElectricRules.check(&points, &limits), we);
759
760 let seven = make_points(&[26.0; 7]);
762 assert!(RuleSet::western_electric()
763 .check(&seven, &limits)
764 .is_empty());
765 }
766
767 #[test]
770 fn default_set_is_the_three_sigma_test() {
771 let limits = ControlLimits {
772 ucl: 30.0,
773 cl: 25.0,
774 lcl: 20.0,
775 };
776 let mut values = vec![26.0; 12];
777 values.push(31.0);
778 let points = make_points(&values);
779
780 assert_eq!(RuleSet::default(), RuleSet::shewhart());
781 assert_eq!(
782 RuleSet::default().check(&points, &limits),
783 vec![(12, ViolationType::BeyondLimits)]
784 );
785 assert!(RuleSet::nelson().check(&points, &limits).len() > 1);
786 }
787
788 #[test]
789 fn named_sets_round_trip_and_names_round_trip() {
790 for name in RuleSet::NAMES {
791 assert!(RuleSet::named(name).is_some(), "{name}");
792 }
793 assert_eq!(RuleSet::named("weco"), None);
794 for v in ViolationType::ALL {
795 assert_eq!(ViolationType::from_name(v.name()), Some(v));
796 }
797 let all: RuleSet = ViolationType::ALL.into_iter().collect();
798 assert_eq!(all.len(), ViolationType::ALL.len());
799 }
800
801 #[test]
804 fn test_rule3_six_increasing() {
805 let limits = ControlLimits {
806 ucl: 30.0,
807 cl: 25.0,
808 lcl: 20.0,
809 };
810 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0, 25.0]);
811 let violations = check_rule3(&points, &limits);
812 assert_eq!(violations.len(), 1);
813 assert_eq!(violations[0].1, ViolationType::SixTrend);
814 }
815
816 #[test]
817 fn test_rule3_six_decreasing() {
818 let limits = ControlLimits {
819 ucl: 30.0,
820 cl: 25.0,
821 lcl: 20.0,
822 };
823 let points = make_points(&[30.0, 29.0, 28.0, 27.0, 26.0, 25.0]);
824 let violations = check_rule3(&points, &limits);
825 assert_eq!(violations.len(), 1);
826 }
827
828 #[test]
829 fn test_rule3_five_not_enough() {
830 let limits = ControlLimits {
831 ucl: 30.0,
832 cl: 25.0,
833 lcl: 20.0,
834 };
835 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0]);
836 let violations = check_rule3(&points, &limits);
837 assert!(violations.is_empty());
838 }
839
840 #[test]
843 fn test_rule4_fourteen_alternating() {
844 let limits = ControlLimits {
845 ucl: 30.0,
846 cl: 25.0,
847 lcl: 20.0,
848 };
849 let values: Vec<f64> = (0..14)
851 .map(|i| if i % 2 == 0 { 24.0 } else { 26.0 })
852 .collect();
853 let points = make_points(&values);
854 let violations = check_rule4(&points, &limits);
855 assert_eq!(violations.len(), 1);
856 assert_eq!(violations[0].1, ViolationType::FourteenAlternating);
857 }
858
859 #[test]
860 fn test_rule4_thirteen_not_enough() {
861 let limits = ControlLimits {
862 ucl: 30.0,
863 cl: 25.0,
864 lcl: 20.0,
865 };
866 let values: Vec<f64> = (0..13)
867 .map(|i| if i % 2 == 0 { 24.0 } else { 26.0 })
868 .collect();
869 let points = make_points(&values);
870 let violations = check_rule4(&points, &limits);
871 assert!(violations.is_empty());
872 }
873
874 #[test]
877 fn test_rule5_two_of_three_above() {
878 let limits = ControlLimits {
879 ucl: 28.0, cl: 25.0,
881 lcl: 22.0,
882 };
883 let points = make_points(&[27.5, 25.0, 27.5]);
886 let violations = check_rule5(&points, &limits);
887 assert_eq!(violations.len(), 1);
888 assert_eq!(violations[0].1, ViolationType::TwoOfThreeBeyond2Sigma);
889 }
890
891 #[test]
892 fn test_rule5_two_of_three_below() {
893 let limits = ControlLimits {
894 ucl: 28.0,
895 cl: 25.0,
896 lcl: 22.0,
897 };
898 let points = make_points(&[22.5, 25.0, 22.5]);
900 let violations = check_rule5(&points, &limits);
901 assert_eq!(violations.len(), 1);
902 }
903
904 #[test]
907 fn test_rule6_four_of_five_above() {
908 let limits = ControlLimits {
909 ucl: 28.0, cl: 25.0,
911 lcl: 22.0,
912 };
913 let points = make_points(&[26.5, 26.5, 25.0, 26.5, 26.5]);
915 let violations = check_rule6(&points, &limits);
916 assert_eq!(violations.len(), 1);
917 assert_eq!(violations[0].1, ViolationType::FourOfFiveBeyond1Sigma);
918 }
919
920 #[test]
923 fn test_rule7_fifteen_within() {
924 let limits = ControlLimits {
925 ucl: 28.0, cl: 25.0,
927 lcl: 22.0,
928 };
929 let values: Vec<f64> = (0..15).map(|i| 24.5 + (i as f64 % 3.0) * 0.25).collect();
931 let points = make_points(&values);
932 let violations = check_rule7(&points, &limits);
933 assert_eq!(violations.len(), 1);
934 assert_eq!(violations[0].1, ViolationType::FifteenWithin1Sigma);
935 }
936
937 #[test]
940 fn test_rule8_eight_beyond() {
941 let limits = ControlLimits {
942 ucl: 28.0, cl: 25.0,
944 lcl: 22.0,
945 };
946 let points = make_points(&[27.0, 23.0, 27.0, 23.0, 27.0, 23.0, 27.0, 23.0]);
948 let violations = check_rule8(&points, &limits);
949 assert_eq!(violations.len(), 1);
950 assert_eq!(violations[0].1, ViolationType::EightBeyond1Sigma);
951 }
952
953 #[test]
954 fn test_rule8_seven_not_enough() {
955 let limits = ControlLimits {
956 ucl: 28.0,
957 cl: 25.0,
958 lcl: 22.0,
959 };
960 let points = make_points(&[27.0, 23.0, 27.0, 23.0, 27.0, 23.0, 27.0]);
961 let violations = check_rule8(&points, &limits);
962 assert!(violations.is_empty());
963 }
964
965 #[test]
968 fn test_western_electric_combines_four_rules() {
969 let limits = ControlLimits {
970 ucl: 30.0,
971 cl: 25.0,
972 lcl: 20.0,
973 };
974 let points = make_points(&[25.0, 31.0, 25.0]);
976 let we = WesternElectricRules;
977 let violations = we.check(&points, &limits);
978 assert!(!violations.is_empty());
979 assert!(violations
980 .iter()
981 .any(|(_, v)| *v == ViolationType::BeyondLimits));
982 }
983
984 #[test]
987 fn test_nelson_detects_trend() {
988 let limits = ControlLimits {
989 ucl: 30.0,
990 cl: 25.0,
991 lcl: 20.0,
992 };
993 let points = make_points(&[20.0, 21.0, 22.0, 23.0, 24.0, 25.0]);
994 let nelson = NelsonRules;
995 let violations = nelson.check(&points, &limits);
996 assert!(violations
997 .iter()
998 .any(|(_, v)| *v == ViolationType::SixTrend));
999 }
1000
1001 #[test]
1002 fn test_nelson_no_violations_in_random_data() {
1003 let limits = ControlLimits {
1004 ucl: 28.0,
1005 cl: 25.0,
1006 lcl: 22.0,
1007 };
1008 let points = make_points(&[25.5, 24.8, 25.2, 24.9, 25.1]);
1010 let nelson = NelsonRules;
1011 let violations = nelson.check(&points, &limits);
1012 assert!(violations.is_empty());
1013 }
1014
1015 #[test]
1016 fn test_nelson_rule2_continuation() {
1017 let limits = ControlLimits {
1019 ucl: 30.0,
1020 cl: 25.0,
1021 lcl: 20.0,
1022 };
1023 let values: Vec<f64> = (0..10).map(|_| 26.0).collect();
1024 let points = make_points(&values);
1025 let violations = check_one_side(&points, &limits, 9, ViolationType::NineOneSide);
1026 assert_eq!(violations.len(), 2);
1028 assert_eq!(violations[0].0, 8);
1029 assert_eq!(violations[1].0, 9);
1030 }
1031
1032 #[test]
1033 fn test_rule5_not_triggered_mixed_sides() {
1034 let limits = ControlLimits {
1035 ucl: 28.0,
1036 cl: 25.0,
1037 lcl: 22.0,
1038 };
1039 let points = make_points(&[27.5, 25.0, 22.5]);
1042 let violations = check_rule5(&points, &limits);
1043 assert!(violations.is_empty());
1044 }
1045
1046 #[test]
1047 fn test_rule7_fourteen_not_enough() {
1048 let limits = ControlLimits {
1049 ucl: 28.0,
1050 cl: 25.0,
1051 lcl: 22.0,
1052 };
1053 let values: Vec<f64> = (0..14).map(|_| 25.5).collect();
1054 let points = make_points(&values);
1055 let violations = check_rule7(&points, &limits);
1056 assert!(violations.is_empty());
1057 }
1058}