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> {
123 if self.feature_count == 0 {
124 return Err(RillError::EmptyFeatures);
125 }
126 ensure_finite("epsilon", self.config.epsilon)?;
127 if self.config.epsilon < 0.0 {
128 return Err(RillError::InvalidParameter {
129 name: "epsilon",
130 value: self.config.epsilon,
131 });
132 }
133 if self.counts.len() != self.feature_count
134 || self.means.len() != self.feature_count
135 || self.m2s.len() != self.feature_count
136 {
137 return Err(RillError::InvalidState(
138 "standard scaler feature_count does not match state lengths".to_owned(),
139 ));
140 }
141 if self.means.iter().any(|value| !value.is_finite())
142 || self.m2s.iter().any(|value| !value.is_finite())
143 {
144 return Err(RillError::InvalidState(
145 "standard scaler state must contain only finite values".to_owned(),
146 ));
147 }
148 if self.counts.windows(2).any(|pair| pair[0] != pair[1]) {
149 return Err(RillError::InvalidState(
150 "standard scaler feature counts must stay synchronized".to_owned(),
151 ));
152 }
153 Ok(())
154 }
155
156 pub fn transform_into(&self, features: &[f64], output: &mut Vec<f64>) -> Result<(), RillError> {
172 validate_features(self.feature_count, features)?;
175
176 debug_assert!(
180 self.counts.len() == self.feature_count
181 && self.means.len() == self.feature_count
182 && self.m2s.len() == self.feature_count,
183 "standard scaler state lengths must match feature_count"
184 );
185 debug_assert!(
186 self.counts.windows(2).all(|pair| pair[0] == pair[1]),
187 "standard scaler feature counts must stay synchronized"
188 );
189 debug_assert!(
190 self.means.iter().all(|v| v.is_finite()) && self.m2s.iter().all(|v| v.is_finite()),
191 "standard scaler state must contain only finite values"
192 );
193
194 output.clear();
195 output.reserve(self.feature_count);
196
197 let iter = features
198 .iter()
199 .zip(&self.counts)
200 .zip(&self.means)
201 .zip(&self.m2s);
202 for (((&x, &n), &mean_storage), &m2) in iter {
203 let scale = if !self.config.with_std || n == 0 {
206 1.0
207 } else {
208 let variance = m2 / n as f64;
209 if variance < self.config.epsilon {
210 1.0
211 } else {
212 variance.sqrt()
213 }
214 };
215 let mean = if self.config.with_mean {
216 mean_storage
217 } else {
218 0.0
219 };
220 let transformed = (x - mean) / scale;
221 ensure_finite("transformed feature", transformed)?;
225 output.push(transformed);
226 }
227 Ok(())
228 }
229}
230
231impl Transformer for StandardScaler {
232 fn input_dim(&self) -> usize {
233 self.feature_count
234 }
235
236 fn output_dim(&self) -> usize {
237 self.feature_count
238 }
239
240 fn transform(&self, features: &[f64]) -> Result<Vec<f64>, RillError> {
241 self.validate()?;
246 let mut output = Vec::with_capacity(self.feature_count);
247 self.transform_into(features, &mut output)?;
248 Ok(output)
249 }
250
251 fn update(&mut self, features: &[f64]) -> Result<(), RillError> {
252 self.validate()?;
253 validate_features(self.feature_count, features)?;
254 let mut next_counts = self.counts.clone();
255 let mut next_means = self.means.clone();
256 let mut next_m2s = self.m2s.clone();
257 for (i, &x) in features.iter().enumerate() {
258 let count = checked_increment(self.counts[i], "standard scaler sample")?;
259 let delta = x - self.means[i];
260 ensure_finite("standard scaler delta", delta)?;
261 let mean = self.means[i] + delta / count as f64;
262 ensure_finite("standard scaler mean", mean)?;
263 let delta2 = x - mean;
264 ensure_finite("standard scaler delta", delta2)?;
265 let m2 = self.m2s[i] + delta * delta2;
266 ensure_finite("standard scaler M2", m2)?;
267 next_counts[i] = count;
268 next_means[i] = mean;
269 next_m2s[i] = m2;
270 }
271 self.counts = next_counts;
272 self.means = next_means;
273 self.m2s = next_m2s;
274 Ok(())
275 }
276
277 fn samples_seen(&self) -> u64 {
278 self.counts.iter().copied().max().unwrap_or(0)
279 }
280
281 fn reset(&mut self) {
282 for c in &mut self.counts {
283 *c = 0;
284 }
285 for m in &mut self.means {
286 *m = 0.0;
287 }
288 for m2 in &mut self.m2s {
289 *m2 = 0.0;
290 }
291 }
292}
293
294#[cfg(feature = "serde")]
295#[derive(serde::Deserialize)]
296struct StandardScalerState {
297 feature_count: usize,
298 config: StandardScalerConfig,
299 counts: Vec<u64>,
300 means: Vec<f64>,
301 m2s: Vec<f64>,
302}
303
304#[cfg(feature = "serde")]
305impl<'de> serde::Deserialize<'de> for StandardScaler {
306 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
307 where
308 D: serde::Deserializer<'de>,
309 {
310 let state = StandardScalerState::deserialize(deserializer)?;
311 let scaler = Self {
312 feature_count: state.feature_count,
313 config: state.config,
314 counts: state.counts,
315 means: state.means,
316 m2s: state.m2s,
317 };
318 scaler.validate().map_err(serde::de::Error::custom)?;
319 Ok(scaler)
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn scaler_zero_state_returns_original() {
329 let s = StandardScaler::new(3).unwrap();
330 let out = s.transform(&[1.0, 2.0, 3.0]).unwrap();
331 assert!((out[0] - 1.0).abs() < 1e-12);
333 assert!((out[1] - 2.0).abs() < 1e-12);
334 assert!((out[2] - 3.0).abs() < 1e-12);
335 }
336
337 #[test]
338 fn scaler_standardizes_after_updates() {
339 let mut s = StandardScaler::new(2).unwrap();
340 s.update(&[1.0, 10.0]).unwrap();
343 s.update(&[3.0, 20.0]).unwrap();
344 let out = s.transform(&[3.0, 20.0]).unwrap();
345 assert!((out[0] - 1.0).abs() < 1e-9);
347 assert!((out[1] - 1.0).abs() < 1e-9);
348 }
349
350 #[test]
351 fn transform_does_not_update_state() {
352 let mut s = StandardScaler::new(1).unwrap();
353 s.update(&[10.0]).unwrap();
354 let mean_before = s.means()[0];
355 let _ = s.transform(&[5.0]).unwrap();
356 assert_eq!(s.means()[0], mean_before);
357 assert_eq!(s.counts[0], 1);
358 }
359
360 #[test]
361 fn update_rejects_overflow_without_mutating_state() {
362 let mut scaler = StandardScaler::new(1).unwrap();
363 scaler.update(&[f64::MAX]).unwrap();
364 let before = scaler.clone();
365 assert!(scaler.update(&[-f64::MAX]).is_err());
366 assert_eq!(scaler.counts, before.counts);
367 assert_eq!(scaler.means, before.means);
368 assert_eq!(scaler.m2s, before.m2s);
369 }
370
371 #[cfg(feature = "serde")]
372 #[test]
373 fn serde_rejects_malformed_state() {
374 let malformed = r#"{
375 "feature_count":2,
376 "config":{"with_mean":true,"with_std":true,"epsilon":1e-12},
377 "counts":[1],
378 "means":[0.0],
379 "m2s":[0.0]
380 }"#;
381 assert!(serde_json::from_str::<StandardScaler>(malformed).is_err());
382 }
383
384 #[test]
385 fn constant_feature_uses_scale_one() {
386 let mut s = StandardScaler::new(1).unwrap();
387 for _ in 0..10 {
388 s.update(&[5.0]).unwrap();
389 }
390 let out = s.transform(&[5.0]).unwrap();
392 assert!(out[0].abs() < 1e-12);
393 assert!(!out[0].is_nan());
394 }
395
396 #[test]
397 fn with_mean_false_keeps_offset() {
398 let mut s = StandardScaler::with_config(
399 1,
400 StandardScalerConfig {
401 with_mean: false,
402 with_std: true,
403 epsilon: 1e-12,
404 },
405 )
406 .unwrap();
407 s.update(&[1.0]).unwrap();
408 s.update(&[3.0]).unwrap();
409 let out = s.transform(&[3.0]).unwrap();
411 assert!((out[0] - 3.0).abs() < 1e-9);
412 }
413
414 #[test]
415 fn dimension_mismatch_rejected() {
416 let mut s = StandardScaler::new(3).unwrap();
417 assert!(s.transform(&[1.0, 2.0]).is_err());
418 assert!(s.update(&[1.0, 2.0]).is_err());
419 }
420
421 #[test]
422 fn zero_features_rejected() {
423 assert!(matches!(
424 StandardScaler::new(0),
425 Err(RillError::EmptyFeatures)
426 ));
427 }
428
429 #[test]
430 fn non_finite_rejected() {
431 let mut s = StandardScaler::new(2).unwrap();
432 assert!(s.update(&[1.0, f64::NAN]).is_err());
433 }
434
435 #[test]
436 fn reset_clears_state() {
437 let mut s = StandardScaler::new(1).unwrap();
438 s.update(&[1.0]).unwrap();
439 s.update(&[2.0]).unwrap();
440 s.reset();
441 assert_eq!(s.counts[0], 0);
442 assert_eq!(s.means()[0], 0.0);
443 }
444
445 #[test]
446 fn transform_into_matches_transform_output() {
447 let mut scaler = StandardScaler::new(4).unwrap();
448 scaler.update(&[1.0, 10.0, 100.0, 1000.0]).unwrap();
450 scaler.update(&[3.0, 20.0, 300.0, 3000.0]).unwrap();
451 scaler.update(&[5.0, 30.0, 500.0, 5000.0]).unwrap();
452 let features = [2.0, 15.0, 200.0, 2000.0];
453 let via_transform = scaler.transform(&features).unwrap();
454 let mut via_into = Vec::new();
455 scaler.transform_into(&features, &mut via_into).unwrap();
456 assert_eq!(via_transform, via_into);
457 }
458
459 #[test]
460 fn transform_into_reuses_buffer_capacity() {
461 let mut scaler = StandardScaler::new(3).unwrap();
462 scaler.update(&[1.0, 2.0, 3.0]).unwrap();
463 scaler.update(&[4.0, 5.0, 6.0]).unwrap();
464 let features = [2.5, 3.5, 4.5];
465 let mut buffer = Vec::with_capacity(64);
466 buffer.extend_from_slice(&[-1.0, -2.0, -3.0, -4.0]);
468 scaler.transform_into(&features, &mut buffer).unwrap();
469 assert_eq!(buffer.len(), 3);
470 assert!(buffer.capacity() >= 64);
472 assert_eq!(buffer, scaler.transform(&features).unwrap());
474 }
475
476 #[test]
477 fn transform_into_rejects_dimension_mismatch() {
478 let scaler = StandardScaler::new(3).unwrap();
479 let mut buffer = Vec::new();
480 assert!(scaler.transform_into(&[1.0, 2.0], &mut buffer).is_err());
481 assert!(buffer.is_empty());
483 }
484
485 #[test]
486 fn transform_into_rejects_non_finite_output() {
487 let scaler = StandardScaler::with_config(
490 1,
491 StandardScalerConfig {
492 with_mean: false,
493 with_std: false,
494 epsilon: 1e-12,
495 },
496 )
497 .unwrap();
498 let mut buffer = Vec::new();
499 assert!(scaler.transform_into(&[f64::NAN], &mut buffer).is_err());
500 }
501
502 #[test]
503 fn transform_into_with_zero_state_returns_original() {
504 let scaler = StandardScaler::new(3).unwrap();
505 let features = [1.5, 2.5, 3.5];
506 let mut buffer = Vec::new();
507 scaler.transform_into(&features, &mut buffer).unwrap();
508 assert_eq!(buffer, vec![1.5, 2.5, 3.5]);
510 }
511}