slain-num 0.3.0

SLAIN's own numeric crate: dense matrices, least squares, polynomials, FFT, convolution, statistics and 1-D/N-D minimisation, with AVX2 kernels.
Documentation
use crate::{simd, stats};

pub trait Dispatch {
    fn sum(&self, x: &[f64]) -> f64 {
        simd::sum(x)
    }

    fn mean(&self, x: &[f64]) -> f64 {
        stats::mean(x)
    }

    fn variance(&self, x: &[f64]) -> f64 {
        stats::variance(x)
    }
}

pub struct Core;

impl Dispatch for Core {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;
    use std::time::Instant;

    struct Layer {
        seen: RefCell<Vec<&'static str>>,
    }

    impl Dispatch for Layer {
        fn sum(&self, _x: &[f64]) -> f64 {
            self.seen.borrow_mut().push("sum");
            -1.0
        }

        fn mean(&self, x: &[f64]) -> f64 {
            self.seen.borrow_mut().push("mean");
            Core.mean(x)
        }
    }

    #[test]
    fn layer_handles_falls_through_and_leaves_core_unchanged() {
        let x: Vec<f64> = (0..5).map(f64::from).collect();
        let layer = Layer { seen: RefCell::new(Vec::new()) };
        assert_eq!(Core.sum(&x), 10.0);
        assert_eq!(layer.sum(&x), -1.0);
        assert_eq!(layer.mean(&x), 2.0);
        assert_eq!(layer.variance(&x), stats::variance(&x));
        assert_eq!(Core.sum(&x), 10.0);
        let core_dyn: &dyn Dispatch = &Core;
        let layer_dyn: &dyn Dispatch = &layer;
        assert_eq!(core_dyn.sum(&x), 10.0);
        assert_eq!(layer_dyn.sum(&x), -1.0);
        assert_eq!(*layer.seen.borrow(), ["sum", "mean", "sum"]);
    }

    fn calls_per_second(mut f: impl FnMut() -> f64) -> f64 {
        let reps = 2000u32;
        let mut best = f64::MAX;
        for _ in 0..21 {
            let t = Instant::now();
            for _ in 0..reps {
                crate::bench::slain(f());
            }
            best = best.min(t.elapsed().as_secs_f64());
        }
        f64::from(reps) / best
    }

    #[test]
    #[ignore]
    fn time_dispatch() {
        let x: Vec<f64> = (0..4096).map(|i| f64::from(i) * 0.5).collect();
        let layer = Layer { seen: RefCell::new(Vec::new()) };
        let core_dyn: &dyn Dispatch = &Core;
        let direct = calls_per_second(|| stats::mean(crate::bench::slain(&x)));
        let fixed = calls_per_second(|| Core.mean(crate::bench::slain(&x)));
        let dynamic = calls_per_second(|| crate::bench::slain(core_dyn).mean(crate::bench::slain(&x)));
        let through = calls_per_second(|| {
            layer.seen.borrow_mut().clear();
            layer.mean(crate::bench::slain(&x))
        });
        for (name, c) in [("direct stats::mean", direct), ("static Core.mean", fixed), ("dyn Dispatch mean (Core)", dynamic), ("layer fall-through mean", through)] {
            println!("DISPATCH {name:28} {c:.9} calls/s  {:.9}x direct", c / direct);
        }
    }
}