Skip to main content

scirs2_stats/distributions/
exponential.rs

1//! Exponential distribution functions
2//!
3//! This module provides functionality for the Exponential distribution.
4
5use crate::error::{StatsError, StatsResult};
6use crate::error_messages::{helpers, validation};
7use crate::sampling::SampleableDistribution;
8use crate::traits::{ContinuousCDF, ContinuousDistribution, Distribution as ScirsDist};
9use scirs2_core::ndarray::Array1;
10use scirs2_core::numeric::{Float, NumCast};
11use scirs2_core::random::{Distribution, Exponential as RandExp};
12use std::fmt::Debug;
13
14/// Exponential distribution structure
15pub struct Exponential<F: Float> {
16    /// Rate parameter λ - inverse of scale
17    pub rate: F,
18    /// Scale parameter (θ = 1/λ)
19    pub scale: F,
20    /// Location parameter
21    pub loc: F,
22    /// Random number generator for this distribution
23    rand_distr: RandExp<f64>,
24}
25
26impl<F: Float + NumCast + Debug + std::fmt::Display> Exponential<F> {
27    /// Create a new exponential distribution with given **rate** and location parameters
28    ///
29    /// # Important: Rate vs Scale
30    ///
31    /// This function takes the **rate parameter λ**, NOT the scale parameter.
32    /// The relationship between rate and scale is:
33    /// - rate = λ = 1/scale
34    /// - scale = θ = 1/rate
35    ///
36    /// For an exponential distribution with rate λ:
37    /// - Mean = 1/λ = scale
38    /// - Variance = 1/λ² = scale²
39    ///
40    /// # Arguments
41    ///
42    /// * `rate` - Rate parameter λ > 0 (where λ = 1/scale)
43    /// * `loc` - Location parameter (default: 0)
44    ///
45    /// # Returns
46    ///
47    /// * A new Exponential distribution instance
48    ///
49    /// # Examples
50    ///
51    /// ```
52    /// use scirs2_stats::distributions::exponential::Exponential;
53    /// use scirs2_stats::traits::Distribution;
54    ///
55    /// // Create exponential with rate=0.5 (equivalent to scale=2.0)
56    /// let exp = Exponential::new(0.5f64, 0.0).expect("Operation failed");
57    /// assert!((exp.rate - 0.5).abs() < 1e-10);
58    /// assert!((exp.scale - 2.0).abs() < 1e-10);
59    /// assert!((exp.mean() - 2.0).abs() < 1e-10);  // mean = 1/rate = 2.0
60    ///
61    /// // If you want to specify scale directly, use from_scale() instead:
62    /// // let exp = Exponential::from_scale(2.0f64, 0.0).expect("Operation failed");
63    /// ```
64    pub fn new(rate: F, loc: F) -> StatsResult<Self> {
65        validation::ensure_positive(rate, "Rate parameter")?;
66
67        // Set scale = 1/rate
68        let scale = F::one() / rate;
69
70        // Convert to f64 for rand_distr
71        let rate_f64 = <f64 as NumCast>::from(rate).expect("Operation failed");
72
73        match RandExp::new(rate_f64) {
74            Ok(rand_distr) => Ok(Exponential {
75                rate,
76                scale,
77                loc,
78                rand_distr,
79            }),
80            Err(_) => Err(helpers::numerical_error(
81                "exponential distribution creation",
82            )),
83        }
84    }
85
86    /// Create a new exponential distribution with given **scale** and location parameters
87    ///
88    /// This is an alternative constructor that takes the **scale parameter θ** instead of rate.
89    /// Many users prefer this interface as scale directly represents the mean of the distribution.
90    ///
91    /// # Scale vs Rate
92    ///
93    /// - scale = θ = 1/λ = mean of the distribution
94    /// - rate = λ = 1/θ
95    ///
96    /// For an exponential distribution with scale θ:
97    /// - Mean = θ = scale
98    /// - Variance = θ² = scale²
99    ///
100    /// # Arguments
101    ///
102    /// * `scale` - Scale parameter θ > 0 (where θ = 1/rate = mean)
103    /// * `loc` - Location parameter (default: 0)
104    ///
105    /// # Returns
106    ///
107    /// * A new Exponential distribution instance
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use scirs2_stats::distributions::exponential::Exponential;
113    /// use scirs2_stats::traits::Distribution;
114    ///
115    /// // Create exponential with scale=2.0 (equivalent to rate=0.5)
116    /// let exp = Exponential::from_scale(2.0f64, 0.0).expect("Operation failed");
117    /// assert!((exp.scale - 2.0).abs() < 1e-10);
118    /// assert!((exp.rate - 0.5).abs() < 1e-10);
119    /// assert!((exp.mean() - 2.0).abs() < 1e-10);  // mean = scale = 2.0
120    ///
121    /// // This is equivalent to:
122    /// // let exp = Exponential::new(0.5f64, 0.0).expect("Operation failed");
123    /// ```
124    pub fn from_scale(scale: F, loc: F) -> StatsResult<Self> {
125        validation::ensure_positive(scale, "scale")?;
126
127        // Set rate = 1/scale
128        let rate = F::one() / scale;
129
130        // Convert to f64 for rand_distr
131        let rate_f64 = <f64 as NumCast>::from(rate).expect("Operation failed");
132
133        match RandExp::new(rate_f64) {
134            Ok(rand_distr) => Ok(Exponential {
135                rate,
136                scale,
137                loc,
138                rand_distr,
139            }),
140            Err(_) => Err(helpers::numerical_error(
141                "exponential distribution creation",
142            )),
143        }
144    }
145
146    /// Calculate the probability density function (PDF) at a given point
147    ///
148    /// # Arguments
149    ///
150    /// * `x` - The point at which to evaluate the PDF
151    ///
152    /// # Returns
153    ///
154    /// * The value of the PDF at the given point
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use scirs2_stats::distributions::exponential::Exponential;
160    ///
161    /// let exp = Exponential::new(1.0f64, 0.0).expect("Operation failed");
162    /// let pdf_at_one = exp.pdf(1.0);
163    /// assert!((pdf_at_one - 0.36787944).abs() < 1e-7);
164    /// ```
165    #[inline]
166    pub fn pdf(&self, x: F) -> F {
167        // Adjust for location
168        let x_adj = x - self.loc;
169
170        // If x is less than loc, PDF is 0
171        if x_adj < F::zero() {
172            return F::zero();
173        }
174
175        // PDF = λ * exp(-λ * x)
176        self.rate * (-self.rate * x_adj).exp()
177    }
178
179    /// Calculate the cumulative distribution function (CDF) at a given point
180    ///
181    /// # Arguments
182    ///
183    /// * `x` - The point at which to evaluate the CDF
184    ///
185    /// # Returns
186    ///
187    /// * The value of the CDF at the given point
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use scirs2_stats::distributions::exponential::Exponential;
193    ///
194    /// let exp = Exponential::new(1.0f64, 0.0).expect("Operation failed");
195    /// let cdf_at_one = exp.cdf(1.0);
196    /// assert!((cdf_at_one - 0.63212056).abs() < 1e-7);
197    /// ```
198    #[inline]
199    pub fn cdf(&self, x: F) -> F {
200        // Adjust for location
201        let x_adj = x - self.loc;
202
203        // If x is less than loc, CDF is 0
204        if x_adj <= F::zero() {
205            return F::zero();
206        }
207
208        // CDF = 1 - exp(-λ * x)
209        F::one() - (-self.rate * x_adj).exp()
210    }
211
212    /// Inverse of the cumulative distribution function (quantile function)
213    ///
214    /// # Arguments
215    ///
216    /// * `p` - Probability value (between 0 and 1)
217    ///
218    /// # Returns
219    ///
220    /// * The value x such that CDF(x) = p
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// use scirs2_stats::distributions::exponential::Exponential;
226    ///
227    /// let exp = Exponential::new(1.0f64, 0.0).expect("Operation failed");
228    /// let x = exp.ppf(0.5).expect("Operation failed");
229    /// assert!((x - 0.69314718).abs() < 1e-7);
230    /// ```
231    #[inline]
232    pub fn ppf(&self, p: F) -> StatsResult<F> {
233        if p < F::zero() || p > F::one() {
234            return Err(StatsError::DomainError(
235                "Probability must be between 0 and 1".to_string(),
236            ));
237        }
238
239        // Special cases
240        if p == F::zero() {
241            return Ok(self.loc);
242        }
243        if p == F::one() {
244            return Ok(F::infinity());
245        }
246
247        // For exponential distribution, the quantile function has a simple analytic form:
248        // x = -ln(1-p) / λ
249        let result = -((F::one() - p).ln()) / self.rate;
250        Ok(result + self.loc)
251    }
252
253    /// Calculate the mean of the distribution
254    ///
255    /// # Returns
256    ///
257    /// * The mean value
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use scirs2_stats::distributions::exponential::Exponential;
263    ///
264    /// let exp = Exponential::new(2.0f64, 1.0).expect("Operation failed");
265    /// assert_eq!(exp.mean(), 1.5); // loc + 1/rate = 1 + 1/2 = 1.5
266    /// ```
267    pub fn mean(&self) -> F {
268        self.loc + self.scale
269    }
270
271    /// Calculate the variance of the distribution
272    ///
273    /// # Returns
274    ///
275    /// * The variance value
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use scirs2_stats::distributions::exponential::Exponential;
281    ///
282    /// let exp = Exponential::new(2.0f64, 0.0).expect("Operation failed");
283    /// assert_eq!(exp.variance(), 0.25); // (1/rate)^2 = (1/2)^2 = 0.25
284    /// ```
285    pub fn variance(&self) -> F {
286        self.scale * self.scale
287    }
288
289    /// Generate random samples from the distribution as an Array1
290    ///
291    /// # Arguments
292    ///
293    /// * `size` - Number of samples to generate
294    ///
295    /// # Returns
296    ///
297    /// * Array1 of random samples
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use scirs2_stats::distributions::exponential::Exponential;
303    ///
304    /// let exp = Exponential::new(1.0f64, 0.0).expect("Operation failed");
305    /// let samples = exp.rvs(1000).expect("Operation failed");
306    /// assert_eq!(samples.len(), 1000);
307    /// ```
308    #[inline]
309    pub fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
310        let samples = self.rvs_vec(size)?;
311        Ok(Array1::from_vec(samples))
312    }
313
314    /// Generate random samples from the distribution as a Vec
315    ///
316    /// # Arguments
317    ///
318    /// * `size` - Number of samples to generate
319    ///
320    /// # Returns
321    ///
322    /// * Vector of random samples
323    ///
324    /// # Examples
325    ///
326    /// ```
327    /// use scirs2_stats::distributions::exponential::Exponential;
328    ///
329    /// let exp = Exponential::new(1.0f64, 0.0).expect("Operation failed");
330    /// let samples = exp.rvs_vec(1000).expect("Operation failed");
331    /// assert_eq!(samples.len(), 1000);
332    /// ```
333    pub fn rvs_vec(&self, size: usize) -> StatsResult<Vec<F>> {
334        let mut rng = scirs2_core::random::thread_rng();
335        let mut samples = Vec::with_capacity(size);
336
337        for _ in 0..size {
338            let sample = self.rand_distr.sample(&mut rng);
339            samples.push(F::from(sample).expect("Failed to convert to float") + self.loc);
340        }
341
342        Ok(samples)
343    }
344}
345
346/// Implementation of the Distribution trait for Exponential
347impl<F: Float + NumCast + Debug + std::fmt::Display> ScirsDist<F> for Exponential<F> {
348    fn mean(&self) -> F {
349        self.mean()
350    }
351
352    fn var(&self) -> F {
353        self.variance()
354    }
355
356    fn std(&self) -> F {
357        self.var().sqrt()
358    }
359
360    fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
361        self.rvs(size)
362    }
363
364    fn entropy(&self) -> F {
365        // Entropy of exponential distribution is 1 - ln(rate)
366        F::one() - self.rate.ln()
367    }
368}
369
370/// Implementation of the ContinuousDistribution trait for Exponential
371impl<F: Float + NumCast + Debug + std::fmt::Display> ContinuousDistribution<F> for Exponential<F> {
372    fn pdf(&self, x: F) -> F {
373        self.pdf(x)
374    }
375
376    fn cdf(&self, x: F) -> F {
377        self.cdf(x)
378    }
379
380    fn ppf(&self, p: F) -> StatsResult<F> {
381        self.ppf(p)
382    }
383}
384
385impl<F: Float + NumCast + Debug + std::fmt::Display> ContinuousCDF<F> for Exponential<F> {
386    // Default implementations from trait are sufficient
387}
388
389/// Implementation of SampleableDistribution for Exponential
390impl<F: Float + NumCast + Debug + std::fmt::Display> SampleableDistribution<F> for Exponential<F> {
391    fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
392        self.rvs_vec(size)
393    }
394}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399    use crate::traits::{ContinuousDistribution, Distribution as ScirsDist};
400    use approx::assert_relative_eq;
401
402    #[test]
403    fn test_exponential_creation() {
404        // Basic exponential distribution with rate=1
405        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
406        assert_eq!(exp.rate, 1.0);
407        assert_eq!(exp.scale, 1.0);
408        assert_eq!(exp.loc, 0.0);
409
410        // From scale parameter
411        let exp_scale = Exponential::from_scale(2.0, 0.0).expect("Operation failed");
412        assert_eq!(exp_scale.rate, 0.5);
413        assert_eq!(exp_scale.scale, 2.0);
414        assert_eq!(exp_scale.loc, 0.0);
415
416        // Custom exponential with location
417        let custom = Exponential::new(2.0, 1.0).expect("Operation failed");
418        assert_eq!(custom.rate, 2.0);
419        assert_eq!(custom.scale, 0.5);
420        assert_eq!(custom.loc, 1.0);
421
422        // Error cases
423        assert!(Exponential::<f64>::new(0.0, 0.0).is_err());
424        assert!(Exponential::<f64>::new(-1.0, 0.0).is_err());
425        assert!(Exponential::<f64>::from_scale(0.0, 0.0).is_err());
426        assert!(Exponential::<f64>::from_scale(-1.0, 0.0).is_err());
427    }
428
429    #[test]
430    fn test_exponential_pdf() {
431        // Standard exponential PDF values (rate=1)
432        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
433
434        // PDF at x = 0
435        let pdf_at_zero = exp.pdf(0.0);
436        assert_relative_eq!(pdf_at_zero, 1.0, epsilon = 1e-10);
437
438        // PDF at x = 1
439        let pdf_at_one = exp.pdf(1.0);
440        assert_relative_eq!(pdf_at_one, 0.36787944, epsilon = 1e-7);
441
442        // PDF at x = 2
443        let pdf_at_two = exp.pdf(2.0);
444        assert_relative_eq!(pdf_at_two, 0.13533528, epsilon = 1e-7);
445
446        // PDF at x < loc
447        assert_relative_eq!(exp.pdf(-1.0), 0.0, epsilon = 1e-10);
448
449        // Custom rate
450        let exp2 = Exponential::new(2.0, 0.0).expect("Operation failed");
451        assert_relative_eq!(exp2.pdf(0.0), 2.0, epsilon = 1e-10);
452        assert_relative_eq!(exp2.pdf(0.5), 0.73575888, epsilon = 1e-7);
453
454        // With location parameter
455        let shifted = Exponential::new(1.0, 1.0).expect("Operation failed");
456        assert_relative_eq!(shifted.pdf(0.5), 0.0, epsilon = 1e-10);
457        assert_relative_eq!(shifted.pdf(1.0), 1.0, epsilon = 1e-10);
458        assert_relative_eq!(shifted.pdf(2.0), 0.36787944, epsilon = 1e-7);
459    }
460
461    #[test]
462    fn test_exponential_cdf() {
463        // Standard exponential CDF values (rate=1)
464        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
465
466        // CDF at x = 0
467        let cdf_at_zero = exp.cdf(0.0);
468        assert_relative_eq!(cdf_at_zero, 0.0, epsilon = 1e-10);
469
470        // CDF at x = 1
471        let cdf_at_one = exp.cdf(1.0);
472        assert_relative_eq!(cdf_at_one, 0.63212056, epsilon = 1e-7);
473
474        // CDF at x = 2
475        let cdf_at_two = exp.cdf(2.0);
476        assert_relative_eq!(cdf_at_two, 0.86466472, epsilon = 1e-7);
477
478        // CDF at x < loc
479        assert_relative_eq!(exp.cdf(-1.0), 0.0, epsilon = 1e-10);
480
481        // Custom rate
482        let exp2 = Exponential::new(2.0, 0.0).expect("Operation failed");
483        assert_relative_eq!(exp2.cdf(0.5), 0.63212056, epsilon = 1e-7);
484        assert_relative_eq!(exp2.cdf(1.0), 0.86466472, epsilon = 1e-7);
485
486        // With location parameter
487        let shifted = Exponential::new(1.0, 1.0).expect("Operation failed");
488        assert_relative_eq!(shifted.cdf(0.5), 0.0, epsilon = 1e-10);
489        assert_relative_eq!(shifted.cdf(1.0), 0.0, epsilon = 1e-10);
490        assert_relative_eq!(shifted.cdf(2.0), 0.63212056, epsilon = 1e-7);
491    }
492
493    #[test]
494    fn test_exponential_ppf() {
495        // Standard exponential (rate=1)
496        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
497
498        // Median
499        let median = exp.ppf(0.5).expect("Operation failed");
500        assert_relative_eq!(median, 0.69314718, epsilon = 1e-7);
501
502        // 95th percentile
503        let p95 = exp.ppf(0.95).expect("Operation failed");
504        assert_relative_eq!(p95, 2.9957323, epsilon = 1e-7);
505
506        // With location parameter
507        let shifted = Exponential::new(1.0, 1.0).expect("Operation failed");
508        assert_relative_eq!(
509            shifted.ppf(0.5).expect("Operation failed"),
510            1.69314718,
511            epsilon = 1e-7
512        );
513
514        // Error cases
515        assert!(exp.ppf(-0.1).is_err());
516        assert!(exp.ppf(1.1).is_err());
517    }
518
519    #[test]
520    fn test_exponential_mean_variance() {
521        // Standard exponential (rate=1)
522        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
523        assert_relative_eq!(exp.mean(), 1.0, epsilon = 1e-10);
524        assert_relative_eq!(exp.variance(), 1.0, epsilon = 1e-10);
525
526        // Custom rate (rate=2)
527        let exp2 = Exponential::new(2.0, 0.0).expect("Operation failed");
528        assert_relative_eq!(exp2.mean(), 0.5, epsilon = 1e-10);
529        assert_relative_eq!(exp2.variance(), 0.25, epsilon = 1e-10);
530
531        // With location (rate=1, loc=1)
532        let shifted = Exponential::new(1.0, 1.0).expect("Operation failed");
533        assert_relative_eq!(shifted.mean(), 2.0, epsilon = 1e-10); // loc + 1/rate
534        assert_relative_eq!(shifted.variance(), 1.0, epsilon = 1e-10); // location doesn't affect variance
535    }
536
537    #[test]
538    fn test_exponential_rvs() {
539        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
540
541        // Generate samples using Vec method
542        let samples_vec = exp.rvs_vec(1000).expect("Operation failed");
543        assert_eq!(samples_vec.len(), 1000);
544
545        // Generate samples using Array1 method
546        let samples_array = exp.rvs(1000).expect("Operation failed");
547        assert_eq!(samples_array.len(), 1000);
548
549        // Basic statistical checks
550        let sum: f64 = samples_vec.iter().sum();
551        let mean = sum / 1000.0;
552
553        // Mean should be close to 1.0 for Exponential(1)
554        assert!((mean - 1.0).abs() < 0.1);
555
556        // Variance check
557        let variance: f64 = samples_vec
558            .iter()
559            .map(|&x| (x - mean) * (x - mean))
560            .sum::<f64>()
561            / 1000.0;
562
563        // Variance should also be close to 1.0
564        // Using a larger tolerance (0.3) for the statistical test since it can be affected by randomness
565        assert!((variance - 1.0).abs() < 0.3);
566
567        // Check all samples are non-negative
568        for &sample in &samples_vec {
569            assert!(sample >= 0.0);
570        }
571    }
572
573    #[test]
574    fn test_exponential_distribution_trait() {
575        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
576
577        // Test Distribution trait methods
578        assert_relative_eq!(exp.mean(), 1.0, epsilon = 1e-10);
579        assert_relative_eq!(exp.var(), 1.0, epsilon = 1e-10);
580        assert_relative_eq!(exp.std(), 1.0, epsilon = 1e-10);
581
582        // Check that rvs returns correct size and type
583        let samples = exp.rvs(100).expect("Operation failed");
584        assert_eq!(samples.len(), 100);
585
586        // Entropy should be 1.0 for standard exponential
587        assert_relative_eq!(exp.entropy(), 1.0, epsilon = 1e-10);
588
589        // Entropy for different rate
590        let exp2 = Exponential::new(2.0, 0.0).expect("Operation failed");
591        // Entropy = 1 - ln(rate) = 1 - ln(2) ≈ 0.3069
592        assert_relative_eq!(exp2.entropy(), 1.0 - 2.0f64.ln(), epsilon = 1e-10);
593    }
594
595    #[test]
596    fn test_exponential_continuous_distribution_trait() {
597        let exp = Exponential::new(1.0, 0.0).expect("Operation failed");
598
599        // Test as a ContinuousDistribution
600        let dist: &dyn ContinuousDistribution<f64> = &exp;
601
602        // Check PDF
603        assert_relative_eq!(dist.pdf(1.0), 0.36787944, epsilon = 1e-7);
604
605        // Check CDF
606        assert_relative_eq!(dist.cdf(1.0), 0.63212056, epsilon = 1e-7);
607
608        // Check PPF
609        assert_relative_eq!(
610            dist.ppf(0.5).expect("Operation failed"),
611            0.69314718,
612            epsilon = 1e-7
613        );
614
615        // Check derived methods using concrete type
616        assert_relative_eq!(exp.sf(1.0), 1.0 - 0.63212056, epsilon = 1e-7);
617
618        // Hazard function for exponential should be constant = rate
619        assert_relative_eq!(exp.hazard(1.0), 1.0, epsilon = 1e-7);
620
621        // Cumulative hazard function for exponential is just rate*x
622        assert_relative_eq!(exp.cumhazard(1.0), 1.0, epsilon = 1e-7);
623
624        // Inverse survival function should work
625        assert_relative_eq!(
626            exp.isf(0.5).expect("Operation failed"),
627            dist.ppf(0.5).expect("Operation failed"),
628            epsilon = 1e-7
629        );
630    }
631}