1use crate::distribution::{Continuous, ContinuousCDF};
2use crate::statistics::{Distribution, Max, Median, Min, Mode};
3use std::f64;
4
5#[derive(Copy, Clone, PartialEq, Debug)]
19pub struct Laplace {
20 location: f64,
21 scale: f64,
22}
23
24#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
26#[non_exhaustive]
27pub enum LaplaceError {
28 LocationInvalid,
30
31 ScaleInvalid,
33}
34
35impl std::fmt::Display for LaplaceError {
36 #[cfg_attr(coverage_nightly, coverage(off))]
37 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
38 match self {
39 LaplaceError::LocationInvalid => write!(f, "Location is NaN"),
40 LaplaceError::ScaleInvalid => write!(f, "Scale is NaN, zero or less than zero"),
41 }
42 }
43}
44
45impl std::error::Error for LaplaceError {}
46
47impl Laplace {
48 pub fn new(location: f64, scale: f64) -> Result<Laplace, LaplaceError> {
67 if location.is_nan() {
68 return Err(LaplaceError::LocationInvalid);
69 }
70
71 if scale.is_nan() || scale <= 0.0 {
72 return Err(LaplaceError::ScaleInvalid);
73 }
74
75 Ok(Laplace { location, scale })
76 }
77
78 pub fn location(&self) -> f64 {
89 self.location
90 }
91
92 pub fn scale(&self) -> f64 {
103 self.scale
104 }
105}
106
107impl std::fmt::Display for Laplace {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 write!(f, "Laplace({}, {})", self.location, self.scale)
110 }
111}
112
113#[cfg(feature = "rand")]
114#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
115impl ::rand::distributions::Distribution<f64> for Laplace {
116 fn sample<R: ::rand::Rng + ?Sized>(&self, rng: &mut R) -> f64 {
117 let x: f64 = rng.gen_range(-0.5..0.5);
118 self.location - self.scale * x.signum() * (1. - 2. * x.abs()).ln()
119 }
120}
121
122impl ContinuousCDF<f64, f64> for Laplace {
123 fn cdf(&self, x: f64) -> f64 {
134 let y = (-(x - self.location).abs() / self.scale).exp() / 2.;
135 if x >= self.location {
136 1. - y
137 } else {
138 y
139 }
140 }
141
142 fn sf(&self, x: f64) -> f64 {
153 let y = (-(x - self.location).abs() / self.scale).exp() / 2.;
154 if x >= self.location {
155 y
156 } else {
157 1. - y
158 }
159 }
160
161 fn inverse_cdf(&self, p: f64) -> f64 {
177 if p <= 0. || 1. <= p {
178 panic!("p must be in [0, 1]");
179 };
180 if p <= 0.5 {
181 self.location + self.scale * (2. * p).ln()
182 } else {
183 self.location - self.scale * (2. - 2. * p).ln()
184 }
185 }
186}
187
188impl Min<f64> for Laplace {
189 fn min(&self) -> f64 {
198 f64::NEG_INFINITY
199 }
200}
201
202impl Max<f64> for Laplace {
203 fn max(&self) -> f64 {
212 f64::INFINITY
213 }
214}
215
216impl Distribution<f64> for Laplace {
217 fn mean(&self) -> Option<f64> {
227 Some(self.location)
228 }
229
230 fn variance(&self) -> Option<f64> {
240 Some(2. * self.scale * self.scale)
241 }
242
243 fn entropy(&self) -> Option<f64> {
253 Some((2. * self.scale).ln() + 1.)
254 }
255
256 fn skewness(&self) -> Option<f64> {
264 Some(0.)
265 }
266}
267
268impl Median<f64> for Laplace {
269 fn median(&self) -> f64 {
279 self.location
280 }
281}
282
283impl Mode<Option<f64>> for Laplace {
284 fn mode(&self) -> Option<f64> {
294 Some(self.location)
295 }
296}
297
298impl Continuous<f64, f64> for Laplace {
299 fn pdf(&self, x: f64) -> f64 {
309 (-(x - self.location).abs() / self.scale).exp() / (2. * self.scale)
310 }
311
312 fn ln_pdf(&self, x: f64) -> f64 {
323 ((-(x - self.location).abs() / self.scale).exp() / (2. * self.scale)).ln()
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 use crate::testing_boiler;
332
333 testing_boiler!(location: f64, scale: f64; Laplace; LaplaceError);
334
335 fn test_rel_close<F>(location: f64, scale: f64, expected: f64, rtol: f64, get_fn: F)
341 where
342 F: Fn(Laplace) -> f64,
343 {
344 let x = create_and_get(location, scale, get_fn);
345 assert_relative_eq!(expected, x, epsilon = 0.0, max_relative = rtol);
346 }
347
348 #[test]
349 fn test_create() {
350 create_ok(1.0, 2.0);
351 create_ok(f64::NEG_INFINITY, 0.1);
352 create_ok(-5.0 - 1.0, 1.0);
353 create_ok(0.0, 5.0);
354 create_ok(1.0, 7.0);
355 create_ok(5.0, 10.0);
356 create_ok(f64::INFINITY, f64::INFINITY);
357 }
358
359 #[test]
360 fn test_bad_create() {
361 test_create_err(2.0, -1.0, LaplaceError::ScaleInvalid);
362 test_create_err(f64::NAN, 1.0, LaplaceError::LocationInvalid);
363 create_err(f64::NAN, -1.0);
364 }
365
366 #[test]
367 fn test_mean() {
368 let mean = |x: Laplace| x.mean().unwrap();
369 test_exact(f64::NEG_INFINITY, 0.1, f64::NEG_INFINITY, mean);
370 test_exact(-5.0 - 1.0, 1.0, -6.0, mean);
371 test_exact(0.0, 5.0, 0.0, mean);
372 test_exact(1.0, 10.0, 1.0, mean);
373 test_exact(f64::INFINITY, f64::INFINITY, f64::INFINITY, mean);
374 }
375
376 #[test]
377 fn test_variance() {
378 let variance = |x: Laplace| x.variance().unwrap();
379 test_absolute(f64::NEG_INFINITY, 0.1, 0.02, 1E-12, variance);
380 test_absolute(-5.0 - 1.0, 1.0, 2.0, 1E-12, variance);
381 test_absolute(0.0, 5.0, 50.0, 1E-12, variance);
382 test_absolute(1.0, 7.0, 98.0, 1E-12, variance);
383 test_absolute(5.0, 10.0, 200.0, 1E-12, variance);
384 test_absolute(f64::INFINITY, f64::INFINITY, f64::INFINITY, 1E-12, variance);
385 }
386 #[test]
387 fn test_entropy() {
388 let entropy = |x: Laplace| x.entropy().unwrap();
389 test_absolute(
390 f64::NEG_INFINITY,
391 0.1,
392 (2.0 * f64::consts::E * 0.1).ln(),
393 1E-12,
394 entropy,
395 );
396 test_absolute(-6.0, 1.0, (2.0 * f64::consts::E).ln(), 1E-12, entropy);
397 test_absolute(1.0, 7.0, (2.0 * f64::consts::E * 7.0).ln(), 1E-12, entropy);
398 test_absolute(5., 10., (2. * f64::consts::E * 10.).ln(), 1E-12, entropy);
399 test_absolute(f64::INFINITY, f64::INFINITY, f64::INFINITY, 1E-12, entropy);
400 }
401
402 #[test]
403 fn test_skewness() {
404 let skewness = |x: Laplace| x.skewness().unwrap();
405 test_exact(f64::NEG_INFINITY, 0.1, 0.0, skewness);
406 test_exact(-6.0, 1.0, 0.0, skewness);
407 test_exact(1.0, 7.0, 0.0, skewness);
408 test_exact(5.0, 10.0, 0.0, skewness);
409 test_exact(f64::INFINITY, f64::INFINITY, 0.0, skewness);
410 }
411
412 #[test]
413 fn test_mode() {
414 let mode = |x: Laplace| x.mode().unwrap();
415 test_exact(f64::NEG_INFINITY, 0.1, f64::NEG_INFINITY, mode);
416 test_exact(-6.0, 1.0, -6.0, mode);
417 test_exact(1.0, 7.0, 1.0, mode);
418 test_exact(5.0, 10.0, 5.0, mode);
419 test_exact(f64::INFINITY, f64::INFINITY, f64::INFINITY, mode);
420 }
421
422 #[test]
423 fn test_median() {
424 let median = |x: Laplace| x.median();
425 test_exact(f64::NEG_INFINITY, 0.1, f64::NEG_INFINITY, median);
426 test_exact(-6.0, 1.0, -6.0, median);
427 test_exact(1.0, 7.0, 1.0, median);
428 test_exact(5.0, 10.0, 5.0, median);
429 test_exact(f64::INFINITY, f64::INFINITY, f64::INFINITY, median);
430 }
431
432 #[test]
433 fn test_min() {
434 test_exact(0.0, 1.0, f64::NEG_INFINITY, |l| l.min());
435 }
436
437 #[test]
438 fn test_max() {
439 test_exact(0.0, 1.0, f64::INFINITY, |l| l.max());
440 }
441
442 #[test]
443 fn test_density() {
444 let pdf = |arg: f64| move |x: Laplace| x.pdf(arg);
445 test_absolute(0.0, 0.1, 1.529511602509129e-06, 1E-12, pdf(1.5));
446 test_absolute(1.0, 0.1, 7.614989872356341e-08, 1E-12, pdf(2.8));
447 test_absolute(-1.0, 0.1, 3.8905661205668983e-19, 1E-12, pdf(-5.4));
448 test_absolute(5.0, 0.1, 5.056107463052243e-43, 1E-12, pdf(-4.9));
449 test_absolute(-5.0, 0.1, 1.9877248679543235e-30, 1E-12, pdf(2.0));
450 test_absolute(f64::INFINITY, 0.1, 0.0, 1E-12, pdf(5.5));
451 test_absolute(f64::NEG_INFINITY, 0.1, 0.0, 1E-12, pdf(-0.0));
452 test_absolute(0.0, 1.0, 0.0, 1E-12, pdf(f64::INFINITY));
453 test_absolute(1.0, 1.0, 0.00915781944436709, 1E-12, pdf(5.0));
454 test_absolute(-1.0, 1.0, 0.5, 1E-12, pdf(-1.0));
455 test_absolute(5.0, 1.0, 0.0012393760883331792, 1E-12, pdf(-1.0));
456 test_absolute(-5.0, 1.0, 0.0002765421850739168, 1E-12, pdf(2.5));
457 test_absolute(f64::INFINITY, 0.1, 0.0, 1E-12, pdf(2.0));
458 test_absolute(f64::NEG_INFINITY, 0.1, 0.0, 1E-12, pdf(15.0));
459 test_absolute(0.0, f64::INFINITY, 0.0, 1E-12, pdf(89.3));
460 test_absolute(1.0, f64::INFINITY, 0.0, 1E-12, pdf(-0.1));
461 test_absolute(-1.0, f64::INFINITY, 0.0, 1E-12, pdf(0.1));
462 test_absolute(5.0, f64::INFINITY, 0.0, 1E-12, pdf(-6.1));
463 test_absolute(-5.0, f64::INFINITY, 0.0, 1E-12, pdf(-10.0));
464 test_is_nan(f64::INFINITY, f64::INFINITY, pdf(2.0));
465 test_is_nan(f64::NEG_INFINITY, f64::INFINITY, pdf(-5.1));
466 }
467
468 #[test]
469 fn test_ln_density() {
470 let ln_pdf = |arg: f64| move |x: Laplace| x.ln_pdf(arg);
471 test_absolute(0.0, 0.1, -13.3905620875659, 1E-12, ln_pdf(1.5));
472 test_absolute(1.0, 0.1, -16.390562087565897, 1E-12, ln_pdf(2.8));
473 test_absolute(-1.0, 0.1, -42.39056208756591, 1E-12, ln_pdf(-5.4));
474 test_absolute(5.0, 0.1, -97.3905620875659, 1E-12, ln_pdf(-4.9));
475 test_absolute(-5.0, 0.1, -68.3905620875659, 1E-12, ln_pdf(2.0));
476 test_exact(f64::INFINITY, 0.1, f64::NEG_INFINITY, ln_pdf(5.5));
477 test_exact(f64::NEG_INFINITY, 0.1, f64::NEG_INFINITY, ln_pdf(-0.0));
478 test_exact(0.0, 1.0, f64::NEG_INFINITY, ln_pdf(f64::INFINITY));
479 test_absolute(1.0, 1.0, -4.693147180559945, 1E-12, ln_pdf(5.0));
480 test_absolute(-1.0, 1.0, -f64::consts::LN_2, 1E-12, ln_pdf(-1.0));
481 test_absolute(5.0, 1.0, -6.693147180559945, 1E-12, ln_pdf(-1.0));
482 test_absolute(-5.0, 1.0, -8.193147180559945, 1E-12, ln_pdf(2.5));
483 test_exact(f64::INFINITY, 0.1, f64::NEG_INFINITY, ln_pdf(2.0));
484 test_exact(f64::NEG_INFINITY, 0.1, f64::NEG_INFINITY, ln_pdf(15.0));
485 test_exact(0.0, f64::INFINITY, f64::NEG_INFINITY, ln_pdf(89.3));
486 test_exact(1.0, f64::INFINITY, f64::NEG_INFINITY, ln_pdf(-0.1));
487 test_exact(-1.0, f64::INFINITY, f64::NEG_INFINITY, ln_pdf(0.1));
488 test_exact(5.0, f64::INFINITY, f64::NEG_INFINITY, ln_pdf(-6.1));
489 test_exact(-5.0, f64::INFINITY, f64::NEG_INFINITY, ln_pdf(-10.0));
490 test_is_nan(f64::INFINITY, f64::INFINITY, ln_pdf(2.0));
491 test_is_nan(f64::NEG_INFINITY, f64::INFINITY, ln_pdf(-5.1));
492 }
493
494 #[test]
495 fn test_cdf() {
496 let cdf = |arg: f64| move |x: Laplace| x.cdf(arg);
497 let loc = 0.0f64;
498 let scale = 1.0f64;
499 let reltol = 1e-15f64;
500
501 let expected = 0.69673467014368328819810023250440977f64;
503 test_rel_close(loc, scale, expected, reltol, cdf(0.5));
504
505 let expected = 0.30326532985631671180189976749559023f64;
507 test_rel_close(loc, scale, expected, reltol, cdf(-0.5));
508
509 let expected = 1.8600379880104179814798479019315592e-44f64;
511 test_rel_close(loc, scale, expected, reltol, cdf(-100.0));
512 }
513
514 #[test]
515 fn test_sf() {
516 let sf = |arg: f64| move |x: Laplace| x.sf(arg);
517 let loc = 0.0f64;
518 let scale = 1.0f64;
519 let reltol = 1e-15f64;
520
521 let expected = 0.30326532985631671180189976749559022f64;
523 test_rel_close(loc, scale, expected, reltol, sf(0.5));
524
525 let expected = 0.69673467014368328819810023250440977f64;
527 test_rel_close(loc, scale, expected, reltol, sf(-0.5));
528
529 let expected = 1.86003798801041798147984790193155916e-44;
531 test_rel_close(loc, scale, expected, reltol, sf(100.0));
532 }
533
534 #[test]
535 fn test_inverse_cdf() {
536 let inverse_cdf = |arg: f64| move |x: Laplace| x.inverse_cdf(arg);
537 let loc = 0.0f64;
538 let scale = 1.0f64;
539 let reltol = 1e-15f64;
540
541 let expected = -22.3327037493805115307626824253854655f64;
543 test_rel_close(loc, scale, expected, reltol, inverse_cdf(1e-10));
544
545 let expected = -6.2146080984221917426367422425949161f64;
547 test_rel_close(loc, scale, expected, reltol, inverse_cdf(0.001));
548
549 let expected = 2.3025850929940456840179914546843642f64;
551 test_rel_close(loc, scale, expected, reltol, inverse_cdf(0.95));
552 }
553
554 #[cfg(feature = "rand")]
555 #[test]
556 fn test_sample() {
557 use ::rand::distributions::Distribution;
558 use ::rand::thread_rng;
559
560 let l = create_ok(0.1, 0.5);
561 l.sample(&mut thread_rng());
562 }
563
564 #[cfg(feature = "rand")]
565 #[test]
566 fn test_sample_distribution() {
567 use ::rand::distributions::Distribution;
568 use ::rand::rngs::StdRng;
569 use ::rand::SeedableRng;
570
571 let location = 0.0;
573 let scale = 1.0;
574 let n = create_ok(location, scale);
575 let trials = 10_000;
576 let tolerance = 250;
577
578 for seed in 0..10 {
579 let mut r: StdRng = SeedableRng::seed_from_u64(seed);
580
581 let result = (0..trials).map(|_| n.sample(&mut r)).fold(0, |sum, val| {
582 if val > 0.0 {
583 sum + 1
584 } else if val < 0.0 {
585 sum - 1
586 } else {
587 0
588 }
589 });
590 assert!(
591 result > -tolerance && result < tolerance,
592 "Balance is {result} for seed {seed}"
593 );
594 }
595 }
596}