1use std::collections::VecDeque;
11
12use crate::error::{RillError, ensure_finite};
13use crate::stats::variance::VarianceKind;
14use crate::traits::OnlineStatistic;
15
16#[derive(Debug, Clone)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct RollingMean {
38 window: VecDeque<f64>,
39 capacity: usize,
40}
41
42impl RollingMean {
43 pub fn new(capacity: usize) -> Result<Self, RillError> {
47 if capacity == 0 {
48 return Err(RillError::InvalidWindowSize);
49 }
50 Ok(Self {
51 window: VecDeque::with_capacity(capacity),
52 capacity,
53 })
54 }
55
56 pub const fn capacity(&self) -> usize {
58 self.capacity
59 }
60
61 pub fn len(&self) -> usize {
63 self.window.len()
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.window.is_empty()
69 }
70
71 pub fn value(&self) -> Option<f64> {
73 if self.window.is_empty() {
74 None
75 } else {
76 let sum: f64 = self.window.iter().sum();
77 Some(sum / self.window.len() as f64)
78 }
79 }
80}
81
82impl OnlineStatistic for RollingMean {
83 fn update(&mut self, value: f64) -> Result<(), RillError> {
84 ensure_finite("value", value)?;
85 if self.window.len() == self.capacity {
86 self.window.pop_front();
87 }
88 self.window.push_back(value);
89 Ok(())
90 }
91
92 fn samples_seen(&self) -> u64 {
93 self.window.len() as u64
94 }
95
96 fn reset(&mut self) {
97 self.window.clear();
98 }
99}
100
101#[derive(Debug, Clone)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct RollingVariance {
108 window: VecDeque<f64>,
109 capacity: usize,
110 kind: VarianceKind,
111}
112
113impl RollingVariance {
114 pub fn new(capacity: usize, kind: VarianceKind) -> Result<Self, RillError> {
118 if capacity == 0 {
119 return Err(RillError::InvalidWindowSize);
120 }
121 Ok(Self {
122 window: VecDeque::with_capacity(capacity),
123 capacity,
124 kind,
125 })
126 }
127
128 pub const fn capacity(&self) -> usize {
130 self.capacity
131 }
132
133 pub const fn kind(&self) -> VarianceKind {
135 self.kind
136 }
137
138 pub fn len(&self) -> usize {
140 self.window.len()
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.window.is_empty()
146 }
147
148 pub fn mean(&self) -> Option<f64> {
150 if self.window.is_empty() {
151 None
152 } else {
153 let sum: f64 = self.window.iter().sum();
154 Some(sum / self.window.len() as f64)
155 }
156 }
157
158 pub fn value(&self) -> Option<f64> {
160 let n = self.window.len();
161 if n == 0 {
162 return None;
163 }
164 let denom = match self.kind {
165 VarianceKind::Population => n,
166 VarianceKind::Sample => {
167 if n < 2 {
168 return None;
169 }
170 n - 1
171 }
172 };
173 let mean = self.mean()?;
174 let ss = self.window.iter().map(|x| (x - mean).powi(2)).sum::<f64>();
175 Some(ss / denom as f64)
176 }
177
178 pub fn std_dev(&self) -> Option<f64> {
180 self.value().map(|v| v.sqrt())
181 }
182}
183
184impl OnlineStatistic for RollingVariance {
185 fn update(&mut self, value: f64) -> Result<(), RillError> {
186 ensure_finite("value", value)?;
187 if self.window.len() == self.capacity {
188 self.window.pop_front();
189 }
190 self.window.push_back(value);
191 Ok(())
192 }
193
194 fn samples_seen(&self) -> u64 {
195 self.window.len() as u64
196 }
197
198 fn reset(&mut self) {
199 self.window.clear();
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn rolling_mean_basic() {
209 let mut rm = RollingMean::new(3).unwrap();
210 for x in [1.0, 2.0, 3.0] {
211 rm.update(x).unwrap();
212 }
213 assert!((rm.value().unwrap() - 2.0).abs() < 1e-12);
214 }
215
216 #[test]
217 fn rolling_mean_evicts_oldest() {
218 let mut rm = RollingMean::new(2).unwrap();
219 rm.update(10.0).unwrap();
220 rm.update(20.0).unwrap();
221 rm.update(30.0).unwrap();
222 assert!((rm.value().unwrap() - 25.0).abs() < 1e-12);
223 }
224
225 #[test]
226 fn rolling_mean_empty_returns_none() {
227 let rm = RollingMean::new(5).unwrap();
228 assert!(rm.value().is_none());
229 }
230
231 #[test]
232 fn rolling_mean_zero_capacity_rejected() {
233 assert!(matches!(
234 RollingMean::new(0),
235 Err(RillError::InvalidWindowSize)
236 ));
237 }
238
239 #[test]
240 fn rolling_variance_population() {
241 let mut rv = RollingVariance::new(4, VarianceKind::Population).unwrap();
242 for x in [1.0, 2.0, 3.0, 4.0] {
243 rv.update(x).unwrap();
244 }
245 assert!((rv.value().unwrap() - 1.25).abs() < 1e-12);
247 }
248
249 #[test]
250 fn rolling_variance_evicts_correctly() {
251 let mut rv = RollingVariance::new(3, VarianceKind::Population).unwrap();
252 for x in [1.0, 2.0, 3.0, 4.0] {
253 rv.update(x).unwrap();
254 }
255 assert!((rv.value().unwrap() - 2.0 / 3.0).abs() < 1e-12);
257 }
258
259 #[test]
260 fn rolling_variance_sample_insufficient() {
261 let mut rv = RollingVariance::new(5, VarianceKind::Sample).unwrap();
262 rv.update(1.0).unwrap();
263 assert!(rv.value().is_none());
264 }
265
266 #[test]
267 fn rolling_variance_zero_capacity_rejected() {
268 assert!(matches!(
269 RollingVariance::new(0, VarianceKind::Population),
270 Err(RillError::InvalidWindowSize)
271 ));
272 }
273}