use crate::error::{StatsError, StatsResult};
const LN_FACT_TABLE: [f64; 21] = [
0.0, 0.0, std::f64::consts::LN_2, 1.791_759_469_228_055, 3.178_053_830_347_945_7, 4.787_491_742_782_046, 6.579_251_212_010_101, 8.525_161_361_065_415, 10.604_602_902_745_25, 12.801_827_480_081_469, 15.104_412_573_075_516, 17.502_307_845_873_887, 19.987_214_495_661_885, 22.552_163_853_123_42, 25.191_221_182_738_68, 27.899_271_383_840_89, 30.671_860_128_909_07, 33.505_073_450_136_89, 36.395_445_208_033_05, 39.339_884_187_199_49, 42.335_616_460_753_485, ];
#[inline]
fn ln_factorial(k: u64) -> f64 {
if k <= 20 {
LN_FACT_TABLE[k as usize]
} else {
let k_f64 = k as f64;
k_f64 * k_f64.ln() - k_f64 + 0.5 * (2.0 * std::f64::consts::PI * k_f64).ln()
}
}
#[inline]
fn pmf(k: u64, lambda: f64) -> StatsResult<f64> {
if lambda <= 0.0 {
return Err(StatsError::InvalidInput {
message: "poisson::pmf: lambda must be positive".to_string(),
});
}
let k_f64 = k as f64;
let log_prob = k_f64 * lambda.ln() - lambda - ln_factorial(k);
Ok(log_prob.exp())
}
#[inline]
fn cdf(k: u64, lambda: f64) -> StatsResult<f64> {
if lambda <= 0.0 {
return Err(StatsError::InvalidInput {
message: "poisson::cdf: lambda must be positive".to_string(),
});
}
let ln_lambda = lambda.ln();
let mut log_fact = 0.0_f64;
let mut cdf_sum = 0.0_f64;
for i in 0..=k {
if i > 0 {
log_fact += (i as f64).ln();
}
cdf_sum += ((i as f64) * ln_lambda - lambda - log_fact).exp();
}
Ok(cdf_sum.clamp(0.0, 1.0))
}
#[derive(Debug, Clone, Copy)]
pub struct Poisson {
pub lambda: f64,
}
impl Poisson {
pub fn new(lambda: f64) -> StatsResult<Self> {
if lambda <= 0.0 {
return Err(StatsError::InvalidInput {
message: "Poisson::new: lambda must be positive".to_string(),
});
}
Ok(Self { lambda })
}
pub fn fit(data: &[f64]) -> StatsResult<Self> {
if data.is_empty() {
return Err(StatsError::InvalidInput {
message: "Poisson::fit: data must not be empty".to_string(),
});
}
let lambda = data.iter().sum::<f64>() / data.len() as f64;
Self::new(lambda)
}
}
impl crate::distributions::traits::Distribution for Poisson {
type X = u64;
fn name(&self) -> &str {
"Poisson"
}
fn num_params(&self) -> usize {
1
}
fn pdf(&self, k: u64) -> StatsResult<f64> {
pmf(k, self.lambda)
}
fn logpdf(&self, k: u64) -> StatsResult<f64> {
let ln_fact = ln_factorial(k);
Ok((k as f64) * self.lambda.ln() - self.lambda - ln_fact)
}
fn cdf(&self, k: u64) -> StatsResult<f64> {
cdf(k, self.lambda)
}
fn inverse_cdf(&self, p: f64) -> StatsResult<u64> {
crate::distributions::traits::discrete_inverse_cdf_search(p, |k| self.cdf(k))
}
fn mean(&self) -> f64 {
self.lambda
}
fn variance(&self) -> f64 {
self.lambda
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_poisson_pmf() {
let lambda = 2.0;
let k = 0;
let result = pmf(k, lambda).unwrap();
assert!(
!result.is_nan(),
"PMF returned NaN for k={}, lambda={}",
k,
lambda
);
}
#[test]
fn test_poisson_cdf() {
let lambda = 2.0;
let k = 5;
let result = cdf(k, lambda);
assert!(
!result.unwrap().is_nan(),
"CDF returned NaN for k={}, lambda={}",
k,
lambda
);
}
#[test]
fn test_poisson_pmf_k_zero() {
let lambda: f64 = 2.0;
let k = 0;
let result = pmf(k, lambda).unwrap();
let expected = (-lambda).exp();
assert!(
(result - expected).abs() < 1e-10,
"PMF for k=0 should be e^(-λ)"
);
}
#[test]
fn test_poisson_pmf_k_greater_than_zero() {
let lambda: f64 = 2.0;
let k = 3;
let result = pmf(k, lambda).unwrap();
let expected =
(lambda.powi(k as i32) * (-lambda).exp()) / (1..=k as usize).product::<usize>() as f64;
assert!(
(result - expected).abs() < 1e-10,
"PMF for k>0 should match formula"
);
}
#[test]
fn test_poisson_pmf_lambda_zero() {
let result = pmf(0, 0.0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
StatsError::InvalidInput { .. }
));
}
#[test]
fn test_poisson_pmf_lambda_negative() {
let result = pmf(0, -1.0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
StatsError::InvalidInput { .. }
));
}
#[test]
fn test_poisson_cdf_k_zero() {
let lambda = 2.0;
let k = 0;
let cdf_result = cdf(k, lambda).unwrap();
let pmf_result = pmf(k, lambda).unwrap();
assert!(
(cdf_result - pmf_result).abs() < 1e-10,
"CDF at k=0 should equal PMF at k=0"
);
}
#[test]
fn test_poisson_pmf_k_too_large() {
let k = if usize::MAX as u64 == u64::MAX {
return;
} else {
usize::MAX as u64 + 1
};
let lambda = 2.0;
let result = pmf(k, lambda);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
StatsError::InvalidInput { .. }
));
}
#[test]
fn test_poisson_cdf_lambda_zero() {
let result = cdf(5, 0.0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
StatsError::InvalidInput { .. }
));
}
#[test]
fn test_poisson_cdf_lambda_negative() {
let result = cdf(5, -1.0);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
StatsError::InvalidInput { .. }
));
}
}