scirs2_stats/distributions/binomial.rs
1//! Binomial distribution functions
2//!
3//! This module provides functionality for the Binomial distribution.
4
5use crate::error::{StatsError, StatsResult};
6use crate::sampling::SampleableDistribution;
7use scirs2_core::numeric::{Float, NumCast};
8use scirs2_core::random::{Binomial as RandBinomial, Distribution};
9use statrs::function::gamma::ln_gamma;
10
11/// Helper to convert f64 constants to generic Float type
12#[inline(always)]
13fn const_f64<F: Float + NumCast>(value: f64) -> F {
14 F::from(value).expect("Failed to convert constant to target float type")
15}
16
17/// Binomial distribution structure
18///
19/// The Binomial distribution is a discrete probability distribution that models
20/// the number of successes in a sequence of n independent trials, each with a success
21/// probability p. It is a generalization of the Bernoulli distribution for n > 1.
22pub struct Binomial<F: Float> {
23 /// Number of trials
24 pub n: usize,
25 /// Success probability (0 ≤ p ≤ 1)
26 pub p: F,
27 /// Random number generator
28 rand_distr: RandBinomial,
29}
30
31impl<F: Float + NumCast + std::fmt::Display> Binomial<F> {
32 /// Create a new Binomial distribution with given parameters
33 ///
34 /// # Arguments
35 ///
36 /// * `n` - Number of trials (n ≥ 0)
37 /// * `p` - Success probability (0 ≤ p ≤ 1)
38 ///
39 /// # Returns
40 ///
41 /// * A new Binomial distribution instance
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use scirs2_stats::distributions::binomial::Binomial;
47 ///
48 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
49 /// ```
50 pub fn new(n: usize, p: F) -> StatsResult<Self> {
51 // Validate parameters
52 if p < F::zero() || p > F::one() {
53 return Err(StatsError::DomainError(
54 "Success probability must be between 0 and 1".to_string(),
55 ));
56 }
57
58 // Create RNG for Binomial distribution
59 let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(p).expect("Test/example failed");
60 let rand_distr = match RandBinomial::new(n as u64, p_f64) {
61 Ok(distr) => distr,
62 Err(_) => {
63 return Err(StatsError::ComputationError(
64 "Failed to create Binomial distribution for sampling".to_string(),
65 ))
66 }
67 };
68
69 Ok(Binomial { n, p, rand_distr })
70 }
71
72 /// Calculate the probability mass function (PMF) at a given point
73 ///
74 /// # Arguments
75 ///
76 /// * `k` - The point at which to evaluate the PMF (0 ≤ k ≤ n)
77 ///
78 /// # Returns
79 ///
80 /// * The value of the PMF at the given point
81 ///
82 /// # Examples
83 ///
84 /// ```
85 /// use scirs2_stats::distributions::binomial::Binomial;
86 ///
87 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
88 /// let pmf_at_5 = binom.pmf(5.0);
89 /// assert!((pmf_at_5 - 0.24609375).abs() < 1e-7);
90 /// ```
91 pub fn pmf(&self, k: F) -> F {
92 let zero = F::zero();
93 let one = F::one();
94
95 // Check if k is a non-negative integer
96 if k < zero
97 || k > F::from(self.n).expect("Failed to convert to float")
98 || !Self::is_integer(k)
99 {
100 return zero;
101 }
102
103 let k_usize = k.to_usize().expect("Test/example failed");
104
105 // Special case for n = 0
106 if self.n == 0 {
107 return if k_usize == 0 { one } else { zero };
108 }
109
110 // Special cases for p = 0 or p = 1
111 if self.p == zero {
112 return if k_usize == 0 { one } else { zero };
113 }
114 if self.p == one {
115 return if k_usize == self.n { one } else { zero };
116 }
117
118 // Normal case: Calculate PMF using the binomial coefficient
119 let binom_coef = self.binom_coef(k_usize);
120 let p_pow_k = self
121 .p
122 .powf(F::from(k_usize).expect("Failed to convert to float"));
123 let q_pow_nk =
124 (one - self.p).powf(F::from(self.n - k_usize).expect("Failed to convert to float"));
125
126 binom_coef * p_pow_k * q_pow_nk
127 }
128
129 /// Calculate the log of the probability mass function (log-PMF) at a given point
130 ///
131 /// # Arguments
132 ///
133 /// * `k` - The point at which to evaluate the log-PMF (0 ≤ k ≤ n)
134 ///
135 /// # Returns
136 ///
137 /// * The value of the log-PMF at the given point
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use scirs2_stats::distributions::binomial::Binomial;
143 ///
144 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
145 /// let log_pmf_at_5 = binom.log_pmf(5.0);
146 /// assert!((log_pmf_at_5 - (-1.402)) < 1e-3);
147 /// ```
148 pub fn log_pmf(&self, k: F) -> F {
149 let zero = F::zero();
150 let one = F::one();
151 let neg_infinity = F::neg_infinity();
152
153 // Check if k is a non-negative integer
154 if k < zero
155 || k > F::from(self.n).expect("Failed to convert to float")
156 || !Self::is_integer(k)
157 {
158 return neg_infinity;
159 }
160
161 let k_usize = k.to_usize().expect("Test/example failed");
162
163 // Special case for n = 0
164 if self.n == 0 {
165 return if k_usize == 0 { zero } else { neg_infinity };
166 }
167
168 // Special cases for p = 0 or p = 1
169 if self.p == zero {
170 return if k_usize == 0 { zero } else { neg_infinity };
171 }
172 if self.p == one {
173 return if k_usize == self.n {
174 zero
175 } else {
176 neg_infinity
177 };
178 }
179
180 // Normal case: Calculate log-PMF
181 let ln_binom_coef = self.ln_binom_coef(k_usize);
182 let ln_p_pow_k = F::from(k_usize).expect("Failed to convert to float") * self.p.ln();
183 let ln_q_pow_nk =
184 F::from(self.n - k_usize).expect("Failed to convert to float") * (one - self.p).ln();
185
186 ln_binom_coef + ln_p_pow_k + ln_q_pow_nk
187 }
188
189 /// Calculate the cumulative distribution function (CDF) at a given point
190 ///
191 /// # Arguments
192 ///
193 /// * `k` - The point at which to evaluate the CDF
194 ///
195 /// # Returns
196 ///
197 /// * The value of the CDF at the given point
198 ///
199 /// # Examples
200 ///
201 /// ```
202 /// use scirs2_stats::distributions::binomial::Binomial;
203 ///
204 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
205 /// let cdf_at_5 = binom.cdf(5.0);
206 /// assert!((cdf_at_5 - 0.623046875).abs() < 1e-7);
207 /// ```
208 pub fn cdf(&self, k: F) -> F {
209 let zero = F::zero();
210 let one = F::one();
211
212 // CDF = 0 for k < 0
213 if k < zero {
214 return zero;
215 }
216
217 // CDF = 1 for k ≥ n
218 if k >= F::from(self.n).expect("Failed to convert to float") {
219 return one;
220 }
221
222 // Floor k to handle non-integer values
223 let k_floor = k.floor();
224 let k_int = k_floor.to_usize().expect("Test/example failed");
225
226 // Calculate CDF as sum of PMFs from 0 to k_int
227 let mut sum = zero;
228 for i in 0..=k_int {
229 sum = sum + self.pmf(F::from(i).expect("Failed to convert to float"));
230 }
231
232 sum
233 }
234
235 /// Inverse of the cumulative distribution function (quantile function)
236 ///
237 /// # Arguments
238 ///
239 /// * `p` - Probability value (between 0 and 1)
240 ///
241 /// # Returns
242 ///
243 /// * The value k such that CDF(k) = p
244 ///
245 /// # Examples
246 ///
247 /// ```
248 /// use scirs2_stats::distributions::binomial::Binomial;
249 ///
250 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
251 /// let quant = binom.ppf(0.5).expect("Test/example failed");
252 /// assert_eq!(quant, 5.0);
253 /// ```
254 pub fn ppf(&self, pval: F) -> StatsResult<F> {
255 let zero = F::zero();
256 let one = F::one();
257
258 if pval < zero || pval > one {
259 return Err(StatsError::DomainError(
260 "Probability must be between 0 and 1".to_string(),
261 ));
262 }
263
264 // Special cases
265 if pval == zero {
266 return Ok(zero);
267 }
268 if pval == one {
269 return Ok(F::from(self.n).expect("Failed to convert to float"));
270 }
271
272 // Binary search to find the quantile
273 let mut low = 0;
274 let mut high = self.n;
275 let mut mid;
276
277 // Maximum number of iterations to avoid infinite loops
278 let max_iter = 100;
279 let mut iter = 0;
280
281 while low < high && iter < max_iter {
282 mid = (low + high) / 2;
283 let mid_f = F::from(mid).expect("Failed to convert to float");
284 let cdf_mid = self.cdf(mid_f);
285
286 if cdf_mid < pval {
287 low = mid + 1;
288 } else {
289 high = mid;
290 }
291
292 iter += 1;
293 }
294
295 // Return the quantile
296 Ok(F::from(low).expect("Failed to convert to float"))
297 }
298
299 /// Generate random samples from the distribution
300 ///
301 /// # Arguments
302 ///
303 /// * `size` - Number of samples to generate
304 ///
305 /// # Returns
306 ///
307 /// * Vector of random samples
308 ///
309 /// # Examples
310 ///
311 /// ```
312 /// use scirs2_stats::distributions::binomial::Binomial;
313 ///
314 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
315 /// let samples = binom.rvs(5).expect("Test/example failed");
316 /// assert_eq!(samples.len(), 5);
317 /// ```
318 pub fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
319 let mut rng = scirs2_core::random::thread_rng();
320 let mut samples = Vec::with_capacity(size);
321
322 for _ in 0..size {
323 // Generate random Binomial sample
324 let sample = self.rand_distr.sample(&mut rng);
325 let sample_f = F::from(sample).expect("Failed to convert to float");
326 samples.push(sample_f);
327 }
328
329 Ok(samples)
330 }
331
332 /// Calculate the mean of the distribution
333 ///
334 /// # Returns
335 ///
336 /// * The mean of the distribution
337 ///
338 /// # Examples
339 ///
340 /// ```
341 /// use scirs2_stats::distributions::binomial::Binomial;
342 ///
343 /// let binom = Binomial::new(10, 0.3f64).expect("Test/example failed");
344 /// let mean = binom.mean();
345 /// assert!((mean - 3.0).abs() < 1e-7);
346 /// ```
347 pub fn mean(&self) -> F {
348 // Mean = n * p
349 F::from(self.n).expect("Failed to convert to float") * self.p
350 }
351
352 /// Calculate the variance of the distribution
353 ///
354 /// # Returns
355 ///
356 /// * The variance of the distribution
357 ///
358 /// # Examples
359 ///
360 /// ```
361 /// use scirs2_stats::distributions::binomial::Binomial;
362 ///
363 /// let binom = Binomial::new(10, 0.3f64).expect("Test/example failed");
364 /// let variance = binom.var();
365 /// assert!((variance - 2.1).abs() < 1e-7);
366 /// ```
367 pub fn var(&self) -> F {
368 // Variance = n * p * (1 - p)
369 let one = F::one();
370 F::from(self.n).expect("Failed to convert to float") * self.p * (one - self.p)
371 }
372
373 /// Calculate the standard deviation of the distribution
374 ///
375 /// # Returns
376 ///
377 /// * The standard deviation of the distribution
378 ///
379 /// # Examples
380 ///
381 /// ```
382 /// use scirs2_stats::distributions::binomial::Binomial;
383 ///
384 /// let binom = Binomial::new(10, 0.3f64).expect("Test/example failed");
385 /// let std_dev = binom.std();
386 /// assert!((std_dev - 1.449138).abs() < 1e-6);
387 /// ```
388 pub fn std(&self) -> F {
389 // Std = sqrt(variance)
390 self.var().sqrt()
391 }
392
393 /// Calculate the skewness of the distribution
394 ///
395 /// # Returns
396 ///
397 /// * The skewness of the distribution
398 ///
399 /// # Examples
400 ///
401 /// ```
402 /// use scirs2_stats::distributions::binomial::Binomial;
403 ///
404 /// let binom = Binomial::new(10, 0.3f64).expect("Test/example failed");
405 /// let skewness = binom.skewness();
406 /// assert!((skewness - 0.277350) < 1e-6);
407 /// ```
408 pub fn skewness(&self) -> F {
409 // Skewness = (1 - 2p) / sqrt(n * p * (1 - p))
410 let one = F::one();
411 let two = const_f64::<F>(2.0);
412
413 let n_f = F::from(self.n).expect("Failed to convert to float");
414 let q = one - self.p; // q = 1 - p
415
416 // Handle special cases
417 if self.p == F::zero() || self.p == F::one() || self.n == 0 {
418 return F::zero(); // Undefined case
419 }
420
421 (one - two * self.p) / (n_f * self.p * q).sqrt()
422 }
423
424 /// Calculate the kurtosis of the distribution
425 ///
426 /// # Returns
427 ///
428 /// * The excess kurtosis of the distribution
429 ///
430 /// # Examples
431 ///
432 /// ```
433 /// use scirs2_stats::distributions::binomial::Binomial;
434 ///
435 /// let binom = Binomial::new(10, 0.3f64).expect("Test/example failed");
436 /// let kurtosis = binom.kurtosis();
437 /// assert!((kurtosis - (-0.12380952)).abs() < 1e-6);
438 /// ```
439 pub fn kurtosis(&self) -> F {
440 // Excess Kurtosis = (1 - 6p(1-p)) / (np(1-p))
441 let one = F::one();
442 let six = const_f64::<F>(6.0);
443
444 let n_f = F::from(self.n).expect("Failed to convert to float");
445 let q = one - self.p; // q = 1 - p
446 let pq = self.p * q;
447
448 // Handle special cases
449 if pq == F::zero() || self.n == 0 {
450 return F::zero(); // Undefined case
451 }
452
453 (one - six * pq) / (n_f * pq)
454 }
455
456 /// Calculate the entropy of the distribution
457 ///
458 /// # Returns
459 ///
460 /// * Approximate entropy value using normal approximation
461 ///
462 /// # Examples
463 ///
464 /// ```
465 /// use scirs2_stats::distributions::binomial::Binomial;
466 ///
467 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
468 /// let entropy = binom.entropy();
469 /// assert!(entropy > 0.0);
470 /// ```
471 pub fn entropy(&self) -> F {
472 // For large n, we can approximate the entropy using the normal distribution
473 // This is not exact but provides a reasonable estimate
474 let half = const_f64::<F>(0.5);
475 let two_pi_e = F::from(2.0 * std::f64::consts::PI * std::f64::consts::E)
476 .expect("Failed to convert to float");
477
478 // Handle special cases
479 if self.n == 0 || self.p == F::zero() || self.p == F::one() {
480 return F::zero();
481 }
482
483 // Use normal approximation: H(X) ≈ 0.5 * ln(2πe * n * p * (1-p))
484 let variance = self.var();
485 half * (two_pi_e * variance).ln()
486 }
487
488 /// Calculate the mode of the distribution
489 ///
490 /// # Returns
491 ///
492 /// * The mode(s) of the distribution as a vector (may have one or two values)
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// use scirs2_stats::distributions::binomial::Binomial;
498 ///
499 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
500 /// let modes = binom.mode();
501 /// assert_eq!(modes, vec![5.0]);
502 /// ```
503 pub fn mode(&self) -> Vec<F> {
504 let n_plus_1 = F::from(self.n + 1).expect("Failed to convert to float");
505
506 // Mode calculation: floor((n+1)*p) and/or ceil((n+1)*p) - 1
507 let lower_mode = ((n_plus_1) * self.p).floor();
508 let upper_mode = ((n_plus_1) * self.p).ceil() - F::one();
509
510 // Return both modes if they are different
511 if lower_mode == upper_mode {
512 vec![lower_mode]
513 } else {
514 vec![lower_mode, upper_mode]
515 }
516 }
517
518 /// Calculate the median of the distribution
519 ///
520 /// # Returns
521 ///
522 /// * The median of the distribution (approximated as floor(mean) or ceil(mean))
523 ///
524 /// # Examples
525 ///
526 /// ```
527 /// use scirs2_stats::distributions::binomial::Binomial;
528 ///
529 /// let binom = Binomial::new(10, 0.5f64).expect("Test/example failed");
530 /// let median = binom.median();
531 /// assert_eq!(median, 5.0);
532 /// ```
533 pub fn median(&self) -> F {
534 let mean = self.mean();
535
536 // Approximate the median as the nearest integer to the mean
537 // This is not exact for all cases but works well for most
538 if mean - mean.floor() < const_f64::<F>(0.5) {
539 mean.floor()
540 } else {
541 mean.ceil()
542 }
543 }
544
545 // Helper method to check if a value is an integer
546 fn is_integer(value: F) -> bool {
547 value == value.floor()
548 }
549
550 // Helper method to calculate binomial coefficient n choose k
551 fn binom_coef(&self, k: usize) -> F {
552 // Calculate directly for small values
553 if self.n <= 20 {
554 let mut result: u64 = 1;
555 for i in 0..k {
556 result = result * (self.n - i) as u64 / (i + 1) as u64;
557 }
558 F::from(result).expect("Failed to convert to float")
559 } else {
560 // For larger values, use ln_gamma for numerical stability
561 self.ln_binom_coef(k).exp()
562 }
563 }
564
565 // Helper method to calculate log of binomial coefficient ln(n choose k)
566 fn ln_binom_coef(&self, k: usize) -> F {
567 let n_f64 = self.n as f64;
568 let k_f64 = k as f64;
569
570 // ln(n choose k) = ln_gamma(n+1) - ln_gamma(k+1) - ln_gamma(n-k+1)
571 let result = ln_gamma(n_f64 + 1.0) - ln_gamma(k_f64 + 1.0) - ln_gamma(n_f64 - k_f64 + 1.0);
572
573 F::from(result).expect("Failed to convert to float")
574 }
575}
576
577/// Create a Binomial distribution with the given parameters.
578///
579/// This is a convenience function to create a Binomial distribution with
580/// the given number of trials and success probability.
581///
582/// # Arguments
583///
584/// * `n` - Number of trials (n ≥ 0)
585/// * `p` - Success probability (0 ≤ p ≤ 1)
586///
587/// # Returns
588///
589/// * A Binomial distribution object
590///
591/// # Examples
592///
593/// ```
594/// use scirs2_stats::distributions;
595///
596/// let b = distributions::binom(10, 0.5f64).expect("Test/example failed");
597/// let pmf_at_5 = b.pmf(5.0);
598/// assert!((pmf_at_5 - 0.24609375).abs() < 1e-7);
599/// ```
600#[allow(dead_code)]
601pub fn binom<F>(n: usize, p: F) -> StatsResult<Binomial<F>>
602where
603 F: Float + NumCast + std::fmt::Display,
604{
605 Binomial::new(n, p)
606}
607
608/// Implementation of SampleableDistribution for Binomial
609impl<F: Float + NumCast + std::fmt::Display> SampleableDistribution<F> for Binomial<F> {
610 fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
611 self.rvs(size)
612 }
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618 use approx::assert_relative_eq;
619
620 #[test]
621 fn test_binomial_creation() {
622 // Valid parameters
623 let binom1 = Binomial::new(10, 0.3).expect("Test/example failed");
624 assert_eq!(binom1.n, 10);
625 assert_eq!(binom1.p, 0.3);
626
627 let binom2 = Binomial::new(0, 0.5).expect("Test/example failed");
628 assert_eq!(binom2.n, 0);
629 assert_eq!(binom2.p, 0.5);
630
631 // Invalid p values
632 assert!(Binomial::<f64>::new(10, -0.1).is_err());
633 assert!(Binomial::<f64>::new(10, 1.1).is_err());
634 }
635
636 #[test]
637 fn test_binomial_pmf() {
638 // Binomial(10, 0.5)
639 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
640
641 // PMF values for different k
642 assert_relative_eq!(binom.pmf(0.0), 0.0009765625, epsilon = 1e-10);
643 assert_relative_eq!(binom.pmf(3.0), 0.1171875, epsilon = 1e-10);
644 assert_relative_eq!(binom.pmf(5.0), 0.24609375, epsilon = 1e-10);
645 assert_relative_eq!(binom.pmf(10.0), 0.0009765625, epsilon = 1e-10);
646
647 // PMF should be 0 outside the domain
648 assert_eq!(binom.pmf(-1.0), 0.0);
649 assert_eq!(binom.pmf(11.0), 0.0);
650
651 // PMF should be 0 for non-integer k
652 assert_eq!(binom.pmf(3.5), 0.0);
653
654 // Special case: Binomial(0, 0.5)
655 let binom_zero = Binomial::new(0, 0.5).expect("Test/example failed");
656 assert_eq!(binom_zero.pmf(0.0), 1.0);
657 assert_eq!(binom_zero.pmf(1.0), 0.0);
658
659 // Special case: Binomial(n, 0)
660 let binom_p0 = Binomial::new(10, 0.0).expect("Test/example failed");
661 assert_eq!(binom_p0.pmf(0.0), 1.0);
662 assert_eq!(binom_p0.pmf(1.0), 0.0);
663
664 // Special case: Binomial(n, 1)
665 let binom_p1 = Binomial::new(10, 1.0).expect("Test/example failed");
666 assert_eq!(binom_p1.pmf(10.0), 1.0);
667 assert_eq!(binom_p1.pmf(9.0), 0.0);
668 }
669
670 #[test]
671 fn test_binomial_log_pmf() {
672 // Binomial(10, 0.5)
673 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
674
675 // Check log_pmf against pmf
676 for k in 0..=10 {
677 let k_f = k as f64;
678 let pmf = binom.pmf(k_f);
679 let log_pmf = binom.log_pmf(k_f);
680
681 if pmf > 0.0 {
682 assert_relative_eq!(log_pmf, pmf.ln(), epsilon = 1e-10);
683 } else {
684 assert!(log_pmf.is_infinite() && log_pmf.is_sign_negative());
685 }
686 }
687
688 // log_pmf should be -infinity outside the domain
689 assert!(binom.log_pmf(-1.0).is_infinite() && binom.log_pmf(-1.0).is_sign_negative());
690 assert!(binom.log_pmf(11.0).is_infinite() && binom.log_pmf(11.0).is_sign_negative());
691
692 // log_pmf should be -infinity for non-integer k
693 assert!(binom.log_pmf(3.5).is_infinite() && binom.log_pmf(3.5).is_sign_negative());
694 }
695
696 #[test]
697 fn test_binomial_cdf() {
698 // Binomial(10, 0.5)
699 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
700
701 // CDF values for different k
702 assert_relative_eq!(binom.cdf(-1.0), 0.0, epsilon = 1e-10);
703 assert_relative_eq!(binom.cdf(0.0), 0.0009765625, epsilon = 1e-10);
704 assert_relative_eq!(binom.cdf(3.0), 0.171875, epsilon = 1e-10);
705 assert_relative_eq!(binom.cdf(5.0), 0.623046875, epsilon = 1e-10);
706 assert_relative_eq!(binom.cdf(10.0), 1.0, epsilon = 1e-10);
707 assert_relative_eq!(binom.cdf(11.0), 1.0, epsilon = 1e-10);
708
709 // Non-integer k should be handled correctly (floor of k is used)
710 assert_relative_eq!(binom.cdf(3.7), binom.cdf(3.0), epsilon = 1e-10);
711 }
712
713 #[test]
714 fn test_binomial_ppf() {
715 // Binomial(10, 0.5)
716 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
717
718 // PPF should be the inverse of CDF
719 for k in 0..=10 {
720 let k_f = k as f64;
721 let cdf = binom.cdf(k_f);
722 let ppf = binom.ppf(cdf).expect("Test/example failed");
723
724 // Tolerance - PPF may give the smallest k such that CDF(k) >= p
725 assert!(ppf <= k_f + 1.0);
726 }
727
728 // Specific values
729 assert_eq!(binom.ppf(0.0).expect("Operation failed"), 0.0);
730 assert_eq!(binom.ppf(1.0).expect("Operation failed"), 10.0);
731
732 // PPF should be 5 for p = 0.5
733 assert_eq!(binom.ppf(0.5).expect("Operation failed"), 5.0);
734
735 // Invalid probability values
736 assert!(binom.ppf(-0.1).is_err());
737 assert!(binom.ppf(1.1).is_err());
738 }
739
740 #[test]
741 fn test_binomial_rvs() {
742 // Binomial(10, 0.5)
743 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
744
745 // Generate samples
746 let samples = binom.rvs(100).expect("Test/example failed");
747
748 // Check number of samples
749 assert_eq!(samples.len(), 100);
750
751 // All samples should be integers between 0 and 10
752 for &s in &samples {
753 assert!(s >= 0.0 && s <= 10.0);
754 assert_eq!(s, s.floor());
755 }
756
757 // Mean should be approximately n*p = 10*0.5 = 5
758 let sum: f64 = samples.iter().sum();
759 let mean = sum / samples.len() as f64;
760
761 // The mean could deviate somewhat due to randomness
762 assert!(mean > 4.0 && mean < 6.0);
763 }
764
765 #[test]
766 fn test_binomial_moments() {
767 // Test with n = 10, p = 0.3
768 let binom = Binomial::new(10, 0.3).expect("Test/example failed");
769
770 // Mean = n * p = 10 * 0.3 = 3
771 assert_eq!(binom.mean(), 3.0);
772
773 // Variance = n * p * (1 - p) = 10 * 0.3 * 0.7 = 2.1
774 assert_relative_eq!(binom.var(), 2.1, epsilon = 1e-10);
775
776 // Standard deviation = sqrt(variance) = sqrt(2.1) ≈ 1.449138
777 assert_relative_eq!(binom.std(), 2.1_f64.sqrt(), epsilon = 1e-10);
778
779 // Skewness = (1 - 2p) / sqrt(n * p * (1 - p)) = (1 - 2*0.3) / sqrt(10 * 0.3 * 0.7) ≈ 0.277350
780 let expected_skewness = (1.0 - 2.0 * 0.3) / (10.0 * 0.3 * 0.7).sqrt();
781 assert_relative_eq!(binom.skewness(), expected_skewness, epsilon = 1e-6);
782
783 // Kurtosis = (1 - 6p(1-p)) / (np(1-p)) = (1 - 6*0.3*0.7) / (10*0.3*0.7) ≈ -0.133333
784 let expected_kurtosis = (1.0 - 6.0 * 0.3 * 0.7) / (10.0 * 0.3 * 0.7);
785 assert_relative_eq!(binom.kurtosis(), expected_kurtosis, epsilon = 1e-6);
786 }
787
788 #[test]
789 fn test_binomial_mode() {
790 // Binomial(10, 0.3) - mode = floor((n+1)*p) = floor(11*0.3) = 3
791 let binom1 = Binomial::new(10, 0.3).expect("Test/example failed");
792 let modes1 = binom1.mode();
793 assert_eq!(modes1, vec![3.0]);
794
795 // Binomial(10, 0.5) - mode = floor((n+1)*p) = floor(11*0.5) = 5
796 let binom2 = Binomial::new(10, 0.5).expect("Test/example failed");
797 let modes2 = binom2.mode();
798 assert_eq!(modes2, vec![5.0]);
799
800 // Edge case: Binomial(9, 0.4) - modes = {floor(10*0.4), ceil(10*0.4)-1} = {4, 3}
801 // For bimodal case, both modes should be returned
802 let binom3 = Binomial::new(9, 0.4).expect("Test/example failed");
803 let modes3 = binom3.mode();
804 assert!(modes3.contains(&3.0) && modes3.contains(&4.0) && modes3.len() == 2);
805 }
806
807 #[test]
808 fn test_binomial_median() {
809 // Binomial(10, 0.5) - median should be equal to mean = 5
810 let binom1 = Binomial::new(10, 0.5).expect("Test/example failed");
811 assert_eq!(binom1.median(), 5.0);
812
813 // Binomial(10, 0.3) - median is approximately equal to mean = 3
814 let binom2 = Binomial::new(10, 0.3).expect("Test/example failed");
815 assert_eq!(binom2.median(), 3.0);
816
817 // Binomial(9, 0.4) - median is approximately equal to mean = 3.6 → 4
818 let binom3 = Binomial::new(9, 0.4).expect("Test/example failed");
819 assert_eq!(binom3.median(), 4.0);
820 }
821
822 #[test]
823 fn test_binomial_coef() {
824 let binom = Binomial::new(10, 0.5).expect("Test/example failed");
825
826 // Check a few known values
827 assert_eq!(binom.binom_coef(0), 1.0);
828 assert_eq!(binom.binom_coef(1), 10.0);
829 assert_eq!(binom.binom_coef(2), 45.0);
830 assert_eq!(binom.binom_coef(5), 252.0);
831 assert_eq!(binom.binom_coef(10), 1.0);
832
833 // Test with larger n
834 let binom_large = Binomial::new(100, 0.5).expect("Test/example failed");
835
836 // C(100, 0) = 1
837 assert_relative_eq!(binom_large.binom_coef(0), 1.0, epsilon = 1e-8);
838
839 // C(100, 1) = 100
840 assert_relative_eq!(binom_large.binom_coef(1), 100.0, epsilon = 1e-8);
841
842 // C(100, 2) = 4950
843 assert_relative_eq!(binom_large.binom_coef(2), 4950.0, epsilon = 1e-8);
844 }
845}