statrs/distribution/
dirac.rs

1use crate::distribution::ContinuousCDF;
2use crate::statistics::*;
3
4/// Implements the [Dirac Delta](https://en.wikipedia.org/wiki/Dirac_delta_function#As_a_distribution)
5/// distribution
6///
7/// # Examples
8///
9/// ```
10/// use statrs::distribution::{Dirac, Continuous};
11/// use statrs::statistics::Distribution;
12///
13/// let n = Dirac::new(3.0).unwrap();
14/// assert_eq!(n.mean().unwrap(), 3.0);
15/// ```
16#[derive(Debug, Copy, Clone, PartialEq)]
17pub struct Dirac(f64);
18
19/// Represents the errors that can occur when creating a [`Dirac`].
20#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
21#[non_exhaustive]
22pub enum DiracError {
23    /// The value v is NaN.
24    ValueInvalid,
25}
26
27impl std::fmt::Display for DiracError {
28    #[cfg_attr(coverage_nightly, coverage(off))]
29    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
30        match self {
31            DiracError::ValueInvalid => write!(f, "Value v is NaN"),
32        }
33    }
34}
35
36impl std::error::Error for DiracError {}
37
38impl Dirac {
39    /// Constructs a new dirac distribution function at value `v`.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if `v` is not-a-number.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use statrs::distribution::Dirac;
49    ///
50    /// let mut result = Dirac::new(0.0);
51    /// assert!(result.is_ok());
52    ///
53    /// result = Dirac::new(f64::NAN);
54    /// assert!(result.is_err());
55    /// ```
56    pub fn new(v: f64) -> Result<Self, DiracError> {
57        if v.is_nan() {
58            Err(DiracError::ValueInvalid)
59        } else {
60            Ok(Dirac(v))
61        }
62    }
63}
64
65impl std::fmt::Display for Dirac {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "δ_{}", self.0)
68    }
69}
70
71#[cfg(feature = "rand")]
72#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
73impl ::rand::distributions::Distribution<f64> for Dirac {
74    fn sample<R: ::rand::Rng + ?Sized>(&self, _: &mut R) -> f64 {
75        self.0
76    }
77}
78
79impl ContinuousCDF<f64, f64> for Dirac {
80    /// Calculates the cumulative distribution function for the
81    /// dirac distribution at `x`
82    ///
83    /// Where the value is 1 if x > `v`, 0 otherwise.
84    fn cdf(&self, x: f64) -> f64 {
85        if x < self.0 {
86            0.0
87        } else {
88            1.0
89        }
90    }
91
92    /// Calculates the survival function for the
93    /// dirac distribution at `x`
94    ///
95    /// Where the value is 0 if x > `v`, 1 otherwise.
96    fn sf(&self, x: f64) -> f64 {
97        if x < self.0 {
98            1.0
99        } else {
100            0.0
101        }
102    }
103}
104
105impl Min<f64> for Dirac {
106    /// Returns the minimum value in the domain of the
107    /// dirac distribution representable by a double precision float
108    ///
109    /// # Formula
110    ///
111    /// ```text
112    /// v
113    /// ```
114    fn min(&self) -> f64 {
115        self.0
116    }
117}
118
119impl Max<f64> for Dirac {
120    /// Returns the maximum value in the domain of the
121    /// dirac distribution representable by a double precision float
122    ///
123    /// # Formula
124    ///
125    /// ```text
126    /// v
127    /// ```
128    fn max(&self) -> f64 {
129        self.0
130    }
131}
132
133impl Distribution<f64> for Dirac {
134    /// Returns the mean of the dirac distribution
135    ///
136    /// # Remarks
137    ///
138    /// Since the only value that can be produced by this distribution is `v` with probability
139    /// 1, it is just `v`.
140    fn mean(&self) -> Option<f64> {
141        Some(self.0)
142    }
143
144    /// Returns the variance of the dirac distribution
145    ///
146    /// # Formula
147    ///
148    /// ```text
149    /// 0
150    /// ```
151    ///
152    /// Since only one value can be produced there is no variance.
153    fn variance(&self) -> Option<f64> {
154        Some(0.0)
155    }
156
157    /// Returns the entropy of the dirac distribution
158    ///
159    /// # Formula
160    ///
161    /// ```text
162    /// 0
163    /// ```
164    ///
165    /// Since this distribution has full certainty, it encodes no information
166    fn entropy(&self) -> Option<f64> {
167        Some(0.0)
168    }
169
170    /// Returns the skewness of the dirac distribution
171    ///
172    /// # Formula
173    ///
174    /// ```text
175    /// 0
176    /// ```
177    fn skewness(&self) -> Option<f64> {
178        Some(0.0)
179    }
180}
181
182impl Median<f64> for Dirac {
183    /// Returns the median of the dirac distribution
184    ///
185    /// # Formula
186    ///
187    /// ```text
188    /// v
189    /// ```
190    ///
191    /// where `v` is the point of the dirac distribution
192    fn median(&self) -> f64 {
193        self.0
194    }
195}
196
197impl Mode<Option<f64>> for Dirac {
198    /// Returns the mode of the dirac distribution
199    ///
200    /// # Formula
201    ///
202    /// ```text
203    /// v
204    /// ```
205    ///
206    /// where `v` is the point of the dirac distribution
207    fn mode(&self) -> Option<f64> {
208        Some(self.0)
209    }
210}
211
212#[rustfmt::skip]
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::testing_boiler;
217
218    testing_boiler!(v: f64; Dirac; DiracError);
219
220    #[test]
221    fn test_create() {
222        create_ok(10.0);
223        create_ok(-5.0);
224        create_ok(10.0);
225        create_ok(100.0);
226        create_ok(f64::INFINITY);
227    }
228
229    #[test]
230    fn test_bad_create() {
231        create_err(f64::NAN);
232    }
233
234    #[test]
235    fn test_variance() {
236        let variance = |x: Dirac| x.variance().unwrap();
237        test_exact(0.0, 0.0, variance);
238        test_exact(-5.0, 0.0, variance);
239        test_exact(f64::INFINITY, 0.0, variance);
240    }
241
242    #[test]
243    fn test_entropy() {
244        let entropy = |x: Dirac| x.entropy().unwrap();
245        test_exact(0.0, 0.0, entropy);
246        test_exact(f64::INFINITY, 0.0, entropy);
247    }
248
249    #[test]
250    fn test_skewness() {
251        let skewness = |x: Dirac| x.skewness().unwrap();
252        test_exact(0.0, 0.0, skewness);
253        test_exact(4.0, 0.0, skewness);
254        test_exact(0.3, 0.0, skewness);
255        test_exact(f64::INFINITY, 0.0, skewness);
256    }
257
258    #[test]
259    fn test_mode() {
260        let mode = |x: Dirac| x.mode().unwrap();
261        test_exact(0.0, 0.0, mode);
262        test_exact(3.0, 3.0, mode);
263        test_exact(f64::INFINITY, f64::INFINITY, mode);
264    }
265
266    #[test]
267    fn test_median() {
268        let median = |x: Dirac| x.median();
269        test_exact(0.0, 0.0, median);
270        test_exact(3.0, 3.0, median);
271        test_exact(f64::INFINITY, f64::INFINITY, median);
272    }
273
274    #[test]
275    fn test_min_max() {
276        let min = |x: Dirac| x.min();
277        let max = |x: Dirac| x.max();
278        test_exact(0.0, 0.0, min);
279        test_exact(3.0, 3.0, min);
280        test_exact(f64::INFINITY, f64::INFINITY, min);
281
282        test_exact(0.0, 0.0, max);
283        test_exact(3.0, 3.0, max);
284        test_exact(f64::NEG_INFINITY, f64::NEG_INFINITY, max);
285    }
286
287    #[test]
288    fn test_cdf() {
289        let cdf = |arg: f64| move |x: Dirac| x.cdf(arg);
290        test_exact(0.0, 1.0, cdf(0.0));
291        test_exact(3.0, 1.0, cdf(3.0));
292        test_exact(f64::INFINITY, 0.0, cdf(1.0));
293        test_exact(f64::INFINITY, 1.0, cdf(f64::INFINITY));
294    }
295
296    #[test]
297    fn test_sf() {
298        let sf = |arg: f64| move |x: Dirac| x.sf(arg);
299        test_exact(0.0, 0.0, sf(0.0));
300        test_exact(3.0, 0.0, sf(3.0));
301        test_exact(f64::INFINITY, 1.0, sf(1.0));
302        test_exact(f64::INFINITY, 0.0, sf(f64::INFINITY));
303    }
304}