use crate::error::FdarError;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ChartRule {
WE1,
WE2,
WE3,
WE4,
Nelson5,
Nelson6,
Nelson7,
CustomRun {
n_points: usize,
k_sigma: f64,
},
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct RuleViolation {
pub rule: ChartRule,
pub indices: Vec<usize>,
}
#[must_use = "violations should not be discarded"]
pub fn evaluate_rules(
values: &[f64],
center: f64,
sigma: f64,
rules: &[ChartRule],
) -> Result<Vec<RuleViolation>, FdarError> {
if sigma <= 0.0 {
return Err(FdarError::InvalidParameter {
parameter: "sigma",
message: format!("sigma must be positive, got {sigma}"),
});
}
let mut violations = Vec::new();
for rule in rules {
let mut rule_violations = evaluate_single_rule(values, center, sigma, rule);
violations.append(&mut rule_violations);
}
Ok(violations)
}
#[must_use = "violations should not be discarded"]
pub fn western_electric_rules(
values: &[f64],
center: f64,
sigma: f64,
) -> Result<Vec<RuleViolation>, FdarError> {
evaluate_rules(
values,
center,
sigma,
&[
ChartRule::WE1,
ChartRule::WE2,
ChartRule::WE3,
ChartRule::WE4,
],
)
}
#[must_use = "violations should not be discarded"]
pub fn nelson_rules(
values: &[f64],
center: f64,
sigma: f64,
) -> Result<Vec<RuleViolation>, FdarError> {
evaluate_rules(
values,
center,
sigma,
&[
ChartRule::WE1,
ChartRule::WE2,
ChartRule::WE3,
ChartRule::WE4,
ChartRule::Nelson5,
ChartRule::Nelson6,
ChartRule::Nelson7,
],
)
}
fn evaluate_single_rule(
values: &[f64],
center: f64,
sigma: f64,
rule: &ChartRule,
) -> Vec<RuleViolation> {
match rule {
ChartRule::WE1 => eval_we1(values, center, sigma),
ChartRule::WE2 => eval_we2(values, center, sigma),
ChartRule::WE3 => eval_we3(values, center, sigma),
ChartRule::WE4 => eval_we4(values, center),
ChartRule::Nelson5 => eval_nelson5(values),
ChartRule::Nelson6 => eval_nelson6(values),
ChartRule::Nelson7 => eval_nelson7(values, center, sigma),
ChartRule::CustomRun { n_points, k_sigma } => {
eval_custom_run(values, center, sigma, *n_points, *k_sigma)
}
}
}
fn eval_we1(values: &[f64], center: f64, sigma: f64) -> Vec<RuleViolation> {
let mut violations = Vec::new();
for (i, &v) in values.iter().enumerate() {
if (v - center).abs() > 3.0 * sigma {
violations.push(RuleViolation {
rule: ChartRule::WE1,
indices: vec![i],
});
}
}
violations
}
fn eval_we2(values: &[f64], center: f64, sigma: f64) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 3 {
return violations;
}
for start in 0..values.len() - 2 {
let window = &values[start..start + 3];
let above_2s: Vec<usize> = window
.iter()
.enumerate()
.filter(|(_, &v)| v - center > 2.0 * sigma)
.map(|(j, _)| start + j)
.collect();
if above_2s.len() >= 2 {
violations.push(RuleViolation {
rule: ChartRule::WE2,
indices: above_2s,
});
}
let below_2s: Vec<usize> = window
.iter()
.enumerate()
.filter(|(_, &v)| center - v > 2.0 * sigma)
.map(|(j, _)| start + j)
.collect();
if below_2s.len() >= 2 {
violations.push(RuleViolation {
rule: ChartRule::WE2,
indices: below_2s,
});
}
}
violations
}
fn eval_we3(values: &[f64], center: f64, sigma: f64) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 5 {
return violations;
}
for start in 0..values.len() - 4 {
let window = &values[start..start + 5];
let above_1s: Vec<usize> = window
.iter()
.enumerate()
.filter(|(_, &v)| v - center > sigma)
.map(|(j, _)| start + j)
.collect();
if above_1s.len() >= 4 {
violations.push(RuleViolation {
rule: ChartRule::WE3,
indices: above_1s,
});
}
let below_1s: Vec<usize> = window
.iter()
.enumerate()
.filter(|(_, &v)| center - v > sigma)
.map(|(j, _)| start + j)
.collect();
if below_1s.len() >= 4 {
violations.push(RuleViolation {
rule: ChartRule::WE3,
indices: below_1s,
});
}
}
violations
}
fn eval_we4(values: &[f64], center: f64) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 8 {
return violations;
}
for start in 0..values.len() - 7 {
let window = &values[start..start + 8];
let all_above = window.iter().all(|&v| v > center);
let all_below = window.iter().all(|&v| v < center);
if all_above || all_below {
violations.push(RuleViolation {
rule: ChartRule::WE4,
indices: (start..start + 8).collect(),
});
}
}
violations
}
fn eval_nelson5(values: &[f64]) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 6 {
return violations;
}
for start in 0..values.len() - 5 {
let window = &values[start..start + 6];
let increasing = window.windows(2).all(|w| w[1] > w[0]);
let decreasing = window.windows(2).all(|w| w[1] < w[0]);
if increasing || decreasing {
violations.push(RuleViolation {
rule: ChartRule::Nelson5,
indices: (start..start + 6).collect(),
});
}
}
violations
}
fn eval_nelson6(values: &[f64]) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 14 {
return violations;
}
for start in 0..values.len() - 13 {
let window = &values[start..start + 14];
let alternating = window
.windows(3)
.all(|w| (w[1] > w[0] && w[1] > w[2]) || (w[1] < w[0] && w[1] < w[2]));
if alternating {
violations.push(RuleViolation {
rule: ChartRule::Nelson6,
indices: (start..start + 14).collect(),
});
}
}
violations
}
fn eval_nelson7(values: &[f64], center: f64, sigma: f64) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if values.len() < 15 {
return violations;
}
for start in 0..values.len() - 14 {
let window = &values[start..start + 15];
let all_within = window.iter().all(|&v| (v - center).abs() <= sigma);
if all_within {
violations.push(RuleViolation {
rule: ChartRule::Nelson7,
indices: (start..start + 15).collect(),
});
}
}
violations
}
fn eval_custom_run(
values: &[f64],
center: f64,
sigma: f64,
n_points: usize,
k_sigma: f64,
) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if k_sigma < 0.0 {
return violations;
}
if n_points == 0 || values.len() < n_points {
return violations;
}
for start in 0..=values.len() - n_points {
let window = &values[start..start + n_points];
let all_beyond = window.iter().all(|&v| (v - center).abs() > k_sigma * sigma);
if all_beyond {
violations.push(RuleViolation {
rule: ChartRule::CustomRun { n_points, k_sigma },
indices: (start..start + n_points).collect(),
});
}
}
violations
}