use crate::error::{StatsError, StatsResult};
pub trait Distribution {
type X: Copy;
fn name(&self) -> &str;
fn num_params(&self) -> usize;
fn pdf(&self, x: Self::X) -> StatsResult<f64>;
fn logpdf(&self, x: Self::X) -> StatsResult<f64> {
self.pdf(x).map(|p| p.ln())
}
fn pmf(&self, x: Self::X) -> StatsResult<f64> {
self.pdf(x)
}
fn logpmf(&self, x: Self::X) -> StatsResult<f64> {
self.logpdf(x)
}
fn cdf(&self, x: Self::X) -> StatsResult<f64>;
fn inverse_cdf(&self, p: f64) -> StatsResult<Self::X>;
fn mean(&self) -> f64;
fn variance(&self) -> f64;
fn std_dev(&self) -> f64 {
self.variance().sqrt()
}
fn log_likelihood(&self, data: &[Self::X]) -> StatsResult<f64> {
let mut ll = 0.0_f64;
for &x in data {
ll += self.logpdf(x)?;
}
Ok(ll)
}
fn log_likelihood_fast(&self, data: &[Self::X]) -> f64 {
let mut ll = 0.0_f64;
for &x in data {
ll += self.logpdf(x).unwrap_or(f64::NEG_INFINITY);
}
ll
}
fn aic(&self, data: &[Self::X]) -> StatsResult<f64> {
let ll = self.log_likelihood(data)?;
Ok(2.0 * self.num_params() as f64 - 2.0 * ll)
}
fn bic(&self, data: &[Self::X]) -> StatsResult<f64> {
let ll = self.log_likelihood(data)?;
let n = data.len() as f64;
Ok(self.num_params() as f64 * n.ln() - 2.0 * ll)
}
}
pub(crate) fn discrete_inverse_cdf_search(
p: f64,
cdf: impl Fn(u64) -> StatsResult<f64>,
) -> StatsResult<u64> {
if !(0.0..=1.0).contains(&p) {
return Err(StatsError::InvalidInput {
message: format!("inverse_cdf: p must be in [0, 1], got {p}"),
});
}
if p == 0.0 {
return Ok(0);
}
let mut hi: u64 = 1;
while cdf(hi)? < p {
hi = hi.saturating_mul(2);
if hi == u64::MAX {
return Err(StatsError::NumericalError {
message: "inverse_cdf: quantile exceeds u64::MAX".to_string(),
});
}
}
let mut lo: u64 = 0;
while lo < hi {
let mid = lo + (hi - lo) / 2;
if cdf(mid)? < p {
lo = mid + 1;
} else {
hi = mid;
}
}
Ok(lo)
}
pub trait DiscreteDistribution: Distribution<X = u64> {}
impl<T: Distribution<X = u64>> DiscreteDistribution for T {}