mentat 0.0.4

A Rust library for scientific computing.
Documentation
#[cfg(test)]
extern crate matplotrust;

extern crate rand;

use rand::prelude::*;
use rand::distributions::Normal as N;
use std::f64::consts::PI;

mod utils;
mod timeseries;

macro_rules! assert_delta {
    ($x:expr, $y:expr, $d:expr) => {
        if !(($x - $y).abs() < $d || ($y - $x).abs() < $d) { panic!(); }
    }
}

fn mean(numbers: &Vec<f64>) -> f64 {
    numbers.iter().sum::<f64>() as f64 / numbers.len() as f64
}

pub struct Normal {
    mean : f64,
    std : f64
}

impl Normal {
    pub fn new(mean: f64, std: f64) -> Normal {
        return Normal {
            mean : mean,
            std: std,
        }
    }

    pub fn sample(&mut self) -> f64 {
        let normal = N::new(self.mean, self.std);
        let v = normal.sample(&mut rand::thread_rng());
        return v;
    }

    pub fn logpdf(&mut self, x: f64) -> f64 {
        return -0.5 * (2.0 * PI).ln() - self.std.ln() - (x - self.mean).powi(2) / (2.0 * self.std * self.std)
    }

    pub fn pdf(&mut self, x: f64) -> f64 {
        return self.logpdf(x).exp();
    }
}

pub struct MonotonicCubicSpline {
    m_x: Vec<f64>,
    m_y: Vec<f64>,
    m_m: Vec<f64>
}

impl MonotonicCubicSpline {

    pub fn new(x : &Vec<f64>, y : &Vec<f64>) -> MonotonicCubicSpline {

        assert!(x.len() == y.len() && x.len() >= 2 && y.len() >= 2, "Must have at least 2 control points.");

        let n = x.len();

        let mut secants = vec![0.0 ; n - 1];
        let mut slopes  = vec![0.0 ; n];

        for i in 0..(n-1) {
            let h = *x.get(i + 1).unwrap() - *x.get(i).unwrap();
            assert!(h > 0.0, "Control points must be monotonically increasing.");
            secants[i] = (*y.get(i + 1).unwrap() - *y.get(i).unwrap()) / h;

        }

        slopes[0] = secants[0];
        for i in 1..(n-1) {
            slopes[i] = (secants[i - 1] + secants[i]) * 0.5;
        }
        slopes[n - 1] = secants[n - 2];

        for i in 0..(n-1) {
            if secants[i] == 0.0 {
                slopes[i] = 0.0;
                slopes[i + 1] = 0.0;
            } else {
                let alpha = slopes[i] / secants[i];
                let beta = slopes[i + 1] / secants[i];
                let h = alpha.hypot(beta);
                if h > 9.0 {
                    let t = 3.0 / h;
                    slopes[i] = t * alpha * secants[i];
                    slopes[i + 1] = t * beta * secants[i];
                }
            }
        }

        let spline = MonotonicCubicSpline {
            m_x: x.clone(),
            m_y: y.clone(),
            m_m: slopes
        };
        return spline;
    }

    pub fn hermite(point: f64, x : (f64, f64), y: (f64, f64), m: (f64, f64)) -> f64 {

        let h: f64 = x.1 - x.0;
        let t = (point - x.0) / h;
        return (y.0 * (1.0 + 2.0 * t) + h * m.0 * t) * (1.0 - t) * (1.0 - t)
            + (y.1 * (3.0 - 2.0 * t) + h * m.1 * (t - 1.0)) * t * t;
    }

    pub fn interpolate(&mut self, point : f64) -> f64 {
        let n = self.m_x.len();

        if point <= *self.m_x.get(0).unwrap() {
            return *self.m_y.get(0).unwrap();
        }
        if point >= *self.m_x.get(n - 1).unwrap() {
            return *self.m_y.get(n - 1).unwrap();
        }

        let mut i = 0;
        while point >= *self.m_x.get(i + 1).unwrap() {
            i += 1;
            if point == *self.m_x.get(i).unwrap() {
                return *self.m_y.get(i).unwrap();
            }
        }
        return MonotonicCubicSpline::hermite(point,
                                             (*self.m_x.get(i).unwrap(), *self.m_x.get(i+1).unwrap()),
                                             (*self.m_y.get(i).unwrap(), *self.m_y.get(i+1).unwrap()),
                                             (*self.m_m.get(i).unwrap(), *self.m_m.get(i+1).unwrap()));
    }

    fn partial(x: Vec<f64>, y: Vec<f64>) -> impl Fn(f64) -> f64 {
        move |p| {
            let mut spline = MonotonicCubicSpline::new(&x, &y);
            spline.interpolate(p)
        }
    }

}

#[cfg(test)]
mod test_normal {

    use super::*;

    #[test]
    fn sample_mean_std() {
        let n = 0..1000000;
        let mut normal = Normal::new(0.0, 1.0);
        let samples = n.map(|_i| normal.sample()).collect::<Vec<f64>>();
        let mu = mean(&samples);
        assert_delta!(0.0, mu, 1e-3);
    }

    #[test]
    fn logpdf() {
        let mut normal = Normal::new(0.0, 1.0);
        let _logpdf = normal.logpdf(0.0);
        print!("{:?}", _logpdf);
        assert_delta!(-0.9189385, _logpdf, 1e-5);
    }

    fn pdf() {
        let mut normal = Normal::new(0.0, 1.0);
        let _pdf = normal.pdf(0.0);
        print!("{:?}", _pdf);
        assert_delta!(0.3989423, _pdf, 1e-5);
    }


    #[test]
    fn interpolation() {
        use matplotrust::*;

        let x = vec![0.0, 2.0, 3.0, 10.0];
        let y = vec![1.0, 4.0, 8.0, 10.5];

        let smooth = MonotonicCubicSpline::partial(x.clone(), y.clone());

        let mut x_interp = Vec::new();
        let mut y_interp = Vec::new();
        for i in 0..100 {
            let p = i as f64 / 10.0;
            x_interp.push(p);
            y_interp.push(smooth(p));
        }
        let mut figure = Figure::new();
        let points = scatter_plot::<f64, f64>(x, y, None);
        let interpolation = line_plot::<f64, f64>(x_interp, y_interp, None);
        figure.add_plot(points);
        figure.add_plot(interpolation);
        figure.save("./docs/figures/monotonic_cubic_spline.png", None);

    }

}