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}