u_analytics/smoothing/
ses.rs1#[derive(Debug, Clone)]
20pub struct SesResult {
21 pub smoothed: Vec<f64>,
23 pub forecast: f64,
25}
26
27pub struct SimpleExponentialSmoothing {
29 alpha: f64,
30}
31
32impl SimpleExponentialSmoothing {
33 pub fn new(alpha: f64) -> Option<Self> {
40 if !alpha.is_finite() || alpha <= 0.0 || alpha >= 1.0 {
41 return None;
42 }
43 Some(Self { alpha })
44 }
45
46 pub fn alpha(&self) -> f64 {
48 self.alpha
49 }
50
51 pub fn smooth(&self, data: &[f64]) -> Option<SesResult> {
55 if data.is_empty() {
56 return None;
57 }
58
59 let mut smoothed = Vec::with_capacity(data.len());
60 let mut s = data[0];
61 smoothed.push(s);
62
63 for &x in &data[1..] {
64 s = self.alpha * x + (1.0 - self.alpha) * s;
65 smoothed.push(s);
66 }
67
68 let forecast = s;
69 Some(SesResult { smoothed, forecast })
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_ses_basic() {
79 let ses = SimpleExponentialSmoothing::new(0.3).unwrap();
80 let data = [10.0, 12.0, 13.0, 11.0, 14.0];
81 let result = ses.smooth(&data).unwrap();
82
83 assert_eq!(result.smoothed.len(), 5);
84 assert!((result.smoothed[0] - 10.0).abs() < 1e-10);
86 assert!((result.smoothed[1] - 10.6).abs() < 1e-10);
88 }
89
90 #[test]
91 fn test_ses_constant_series() {
92 let ses = SimpleExponentialSmoothing::new(0.5).unwrap();
93 let data = [5.0; 10];
94 let result = ses.smooth(&data).unwrap();
95
96 for &v in &result.smoothed {
97 assert!((v - 5.0).abs() < 1e-10);
98 }
99 assert!((result.forecast - 5.0).abs() < 1e-10);
100 }
101
102 #[test]
103 fn test_ses_alpha_effect() {
104 let data = [10.0, 20.0, 10.0];
106
107 let low = SimpleExponentialSmoothing::new(0.1).unwrap();
108 let high = SimpleExponentialSmoothing::new(0.9).unwrap();
109
110 let r_low = low.smooth(&data).unwrap();
111 let r_high = high.smooth(&data).unwrap();
112
113 assert!(r_high.smoothed[1] > r_low.smoothed[1]);
115 }
116
117 #[test]
118 fn test_ses_single_point() {
119 let ses = SimpleExponentialSmoothing::new(0.5).unwrap();
120 let result = ses.smooth(&[42.0]).unwrap();
121 assert_eq!(result.smoothed.len(), 1);
122 assert!((result.forecast - 42.0).abs() < 1e-10);
123 }
124
125 #[test]
126 fn test_ses_empty() {
127 let ses = SimpleExponentialSmoothing::new(0.5).unwrap();
128 assert!(ses.smooth(&[]).is_none());
129 }
130
131 #[test]
132 fn test_ses_invalid_alpha() {
133 assert!(SimpleExponentialSmoothing::new(0.0).is_none());
134 assert!(SimpleExponentialSmoothing::new(1.0).is_none());
135 assert!(SimpleExponentialSmoothing::new(-0.1).is_none());
136 assert!(SimpleExponentialSmoothing::new(f64::NAN).is_none());
137 }
138}