rs_stats/distributions/exponential_distribution.rs
1//! # Exponential Distribution
2//!
3//! This module implements the exponential distribution, a continuous probability distribution
4//! that models the time between events in a Poisson point process.
5//!
6//! ## Key Characteristics
7//! - Continuous probability distribution
8//! - Memoryless property (future states depend only on the present, not the past)
9//! - Used to model waiting times and inter-arrival times
10//!
11//! ## Common Applications
12//! - Time between arrivals in a Poisson process
13//! - Lifetime analysis (e.g., how long until a component fails)
14//! - Queue theory and service times
15//! - Radioactive decay
16//!
17//! ## Mathematical Formulation
18//! The probability density function (PDF) is given by:
19//!
20//! f(x; λ) = λe^(-λx) for x ≥ 0
21//!
22//! where:
23//! - λ (lambda) is the rate parameter (λ > 0)
24//! - x is the random variable (time or space)
25//!
26//! The cumulative distribution function (CDF) is:
27//!
28//! F(x; λ) = 1 - e^(-λx) for x ≥ 0
29
30use rand::Rng;
31use rand::distributions::Distribution;
32use rand_distr::Exp;
33use serde::{Deserialize, Serialize};
34
35/// Configuration for the Exponential distribution.
36///
37/// # Fields
38/// * `lambda` - The rate parameter (must be positive)
39///
40/// # Examples
41/// ```
42/// use rs_stats::distributions::exponential_distribution::ExponentialConfig;
43///
44/// let config = ExponentialConfig { lambda: 2.0 };
45/// assert!(config.lambda > 0.0);
46/// ```
47#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
48pub struct ExponentialConfig {
49 /// The rate parameter.
50 pub lambda: f64,
51}
52
53impl ExponentialConfig {
54 /// Creates a new ExponentialConfig with validation
55 ///
56 /// # Arguments
57 /// * `lambda` - The rate parameter (must be positive)
58 ///
59 /// # Returns
60 /// `Some(ExponentialConfig)` if parameter is valid, `None` otherwise
61 pub fn new(lambda: f64) -> Option<Self> {
62 if lambda > 0.0 {
63 Some(Self { lambda })
64 } else {
65 None
66 }
67 }
68}
69
70/// Probability density function (PDF) for the Exponential distribution.
71///
72/// Calculates the probability density at point `x` for an exponential distribution
73/// with rate parameter `lambda`.
74///
75/// # Arguments
76/// * `x` - The point at which to evaluate the PDF (must be non-negative)
77/// * `lambda` - The rate parameter (must be positive)
78///
79/// # Returns
80/// The probability density at point `x`.
81///
82/// # Panics
83/// Panics if:
84/// - `x` is negative
85/// - `lambda` is not positive
86///
87/// # Examples
88/// ```
89/// use rs_stats::distributions::exponential_distribution::exponential_pdf;
90///
91/// // Calculate PDF at x = 1.0 with rate parameter lambda = 2.0
92/// let pdf = exponential_pdf(1.0, 2.0);
93/// assert!((pdf - 0.27067).abs() < 1e-5);
94/// ```
95pub fn exponential_pdf(x: f64, lambda: f64) -> f64 {
96 assert!(x >= 0.0, "x must be non-negative");
97 assert!(lambda > 0.0, "lambda must be positive");
98
99 if x == 0.0 {
100 lambda
101 } else {
102 lambda * (-lambda * x).exp()
103 }
104}
105
106/// Cumulative distribution function (CDF) for the Exponential distribution.
107///
108/// Calculates the probability of a random variable being less than or equal to `x`
109/// for an exponential distribution with rate parameter `lambda`.
110///
111/// # Arguments
112/// * `x` - The point at which to evaluate the CDF (must be non-negative)
113/// * `lambda` - The rate parameter (must be positive)
114///
115/// # Returns
116/// The cumulative probability at point `x`.
117///
118/// # Panics
119/// Panics if:
120/// - `x` is negative
121/// - `lambda` is not positive
122///
123/// # Examples
124/// ```
125/// use rs_stats::distributions::exponential_distribution::exponential_cdf;
126///
127/// // Calculate CDF at x = 1.0 with rate parameter lambda = 2.0
128/// let cdf = exponential_cdf(1.0, 2.0);
129/// assert!((cdf - 0.86466).abs() < 1e-5);
130/// ```
131pub fn exponential_cdf(x: f64, lambda: f64) -> f64 {
132 assert!(x >= 0.0, "x must be non-negative");
133 assert!(lambda > 0.0, "lambda must be positive");
134
135 1.0 - (-lambda * x).exp()
136}
137
138/// Inverse cumulative distribution function for the Exponential distribution.
139///
140/// Calculates the value of `x` for which the CDF equals the given probability `p`.
141///
142/// # Arguments
143/// * `p` - The probability (must be between 0 and 1)
144/// * `lambda` - The rate parameter (must be positive)
145///
146/// # Returns
147/// The value `x` such that P(X ≤ x) = p.
148///
149/// # Panics
150/// Panics if:
151/// - `p` is not between 0 and 1
152/// - `lambda` is not positive
153///
154/// # Examples
155/// ```
156/// use rs_stats::distributions::exponential_distribution::{exponential_inverse_cdf, exponential_cdf};
157///
158/// // Calculate inverse CDF for p = 0.5 with rate parameter lambda = 2.0
159/// let x = exponential_inverse_cdf(0.5, 2.0);
160///
161/// // Verify that CDF(x) is approximately p
162/// let p = exponential_cdf(x, 2.0);
163/// assert!((p - 0.5).abs() < 1e-10);
164/// ```
165pub fn exponential_inverse_cdf(p: f64, lambda: f64) -> f64 {
166 assert!((0.0..=1.0).contains(&p), "p must be between 0 and 1");
167 assert!(lambda > 0.0, "lambda must be positive");
168
169 -((1.0 - p).ln()) / lambda
170}
171
172/// Mean (expected value) of the Exponential distribution.
173///
174/// Calculates the mean of an exponential distribution with rate parameter `lambda`.
175///
176/// # Arguments
177/// * `lambda` - The rate parameter (must be positive)
178///
179/// # Returns
180/// The mean of the distribution.
181///
182/// # Panics
183/// Panics if `lambda` is not positive
184///
185/// # Examples
186/// ```
187/// use rs_stats::distributions::exponential_distribution::exponential_mean;
188///
189/// // Mean of exponential distribution with rate parameter lambda = 2.0
190/// let mean = exponential_mean(2.0);
191/// assert!((mean - 0.5).abs() < 1e-10);
192/// ```
193pub fn exponential_mean(lambda: f64) -> f64 {
194 assert!(lambda > 0.0, "lambda must be positive");
195
196 1.0 / lambda
197}
198
199/// Variance of the Exponential distribution.
200///
201/// Calculates the variance of an exponential distribution with rate parameter `lambda`.
202///
203/// # Arguments
204/// * `lambda` - The rate parameter (must be positive)
205///
206/// # Returns
207/// The variance of the distribution.
208///
209/// # Panics
210/// Panics if `lambda` is not positive
211///
212/// # Examples
213/// ```
214/// use rs_stats::distributions::exponential_distribution::exponential_variance;
215///
216/// // Variance of exponential distribution with rate parameter lambda = 2.0
217/// let variance = exponential_variance(2.0);
218/// assert!((variance - 0.25).abs() < 1e-10);
219/// ```
220pub fn exponential_variance(lambda: f64) -> f64 {
221 assert!(lambda > 0.0, "lambda must be positive");
222
223 1.0 / (lambda * lambda)
224}
225
226/// Generate a random sample from an Exponential distribution.
227///
228/// # Arguments
229/// * `lambda` - The rate parameter (must be positive)
230/// * `rng` - A random number generator
231///
232/// # Returns
233/// A random value from the exponential distribution.
234///
235/// # Panics
236/// Panics if `lambda` is not positive
237///
238/// # Examples
239/// ```
240/// use rs_stats::distributions::exponential_distribution::exponential_sample;
241/// use rand::thread_rng;
242///
243/// let mut rng = thread_rng();
244/// let sample = exponential_sample(2.0, &mut rng);
245/// assert!(sample >= 0.0); // Exponential distribution is always non-negative
246/// ```
247pub fn exponential_sample<R: Rng + ?Sized>(lambda: f64, rng: &mut R) -> f64 {
248 assert!(lambda > 0.0, "lambda must be positive");
249
250 let exp = Exp::new(lambda).unwrap();
251 exp.sample(rng)
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use rand::thread_rng;
258
259 const EPSILON: f64 = 1e-10;
260
261 #[test]
262 fn test_exponential_pdf() {
263 let lambda = 2.0;
264
265 // PDF at x = 0
266 let result = exponential_pdf(0.0, lambda);
267 assert_eq!(result, lambda);
268
269 // PDF at x = 1
270 let result = exponential_pdf(1.0, lambda);
271 let expected = lambda * (-lambda).exp();
272 assert!((result - expected).abs() < EPSILON);
273
274 // PDF at x = 0.5
275 let result = exponential_pdf(0.5, lambda);
276 let expected = lambda * (-lambda * 0.5).exp();
277 assert!((result - expected).abs() < EPSILON);
278 }
279
280 #[test]
281 fn test_exponential_cdf() {
282 let lambda = 2.0;
283
284 // CDF at x = 0
285 let result = exponential_cdf(0.0, lambda);
286 assert!((result - 0.0).abs() < EPSILON);
287
288 // CDF at x = 1
289 let result = exponential_cdf(1.0, lambda);
290 let expected = 1.0 - (-lambda).exp();
291 assert!((result - expected).abs() < EPSILON);
292
293 // CDF at x = 0.5
294 let result = exponential_cdf(0.5, lambda);
295 let expected = 1.0 - (-lambda * 0.5).exp();
296 assert!((result - expected).abs() < EPSILON);
297 }
298
299 #[test]
300 fn test_exponential_inverse_cdf() {
301 let lambda = 2.0;
302
303 // Test inverse CDF with various probabilities
304 let test_cases = vec![0.1, 0.25, 0.5, 0.75, 0.9];
305
306 for p in test_cases {
307 let x = exponential_inverse_cdf(p, lambda);
308 let cdf = exponential_cdf(x, lambda);
309 assert!(
310 (cdf - p).abs() < EPSILON,
311 "Inverse CDF failed for p = {}: got {}, expected {}",
312 p,
313 cdf,
314 p
315 );
316 }
317 }
318
319 #[test]
320 fn test_exponential_mean() {
321 let lambda = 2.0;
322 let result = exponential_mean(lambda);
323 let expected = 1.0 / lambda;
324 assert!((result - expected).abs() < EPSILON);
325 }
326
327 #[test]
328 fn test_exponential_variance() {
329 let lambda = 2.0;
330 let result = exponential_variance(lambda);
331 let expected = 1.0 / (lambda * lambda);
332 assert!((result - expected).abs() < EPSILON);
333 }
334
335 #[test]
336 fn test_exponential_sample() {
337 let lambda = 2.0;
338 let mut rng = thread_rng();
339
340 // Test that samples are non-negative
341 for _ in 0..100 {
342 let sample = exponential_sample(lambda, &mut rng);
343 assert!(sample >= 0.0);
344 }
345
346 // Test that mean of samples is close to theoretical mean
347 // This is a statistical test, so we allow some margin of error
348 let num_samples = 10000;
349 let mut sum = 0.0;
350
351 for _ in 0..num_samples {
352 sum += exponential_sample(lambda, &mut rng);
353 }
354
355 let sample_mean = sum / (num_samples as f64);
356 let theoretical_mean = exponential_mean(lambda);
357
358 // Allow a 10% margin of error for the mean
359 assert!(
360 (sample_mean - theoretical_mean).abs() < theoretical_mean * 0.1,
361 "Sample mean {} is too far from theoretical mean {}",
362 sample_mean,
363 theoretical_mean
364 );
365 }
366
367 #[test]
368 #[should_panic(expected = "lambda must be positive")]
369 fn test_exponential_pdf_invalid_lambda() {
370 exponential_pdf(1.0, -2.0);
371 }
372
373 #[test]
374 #[should_panic(expected = "x must be non-negative")]
375 fn test_exponential_pdf_invalid_x() {
376 exponential_pdf(-1.0, 2.0);
377 }
378
379 #[test]
380 fn test_exponential_config() {
381 // Valid config
382 let config = ExponentialConfig::new(2.0);
383 assert!(config.is_some());
384
385 // Invalid config
386 let config = ExponentialConfig::new(0.0);
387 assert!(config.is_none());
388
389 let config = ExponentialConfig::new(-1.0);
390 assert!(config.is_none());
391 }
392}