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