rs_stats/distributions/traits.rs
1//! # Distribution Traits
2//!
3//! Defines the [`Distribution`] and [`DiscreteDistribution`] traits that provide
4//! a unified interface for all statistical distributions in this crate.
5//!
6//! ## Usage
7//!
8//! ```
9//! use rs_stats::distributions::traits::Distribution;
10//! use rs_stats::distributions::normal_distribution::Normal;
11//!
12//! let n = Normal::new(0.0, 1.0).unwrap();
13//! let pdf = n.pdf(0.0).unwrap();
14//! assert!((pdf - 0.398_942_280_4).abs() < 1e-8);
15//! ```
16
17use crate::error::StatsResult;
18
19// ── Continuous distributions ───────────────────────────────────────────────────
20
21/// Unified interface for continuous probability distributions.
22///
23/// All methods return `StatsResult` to propagate domain errors (e.g. `p ∉ [0,1]`).
24///
25/// The trait is **object-safe**: `Box<dyn Distribution>` works at runtime.
26/// The `fit` associated function is intentionally *not* part of the trait to preserve
27/// object safety; each concrete type exposes `Dist::fit(data)` directly.
28pub trait Distribution {
29 /// Human-readable distribution name, e.g. `"Normal"`.
30 fn name(&self) -> &str;
31
32 /// Number of free parameters (used when computing AIC / BIC).
33 fn num_params(&self) -> usize;
34
35 /// Probability density function f(x).
36 fn pdf(&self, x: f64) -> StatsResult<f64>;
37
38 /// Natural logarithm of the PDF: ln f(x).
39 ///
40 /// Default implementation delegates to `pdf`; override for numerical stability.
41 fn logpdf(&self, x: f64) -> StatsResult<f64> {
42 self.pdf(x).map(|p| p.ln())
43 }
44
45 /// Cumulative distribution function F(x) = P(X ≤ x).
46 fn cdf(&self, x: f64) -> StatsResult<f64>;
47
48 /// Quantile (inverse CDF): find x such that F(x) = p.
49 fn inverse_cdf(&self, p: f64) -> StatsResult<f64>;
50
51 /// Mean (expected value) μ.
52 fn mean(&self) -> f64;
53
54 /// Variance σ².
55 fn variance(&self) -> f64;
56
57 /// Standard deviation σ = √(variance).
58 fn std_dev(&self) -> f64 {
59 self.variance().sqrt()
60 }
61
62 /// Sum of log-likelihoods: Σ ln f(xᵢ).
63 fn log_likelihood(&self, data: &[f64]) -> StatsResult<f64> {
64 let mut ll = 0.0_f64;
65 for &x in data {
66 ll += self.logpdf(x)?;
67 }
68 Ok(ll)
69 }
70
71 /// Akaike Information Criterion: AIC = 2k − 2·ln(L̂).
72 fn aic(&self, data: &[f64]) -> StatsResult<f64> {
73 let ll = self.log_likelihood(data)?;
74 Ok(2.0 * self.num_params() as f64 - 2.0 * ll)
75 }
76
77 /// Bayesian Information Criterion: BIC = k·ln(n) − 2·ln(L̂).
78 fn bic(&self, data: &[f64]) -> StatsResult<f64> {
79 let ll = self.log_likelihood(data)?;
80 let n = data.len() as f64;
81 Ok(self.num_params() as f64 * n.ln() - 2.0 * ll)
82 }
83}
84
85// ── Discrete distributions ─────────────────────────────────────────────────────
86
87/// Unified interface for discrete probability distributions.
88///
89/// Works with non-negative integer observations represented as `u64`.
90///
91/// Object-safe: `Box<dyn DiscreteDistribution>` is valid.
92pub trait DiscreteDistribution {
93 /// Human-readable distribution name.
94 fn name(&self) -> &str;
95
96 /// Number of free parameters (used for AIC / BIC).
97 fn num_params(&self) -> usize;
98
99 /// Probability mass function P(X = k).
100 fn pmf(&self, k: u64) -> StatsResult<f64>;
101
102 /// Natural logarithm of the PMF: ln P(X = k).
103 ///
104 /// Default: delegates to `pmf`; override for stability when p is tiny.
105 fn logpmf(&self, k: u64) -> StatsResult<f64> {
106 self.pmf(k).map(|p| p.ln())
107 }
108
109 /// Cumulative distribution function P(X ≤ k).
110 fn cdf(&self, k: u64) -> StatsResult<f64>;
111
112 /// Mean (expected value) μ.
113 fn mean(&self) -> f64;
114
115 /// Variance σ².
116 fn variance(&self) -> f64;
117
118 /// Standard deviation σ = √(variance).
119 fn std_dev(&self) -> f64 {
120 self.variance().sqrt()
121 }
122
123 /// Sum of log-PMFs: Σ ln P(X = kᵢ).
124 fn log_likelihood(&self, data: &[u64]) -> StatsResult<f64> {
125 let mut ll = 0.0_f64;
126 for &k in data {
127 ll += self.logpmf(k)?;
128 }
129 Ok(ll)
130 }
131
132 /// AIC = 2k − 2·ln(L̂).
133 fn aic(&self, data: &[u64]) -> StatsResult<f64> {
134 let ll = self.log_likelihood(data)?;
135 Ok(2.0 * self.num_params() as f64 - 2.0 * ll)
136 }
137
138 /// BIC = k·ln(n) − 2·ln(L̂).
139 fn bic(&self, data: &[u64]) -> StatsResult<f64> {
140 let ll = self.log_likelihood(data)?;
141 let n = data.len() as f64;
142 Ok(self.num_params() as f64 * n.ln() - 2.0 * ll)
143 }
144}