1use crate::error::{RillError, checked_increment, ensure_finite, validate_features};
7use crate::traits::Transformer;
8
9#[derive(Debug, Clone)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct StandardScalerConfig {
13 pub with_mean: bool,
15 pub with_std: bool,
17 pub epsilon: f64,
20}
21
22impl Default for StandardScalerConfig {
23 fn default() -> Self {
24 Self {
25 with_mean: true,
26 with_std: true,
27 epsilon: 1e-12,
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45pub struct StandardScaler {
46 feature_count: usize,
47 config: StandardScalerConfig,
48 counts: Vec<u64>,
49 means: Vec<f64>,
50 m2s: Vec<f64>,
51}
52
53impl StandardScaler {
54 pub fn new(feature_count: usize) -> Result<Self, RillError> {
56 Self::with_config(feature_count, StandardScalerConfig::default())
57 }
58
59 pub fn with_config(
61 feature_count: usize,
62 config: StandardScalerConfig,
63 ) -> Result<Self, RillError> {
64 if feature_count == 0 {
65 return Err(RillError::EmptyFeatures);
66 }
67 ensure_finite("epsilon", config.epsilon)?;
68 if config.epsilon < 0.0 {
69 return Err(RillError::InvalidParameter {
70 name: "epsilon",
71 value: config.epsilon,
72 });
73 }
74 Ok(Self {
75 feature_count,
76 config,
77 counts: vec![0; feature_count],
78 means: vec![0.0; feature_count],
79 m2s: vec![0.0; feature_count],
80 })
81 }
82
83 pub fn means(&self) -> &[f64] {
85 &self.means
86 }
87
88 pub fn variances(&self) -> Vec<f64> {
90 self.m2s
91 .iter()
92 .zip(&self.counts)
93 .map(|(&m2, &n)| if n == 0 { 0.0 } else { m2 / n as f64 })
94 .collect()
95 }
96
97 pub fn std_devs(&self) -> Vec<f64> {
99 self.variances().iter().map(|v| v.sqrt()).collect()
100 }
101
102 pub fn scales(&self) -> Vec<f64> {
104 self.variances()
105 .iter()
106 .map(|&var| {
107 if var < self.config.epsilon {
108 1.0
109 } else {
110 var.sqrt()
111 }
112 })
113 .collect()
114 }
115
116 pub fn validate(&self) -> Result<(), RillError> {
121 if self.feature_count == 0 {
122 return Err(RillError::EmptyFeatures);
123 }
124 ensure_finite("epsilon", self.config.epsilon)?;
125 if self.config.epsilon < 0.0 {
126 return Err(RillError::InvalidParameter {
127 name: "epsilon",
128 value: self.config.epsilon,
129 });
130 }
131 if self.counts.len() != self.feature_count
132 || self.means.len() != self.feature_count
133 || self.m2s.len() != self.feature_count
134 {
135 return Err(RillError::InvalidState(
136 "standard scaler feature_count does not match state lengths".to_owned(),
137 ));
138 }
139 if self.means.iter().any(|value| !value.is_finite())
140 || self.m2s.iter().any(|value| !value.is_finite())
141 {
142 return Err(RillError::InvalidState(
143 "standard scaler state must contain only finite values".to_owned(),
144 ));
145 }
146 if self.counts.windows(2).any(|pair| pair[0] != pair[1]) {
147 return Err(RillError::InvalidState(
148 "standard scaler feature counts must stay synchronized".to_owned(),
149 ));
150 }
151 Ok(())
152 }
153}
154
155impl Transformer for StandardScaler {
156 fn input_dim(&self) -> usize {
157 self.feature_count
158 }
159
160 fn output_dim(&self) -> usize {
161 self.feature_count
162 }
163
164 fn transform(&self, features: &[f64]) -> Result<Vec<f64>, RillError> {
165 self.validate()?;
166 validate_features(self.feature_count, features)?;
167 let scales = self.scales();
168 features
169 .iter()
170 .enumerate()
171 .map(|(i, &x)| {
172 let mean = if self.config.with_mean {
173 self.means[i]
174 } else {
175 0.0
176 };
177 let scale = if self.config.with_std { scales[i] } else { 1.0 };
178 let transformed = (x - mean) / scale;
179 ensure_finite("transformed feature", transformed)?;
180 Ok(transformed)
181 })
182 .collect()
183 }
184
185 fn update(&mut self, features: &[f64]) -> Result<(), RillError> {
186 self.validate()?;
187 validate_features(self.feature_count, features)?;
188 let mut next_counts = self.counts.clone();
189 let mut next_means = self.means.clone();
190 let mut next_m2s = self.m2s.clone();
191 for (i, &x) in features.iter().enumerate() {
192 let count = checked_increment(self.counts[i], "standard scaler sample")?;
193 let delta = x - self.means[i];
194 ensure_finite("standard scaler delta", delta)?;
195 let mean = self.means[i] + delta / count as f64;
196 ensure_finite("standard scaler mean", mean)?;
197 let delta2 = x - mean;
198 ensure_finite("standard scaler delta", delta2)?;
199 let m2 = self.m2s[i] + delta * delta2;
200 ensure_finite("standard scaler M2", m2)?;
201 next_counts[i] = count;
202 next_means[i] = mean;
203 next_m2s[i] = m2;
204 }
205 self.counts = next_counts;
206 self.means = next_means;
207 self.m2s = next_m2s;
208 Ok(())
209 }
210
211 fn samples_seen(&self) -> u64 {
212 self.counts.iter().copied().max().unwrap_or(0)
213 }
214
215 fn reset(&mut self) {
216 for c in &mut self.counts {
217 *c = 0;
218 }
219 for m in &mut self.means {
220 *m = 0.0;
221 }
222 for m2 in &mut self.m2s {
223 *m2 = 0.0;
224 }
225 }
226}
227
228#[cfg(feature = "serde")]
229#[derive(serde::Deserialize)]
230struct StandardScalerState {
231 feature_count: usize,
232 config: StandardScalerConfig,
233 counts: Vec<u64>,
234 means: Vec<f64>,
235 m2s: Vec<f64>,
236}
237
238#[cfg(feature = "serde")]
239impl<'de> serde::Deserialize<'de> for StandardScaler {
240 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241 where
242 D: serde::Deserializer<'de>,
243 {
244 let state = StandardScalerState::deserialize(deserializer)?;
245 let scaler = Self {
246 feature_count: state.feature_count,
247 config: state.config,
248 counts: state.counts,
249 means: state.means,
250 m2s: state.m2s,
251 };
252 scaler.validate().map_err(serde::de::Error::custom)?;
253 Ok(scaler)
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn scaler_zero_state_returns_original() {
263 let s = StandardScaler::new(3).unwrap();
264 let out = s.transform(&[1.0, 2.0, 3.0]).unwrap();
265 assert!((out[0] - 1.0).abs() < 1e-12);
267 assert!((out[1] - 2.0).abs() < 1e-12);
268 assert!((out[2] - 3.0).abs() < 1e-12);
269 }
270
271 #[test]
272 fn scaler_standardizes_after_updates() {
273 let mut s = StandardScaler::new(2).unwrap();
274 s.update(&[1.0, 10.0]).unwrap();
277 s.update(&[3.0, 20.0]).unwrap();
278 let out = s.transform(&[3.0, 20.0]).unwrap();
279 assert!((out[0] - 1.0).abs() < 1e-9);
281 assert!((out[1] - 1.0).abs() < 1e-9);
282 }
283
284 #[test]
285 fn transform_does_not_update_state() {
286 let mut s = StandardScaler::new(1).unwrap();
287 s.update(&[10.0]).unwrap();
288 let mean_before = s.means()[0];
289 let _ = s.transform(&[5.0]).unwrap();
290 assert_eq!(s.means()[0], mean_before);
291 assert_eq!(s.counts[0], 1);
292 }
293
294 #[test]
295 fn update_rejects_overflow_without_mutating_state() {
296 let mut scaler = StandardScaler::new(1).unwrap();
297 scaler.update(&[f64::MAX]).unwrap();
298 let before = scaler.clone();
299 assert!(scaler.update(&[-f64::MAX]).is_err());
300 assert_eq!(scaler.counts, before.counts);
301 assert_eq!(scaler.means, before.means);
302 assert_eq!(scaler.m2s, before.m2s);
303 }
304
305 #[cfg(feature = "serde")]
306 #[test]
307 fn serde_rejects_malformed_state() {
308 let malformed = r#"{
309 "feature_count":2,
310 "config":{"with_mean":true,"with_std":true,"epsilon":1e-12},
311 "counts":[1],
312 "means":[0.0],
313 "m2s":[0.0]
314 }"#;
315 assert!(serde_json::from_str::<StandardScaler>(malformed).is_err());
316 }
317
318 #[test]
319 fn constant_feature_uses_scale_one() {
320 let mut s = StandardScaler::new(1).unwrap();
321 for _ in 0..10 {
322 s.update(&[5.0]).unwrap();
323 }
324 let out = s.transform(&[5.0]).unwrap();
326 assert!(out[0].abs() < 1e-12);
327 assert!(!out[0].is_nan());
328 }
329
330 #[test]
331 fn with_mean_false_keeps_offset() {
332 let mut s = StandardScaler::with_config(
333 1,
334 StandardScalerConfig {
335 with_mean: false,
336 with_std: true,
337 epsilon: 1e-12,
338 },
339 )
340 .unwrap();
341 s.update(&[1.0]).unwrap();
342 s.update(&[3.0]).unwrap();
343 let out = s.transform(&[3.0]).unwrap();
345 assert!((out[0] - 3.0).abs() < 1e-9);
346 }
347
348 #[test]
349 fn dimension_mismatch_rejected() {
350 let mut s = StandardScaler::new(3).unwrap();
351 assert!(s.transform(&[1.0, 2.0]).is_err());
352 assert!(s.update(&[1.0, 2.0]).is_err());
353 }
354
355 #[test]
356 fn zero_features_rejected() {
357 assert!(matches!(
358 StandardScaler::new(0),
359 Err(RillError::EmptyFeatures)
360 ));
361 }
362
363 #[test]
364 fn non_finite_rejected() {
365 let mut s = StandardScaler::new(2).unwrap();
366 assert!(s.update(&[1.0, f64::NAN]).is_err());
367 }
368
369 #[test]
370 fn reset_clears_state() {
371 let mut s = StandardScaler::new(1).unwrap();
372 s.update(&[1.0]).unwrap();
373 s.update(&[2.0]).unwrap();
374 s.reset();
375 assert_eq!(s.counts[0], 0);
376 assert_eq!(s.means()[0], 0.0);
377 }
378}