probability_rs/dist/
lognormal.rs1use crate::dist::normal::Normal;
2use crate::dist::{Continuous, DistError, Distribution, Moments};
3use crate::rng::RngCore;
4
5#[derive(Debug, Clone, Copy)]
7pub struct LogNormal {
8 mu: f64,
9 sigma: f64,
10 normal: Normal,
11}
12
13impl LogNormal {
14 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistError> {
15 if !(sigma > 0.0 && sigma.is_finite() && mu.is_finite()) {
16 return Err(DistError::InvalidParameter);
17 }
18 let normal = Normal::new(mu, sigma)?;
19 Ok(Self { mu, sigma, normal })
20 }
21 #[inline]
22 pub fn mu(&self) -> f64 {
23 self.mu
24 }
25 #[inline]
26 pub fn sigma(&self) -> f64 {
27 self.sigma
28 }
29}
30
31impl Distribution for LogNormal {
32 type Value = f64;
33 fn cdf(&self, x: f64) -> f64 {
34 if x <= 0.0 || !x.is_finite() {
35 return 0.0;
36 }
37 self.normal.cdf(x.ln())
38 }
39 fn in_support(&self, x: f64) -> bool {
40 x > 0.0 && x.is_finite()
41 }
42 fn sample<R: RngCore>(&self, rng: &mut R) -> f64 {
43 (self.normal.sample(rng)).exp()
44 }
45}
46
47impl Continuous for LogNormal {
48 fn pdf(&self, x: f64) -> f64 {
49 if !self.in_support(x) {
50 return 0.0;
51 }
52 let z = x.ln();
53 self.normal.pdf(z) / x
54 }
55 fn inv_cdf(&self, p: f64) -> f64 {
56 debug_assert!((0.0..=1.0).contains(&p));
57 self.normal.inv_cdf(p).exp()
58 }
59}
60
61impl Moments for LogNormal {
62 fn mean(&self) -> f64 {
63 (self.mu + 0.5 * self.sigma * self.sigma).exp()
64 }
65 fn variance(&self) -> f64 {
66 let s2 = self.sigma * self.sigma;
67 ((2.0 * s2).exp() - (s2).exp()) * (self.mu + s2).exp()
68 }
69 fn skewness(&self) -> f64 {
70 let s2 = self.sigma * self.sigma;
71 let es2 = s2.exp();
72 (es2 + 2.0) * (es2 - 1.0).sqrt()
73 }
74 fn kurtosis(&self) -> f64 {
75 let s2 = self.sigma * self.sigma;
76 let es2 = s2.exp();
77 es2.powi(4) + 2.0 * es2.powi(3) + 3.0 * es2.powi(2) - 6.0
78 }
79 fn entropy(&self) -> f64 {
80 self.mu + 0.5 * (2.0 * std::f64::consts::PI * std::f64::consts::E * self.sigma * self.sigma).ln()
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 #[test]
89 fn pdf_cdf_basic() {
90 let ln = LogNormal::new(0.0, 1.0).unwrap();
91 assert!(ln.pdf(1.0) > 0.0);
92 assert!((ln.cdf(1.0) - 0.5).abs() < 2e-6);
93 }
94 #[test]
95 fn moments_higher() {
96 let ln = LogNormal::new(0.0_f64, 1.0_f64).unwrap();
97 let s2: f64 = 1.0;
98 let es2: f64 = s2.exp();
99 let skew = (es2 + 2.0) * (es2 - 1.0).sqrt();
100 let kurt = es2.powi(4) + 2.0*es2.powi(3) + 3.0*es2.powi(2) - 6.0;
101 assert!((ln.skewness() - skew).abs() < 1e-12);
102 assert!((ln.kurtosis() - kurt).abs() < 1e-12);
103 }
104}