pub use crate::triangular::Triangular;
use num::traits::{Float, FloatConst, NumAssignOps, NumCast};
use rand::{
distributions::{uniform::SampleUniform, Distribution, OpenClosed01},
Rng,
};
pub trait Num
where
Self: Float + FloatConst + NumCast + NumAssignOps,
Self: SampleUniform,
{
fn sample_open_closed_01<R: Rng + ?Sized>(rng: &mut R) -> Self;
}
impl<T> Num for T
where
T: Float + FloatConst + NumCast + NumAssignOps,
T: SampleUniform,
OpenClosed01: Distribution<T>,
{
fn sample_open_closed_01<R: Rng + ?Sized>(rng: &mut R) -> Self {
OpenClosed01.sample(rng)
}
}
pub trait MultivarDist<T>: Distribution<Vec<T>> {
fn dim(&self) -> usize;
}
#[derive(Clone, Copy, Default, Debug)]
pub struct StdNormDist;
impl<T: Num> Distribution<T> for StdNormDist {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T {
let pi: T = T::PI();
let r = (T::from(-2.0).unwrap() * T::ln(T::sample_open_closed_01(rng))).sqrt();
let sin = rng.gen_range(-pi..pi).sin();
r * sin
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct StdNormDistPair;
impl<T: Num> Distribution<[T; 2]> for StdNormDistPair {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> [T; 2] {
let pi: T = T::PI();
let r = (T::from(-2.0).unwrap() * T::ln(T::sample_open_closed_01(rng))).sqrt();
let (sin, cos) = rng.gen_range(-pi..pi).sin_cos();
[r * sin, r * cos]
}
}
impl<T: Num> Distribution<(T, T)> for StdNormDistPair {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> (T, T) {
let [x, y]: [T; 2] = self.sample(rng);
(x, y)
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct StdNormDistVec(
pub usize,
);
impl<T: Num> Distribution<Vec<T>> for StdNormDistVec {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec<T> {
let dim = self.0;
let mut vec = Vec::with_capacity(dim);
for _ in 0..dim / 2 {
let (x, y) = StdNormDistPair.sample(rng);
vec.push(x);
vec.push(y);
}
if dim % 2 == 1 {
vec.push(StdNormDist.sample(rng));
}
vec
}
}
#[derive(Clone, Debug)]
pub struct NormDist<T> {
pub average: T,
pub stddev: T,
}
impl<T> NormDist<T> {
pub const fn new(average: T, stddev: T) -> NormDist<T> {
NormDist { average, stddev }
}
}
impl<T: Num> Distribution<T> for NormDist<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T {
let x: T = StdNormDist.sample(rng);
x * self.stddev + self.average
}
}
#[derive(Debug)]
pub struct MultivarNormDist<T> {
averages: Vec<T>,
factors: Triangular<T>,
}
impl<T: Num> MultivarNormDist<T> {
pub fn new(averages: Vec<T>, covariances: Triangular<T>) -> MultivarNormDist<T> {
let dim = covariances.dim();
assert_eq!(
averages.len(),
dim,
"dimensions of averages and covariances do not match"
);
let mut factors = covariances;
for i in 0..dim {
for j in 0..=i {
unsafe {
let mut sum = *factors.get_unchecked((i, j));
for k in 0..j {
sum -= *factors.get_unchecked((i, k)) * *factors.get_unchecked((j, k));
}
let v = if i == j {
sum.sqrt()
} else {
sum / *factors.get_unchecked((j, j))
};
*factors.get_unchecked_mut((i, j)) = if v.is_finite() { v } else { T::zero() }
}
}
}
MultivarNormDist { averages, factors }
}
}
impl<T: Num> MultivarDist<T> for MultivarNormDist<T> {
fn dim(&self) -> usize {
self.factors.dim()
}
}
impl<T: Num> Distribution<Vec<T>> for MultivarNormDist<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec<T> {
let dim = self.dim();
let mut result: Vec<T> = StdNormDistVec(dim).sample(rng);
unsafe {
for i in (0..dim).rev() {
let mut sum = T::zero();
for j in 0..=i {
sum += *result.get_unchecked(j) * *self.factors.get_unchecked((i, j));
}
*result.get_unchecked_mut(i) = sum;
}
for i in 0..dim {
*result.get_unchecked_mut(i) += *self.averages.get_unchecked(i);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::{MultivarNormDist, StdNormDist};
use crate::triangular::Triangular;
use rand::{distributions::Distribution, thread_rng};
const STATCNT: usize = 10000;
#[test]
fn test_std_norm_dist() {
let values: Vec<f64> = StdNormDist
.sample_iter(thread_rng())
.take(STATCNT)
.collect();
let (mut avg, mut var): (f64, f64) = (0.0, 0.0);
for value in values.iter() {
avg += value;
var += value * value;
}
avg /= STATCNT as f64;
var /= STATCNT as f64;
assert!(avg.abs() < 0.1);
assert!((var - 1.0).abs() < 0.1);
}
#[test]
fn test_multivar_norm_dist() {
let avg_goal: Vec<f64> = vec![0.0, 0.0, 0.0];
let cov_goal = Triangular::<f64>::new(3, |idx| match idx {
(0, 0) => 1.0,
(1, 1) => 0.5,
(2, 2) => 2.0,
(2, 0) => 0.25,
(0, 2) => 0.25,
_ => 0.0,
});
let cov_goal = cov_goal;
let dist = MultivarNormDist::new(avg_goal.clone(), cov_goal.clone());
let values: Vec<Vec<f64>> = dist.sample_iter(thread_rng()).take(STATCNT).collect();
let mut avg: Vec<f64> = vec![0.0; 3];
for value in values.iter() {
for i in 0..3 {
avg[i] += value[i];
}
}
for i in 0..3 {
avg[i] /= STATCNT as f64;
}
let mut cov = Triangular::<f64>::new(3, |(i, j)| {
let mut sum = 0.0;
for value in values.iter() {
sum += (value[i] - avg[i]) * (value[j] - avg[j]);
}
sum
});
for i in 0..3 {
for j in 0..=i {
cov[(i, j)] /= STATCNT as f64;
}
}
for i in 0..3 {
assert!((avg[i] - avg_goal[i]).abs() < 0.1);
for j in 0..=i {
assert!((cov[(i, j)] - cov_goal[(i, j)]).abs() < 0.1);
}
}
}
}