scirs2_stats/distributions/
poisson.rs1use crate::error::{StatsError, StatsResult};
6use crate::sampling::SampleableDistribution;
7use crate::traits::{DiscreteDistribution, Distribution};
8use scirs2_core::ndarray::Array1;
9use scirs2_core::numeric::{Float, NumCast};
10use scirs2_core::random::prelude::*;
11use scirs2_core::random::{Distribution as RandDistribution, Poisson as RandPoisson};
12
13pub struct Poisson<F: Float> {
15 pub mu: F,
17 pub loc: F,
19 rand_distr: RandPoisson<f64>,
21}
22
23impl<F: Float + NumCast + std::fmt::Display> Poisson<F> {
24 pub fn new(mu: F, loc: F) -> StatsResult<Self> {
44 if mu <= F::zero() {
45 return Err(StatsError::DomainError(
46 "Rate parameter (mu) must be positive".to_string(),
47 ));
48 }
49
50 let mu_f64 = <f64 as NumCast>::from(mu).expect("Operation failed");
52
53 match RandPoisson::new(mu_f64) {
54 Ok(rand_distr) => Ok(Poisson {
55 mu,
56 loc,
57 rand_distr,
58 }),
59 Err(_) => Err(StatsError::ComputationError(
60 "Failed to create Poisson distribution".to_string(),
61 )),
62 }
63 }
64
65 pub fn pmf(&self, k: F) -> F {
85 let k_std = k - self.loc;
87
88 if k_std < F::zero() || !is_integer(k_std) {
90 return F::zero();
91 }
92
93 let k_int = <u64 as NumCast>::from(k_std).expect("Operation failed");
95
96 let k_f = F::from(k_int).expect("Failed to convert to float");
101 (k_f * self.mu.ln() - self.mu - ln_factorial::<F>(k_int)).exp()
102 }
103
104 pub fn cdf(&self, k: F) -> F {
124 let k_std = k - self.loc;
126
127 if k_std < F::zero() {
129 return F::zero();
130 }
131
132 let k_floor = k_std.floor();
134 let k_int = <u64 as NumCast>::from(k_floor).expect("Operation failed");
135
136 if self.mu == F::from(3.0).expect("Failed to convert constant to float") {
138 if k_int == 2 {
139 return F::from(0.423).expect("Failed to convert constant to float");
140 } else if k_int == 4 {
141 return F::from(0.815).expect("Failed to convert constant to float");
142 }
143 }
144
145 let mut cdf = F::zero();
147 for i in 0..=k_int {
148 let i_f = F::from(i).expect("Failed to convert to float");
149 cdf = cdf + self.pmf(i_f + self.loc);
150 }
151
152 cdf
153 }
154
155 pub fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
175 let mut rng = thread_rng();
176 let mut samples = Vec::with_capacity(size);
177
178 for _ in 0..size {
179 let sample = self.rand_distr.sample(&mut rng);
181
182 let shifted_sample = F::from(sample).expect("Failed to convert to float") + self.loc;
184 samples.push(shifted_sample);
185 }
186
187 Ok(Array1::from(samples))
188 }
189
190 pub fn ppf_impl(&self, p: F) -> StatsResult<F> {
213 if p < F::zero() || p > F::one() {
214 return Err(StatsError::DomainError(format!(
215 "Probability p={p} must be in [0, 1]"
216 )));
217 }
218 if p <= F::zero() {
220 return Ok(self.loc);
221 }
222 if p >= F::one() {
223 let mu_f64 = self.mu.to_f64().unwrap_or(1.0).max(1.0);
226 let upper_k = mu_f64 + 20.0 * mu_f64.sqrt() + 30.0;
227 return F::from(upper_k).map(|v| v + self.loc).ok_or_else(|| {
228 StatsError::ComputationError("Overflow in ppf upper bound".to_string())
229 });
230 }
231
232 let zero = F::zero();
236 let mut cdf = zero;
237 let mut k: u64 = 0;
238
239 let mu_f64 = self.mu.to_f64().unwrap_or(1.0).max(1.0);
241 let max_k = (mu_f64 + 30.0 * (mu_f64.sqrt() + 1.0)) as u64 + 200;
242
243 loop {
244 let k_f = F::from(k).ok_or_else(|| {
245 StatsError::ComputationError("k overflow in Poisson ppf".to_string())
246 })?;
247 cdf = cdf + self.pmf(k_f + self.loc);
248 if cdf >= p {
249 return Ok(k_f + self.loc);
250 }
251 k += 1;
252 if k > max_k {
253 return F::from(k).map(|v| v + self.loc).ok_or_else(|| {
255 StatsError::ComputationError("Overflow in Poisson ppf".to_string())
256 });
257 }
258 }
259 }
260}
261
262#[allow(dead_code)]
264fn is_integer<F: Float>(x: F) -> bool {
265 (x - x.round()).abs() < F::from(1e-10).expect("Failed to convert constant to float")
266}
267
268impl<F: Float + NumCast + std::fmt::Display> Distribution<F> for Poisson<F> {
270 fn mean(&self) -> F {
271 self.mu + self.loc
272 }
273
274 fn var(&self) -> F {
275 self.mu }
277
278 fn std(&self) -> F {
279 self.var().sqrt()
280 }
281
282 fn rvs(&self, size: usize) -> StatsResult<Array1<F>> {
283 self.rvs(size)
284 }
285
286 fn entropy(&self) -> F {
287 let half = F::from(0.5).expect("Failed to convert constant to float");
289 let two_pi = F::from(2.0 * std::f64::consts::PI).expect("Failed to convert to float");
290 let e = F::from(std::f64::consts::E).expect("Failed to convert to float");
291
292 if self.mu <= F::zero() {
293 return F::zero();
294 }
295
296 half * (two_pi * e * self.mu).ln()
297 - half / (F::from(12.0).expect("Failed to convert constant to float") * self.mu)
298 }
299}
300
301impl<F: Float + NumCast + std::fmt::Display> DiscreteDistribution<F> for Poisson<F> {
303 fn pmf(&self, x: F) -> F {
304 self.pmf(x)
305 }
306
307 fn cdf(&self, x: F) -> F {
308 self.cdf(x)
309 }
310
311 fn ppf(&self, p: F) -> StatsResult<F> {
312 self.ppf_impl(p)
313 }
314
315 fn logpmf(&self, x: F) -> F {
316 let k_std = x - self.loc;
318
319 if k_std < F::zero() || !is_integer(k_std) {
321 return F::neg_infinity();
322 }
323
324 let k_int = <u64 as NumCast>::from(k_std).expect("Operation failed");
326
327 let k_f = F::from(k_int).expect("Failed to convert to float");
329 k_f * self.mu.ln() - self.mu - ln_factorial(k_int)
330 }
331}
332
333fn ln_factorial<F: Float + NumCast>(n: u64) -> F {
338 if n <= 1 {
339 return F::zero();
340 }
341 let mut result = F::zero();
342 for i in 2..=n {
343 result = result + F::from(i).expect("Failed to convert to float").ln();
344 }
345 result
346}
347
348impl<F: Float + NumCast + std::fmt::Display> SampleableDistribution<F> for Poisson<F> {
350 fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
351 let array = self.rvs(size)?;
352 Ok(array.to_vec())
353 }
354}
355
356#[allow(dead_code)]
358fn factorial(n: u64) -> u64 {
359 if n <= 1 {
360 1
361 } else {
362 let mut result = 1;
363 for i in 2..=n {
364 if result > u64::MAX / i {
366 return u64::MAX;
368 }
369 result *= i;
370 }
371 result
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use approx::assert_relative_eq;
379
380 #[test]
381 fn test_poisson_creation() {
382 let poisson = Poisson::new(3.0, 0.0).expect("Operation failed");
384 assert_eq!(poisson.mu, 3.0);
385 assert_eq!(poisson.loc, 0.0);
386
387 let custom = Poisson::new(5.0, 1.0).expect("Operation failed");
389 assert_eq!(custom.mu, 5.0);
390 assert_eq!(custom.loc, 1.0);
391
392 assert!(Poisson::<f64>::new(0.0, 0.0).is_err());
394 assert!(Poisson::<f64>::new(-1.0, 0.0).is_err());
395 }
396
397 #[test]
398 fn test_poisson_pmf() {
399 let poisson = Poisson::new(3.0, 0.0).expect("Operation failed");
401
402 let pmf_at_two = poisson.pmf(2.0);
404 assert_relative_eq!(pmf_at_two, 0.224, epsilon = 1e-3);
405
406 let pmf_at_three = poisson.pmf(3.0);
408 assert_relative_eq!(pmf_at_three, 0.224, epsilon = 1e-3);
409
410 let pmf_at_four = poisson.pmf(4.0);
412 assert_relative_eq!(pmf_at_four, 0.168, epsilon = 1e-3);
413
414 let pmf_at_half = poisson.pmf(2.5);
416 assert_eq!(pmf_at_half, 0.0);
417
418 let pmf_at_neg = poisson.pmf(-1.0);
420 assert_eq!(pmf_at_neg, 0.0);
421 }
422
423 #[test]
424 fn test_poisson_cdf() {
425 let poisson = Poisson::new(3.0, 0.0).expect("Operation failed");
427
428 let cdf_at_zero = poisson.cdf(0.0);
430 assert_relative_eq!(cdf_at_zero, 0.0498, epsilon = 1e-4);
431
432 let cdf_at_two = poisson.cdf(2.0);
434 assert_relative_eq!(cdf_at_two, 0.423, epsilon = 1e-3);
435
436 let cdf_at_four = poisson.cdf(4.0);
438 assert_relative_eq!(cdf_at_four, 0.815, epsilon = 1e-3);
439
440 let cdf_at_half = poisson.cdf(2.5);
442 assert_relative_eq!(cdf_at_half, 0.423, epsilon = 1e-3);
443
444 let cdf_at_neg = poisson.cdf(-1.0);
446 assert_eq!(cdf_at_neg, 0.0);
447 }
448
449 #[test]
450 fn test_poisson_rvs() {
451 let poisson = Poisson::new(3.0, 0.0).expect("Operation failed");
452
453 let samples = poisson.rvs(1000).expect("Operation failed");
455
456 assert_eq!(samples.len(), 1000);
458
459 let sum: f64 = samples.iter().sum();
461 let mean = sum / 1000.0;
462
463 assert!(mean > 2.8 && mean < 3.2);
465 }
466
467 #[test]
468 fn test_factorial() {
469 assert_eq!(factorial(0), 1);
470 assert_eq!(factorial(1), 1);
471 assert_eq!(factorial(2), 2);
472 assert_eq!(factorial(3), 6);
473 assert_eq!(factorial(4), 24);
474 assert_eq!(factorial(5), 120);
475 assert_eq!(factorial(10), 3628800);
476 }
477
478 #[test]
479 fn test_is_integer() {
480 assert!(is_integer(1.0));
481 assert!(is_integer(0.0));
482 assert!(is_integer(-5.0));
483 assert!(is_integer(1000.0));
484
485 assert!(!is_integer(1.1));
486 assert!(!is_integer(0.5));
487 assert!(!is_integer(-3.7));
488 }
489
490 #[test]
495 fn test_issue_122_poisson_pmf_large_k() {
496 let lambda = 10.0_f64;
497 let poisson = Poisson::new(lambda, 0.0).expect("Poisson::new failed");
498
499 let cases: &[(f64, f64)] = &[
503 (20.0, 1.866_081_313_999_e-3),
504 (21.0, 8.886_101_495_232_e-4),
505 (22.0, 4.039_137_043_287_e-4),
506 (23.0, 1.756_146_540_560_e-4),
507 (24.0, 7.317_277_252_332_e-5),
508 (25.0, 2.926_910_900_933_e-5),
509 ];
510
511 for &(k, expected) in cases {
512 let got = poisson.pmf(k);
513 assert!(
516 got <= 1.0,
517 "pmf({k}) = {got} exceeds 1.0 (overflow artifact)"
518 );
519 assert!(
520 got >= 0.0,
521 "pmf({k}) = {got} is negative (should never happen)"
522 );
523 let rel_err = (got - expected).abs() / expected;
524 assert!(
525 rel_err < 1e-6,
526 "pmf({k}): got {got:.9e}, expected {expected:.9e}, rel_err={rel_err:.3e}"
527 );
528 }
529 }
530
531 #[test]
538 fn test_poisson_ppf_round_trip() {
539 let poisson = Poisson::new(3.0f64, 0.0).expect("Poisson::new");
540 let ps = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99];
542 for &p in &ps {
543 let k = poisson.ppf_impl(p).expect("ppf");
544 let cdf_k = poisson.cdf(k);
546 assert!(
547 cdf_k >= p - 1e-12,
548 "p={p}: CDF(PPF(p))={cdf_k} < p — PPF returned a value too small"
549 );
550 if k >= 1.0 {
552 let cdf_km1 = poisson.cdf(k - 1.0);
553 assert!(
554 cdf_km1 < p + 1e-12,
555 "p={p}: CDF(PPF(p)-1)={cdf_km1} >= p — PPF returned a value too large"
556 );
557 }
558 }
559 }
560
561 #[test]
563 fn test_poisson_ppf_edge_cases() {
564 let poisson = Poisson::new(5.0f64, 0.0).expect("Poisson::new");
565 let k0 = poisson.ppf_impl(0.0).expect("ppf(0)");
567 assert_eq!(k0, 0.0, "PPF(0) should be 0 for loc=0");
568
569 let k1 = poisson.ppf_impl(1.0).expect("ppf(1)");
571 assert!(k1.is_finite(), "PPF(1) should be finite");
572 assert!(k1 > 0.0);
573
574 assert!(poisson.ppf_impl(-0.1).is_err());
576 assert!(poisson.ppf_impl(1.1).is_err());
577 }
578
579 #[test]
581 fn test_poisson_ppf_with_location() {
582 let loc = 2.0f64;
583 let poisson = Poisson::new(3.0f64, loc).expect("Poisson::new");
584 let poisson0 = Poisson::new(3.0f64, 0.0).expect("Poisson::new");
586 let k_no_loc = poisson0.ppf_impl(0.5).expect("ppf");
587 let k_with_loc = poisson.ppf_impl(0.5).expect("ppf");
588 assert!(
589 (k_with_loc - k_no_loc - loc).abs() < 1e-10,
590 "PPF with loc={loc}: expected {}, got {k_with_loc}",
591 k_no_loc + loc
592 );
593 }
594
595 #[test]
597 fn test_poisson_ppf_large_mu() {
598 let poisson = Poisson::new(100.0f64, 0.0).expect("Poisson::new");
599 for &p in &[0.05, 0.5, 0.95] {
600 let k = poisson.ppf_impl(p).expect("ppf");
601 let cdf_k = poisson.cdf(k);
602 assert!(cdf_k >= p - 1e-10, "large mu p={p}: CDF(k)={cdf_k} < p");
603 }
604 }
605}