probability_rs/dist/
normal.rs1use crate::dist::{Continuous, DistError, Distribution, Moments};
2use crate::{num, rng::RngCore};
3
4#[derive(Debug, Clone, Copy)]
5pub struct Normal {
6 mu: f64,
7 sigma: f64,
8 inv_sigma: f64,
9 norm: f64, }
11
12impl Normal {
13 pub fn new(mu: f64, sigma: f64) -> Result<Self, DistError> {
14 if !(sigma > 0.0 && sigma.is_finite() && mu.is_finite()) {
15 return Err(DistError::InvalidParameter);
16 }
17 let inv_sigma = 1.0 / sigma;
18 let norm = num::INV_SQRT_2PI * inv_sigma;
19 Ok(Self {
20 mu,
21 sigma,
22 inv_sigma,
23 norm,
24 })
25 }
26 #[inline]
27 pub fn mean_param(&self) -> f64 {
28 self.mu
29 }
30 #[inline]
31 pub fn sigma(&self) -> f64 {
32 self.sigma
33 }
34}
35
36impl Distribution for Normal {
37 type Value = f64;
38 fn cdf(&self, x: f64) -> f64 {
39 let z = (x - self.mu) * self.inv_sigma;
40 num::standard_normal_cdf(z)
41 }
42 fn in_support(&self, x: f64) -> bool {
43 x.is_finite()
44 }
45 fn sample<R: RngCore>(&self, rng: &mut R) -> f64 {
46 loop {
48 let u1 = 2.0 * rng.next_f64() - 1.0; let u2 = 2.0 * rng.next_f64() - 1.0;
50 let s = u1 * u1 + u2 * u2;
51 if s >= 1.0 || s == 0.0 {
52 continue;
53 }
54 let factor = (-2.0 * s.ln() / s).sqrt();
55 return self.mu + self.sigma * u1 * factor;
56 }
57 }
58}
59
60impl Continuous for Normal {
61 fn pdf(&self, x: f64) -> f64 {
62 if !self.in_support(x) {
63 return 0.0;
64 }
65 let z = (x - self.mu) * self.inv_sigma;
66 self.norm * (-0.5 * z * z).exp()
67 }
68 fn inv_cdf(&self, p: f64) -> f64 {
69 self.mu + self.sigma * num::standard_normal_inv_cdf(p)
70 }
71}
72
73impl Moments for Normal {
74 fn mean(&self) -> f64 {
75 self.mu
76 }
77 fn variance(&self) -> f64 {
78 self.sigma * self.sigma
79 }
80 fn skewness(&self) -> f64 { 0.0 }
81 fn kurtosis(&self) -> f64 { 0.0 }
82 fn entropy(&self) -> f64 {
83 0.5 * (2.0 * std::f64::consts::PI * std::f64::consts::E * self.sigma * self.sigma).ln()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::rng::SplitMix64;
91
92 #[test]
93 fn normal_basic() {
94 let n = Normal::new(0.0, 1.0).unwrap();
95 assert!((n.pdf(0.0) - 0.3989422804014327).abs() < 1e-12);
96 assert!((n.cdf(0.0) - 0.5).abs() < 2e-6);
98 let q = n.inv_cdf(0.975);
99 assert!((q - 1.959963).abs() < 5e-4);
100 }
101
102 #[test]
103 fn cdf_symmetry() {
104 let n = Normal::new(0.0, 1.0).unwrap();
105 let z = 0.7;
106 let f = n.cdf(z);
107 let f_neg = n.cdf(-z);
108 assert!((f + f_neg - 1.0).abs() < 3e-15);
109 }
110
111 #[test]
112 fn moments_higher() {
113 let n = Normal::new(0.0, 2.0).unwrap();
114 assert_eq!(n.skewness(), 0.0);
115 assert_eq!(n.kurtosis(), 0.0);
116 assert_eq!(n.kurtosis_full(), 3.0);
117 }
118
119 #[test]
120 fn sampling_determinism() {
121 let n = Normal::new(0.0, 1.0).unwrap();
122 let mut rng1 = SplitMix64::seed_from_u64(42);
123 let mut rng2 = SplitMix64::seed_from_u64(42);
124 let x1 = n.sample(&mut rng1);
125 let x2 = n.sample(&mut rng2);
126 assert_eq!(x1.to_bits(), x2.to_bits());
127 }
128
129 #[test]
130 fn entropy_normal() {
131 let n = Normal::new(0.0, 2.0).unwrap();
132 let expected = 0.5 * (2.0 * std::f64::consts::PI * std::f64::consts::E * 4.0).ln();
133 assert!((n.entropy() - expected).abs() < 1e-12);
134 }
135}