1#[cfg(not(any(feature = "std", test)))]
11use crate::math::FloatExt;
12use crate::math::{u64_f64, usize_f64};
13
14#[derive(Clone, Debug)]
23pub struct ChangeRate {
24 prev_value: f64,
26 prev_ts: u64,
28 rate: f64,
30 initialized: bool,
32}
33
34impl ChangeRate {
35 #[must_use]
37 pub const fn new() -> Self {
38 Self {
39 prev_value: 0.0,
40 prev_ts: 0,
41 rate: 0.0,
42 initialized: false,
43 }
44 }
45
46 pub fn observe(&mut self, value: f64, timestamp_ms: u64) -> Option<f64> {
51 if !self.initialized {
52 self.prev_value = value;
53 self.prev_ts = timestamp_ms;
54 self.initialized = true;
55 return None;
56 }
57
58 let dt = timestamp_ms.saturating_sub(self.prev_ts);
59 if dt == 0 {
60 return Some(self.rate);
61 }
62
63 self.rate = (value - self.prev_value) / u64_f64(dt);
64 self.prev_value = value;
65 self.prev_ts = timestamp_ms;
66 Some(self.rate)
67 }
68
69 #[inline]
71 #[must_use]
72 pub const fn rate(&self) -> f64 {
73 self.rate
74 }
75
76 #[inline]
78 #[must_use]
79 pub const fn is_initialized(&self) -> bool {
80 self.initialized
81 }
82
83 pub const fn reset(&mut self) {
85 self.prev_value = 0.0;
86 self.prev_ts = 0;
87 self.rate = 0.0;
88 self.initialized = false;
89 }
90}
91
92impl Default for ChangeRate {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98#[derive(Clone, Debug)]
107pub struct ExponentialMovingAverage {
108 alpha: f64,
110 value: f64,
112 count: u64,
114}
115
116impl ExponentialMovingAverage {
117 #[must_use]
123 pub fn new(alpha: f64) -> Self {
124 assert!(alpha > 0.0 && alpha <= 1.0, "alpha must be in (0.0, 1.0]");
125 Self {
126 alpha,
127 value: 0.0,
128 count: 0,
129 }
130 }
131
132 #[must_use]
140 pub fn from_span(span: u64) -> Self {
141 assert!(span > 0, "span must be > 0");
142 Self::new(2.0 / (u64_f64(span) + 1.0))
143 }
144
145 pub fn observe(&mut self, value: f64) {
147 if self.count == 0 {
148 self.value = value;
149 } else {
150 self.value = self.alpha.mul_add(value, (1.0 - self.alpha) * self.value);
151 }
152 self.count += 1;
153 }
154
155 #[inline]
157 #[must_use]
158 pub const fn value(&self) -> f64 {
159 self.value
160 }
161
162 #[inline]
164 #[must_use]
165 pub const fn count(&self) -> u64 {
166 self.count
167 }
168
169 #[inline]
171 #[must_use]
172 pub const fn alpha(&self) -> f64 {
173 self.alpha
174 }
175
176 pub const fn reset(&mut self) {
178 self.value = 0.0;
179 self.count = 0;
180 }
181}
182
183pub struct SimpleMovingAverage<const N: usize> {
191 buffer: [f64; N],
193 write_pos: usize,
195 count: usize,
197 sum: f64,
199}
200
201impl<const N: usize> SimpleMovingAverage<N> {
202 #[must_use]
204 pub const fn new() -> Self {
205 Self {
206 buffer: [0.0; N],
207 write_pos: 0,
208 count: 0,
209 sum: 0.0,
210 }
211 }
212
213 pub fn observe(&mut self, value: f64) {
221 assert!(
222 N > 0,
223 "SimpleMovingAverage needs N > 0 (got a zero-width window)"
224 );
225 if self.count >= N {
226 self.sum -= self.buffer[self.write_pos];
227 }
228 self.buffer[self.write_pos] = value;
229 self.sum += value;
230 self.write_pos = (self.write_pos + 1) % N;
231 if self.count < N {
232 self.count += 1;
233 }
234 }
235
236 #[inline]
238 #[must_use]
239 pub fn value(&self) -> f64 {
240 if self.count == 0 {
241 return 0.0;
242 }
243 self.sum / usize_f64(self.count)
244 }
245
246 #[inline]
248 #[must_use]
249 pub const fn len(&self) -> usize {
250 self.count
251 }
252
253 #[inline]
255 #[must_use]
256 pub const fn is_empty(&self) -> bool {
257 self.count == 0
258 }
259
260 #[inline]
262 #[must_use]
263 pub const fn is_full(&self) -> bool {
264 self.count >= N
265 }
266
267 pub const fn reset(&mut self) {
269 self.write_pos = 0;
270 self.count = 0;
271 self.sum = 0.0;
272 }
273}
274
275impl<const N: usize> Default for SimpleMovingAverage<N> {
276 fn default() -> Self {
277 Self::new()
278 }
279}
280
281#[derive(Clone, Debug)]
290pub struct LinearRegression {
291 count: u64,
293 mean_x: f64,
295 mean_y: f64,
297 co_moment_xy: f64,
299 m2_x: f64,
301}
302
303impl LinearRegression {
304 #[must_use]
306 pub const fn new() -> Self {
307 Self {
308 count: 0,
309 mean_x: 0.0,
310 mean_y: 0.0,
311 co_moment_xy: 0.0,
312 m2_x: 0.0,
313 }
314 }
315
316 pub fn observe(&mut self, x: f64, y: f64) {
318 self.count += 1;
319 let n = u64_f64(self.count);
320
321 let dx = x - self.mean_x;
322 let dy = y - self.mean_y;
323
324 self.mean_x += dx / n;
325 self.mean_y += dy / n;
326
327 let dx2 = x - self.mean_x;
329 self.co_moment_xy += dx * (y - self.mean_y);
330 self.m2_x += dx * dx2;
331 }
332
333 #[must_use]
335 pub fn slope(&self) -> f64 {
336 if self.count < 2 || self.m2_x.abs() < f64::EPSILON {
337 return 0.0;
338 }
339 self.co_moment_xy / self.m2_x
340 }
341
342 #[must_use]
344 pub fn intercept(&self) -> f64 {
345 self.slope().mul_add(-self.mean_x, self.mean_y)
346 }
347
348 #[must_use]
350 pub fn predict(&self, x: f64) -> f64 {
351 self.slope().mul_add(x, self.intercept())
352 }
353
354 #[must_use]
356 pub const fn r_squared(&self) -> f64 {
357 if self.count < 2 {
358 return 0.0;
359 }
360 0.0 }
371
372 #[inline]
374 #[must_use]
375 pub const fn count(&self) -> u64 {
376 self.count
377 }
378
379 pub const fn reset(&mut self) {
381 self.count = 0;
382 self.mean_x = 0.0;
383 self.mean_y = 0.0;
384 self.co_moment_xy = 0.0;
385 self.m2_x = 0.0;
386 }
387}
388
389impl Default for LinearRegression {
390 fn default() -> Self {
391 Self::new()
392 }
393}
394
395#[derive(Clone, Debug)]
397pub struct LinearRegressionFull {
398 inner: LinearRegression,
400 m2_y: f64,
402}
403
404impl LinearRegressionFull {
405 #[must_use]
407 pub const fn new() -> Self {
408 Self {
409 inner: LinearRegression::new(),
410 m2_y: 0.0,
411 }
412 }
413
414 pub fn observe(&mut self, x: f64, y: f64) {
416 let dy = y - self.inner.mean_y;
417 self.inner.observe(x, y);
418 let dy2 = y - self.inner.mean_y;
419 self.m2_y += dy * dy2;
420 }
421
422 #[must_use]
424 pub fn slope(&self) -> f64 {
425 self.inner.slope()
426 }
427
428 #[must_use]
430 pub fn intercept(&self) -> f64 {
431 self.inner.intercept()
432 }
433
434 #[must_use]
436 pub fn predict(&self, x: f64) -> f64 {
437 self.inner.predict(x)
438 }
439
440 #[must_use]
442 pub fn r_squared(&self) -> f64 {
443 if self.inner.count < 2
444 || self.inner.m2_x.abs() < f64::EPSILON
445 || self.m2_y.abs() < f64::EPSILON
446 {
447 return 0.0;
448 }
449 let r = self.inner.co_moment_xy / (self.inner.m2_x * self.m2_y).sqrt();
450 (r * r).clamp(0.0, 1.0)
451 }
452
453 #[inline]
455 #[must_use]
456 pub const fn count(&self) -> u64 {
457 self.inner.count
458 }
459
460 pub const fn reset(&mut self) {
462 self.inner.reset();
463 self.m2_y = 0.0;
464 }
465}
466
467impl Default for LinearRegressionFull {
468 fn default() -> Self {
469 Self::new()
470 }
471}
472
473#[cfg(test)]
478#[allow(clippy::float_cmp)]
479mod tests {
480 use super::*;
481
482 #[test]
485 fn change_rate_first_observation() {
486 let mut cr = ChangeRate::new();
487 assert!(cr.observe(10.0, 1000).is_none());
488 assert!(cr.is_initialized());
489 }
490
491 #[test]
492 fn change_rate_constant() {
493 let mut cr = ChangeRate::new();
494 cr.observe(10.0, 0);
495 let rate = cr.observe(10.0, 100).unwrap();
496 assert!((rate - 0.0).abs() < 1e-10);
497 }
498
499 #[test]
500 fn change_rate_linear() {
501 let mut cr = ChangeRate::new();
502 cr.observe(0.0, 0);
503 let rate = cr.observe(50.0, 100).unwrap();
505 assert!((rate - 0.5).abs() < 1e-10);
506 }
507
508 #[test]
509 fn change_rate_negative() {
510 let mut cr = ChangeRate::new();
511 cr.observe(100.0, 0);
512 let rate = cr.observe(50.0, 100).unwrap();
513 assert!((rate - (-0.5)).abs() < 1e-10);
514 }
515
516 #[test]
517 fn change_rate_same_timestamp() {
518 let mut cr = ChangeRate::new();
519 cr.observe(10.0, 100);
520 let rate = cr.observe(20.0, 100).unwrap();
521 assert!((rate - 0.0).abs() < 1e-10);
523 }
524
525 #[test]
526 fn change_rate_reset() {
527 let mut cr = ChangeRate::new();
528 cr.observe(10.0, 0);
529 cr.observe(20.0, 100);
530 cr.reset();
531 assert!(!cr.is_initialized());
532 assert!((cr.rate() - 0.0).abs() < 1e-10);
533 }
534
535 #[test]
536 fn change_rate_default() {
537 let cr = ChangeRate::default();
538 assert!(!cr.is_initialized());
539 }
540
541 #[test]
544 fn ema_first_value() {
545 let mut ema = ExponentialMovingAverage::new(0.5);
546 ema.observe(10.0);
547 assert!((ema.value() - 10.0).abs() < 1e-10);
548 assert_eq!(ema.count(), 1);
549 }
550
551 #[test]
552 fn ema_convergence() {
553 let mut ema = ExponentialMovingAverage::new(0.1);
554 for _ in 0..100 {
556 ema.observe(50.0);
557 }
558 assert!((ema.value() - 50.0).abs() < 1e-6);
559 }
560
561 #[test]
562 fn ema_from_span() {
563 let ema = ExponentialMovingAverage::from_span(9);
564 assert!((ema.alpha() - 0.2).abs() < 1e-10);
566 }
567
568 #[test]
569 fn ema_step_response() {
570 let mut ema = ExponentialMovingAverage::new(0.5);
571 ema.observe(0.0);
572 ema.observe(100.0);
573 assert!((ema.value() - 50.0).abs() < 1e-10);
575 }
576
577 #[test]
578 fn ema_reset() {
579 let mut ema = ExponentialMovingAverage::new(0.3);
580 ema.observe(10.0);
581 ema.reset();
582 assert_eq!(ema.count(), 0);
583 assert!((ema.value() - 0.0).abs() < 1e-10);
584 }
585
586 #[test]
589 fn sma_basic() {
590 let mut sma = SimpleMovingAverage::<3>::new();
591 sma.observe(10.0);
592 sma.observe(20.0);
593 sma.observe(30.0);
594 assert!((sma.value() - 20.0).abs() < 1e-10);
595 assert_eq!(sma.len(), 3);
596 assert!(sma.is_full());
597 }
598
599 #[test]
600 fn sma_overflow() {
601 let mut sma = SimpleMovingAverage::<3>::new();
602 sma.observe(10.0);
603 sma.observe(20.0);
604 sma.observe(30.0);
605 sma.observe(40.0); assert!((sma.value() - 30.0).abs() < 1e-10);
608 }
609
610 #[test]
611 fn sma_empty() {
612 let sma = SimpleMovingAverage::<8>::new();
613 assert!(sma.is_empty());
614 assert!((sma.value() - 0.0).abs() < 1e-10);
615 }
616
617 #[test]
618 fn sma_reset() {
619 let mut sma = SimpleMovingAverage::<4>::new();
620 sma.observe(10.0);
621 sma.reset();
622 assert!(sma.is_empty());
623 }
624
625 #[test]
626 fn sma_default() {
627 let sma = SimpleMovingAverage::<16>::default();
628 assert!(sma.is_empty());
629 }
630
631 #[test]
634 fn linreg_perfect_line() {
635 let mut lr = LinearRegression::new();
636 for i in 0..100 {
638 let x = f64::from(i);
639 lr.observe(x, 2.0f64.mul_add(x, 1.0));
640 }
641 assert!((lr.slope() - 2.0).abs() < 1e-6, "slope = {}", lr.slope());
642 assert!(
643 (lr.intercept() - 1.0).abs() < 1e-6,
644 "intercept = {}",
645 lr.intercept()
646 );
647 }
648
649 #[test]
650 fn linreg_predict() {
651 let mut lr = LinearRegression::new();
652 for i in 0..50 {
654 let x = f64::from(i);
655 lr.observe(x, 3.0f64.mul_add(x, -2.0));
656 }
657 let pred = lr.predict(100.0);
658 assert!((pred - 298.0).abs() < 1e-4, "pred = {pred}");
659 }
660
661 #[test]
662 fn linreg_constant() {
663 let mut lr = LinearRegression::new();
664 for i in 0..50 {
665 lr.observe(f64::from(i), 42.0);
666 }
667 assert!((lr.slope() - 0.0).abs() < 1e-10);
668 assert!((lr.intercept() - 42.0).abs() < 1e-6);
669 }
670
671 #[test]
672 fn linreg_single_point() {
673 let mut lr = LinearRegression::new();
674 lr.observe(5.0, 10.0);
675 assert!((lr.slope() - 0.0).abs() < 1e-10);
676 assert_eq!(lr.count(), 1);
677 }
678
679 #[test]
680 fn linreg_reset() {
681 let mut lr = LinearRegression::new();
682 lr.observe(1.0, 2.0);
683 lr.observe(2.0, 4.0);
684 lr.reset();
685 assert_eq!(lr.count(), 0);
686 }
687
688 #[test]
689 fn linreg_default() {
690 let lr = LinearRegression::default();
691 assert_eq!(lr.count(), 0);
692 }
693
694 #[test]
697 fn linreg_full_r_squared_perfect() {
698 let mut lr = LinearRegressionFull::new();
699 for i in 0..100 {
701 let x = f64::from(i);
702 lr.observe(x, 2.0f64.mul_add(x, 1.0));
703 }
704 assert!(
705 (lr.r_squared() - 1.0).abs() < 1e-6,
706 "R² = {}",
707 lr.r_squared()
708 );
709 }
710
711 #[test]
712 fn linreg_full_r_squared_no_correlation() {
713 let mut lr = LinearRegressionFull::new();
714 for i in 0..100 {
716 lr.observe(f64::from(i), 42.0);
717 }
718 assert!(lr.r_squared() < 0.01, "R² = {}", lr.r_squared());
719 }
720
721 #[test]
722 fn linreg_full_predict() {
723 let mut lr = LinearRegressionFull::new();
724 for i in 0..50 {
725 let x = f64::from(i);
726 lr.observe(x, x.mul_add(0.5, 10.0));
727 }
728 let pred = lr.predict(100.0);
729 assert!((pred - 60.0).abs() < 1e-4, "pred = {pred}");
730 }
731
732 #[test]
733 fn linreg_full_reset() {
734 let mut lr = LinearRegressionFull::new();
735 lr.observe(1.0, 2.0);
736 lr.reset();
737 assert_eq!(lr.count(), 0);
738 }
739
740 #[test]
741 fn linreg_full_default() {
742 let lr = LinearRegressionFull::default();
743 assert_eq!(lr.count(), 0);
744 }
745}