Skip to main content

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}