use super::super::{Cdf, Moments, Pdf, Quantile, Sample};
use crate::distributions::{LogNormalDistribution, NormalDistribution};
use crate::rng::SplitMix64;
use std::f64::consts::PI;
impl LogNormalDistribution {
fn log_normal(&self) -> NormalDistribution {
NormalDistribution {
mean: self.mean_log_value,
standard_deviation: self.std_log_value,
..Default::default()
}
}
}
impl Pdf for LogNormalDistribution {
fn pdf(&self, x: f64) -> f64 {
if x <= 0.0 {
return 0.0;
}
let (mu, sigma) = (self.mean_log_value, self.std_log_value);
let z = (x.ln() - mu) / sigma;
(-0.5 * z * z).exp() / (x * sigma * (2.0 * PI).sqrt())
}
}
impl Cdf for LogNormalDistribution {
fn cdf(&self, x: f64) -> f64 {
if x <= 0.0 {
0.0
} else {
self.log_normal().cdf(x.ln())
}
}
}
impl Quantile for LogNormalDistribution {
fn quantile(&self, p: f64) -> f64 {
self.log_normal().quantile(p).exp()
}
}
impl Moments for LogNormalDistribution {
fn mean(&self) -> Option<f64> {
let (mu, sigma) = (self.mean_log_value, self.std_log_value);
Some((0.5 * sigma).mul_add(sigma, mu).exp())
}
fn variance(&self) -> Option<f64> {
let (mu, sigma) = (self.mean_log_value, self.std_log_value);
let s2 = sigma * sigma;
Some(s2.exp_m1() * 2.0f64.mul_add(mu, s2).exp())
}
}
impl Sample for LogNormalDistribution {
fn sample(&self, rng: &mut SplitMix64) -> f64 {
self.std_log_value
.mul_add(rng.standard_normal(), self.mean_log_value)
.exp()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn median_is_exp_mu() {
let d = LogNormalDistribution {
mean_log_value: 0.3,
std_log_value: 0.6,
..Default::default()
};
assert!((d.quantile(0.5) - 0.3_f64.exp()).abs() < 1e-9);
}
#[test]
fn mean_matches_closed_form() {
let d = LogNormalDistribution {
mean_log_value: 0.0,
std_log_value: 1.0,
..Default::default()
};
assert!(matches!(d.mean(), Some(m) if (m - 1.648_721_271).abs() < 1e-6));
}
}