1use cjc_repro::KahanAccumulatorF64;
23
24pub fn window_sum(data: &[f64], window_size: usize) -> Vec<f64> {
34 if window_size == 0 || window_size > data.len() {
35 return Vec::new();
36 }
37 let n = data.len() - window_size + 1;
38 let mut result = Vec::with_capacity(n);
39
40 for i in 0..n {
41 let mut acc = KahanAccumulatorF64::new();
42 for j in 0..window_size {
43 acc.add(data[i + j]);
44 }
45 result.push(acc.finalize());
46 }
47
48 result
49}
50
51pub fn window_mean(data: &[f64], window_size: usize) -> Vec<f64> {
56 if window_size == 0 || window_size > data.len() {
57 return Vec::new();
58 }
59 let n = data.len() - window_size + 1;
60 let ws = window_size as f64;
61 let mut result = Vec::with_capacity(n);
62
63 for i in 0..n {
64 let mut acc = KahanAccumulatorF64::new();
65 for j in 0..window_size {
66 acc.add(data[i + j]);
67 }
68 result.push(acc.finalize() / ws);
69 }
70
71 result
72}
73
74pub fn window_min(data: &[f64], window_size: usize) -> Vec<f64> {
79 if window_size == 0 || window_size > data.len() {
80 return Vec::new();
81 }
82 let n = data.len() - window_size + 1;
83 let mut result = Vec::with_capacity(n);
84
85 for i in 0..n {
86 let mut min_val = data[i];
87 for j in 1..window_size {
88 let v = data[i + j];
89 if v < min_val {
90 min_val = v;
91 }
92 }
93 result.push(min_val);
94 }
95
96 result
97}
98
99pub fn window_max(data: &[f64], window_size: usize) -> Vec<f64> {
104 if window_size == 0 || window_size > data.len() {
105 return Vec::new();
106 }
107 let n = data.len() - window_size + 1;
108 let mut result = Vec::with_capacity(n);
109
110 for i in 0..n {
111 let mut max_val = data[i];
112 for j in 1..window_size {
113 let v = data[i + j];
114 if v > max_val {
115 max_val = v;
116 }
117 }
118 result.push(max_val);
119 }
120
121 result
122}
123
124#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_window_sum_basic() {
134 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
135 let result = window_sum(&data, 3);
136 assert_eq!(result, vec![6.0, 9.0, 12.0]);
137 }
138
139 #[test]
140 fn test_window_sum_full() {
141 let data = vec![1.0, 2.0, 3.0];
142 let result = window_sum(&data, 3);
143 assert_eq!(result, vec![6.0]);
144 }
145
146 #[test]
147 fn test_window_sum_single() {
148 let data = vec![1.0, 2.0, 3.0];
149 let result = window_sum(&data, 1);
150 assert_eq!(result, vec![1.0, 2.0, 3.0]);
151 }
152
153 #[test]
154 fn test_window_sum_empty_on_too_large() {
155 let data = vec![1.0, 2.0];
156 let result = window_sum(&data, 5);
157 assert!(result.is_empty());
158 }
159
160 #[test]
161 fn test_window_sum_empty_on_zero() {
162 let data = vec![1.0, 2.0, 3.0];
163 let result = window_sum(&data, 0);
164 assert!(result.is_empty());
165 }
166
167 #[test]
168 fn test_window_mean_basic() {
169 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
170 let result = window_mean(&data, 3);
171 assert_eq!(result, vec![2.0, 3.0, 4.0]);
172 }
173
174 #[test]
175 fn test_window_min_basic() {
176 let data = vec![3.0, 1.0, 4.0, 1.0, 5.0];
177 let result = window_min(&data, 3);
178 assert_eq!(result, vec![1.0, 1.0, 1.0]);
179 }
180
181 #[test]
182 fn test_window_max_basic() {
183 let data = vec![3.0, 1.0, 4.0, 1.0, 5.0];
184 let result = window_max(&data, 3);
185 assert_eq!(result, vec![4.0, 4.0, 5.0]);
186 }
187
188 #[test]
189 fn test_window_determinism() {
190 let data: Vec<f64> = (0..100).map(|i| i as f64 * 0.1).collect();
191 let r1 = window_sum(&data, 7);
192 let r2 = window_sum(&data, 7);
193 assert_eq!(r1, r2, "window_sum must be deterministic");
194 }
195
196 #[test]
197 fn test_window_kahan_accuracy() {
198 let n = 1000;
201 let data: Vec<f64> = vec![0.1; n];
202 let result = window_sum(&data, n);
203 assert_eq!(result.len(), 1);
204 let err = (result[0] - 100.0).abs();
207 assert!(
208 err < 1e-12,
209 "Kahan sum of 1000×0.1 should be close to 100.0, got {} (err={})",
210 result[0], err,
211 );
212 }
213}