stats_claw/distributions/symmetric/
laplace.rs1use super::super::{Cdf, Moments, Pdf, Quantile, Sample};
23use crate::distributions::LaplaceDistribution;
24use crate::rng::SplitMix64;
25
26impl Pdf for LaplaceDistribution {
27 fn pdf(&self, x: f64) -> f64 {
28 (-(x - self.location).abs() / self.scale).exp() / (2.0 * self.scale)
29 }
30}
31
32impl Cdf for LaplaceDistribution {
33 fn cdf(&self, x: f64) -> f64 {
34 let z = (x - self.location) / self.scale;
35 if z < 0.0 {
36 0.5 * z.exp()
37 } else {
38 (-z).exp().mul_add(-0.5, 1.0)
39 }
40 }
41}
42
43impl Quantile for LaplaceDistribution {
44 fn quantile(&self, p: f64) -> f64 {
45 if p <= 0.5 {
46 self.scale.mul_add((2.0 * p).ln(), self.location)
47 } else {
48 let upper_tail = 2.0f64.mul_add(-p, 2.0);
49 self.scale.mul_add(-upper_tail.ln(), self.location)
50 }
51 }
52}
53
54impl Moments for LaplaceDistribution {
55 fn mean(&self) -> Option<f64> {
56 Some(self.location)
57 }
58 fn variance(&self) -> Option<f64> {
59 Some(2.0 * self.scale * self.scale)
60 }
61}
62
63impl Sample for LaplaceDistribution {
64 fn sample(&self, rng: &mut SplitMix64) -> f64 {
65 self.quantile(rng.next_f64())
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
75 fn peak_density_at_location() {
76 let d = LaplaceDistribution {
77 location: 0.0,
78 scale: 1.0,
79 ..Default::default()
80 };
81 assert!((d.pdf(0.0) - 0.5).abs() < 1e-12, "peak was {}", d.pdf(0.0));
82 }
83
84 #[test]
86 fn median_is_location() {
87 let d = LaplaceDistribution {
88 location: 3.0,
89 scale: 2.0,
90 ..Default::default()
91 };
92 assert!((d.quantile(0.5) - 3.0).abs() < 1e-12);
93 }
94}