use crate::error::{StatsError, StatsResult};
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
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()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PoissonConfig<T>
where
T: ToPrimitive,
{
pub lambda: T,
}
impl<T> PoissonConfig<T>
where
T: ToPrimitive,
{
pub fn new(lambda: T) -> StatsResult<Self> {
let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
message: "PoissonConfig::new: Failed to convert lambda to f64".to_string(),
})?;
if lambda_64 > 0.0 {
Ok(Self { lambda })
} else {
Err(StatsError::InvalidInput {
message: "PoissonConfig::new: lambda must be positive".to_string(),
})
}
}
}
#[inline]
pub fn pmf<T>(k: u64, lambda: T) -> StatsResult<f64>
where
T: ToPrimitive,
{
let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
message: "poisson_distribution::pmf: Failed to convert lambda to f64".to_string(),
})?;
if lambda_64 <= 0.0 {
return Err(StatsError::InvalidInput {
message: "poisson_distribution::pmf: lambda must be positive".to_string(),
});
}
if k > usize::MAX as u64 {
return Err(StatsError::InvalidInput {
message: "poisson_distribution::pmf: k is too large to compute factorial".to_string(),
});
}
let k_f64 = k as f64;
let log_lambda_power = k_f64 * lambda_64.ln();
let log_fact = ln_factorial(k);
let log_prob = log_lambda_power - lambda_64 - log_fact;
Ok(log_prob.exp())
}
#[inline]
pub fn cdf<T>(k: u64, lambda: T) -> StatsResult<f64>
where
T: ToPrimitive,
{
let lambda_64 = lambda.to_f64().ok_or_else(|| StatsError::ConversionError {
message: "poisson_distribution::cdf: Failed to convert lambda to f64".to_string(),
})?;
if lambda_64 <= 0.0 {
return Err(StatsError::InvalidInput {
message: "poisson_distribution::cdf: lambda must be positive".to_string(),
});
}
let ln_lambda = lambda_64.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();
}
let log_pmf = (i as f64) * ln_lambda - lambda_64 - log_fact;
cdf_sum += log_pmf.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::DiscreteDistribution for Poisson {
fn name(&self) -> &str {
"Poisson"
}
fn num_params(&self) -> usize {
1
}
fn pmf(&self, k: u64) -> StatsResult<f64> {
pmf(k, self.lambda)
}
fn logpmf(&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 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 { .. }
));
}
}