use crate::distributions::Distribution;
use num::Complex;
use statrs::function::gamma::{gamma_li, gamma_ui};
use RustQuant_error::RustQuantError;
pub struct Poisson {
lambda: f64,
}
impl Poisson {
#[must_use]
pub fn new(lambda: f64) -> Poisson {
assert!(lambda > 0.0);
Poisson { lambda }
}
}
impl Distribution for Poisson {
fn cf(&self, t: f64) -> Complex<f64> {
let i: Complex<f64> = Complex::i();
(self.lambda * ((i * t).exp() - 1.0)).exp()
}
fn pdf(&self, x: f64) -> f64 {
self.pmf(x)
}
fn pmf(&self, x: f64) -> f64 {
(self.lambda).powi(x as i32) * (-(self.lambda)).exp()
/ ((1..=x as usize).product::<usize>() as f64)
}
fn cdf(&self, x: f64) -> f64 {
1.0 - gamma_li(x + 1., self.lambda) / gamma_ui(x + 1., self.lambda)
}
fn inv_cdf(&self, p: f64) -> f64 {
if !(0.0..=1.0).contains(&p) {
return f64::NAN;
}
if (p - 1.0).abs() < f64::EPSILON {
return f64::INFINITY;
}
let mut sum = 0.0;
let mut k = 0;
while sum < p {
sum += self.pmf(f64::from(k));
k += 1;
}
f64::from(k - 1)
}
fn mean(&self) -> f64 {
self.lambda
}
fn median(&self) -> f64 {
(self.lambda + 1.0 / 3.0 - 0.02 / self.lambda).floor()
}
fn mode(&self) -> f64 {
self.lambda.floor()
}
fn variance(&self) -> f64 {
self.lambda
}
fn skewness(&self) -> f64 {
self.lambda.sqrt().recip()
}
fn kurtosis(&self) -> f64 {
self.lambda.recip()
}
fn entropy(&self) -> f64 {
todo!()
}
fn mgf(&self, t: f64) -> f64 {
(self.lambda * (t.exp() - 1.0)).exp()
}
fn sample(&self, n: usize) -> Result<Vec<f64>, RustQuantError> {
use rand::thread_rng;
use rand_distr::{Distribution, Poisson};
assert!(n > 0);
let mut rng = thread_rng();
let dist = Poisson::new(self.lambda)?;
let mut variates: Vec<f64> = Vec::with_capacity(n);
for _ in 0..variates.capacity() {
variates.push(dist.sample(&mut rng) as usize as f64);
}
Ok(variates)
}
}
#[cfg(test)]
mod tests {
use super::*;
use RustQuant_utils::{assert_approx_equal, RUSTQUANT_EPSILON as EPS};
#[allow(clippy::similar_names)]
#[test]
fn test_poisson_distribution() -> Result<(), RustQuantError> {
let dist: Poisson = Poisson::new(1.0);
let cf = dist.cf(1.0);
assert_approx_equal!(cf.re, 0.420_793_617_430_045_7, EPS);
assert_approx_equal!(cf.im, 0.470_842_643_309_935_9, EPS);
let pmf = dist.pmf(1.);
assert_approx_equal!(pmf, 0.367_879_441_171_442_33, EPS);
assert_approx_equal!(dist.pdf(1.), pmf, EPS);
let cdf = dist.cdf(1.);
assert_approx_equal!(cdf, 0.640_859_085_770_477_5, EPS);
let icdf = dist.inv_cdf(0.5);
assert_approx_equal!(icdf, 1.0, EPS);
assert!(dist.inv_cdf(1.1).is_nan());
assert!(dist.inv_cdf(-0.1).is_nan());
assert!(dist.inv_cdf(1.0).is_infinite() && dist.inv_cdf(1.0).is_sign_positive());
assert_approx_equal!(dist.mean(), 1.0, EPS);
assert_approx_equal!(dist.median(), 1.0, EPS);
assert_approx_equal!(dist.mode(), 1.0, EPS);
assert_approx_equal!(dist.variance(), 1.0, EPS);
assert_approx_equal!(dist.skewness(), 1.0, EPS);
assert_approx_equal!(dist.kurtosis(), 1.0, EPS);
let mgf = dist.mgf(1.0);
assert_approx_equal!(mgf, 5.574_941_5, 1e-7);
let sample = dist.sample(1000)?;
let mean = sample.iter().sum::<f64>() / sample.len() as f64;
assert_approx_equal!(mean, dist.mean(), 0.1);
Ok(())
}
}