1#![forbid(unsafe_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum FilterError {
18 EmptySamples,
19 InvalidSample,
20 InvalidWindowSize,
21 InvalidAlpha,
22}
23
24fn validate_samples(samples: &[f64]) -> Result<(), FilterError> {
25 if samples.is_empty() {
26 return Err(FilterError::EmptySamples);
27 }
28
29 if samples.iter().any(|sample| !sample.is_finite()) {
30 return Err(FilterError::InvalidSample);
31 }
32
33 Ok(())
34}
35
36fn validate_alpha(alpha: f64) -> Result<(), FilterError> {
37 if !alpha.is_finite() || !(0.0..=1.0).contains(&alpha) {
38 Err(FilterError::InvalidAlpha)
39 } else {
40 Ok(())
41 }
42}
43
44pub fn moving_average_filter(samples: &[f64], window_size: usize) -> Result<Vec<f64>, FilterError> {
45 validate_samples(samples)?;
46
47 if window_size == 0 {
48 return Err(FilterError::InvalidWindowSize);
49 }
50
51 let mut running_sum = 0.0;
52 let mut output = Vec::with_capacity(samples.len());
53
54 for (index, sample) in samples.iter().copied().enumerate() {
55 running_sum += sample;
56
57 if index >= window_size {
58 running_sum -= samples[index - window_size];
59 }
60
61 let divisor = usize::min(index + 1, window_size) as f64;
62 output.push(running_sum / divisor);
63 }
64
65 Ok(output)
66}
67
68pub fn first_order_low_pass(samples: &[f64], alpha: f64) -> Result<Vec<f64>, FilterError> {
69 validate_samples(samples)?;
70 validate_alpha(alpha)?;
71
72 let mut output = Vec::with_capacity(samples.len());
73 let mut previous = samples[0];
74 output.push(previous);
75
76 for sample in &samples[1..] {
77 previous = alpha * sample + (1.0 - alpha) * previous;
78 output.push(previous);
79 }
80
81 Ok(output)
82}
83
84pub fn first_order_high_pass(samples: &[f64], alpha: f64) -> Result<Vec<f64>, FilterError> {
85 validate_samples(samples)?;
86 validate_alpha(alpha)?;
87
88 let mut output = Vec::with_capacity(samples.len());
89 let mut previous_input = samples[0];
90 let mut previous_output = samples[0];
91 output.push(previous_output);
92
93 for sample in &samples[1..] {
94 let current = alpha * (previous_output + sample - previous_input);
95 output.push(current);
96 previous_input = *sample;
97 previous_output = current;
98 }
99
100 Ok(output)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::{FilterError, first_order_high_pass, first_order_low_pass, moving_average_filter};
106
107 #[test]
108 fn applies_moving_average_filter() {
109 assert_eq!(
110 moving_average_filter(&[1.0, 3.0, 5.0, 7.0], 2).unwrap(),
111 vec![1.0, 2.0, 4.0, 6.0]
112 );
113 }
114
115 #[test]
116 fn applies_first_order_low_pass_filter() {
117 assert_eq!(
118 first_order_low_pass(&[0.0, 1.0, 1.0], 0.5).unwrap(),
119 vec![0.0, 0.5, 0.75]
120 );
121 }
122
123 #[test]
124 fn applies_first_order_high_pass_filter() {
125 let filtered = first_order_high_pass(&[1.0, 1.0, 1.0], 0.5).unwrap();
126
127 assert_eq!(filtered, vec![1.0, 0.5, 0.25]);
128 }
129
130 #[test]
131 fn rejects_invalid_inputs() {
132 assert_eq!(
133 moving_average_filter(&[], 2),
134 Err(FilterError::EmptySamples)
135 );
136 assert_eq!(
137 moving_average_filter(&[1.0], 0),
138 Err(FilterError::InvalidWindowSize)
139 );
140 assert_eq!(
141 first_order_low_pass(&[1.0, f64::NAN], 0.5),
142 Err(FilterError::InvalidSample)
143 );
144 assert_eq!(
145 first_order_high_pass(&[1.0], 1.5),
146 Err(FilterError::InvalidAlpha)
147 );
148 }
149
150 #[test]
151 fn handles_single_value_inputs() {
152 assert_eq!(moving_average_filter(&[2.0], 4).unwrap(), vec![2.0]);
153 assert_eq!(first_order_low_pass(&[2.0], 0.2).unwrap(), vec![2.0]);
154 assert_eq!(first_order_high_pass(&[2.0], 0.2).unwrap(), vec![2.0]);
155 }
156}