#[cfg(not(feature = "std"))]
use crate::math::FloatExt;
use crate::math::{u64_f64, usize_f64};
#[derive(Clone, Debug)]
pub struct ChangeRate {
prev_value: f64,
prev_ts: u64,
rate: f64,
initialized: bool,
}
impl ChangeRate {
#[must_use]
pub const fn new() -> Self {
Self {
prev_value: 0.0,
prev_ts: 0,
rate: 0.0,
initialized: false,
}
}
pub fn observe(&mut self, value: f64, timestamp_ms: u64) -> Option<f64> {
if !self.initialized {
self.prev_value = value;
self.prev_ts = timestamp_ms;
self.initialized = true;
return None;
}
let dt = timestamp_ms.saturating_sub(self.prev_ts);
if dt == 0 {
return Some(self.rate);
}
self.rate = (value - self.prev_value) / u64_f64(dt);
self.prev_value = value;
self.prev_ts = timestamp_ms;
Some(self.rate)
}
#[inline]
#[must_use]
pub const fn rate(&self) -> f64 {
self.rate
}
#[inline]
#[must_use]
pub const fn is_initialized(&self) -> bool {
self.initialized
}
pub const fn reset(&mut self) {
self.prev_value = 0.0;
self.prev_ts = 0;
self.rate = 0.0;
self.initialized = false;
}
}
impl Default for ChangeRate {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct ExponentialMovingAverage {
alpha: f64,
value: f64,
count: u64,
}
impl ExponentialMovingAverage {
#[must_use]
pub fn new(alpha: f64) -> Self {
assert!(alpha > 0.0 && alpha <= 1.0, "alpha must be in (0.0, 1.0]");
Self {
alpha,
value: 0.0,
count: 0,
}
}
#[must_use]
pub fn from_span(span: u64) -> Self {
assert!(span > 0, "span must be > 0");
Self::new(2.0 / (u64_f64(span) + 1.0))
}
pub fn observe(&mut self, value: f64) {
if self.count == 0 {
self.value = value;
} else {
self.value = self.alpha.mul_add(value, (1.0 - self.alpha) * self.value);
}
self.count += 1;
}
#[inline]
#[must_use]
pub const fn value(&self) -> f64 {
self.value
}
#[inline]
#[must_use]
pub const fn count(&self) -> u64 {
self.count
}
#[inline]
#[must_use]
pub const fn alpha(&self) -> f64 {
self.alpha
}
pub const fn reset(&mut self) {
self.value = 0.0;
self.count = 0;
}
}
pub struct SimpleMovingAverage<const N: usize> {
buffer: [f64; N],
write_pos: usize,
count: usize,
sum: f64,
}
impl<const N: usize> SimpleMovingAverage<N> {
#[must_use]
pub const fn new() -> Self {
Self {
buffer: [0.0; N],
write_pos: 0,
count: 0,
sum: 0.0,
}
}
pub fn observe(&mut self, value: f64) {
assert!(
N > 0,
"SimpleMovingAverage needs N > 0 (got a zero-width window)"
);
if self.count >= N {
self.sum -= self.buffer[self.write_pos];
}
self.buffer[self.write_pos] = value;
self.sum += value;
self.write_pos = (self.write_pos + 1) % N;
if self.count < N {
self.count += 1;
}
}
#[inline]
#[must_use]
pub fn value(&self) -> f64 {
if self.count == 0 {
return 0.0;
}
self.sum / usize_f64(self.count)
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.count
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
#[must_use]
pub const fn is_full(&self) -> bool {
self.count >= N
}
pub const fn reset(&mut self) {
self.write_pos = 0;
self.count = 0;
self.sum = 0.0;
}
}
impl<const N: usize> Default for SimpleMovingAverage<N> {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct LinearRegression {
count: u64,
mean_x: f64,
mean_y: f64,
co_moment_xy: f64,
m2_x: f64,
}
impl LinearRegression {
#[must_use]
pub const fn new() -> Self {
Self {
count: 0,
mean_x: 0.0,
mean_y: 0.0,
co_moment_xy: 0.0,
m2_x: 0.0,
}
}
pub fn observe(&mut self, x: f64, y: f64) {
self.count += 1;
let n = u64_f64(self.count);
let dx = x - self.mean_x;
let dy = y - self.mean_y;
self.mean_x += dx / n;
self.mean_y += dy / n;
let dx2 = x - self.mean_x;
self.co_moment_xy += dx * (y - self.mean_y);
self.m2_x += dx * dx2;
}
#[must_use]
pub fn slope(&self) -> f64 {
if self.count < 2 || self.m2_x.abs() < f64::EPSILON {
return 0.0;
}
self.co_moment_xy / self.m2_x
}
#[must_use]
pub fn intercept(&self) -> f64 {
self.slope().mul_add(-self.mean_x, self.mean_y)
}
#[must_use]
pub fn predict(&self, x: f64) -> f64 {
self.slope().mul_add(x, self.intercept())
}
#[must_use]
pub const fn r_squared(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
0.0 }
#[inline]
#[must_use]
pub const fn count(&self) -> u64 {
self.count
}
pub const fn reset(&mut self) {
self.count = 0;
self.mean_x = 0.0;
self.mean_y = 0.0;
self.co_moment_xy = 0.0;
self.m2_x = 0.0;
}
}
impl Default for LinearRegression {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct LinearRegressionFull {
inner: LinearRegression,
m2_y: f64,
}
impl LinearRegressionFull {
#[must_use]
pub const fn new() -> Self {
Self {
inner: LinearRegression::new(),
m2_y: 0.0,
}
}
pub fn observe(&mut self, x: f64, y: f64) {
let dy = y - self.inner.mean_y;
self.inner.observe(x, y);
let dy2 = y - self.inner.mean_y;
self.m2_y += dy * dy2;
}
#[must_use]
pub fn slope(&self) -> f64 {
self.inner.slope()
}
#[must_use]
pub fn intercept(&self) -> f64 {
self.inner.intercept()
}
#[must_use]
pub fn predict(&self, x: f64) -> f64 {
self.inner.predict(x)
}
#[must_use]
pub fn r_squared(&self) -> f64 {
if self.inner.count < 2
|| self.inner.m2_x.abs() < f64::EPSILON
|| self.m2_y.abs() < f64::EPSILON
{
return 0.0;
}
let r = self.inner.co_moment_xy / (self.inner.m2_x * self.m2_y).sqrt();
(r * r).clamp(0.0, 1.0)
}
#[inline]
#[must_use]
pub const fn count(&self) -> u64 {
self.inner.count
}
pub const fn reset(&mut self) {
self.inner.reset();
self.m2_y = 0.0;
}
}
impl Default for LinearRegressionFull {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn change_rate_first_observation() {
let mut cr = ChangeRate::new();
assert!(cr.observe(10.0, 1000).is_none());
assert!(cr.is_initialized());
}
#[test]
fn change_rate_constant() {
let mut cr = ChangeRate::new();
cr.observe(10.0, 0);
let rate = cr.observe(10.0, 100).unwrap();
assert!((rate - 0.0).abs() < 1e-10);
}
#[test]
fn change_rate_linear() {
let mut cr = ChangeRate::new();
cr.observe(0.0, 0);
let rate = cr.observe(50.0, 100).unwrap();
assert!((rate - 0.5).abs() < 1e-10);
}
#[test]
fn change_rate_negative() {
let mut cr = ChangeRate::new();
cr.observe(100.0, 0);
let rate = cr.observe(50.0, 100).unwrap();
assert!((rate - (-0.5)).abs() < 1e-10);
}
#[test]
fn change_rate_same_timestamp() {
let mut cr = ChangeRate::new();
cr.observe(10.0, 100);
let rate = cr.observe(20.0, 100).unwrap();
assert!((rate - 0.0).abs() < 1e-10);
}
#[test]
fn change_rate_reset() {
let mut cr = ChangeRate::new();
cr.observe(10.0, 0);
cr.observe(20.0, 100);
cr.reset();
assert!(!cr.is_initialized());
assert!((cr.rate() - 0.0).abs() < 1e-10);
}
#[test]
fn change_rate_default() {
let cr = ChangeRate::default();
assert!(!cr.is_initialized());
}
#[test]
fn ema_first_value() {
let mut ema = ExponentialMovingAverage::new(0.5);
ema.observe(10.0);
assert!((ema.value() - 10.0).abs() < 1e-10);
assert_eq!(ema.count(), 1);
}
#[test]
fn ema_convergence() {
let mut ema = ExponentialMovingAverage::new(0.1);
for _ in 0..100 {
ema.observe(50.0);
}
assert!((ema.value() - 50.0).abs() < 1e-6);
}
#[test]
fn ema_from_span() {
let ema = ExponentialMovingAverage::from_span(9);
assert!((ema.alpha() - 0.2).abs() < 1e-10);
}
#[test]
fn ema_step_response() {
let mut ema = ExponentialMovingAverage::new(0.5);
ema.observe(0.0);
ema.observe(100.0);
assert!((ema.value() - 50.0).abs() < 1e-10);
}
#[test]
fn ema_reset() {
let mut ema = ExponentialMovingAverage::new(0.3);
ema.observe(10.0);
ema.reset();
assert_eq!(ema.count(), 0);
assert!((ema.value() - 0.0).abs() < 1e-10);
}
#[test]
fn sma_basic() {
let mut sma = SimpleMovingAverage::<3>::new();
sma.observe(10.0);
sma.observe(20.0);
sma.observe(30.0);
assert!((sma.value() - 20.0).abs() < 1e-10);
assert_eq!(sma.len(), 3);
assert!(sma.is_full());
}
#[test]
fn sma_overflow() {
let mut sma = SimpleMovingAverage::<3>::new();
sma.observe(10.0);
sma.observe(20.0);
sma.observe(30.0);
sma.observe(40.0); assert!((sma.value() - 30.0).abs() < 1e-10);
}
#[test]
fn sma_empty() {
let sma = SimpleMovingAverage::<8>::new();
assert!(sma.is_empty());
assert!((sma.value() - 0.0).abs() < 1e-10);
}
#[test]
fn sma_reset() {
let mut sma = SimpleMovingAverage::<4>::new();
sma.observe(10.0);
sma.reset();
assert!(sma.is_empty());
}
#[test]
fn sma_default() {
let sma = SimpleMovingAverage::<16>::default();
assert!(sma.is_empty());
}
#[test]
fn linreg_perfect_line() {
let mut lr = LinearRegression::new();
for i in 0..100 {
let x = f64::from(i);
lr.observe(x, 2.0f64.mul_add(x, 1.0));
}
assert!((lr.slope() - 2.0).abs() < 1e-6, "slope = {}", lr.slope());
assert!(
(lr.intercept() - 1.0).abs() < 1e-6,
"intercept = {}",
lr.intercept()
);
}
#[test]
fn linreg_predict() {
let mut lr = LinearRegression::new();
for i in 0..50 {
let x = f64::from(i);
lr.observe(x, 3.0f64.mul_add(x, -2.0));
}
let pred = lr.predict(100.0);
assert!((pred - 298.0).abs() < 1e-4, "pred = {pred}");
}
#[test]
fn linreg_constant() {
let mut lr = LinearRegression::new();
for i in 0..50 {
lr.observe(f64::from(i), 42.0);
}
assert!((lr.slope() - 0.0).abs() < 1e-10);
assert!((lr.intercept() - 42.0).abs() < 1e-6);
}
#[test]
fn linreg_single_point() {
let mut lr = LinearRegression::new();
lr.observe(5.0, 10.0);
assert!((lr.slope() - 0.0).abs() < 1e-10);
assert_eq!(lr.count(), 1);
}
#[test]
fn linreg_reset() {
let mut lr = LinearRegression::new();
lr.observe(1.0, 2.0);
lr.observe(2.0, 4.0);
lr.reset();
assert_eq!(lr.count(), 0);
}
#[test]
fn linreg_default() {
let lr = LinearRegression::default();
assert_eq!(lr.count(), 0);
}
#[test]
fn linreg_full_r_squared_perfect() {
let mut lr = LinearRegressionFull::new();
for i in 0..100 {
let x = f64::from(i);
lr.observe(x, 2.0f64.mul_add(x, 1.0));
}
assert!(
(lr.r_squared() - 1.0).abs() < 1e-6,
"R² = {}",
lr.r_squared()
);
}
#[test]
fn linreg_full_r_squared_no_correlation() {
let mut lr = LinearRegressionFull::new();
for i in 0..100 {
lr.observe(f64::from(i), 42.0);
}
assert!(lr.r_squared() < 0.01, "R² = {}", lr.r_squared());
}
#[test]
fn linreg_full_predict() {
let mut lr = LinearRegressionFull::new();
for i in 0..50 {
let x = f64::from(i);
lr.observe(x, x.mul_add(0.5, 10.0));
}
let pred = lr.predict(100.0);
assert!((pred - 60.0).abs() < 1e-4, "pred = {pred}");
}
#[test]
fn linreg_full_reset() {
let mut lr = LinearRegressionFull::new();
lr.observe(1.0, 2.0);
lr.reset();
assert_eq!(lr.count(), 0);
}
#[test]
fn linreg_full_default() {
let lr = LinearRegressionFull::default();
assert_eq!(lr.count(), 0);
}
}