rs_stats/distributions/
exponential_distribution.rs1use crate::error::{StatsError, StatsResult};
31use num_traits::ToPrimitive;
32use serde::{Deserialize, Serialize};
33
34#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
47pub struct ExponentialConfig<T>
48where
49 T: ToPrimitive,
50{
51 pub lambda: T,
53}
54
55impl<T> ExponentialConfig<T>
56where
57 T: ToPrimitive,
58{
59 pub fn new(lambda: T) -> StatsResult<Self> {
67 let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
68 message: "ExponentialConfig::new: Failed to convert lambda to f64".to_string(),
69 })?;
70
71 if lambda_64 > 0.0 {
72 Ok(Self { lambda })
73 } else {
74 Err(StatsError::InvalidInput {
75 message: "ExponentialConfig::new: lambda must be positive".to_string(),
76 })
77 }
78 }
79}
80
81#[inline]
108pub fn exponential_pdf<T>(x: T, lambda: T) -> StatsResult<f64>
109where
110 T: ToPrimitive,
111{
112 let x_64 = x.to_f64().ok_or_else(|| StatsError::ConversionError {
113 message: "exponential_pdf: Failed to convert x to f64".to_string(),
114 })?;
115
116 if x_64 < 0.0 {
117 return Err(StatsError::InvalidInput {
118 message: "exponential_pdf: x must be non-negative".to_string(),
119 });
120 }
121
122 let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
123 message: "exponential_pdf: Failed to convert lambda to f64".to_string(),
124 })?;
125
126 if lambda_64 <= 0.0 {
127 return Err(StatsError::InvalidInput {
128 message: "exponential_pdf: lambda must be positive".to_string(),
129 });
130 }
131
132 Ok(if x_64 == 0.0 {
133 lambda_64
134 } else {
135 lambda_64 * (-lambda_64 * x_64).exp()
136 })
137}
138
139#[inline]
166pub fn exponential_cdf<T>(x: T, lambda: T) -> StatsResult<f64>
167where
168 T: ToPrimitive,
169{
170 let x_64 = x.to_f64().ok_or_else(|| StatsError::ConversionError {
171 message: "exponential_cdf: Failed to convert x to f64".to_string(),
172 })?;
173
174 if x_64 < 0.0 {
175 return Err(StatsError::InvalidInput {
176 message: "exponential_cdf: x must be non-negative".to_string(),
177 });
178 }
179
180 let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
181 message: "exponential_cdf: Failed to convert lambda to f64".to_string(),
182 })?;
183
184 if lambda_64 <= 0.0 {
185 return Err(StatsError::InvalidInput {
186 message: "exponential_cdf: lambda must be positive".to_string(),
187 });
188 }
189 Ok(1.0 - (-lambda_64 * x_64).exp())
190}
191
192#[inline]
221pub fn exponential_inverse_cdf<T>(p: T, lambda: T) -> StatsResult<f64>
222where
223 T: ToPrimitive,
224{
225 let p_64 = p.to_f64().ok_or_else(|| StatsError::ConversionError {
226 message: "exponential_inverse_cdf: Failed to convert p to f64".to_string(),
227 })?;
228
229 if !(0.0..=1.0).contains(&p_64) {
230 return Err(StatsError::InvalidInput {
231 message: "exponential_inverse_cdf: p must be between 0 and 1".to_string(),
232 });
233 }
234
235 let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
236 message: "exponential_inverse_cdf: Failed to convert lambda to f64".to_string(),
237 })?;
238
239 if lambda_64 <= 0.0 {
240 return Err(StatsError::InvalidInput {
241 message: "exponential_inverse_cdf: lambda must be positive".to_string(),
242 });
243 }
244 Ok(-((1.0 - p_64).ln()) / lambda_64)
245}
246
247#[inline]
271pub fn exponential_mean<T>(lambda: T) -> StatsResult<f64>
272where
273 T: ToPrimitive,
274{
275 let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
276 message: "exponential_mean: Failed to convert lambda to f64".to_string(),
277 })?;
278 if lambda_64 <= 0.0 {
279 return Err(StatsError::InvalidInput {
280 message: "exponential_mean: lambda must be positive".to_string(),
281 });
282 }
283
284 Ok(1.0 / lambda_64)
285}
286
287#[inline]
311pub fn exponential_variance(lambda: f64) -> StatsResult<f64> {
312 if lambda <= 0.0 {
313 return Err(StatsError::InvalidInput {
314 message: "exponential_variance: lambda must be positive".to_string(),
315 });
316 }
317
318 Ok(1.0 / (lambda * lambda))
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 const EPSILON: f64 = 1e-10;
326
327 #[test]
328 fn test_exponential_pdf() {
329 let lambda = 2.0;
330
331 let result = exponential_pdf(0.0, lambda).unwrap();
333 assert_eq!(result, lambda);
334
335 let result = exponential_pdf(1.0, lambda).unwrap();
337 let expected = lambda * (-lambda).exp();
338 assert!((result - expected).abs() < EPSILON);
339
340 let result = exponential_pdf(0.5, lambda).unwrap();
342 let expected = lambda * (-lambda * 0.5).exp();
343 assert!((result - expected).abs() < EPSILON);
344 }
345
346 #[test]
347 fn test_exponential_cdf() {
348 let lambda = 2.0_f64;
349
350 let result = exponential_cdf(0.0, lambda).unwrap();
352 assert!((result - 0.0).abs() < EPSILON);
353
354 let result = exponential_cdf(1.0, lambda).unwrap();
356 let expected = 1.0 - (-lambda).exp();
357 assert!((result - expected).abs() < EPSILON);
358
359 let result = exponential_cdf(0.5, lambda).unwrap();
361 let expected = 1.0 - (-lambda * 0.5).exp();
362 assert!((result - expected).abs() < EPSILON);
363 }
364
365 #[test]
366 fn test_exponential_inverse_cdf() {
367 let lambda = 2.0_f64;
368
369 let test_cases = vec![0.1, 0.25, 0.5, 0.75, 0.9];
371
372 for p in test_cases {
373 let x = exponential_inverse_cdf(p, lambda).unwrap();
374 let cdf = exponential_cdf(x, lambda).unwrap();
375 assert!(
376 (cdf - p).abs() < EPSILON,
377 "Inverse CDF failed for p = {}: got {}, expected {}",
378 p,
379 cdf,
380 p
381 );
382 }
383 }
384
385 #[test]
386 fn test_exponential_mean() {
387 let lambda = 2.0;
388 let result = exponential_mean(lambda).unwrap();
389 let expected = 1.0 / lambda;
390 assert!((result - expected).abs() < EPSILON);
391 }
392
393 #[test]
394 fn test_exponential_variance() {
395 let lambda = 2.0;
396 let result = exponential_variance(lambda).unwrap();
397 let expected = 1.0 / (lambda * lambda);
398 assert!((result - expected).abs() < EPSILON);
399 }
400
401 #[test]
402 fn test_exponential_pdf_invalid_lambda() {
403 let result = exponential_pdf(1.0, -2.0);
404 assert!(result.is_err());
405 match result {
406 Err(StatsError::InvalidInput { message }) => {
407 assert!(message.contains("lambda must be positive"));
408 }
409 _ => panic!("Expected InvalidInput error"),
410 }
411 }
412
413 #[test]
414 fn test_exponential_pdf_invalid_x() {
415 let result = exponential_pdf(-1.0, 2.0);
416 assert!(result.is_err());
417 match result {
418 Err(StatsError::InvalidInput { message }) => {
419 assert!(message.contains("x must be non-negative"));
420 }
421 _ => panic!("Expected InvalidInput error"),
422 }
423 }
424
425 #[test]
426 fn test_exponential_config() {
427 let config = ExponentialConfig::new(2.0);
429 assert!(config.is_ok());
430
431 let config = ExponentialConfig::new(0.0);
433 assert!(config.is_err());
434
435 let config = ExponentialConfig::new(-1.0);
436 assert!(config.is_err());
437 }
438
439 #[test]
440 fn test_exponential_inverse_cdf_p_negative() {
441 let result = exponential_inverse_cdf(-0.1, 2.0);
442 assert!(result.is_err());
443 assert!(matches!(
444 result.unwrap_err(),
445 StatsError::InvalidInput { .. }
446 ));
447 }
448
449 #[test]
450 fn test_exponential_inverse_cdf_p_greater_than_one() {
451 let result = exponential_inverse_cdf(1.5, 2.0);
452 assert!(result.is_err());
453 assert!(matches!(
454 result.unwrap_err(),
455 StatsError::InvalidInput { .. }
456 ));
457 }
458
459 #[test]
460 fn test_exponential_cdf_invalid_lambda() {
461 let result = exponential_cdf(1.0, -2.0);
462 assert!(result.is_err());
463 assert!(matches!(
464 result.unwrap_err(),
465 StatsError::InvalidInput { .. }
466 ));
467 }
468
469 #[test]
470 fn test_exponential_cdf_invalid_x() {
471 let result = exponential_cdf(-1.0, 2.0);
472 assert!(result.is_err());
473 assert!(matches!(
474 result.unwrap_err(),
475 StatsError::InvalidInput { .. }
476 ));
477 }
478
479 #[test]
480 fn test_exponential_mean_invalid_lambda() {
481 let result = exponential_mean(0.0);
482 assert!(result.is_err());
483 assert!(matches!(
484 result.unwrap_err(),
485 StatsError::InvalidInput { .. }
486 ));
487 }
488
489 #[test]
490 fn test_exponential_variance_invalid_lambda() {
491 let result = exponential_variance(0.0);
492 assert!(result.is_err());
493 assert!(matches!(
494 result.unwrap_err(),
495 StatsError::InvalidInput { .. }
496 ));
497 }
498
499 #[test]
500 fn test_exponential_pdf_x_positive() {
501 let result = exponential_pdf(0.5, 2.0).unwrap();
503 let lambda: f64 = 2.0;
504 let x: f64 = 0.5;
505 let expected = lambda * (-lambda * x).exp();
506 assert!((result - expected).abs() < EPSILON);
507 }
508
509 #[test]
510 fn test_exponential_inverse_cdf_p_zero() {
511 let result = exponential_inverse_cdf(0.0, 2.0).unwrap();
513 assert_eq!(result, 0.0);
514 }
515
516 #[test]
517 fn test_exponential_inverse_cdf_p_one() {
518 let result = exponential_inverse_cdf(1.0, 2.0).unwrap();
521 assert!(result.is_infinite() || result > 1e10);
522 }
523
524 #[test]
525 fn test_exponential_inverse_cdf_lambda_zero() {
526 let result = exponential_inverse_cdf(0.5, 0.0);
527 assert!(result.is_err());
528 assert!(matches!(
529 result.unwrap_err(),
530 StatsError::InvalidInput { .. }
531 ));
532 }
533
534 #[test]
535 fn test_exponential_inverse_cdf_lambda_negative() {
536 let result = exponential_inverse_cdf(0.5, -1.0);
537 assert!(result.is_err());
538 assert!(matches!(
539 result.unwrap_err(),
540 StatsError::InvalidInput { .. }
541 ));
542 }
543}