Skip to main content

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}