use core::ops::{Add, Div, Mul}; use num_traits::{float::FloatCore, AsPrimitive, Zero}; use crate::{Anim, AnimWithDur, Fun}; impl AnimWithDur where F: Fun, { pub fn fold(&self, init: B, mut f: G) -> B where G: FnMut(B, F::V) -> B, { let mut b = init; for t in 0..self.1 { b = f(b, self.0.eval(t)) } b } } impl AnimWithDur where F: Fun, F::V: Add + Zero, { pub fn sum(&self) -> F::V { self.fold(Zero::zero(), |a, b| a + b) } } impl AnimWithDur where F: Fun, F::T: Clone, F::V: 'static + Add + Div + Zero + Copy, usize: AsPrimitive, { pub fn mean(&self) -> F::V { let len = self.1.clone().as_(); self.sum() / len } } #[derive(Debug, Clone)] pub struct Line { pub y_intercept: V, pub slope: V, } impl Fun for Line where V: Add + Mul + Clone, { type T = V; type V = V; fn eval(&self, t: V) -> V { self.y_intercept.clone() + self.slope.clone() * t } } pub fn simple_linear_regression_with_slope(slope: V, values: A) -> Anim> where V: 'static + FloatCore + Copy, F: Fun, A: Into>, usize: AsPrimitive, { // https://en.wikipedia.org/wiki/Simple_linear_regression#Fitting_the_regression_line let values = values.into(); let (x, y) = values.unzip(); let x_mean = x.as_ref().mean(); let y_mean = y.as_ref().mean(); let y_intercept = y_mean - slope * x_mean; Anim(Line { y_intercept, slope }) } pub fn simple_linear_regression(values: A) -> Anim> where V: 'static + FloatCore + Copy, F: Fun, A: Into>, usize: AsPrimitive, { // https://en.wikipedia.org/wiki/Simple_linear_regression#Fitting_the_regression_line let values = values.into(); let (x, y) = values.unzip(); let x_mean = x.as_ref().mean(); let y_mean = y.as_ref().mean(); let numerator = values .as_ref() .map(|(x, y)| (x - x_mean) * (y - y_mean)) .sum(); let denominator = x.as_ref().map(|x| (x - x_mean) * (x - x_mean)).sum(); let slope = numerator / denominator; let y_intercept = y_mean - slope * x_mean; Anim(Line { y_intercept, slope }) } #[cfg(all(test, feature = "alloc"))] mod tests { use assert_approx_eq::assert_approx_eq; extern crate alloc; use alloc::vec; use super::simple_linear_regression; #[test] fn test_perfect_regression() { let straight_line = vec![(1.0, 1.0), (2.0, 2.0)]; let line = simple_linear_regression(straight_line.as_slice()); assert_approx_eq!(line.eval(1.0), 1.0f64); assert_approx_eq!(line.eval(10.0), 10.0); assert_approx_eq!(line.eval(-10.0), -10.0); } #[test] fn test_negative_perfect_regression() { let straight_line = vec![(1.0, -1.0), (2.0, -2.0)]; let line = simple_linear_regression(straight_line.as_slice()); assert_approx_eq!(line.eval(1.0), -1.0f64); assert_approx_eq!(line.eval(10.0), -10.0); assert_approx_eq!(line.eval(-10.0), 10.0); } }