Skip to main content

scirs2_stats/distributions/
negative_binomial.rs

1//! Negative Binomial distribution functions
2//!
3//! This module provides functionality for the Negative Binomial distribution.
4
5use crate::error::{StatsError, StatsResult};
6use crate::sampling::SampleableDistribution;
7use scirs2_core::numeric::{Float, NumCast};
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::Distribution;
10use statrs::function::gamma::ln_gamma;
11
12/// Negative Binomial distribution structure
13///
14/// The Negative Binomial distribution models the number of failures before r successes
15/// in a sequence of independent Bernoulli trials, each with success probability p.
16/// It is a generalization of the Geometric distribution for r > 1.
17///
18/// In this implementation, we use the convention where the random variable X
19/// represents the number of failures (not the number of trials) before the r-th success.
20pub struct NegativeBinomial<F: Float> {
21    /// Number of successes to achieve
22    pub r: F,
23    /// Success probability (0 < p ≤ 1)
24    pub p: F,
25}
26
27impl<F: Float + NumCast + std::fmt::Display> NegativeBinomial<F> {
28    /// Create a new Negative Binomial distribution with given parameters
29    ///
30    /// # Arguments
31    ///
32    /// * `r` - Number of successes to achieve (r > 0)
33    /// * `p` - Success probability (0 < p ≤ 1)
34    ///
35    /// # Returns
36    ///
37    /// * A new Negative Binomial distribution instance
38    ///
39    /// # Examples
40    ///
41    /// ```
42    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
43    ///
44    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
45    /// ```
46    pub fn new(r: F, p: F) -> StatsResult<Self> {
47        // Validate parameters
48        if r <= F::zero() {
49            return Err(StatsError::DomainError(
50                "Number of successes must be positive".to_string(),
51            ));
52        }
53
54        if p <= F::zero() || p > F::one() {
55            return Err(StatsError::DomainError(
56                "Success probability must be between 0 and 1, exclusive of 0".to_string(),
57            ));
58        }
59
60        Ok(NegativeBinomial { r, p })
61    }
62
63    /// Calculate the probability mass function (PMF) at a given point
64    ///
65    /// # Arguments
66    ///
67    /// * `k` - The point at which to evaluate the PMF (number of failures before r successes)
68    ///
69    /// # Returns
70    ///
71    /// * The value of the PMF at the given point
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
77    ///
78    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
79    /// let pmf_at_7 = nb.pmf(7.0);
80    /// assert!((pmf_at_7 - 0.0660).abs() < 1e-4);
81    /// ```
82    pub fn pmf(&self, k: F) -> F {
83        let zero = F::zero();
84        let one = F::one();
85
86        // Check if k is a non-negative integer
87        if k < zero || !Self::is_integer(k) {
88            return zero;
89        }
90
91        let k_usize = k.to_usize().unwrap_or(0);
92        let k_f = F::from(k_usize).unwrap_or_else(|| F::zero());
93
94        // Handle special cases
95        if self.p == one {
96            return if k_f == zero { one } else { zero };
97        }
98
99        // For integer r, we can use the binomial coefficient formula:
100        // PMF(k) = C(k+r-1, k) * p^r * (1-p)^k
101        if Self::is_integer(self.r) {
102            let r_usize = self.r.to_usize().unwrap_or(1);
103            if r_usize > 0 {
104                return self.binomial_pmf(k_usize);
105            }
106        }
107
108        // For non-integer r, we use the gamma function approach:
109        // PMF(k) = Gamma(r+k)/(Gamma(r)*k!) * p^r * (1-p)^k
110        self.gamma_pmf(k_f)
111    }
112
113    // Helper method for PMF calculation for integer r using binomial coefficient
114    fn binomial_pmf(&self, k: usize) -> F {
115        let one = F::one();
116        let k_f = F::from(k).unwrap_or_else(|| F::zero());
117
118        let r_usize = self.r.to_usize().unwrap_or(1);
119        let r_f = F::from(r_usize).unwrap_or_else(|| F::one());
120
121        // Calculate binomial coefficient C(k+r-1, k)
122        let binom_coef = self.binom_coef(k + r_usize - 1, k);
123
124        // Calculate p^r * (1-p)^k
125        let p_pow_r = self.p.powf(r_f);
126        let q_pow_k = (one - self.p).powf(k_f);
127
128        binom_coef * p_pow_r * q_pow_k
129    }
130
131    // Helper method for PMF calculation for non-integer r using gamma function
132    fn gamma_pmf(&self, k: F) -> F {
133        // Use F::one() directly in the code
134
135        // Convert k to f64 for gamma function
136        let k_f64 = <f64 as scirs2_core::numeric::NumCast>::from(k).unwrap_or(0.0);
137        let r_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.r).unwrap_or(1.0);
138
139        // Calculate ln(Gamma(r+k)/(Gamma(r)*k!))
140        let ln_coef = ln_gamma(r_f64 + k_f64) - ln_gamma(r_f64) - ln_gamma(k_f64 + 1.0);
141
142        // Calculate ln(p^r * (1-p)^k)
143        let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.p).unwrap_or(0.5);
144        let ln_prob = r_f64 * p_f64.ln() + k_f64 * (1.0 - p_f64).ln();
145
146        // Combine and convert back to F
147        F::from((ln_coef + ln_prob).exp()).unwrap_or_else(|| F::zero())
148    }
149
150    /// Calculate the log of the probability mass function (log-PMF) at a given point
151    ///
152    /// # Arguments
153    ///
154    /// * `k` - The point at which to evaluate the log-PMF (number of failures before r successes)
155    ///
156    /// # Returns
157    ///
158    /// * The value of the log-PMF at the given point
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
164    ///
165    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
166    /// let log_pmf_at_7 = nb.log_pmf(7.0);
167    /// assert!((log_pmf_at_7 - (-2.717)).abs() < 1e-3);
168    /// ```
169    pub fn log_pmf(&self, k: F) -> F {
170        let zero = F::zero();
171        let one = F::one();
172        let neg_infinity = F::neg_infinity();
173
174        // Check if k is a non-negative integer
175        if k < zero || !Self::is_integer(k) {
176            return neg_infinity;
177        }
178
179        let k_usize = k.to_usize().expect("Operation failed");
180        let k_f = F::from(k_usize).expect("Failed to convert to float");
181
182        // Handle special cases
183        if self.p == one {
184            return if k_f == zero { zero } else { neg_infinity };
185        }
186
187        // For integer r, we can use the binomial coefficient formula
188        if Self::is_integer(self.r) {
189            let r_usize = self.r.to_usize().expect("Operation failed");
190            if r_usize > 0 {
191                return self.log_binomial_pmf(k_usize);
192            }
193        }
194
195        // For non-integer r, we use the gamma function approach
196        self.log_gamma_pmf(k_f)
197    }
198
199    // Helper method for log-PMF calculation for integer r
200    fn log_binomial_pmf(&self, k: usize) -> F {
201        let one = F::one();
202        let k_f = F::from(k).expect("Failed to convert to float");
203
204        let r_usize = self.r.to_usize().expect("Operation failed");
205        let r_f = F::from(r_usize).expect("Failed to convert to float");
206
207        // Calculate ln(C(k+r-1, k))
208        let ln_binom_coef = self.ln_binom_coef(k + r_usize - 1, k);
209
210        // Calculate ln(p^r * (1-p)^k)
211        let ln_p_pow_r = r_f * self.p.ln();
212        let ln_q_pow_k = k_f * (one - self.p).ln();
213
214        ln_binom_coef + ln_p_pow_r + ln_q_pow_k
215    }
216
217    // Helper method for log-PMF calculation for non-integer r
218    fn log_gamma_pmf(&self, k: F) -> F {
219        // Use F::one() directly in the code
220
221        // Convert k to f64 for gamma function
222        let k_f64 = <f64 as scirs2_core::numeric::NumCast>::from(k).unwrap_or(0.0);
223        let r_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.r).unwrap_or(1.0);
224
225        // Calculate ln(Gamma(r+k)/(Gamma(r)*k!))
226        let ln_coef = ln_gamma(r_f64 + k_f64) - ln_gamma(r_f64) - ln_gamma(k_f64 + 1.0);
227
228        // Calculate ln(p^r * (1-p)^k)
229        let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.p).unwrap_or(0.5);
230        let ln_prob = r_f64 * p_f64.ln() + k_f64 * (1.0 - p_f64).ln();
231
232        // Convert back to F
233        F::from(ln_coef + ln_prob).expect("Failed to convert to float")
234    }
235
236    /// Calculate the cumulative distribution function (CDF) at a given point
237    ///
238    /// # Arguments
239    ///
240    /// * `k` - The point at which to evaluate the CDF
241    ///
242    /// # Returns
243    ///
244    /// * The value of the CDF at the given point
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
250    ///
251    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
252    /// let cdf_at_7 = nb.cdf(7.0);
253    /// assert!((cdf_at_7 - 0.2763).abs() < 1e-4);
254    /// ```
255    pub fn cdf(&self, k: F) -> F {
256        let zero = F::zero();
257        let one = F::one();
258
259        // CDF = 0 for k < 0
260        if k < zero {
261            return zero;
262        }
263
264        // Handle special cases
265        if self.p == one {
266            return one; // Always 1 for p=1
267        }
268
269        // Floor k to handle non-integer values
270        let k_floor = k.floor();
271        let k_int = k_floor.to_usize().expect("Operation failed");
272
273        // Calculate CDF as sum of PMFs from 0 to k_int
274        let mut sum = zero;
275        for i in 0..=k_int {
276            sum = sum + self.pmf(F::from(i).expect("Failed to convert to float"));
277        }
278
279        sum
280    }
281
282    /// Inverse of the cumulative distribution function (quantile function)
283    ///
284    /// # Arguments
285    ///
286    /// * `p` - Probability value (between 0 and 1)
287    ///
288    /// # Returns
289    ///
290    /// * The value k such that CDF(k) = p
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
296    ///
297    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
298    /// let quant = nb.ppf(0.5).expect("Operation failed");
299    /// assert_eq!(quant, 11.0);
300    /// ```
301    pub fn ppf(&self, pval: F) -> StatsResult<F> {
302        let zero = F::zero();
303        let one = F::one();
304
305        if pval < zero || pval > one {
306            return Err(StatsError::DomainError(
307                "Probability must be between 0 and 1".to_string(),
308            ));
309        }
310
311        // Special cases
312        if pval <= zero {
313            return Ok(zero);
314        }
315        if pval >= one {
316            return Ok(F::infinity());
317        }
318
319        // Handle case when p = 1.0 separately
320        if self.p == one {
321            return Ok(zero); // When p=1, X is always 0
322        }
323
324        // For actual calculation, we need to find the smallest k where CDF(k) >= p_val
325        // Use an iterative approach
326        let mut k = zero;
327
328        // Maximum number of iterations to avoid infinite loops
329        let max_iter = 1000;
330        let mut iter = 0;
331
332        while self.cdf(k) < pval && iter < max_iter {
333            k = k + one;
334            iter += 1;
335        }
336
337        if iter == max_iter {
338            return Err(StatsError::ComputationError(
339                "Failed to converge in PPF calculation".to_string(),
340            ));
341        }
342
343        Ok(k)
344    }
345
346    /// Generate random samples from the distribution
347    ///
348    /// # Arguments
349    ///
350    /// * `size` - Number of samples to generate
351    ///
352    /// # Returns
353    ///
354    /// * Vector of random samples
355    ///
356    /// # Examples
357    ///
358    /// ```
359    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
360    ///
361    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
362    /// let samples = nb.rvs(10).expect("Operation failed");
363    /// assert_eq!(samples.len(), 10);
364    /// ```
365    pub fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
366        let mut rng = thread_rng();
367        let mut samples = Vec::with_capacity(size);
368
369        // For integer r, we can use a sum of geometric variables
370        if Self::is_integer(self.r) {
371            let r_usize = self.r.to_usize().expect("Operation failed");
372
373            for _ in 0..size {
374                // Generate r geometric variables and sum them
375                let mut sum = 0;
376                for _ in 0..r_usize {
377                    // Generate geometric random variable (# failures before first success)
378                    let u: f64 = rng.random_range(0.0..1.0);
379                    let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.p).unwrap_or(0.5);
380                    let geom_sample = (u.ln()
381                        / (F::from(1.0)
382                            .expect("Failed to convert constant to float")
383                            .to_f64()
384                            .expect("Operation failed")
385                            - p_f64)
386                            .ln())
387                    .floor() as usize;
388                    sum += geom_sample;
389                }
390                samples.push(F::from(sum).expect("Failed to convert to float"));
391            }
392        } else {
393            // For non-integer r..use gamma-Poisson mixture
394            let r_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.r).unwrap_or(1.0);
395            let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(self.p).unwrap_or(0.5);
396
397            for _ in 0..size {
398                // Generate gamma random variable with shape r and scale (1-p)/p
399                let gamma_distr = scirs2_core::random::Gamma::new(r_f64, (1.0 - p_f64) / p_f64)
400                    .map_err(|_| {
401                        StatsError::ComputationError(
402                            "Failed to create gamma distribution".to_string(),
403                        )
404                    })?;
405                let gamma_sample: f64 = gamma_distr.sample(&mut rng);
406
407                // Generate Poisson random variable with mean gamma_sample
408                let poisson_distr =
409                    scirs2_core::random::Poisson::new(gamma_sample).map_err(|_| {
410                        StatsError::ComputationError(
411                            "Failed to create Poisson distribution".to_string(),
412                        )
413                    })?;
414                let poisson_sample = poisson_distr.sample(&mut rng);
415
416                samples.push(F::from(poisson_sample).expect("Failed to convert to float"));
417            }
418        }
419
420        Ok(samples)
421    }
422
423    /// Calculate the mean of the distribution
424    ///
425    /// # Returns
426    ///
427    /// * The mean of the distribution
428    ///
429    /// # Examples
430    ///
431    /// ```
432    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
433    ///
434    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
435    /// let mean = nb.mean();
436    /// assert!((mean - 11.666667).abs() < 1e-6);
437    /// ```
438    pub fn mean(&self) -> F {
439        // Mean = r * (1-p) / p
440        let one = F::one();
441        let q = one - self.p; // q = 1 - p
442        self.r * q / self.p
443    }
444
445    /// Calculate the variance of the distribution
446    ///
447    /// # Returns
448    ///
449    /// * The variance of the distribution
450    ///
451    /// # Examples
452    ///
453    /// ```
454    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
455    ///
456    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
457    /// let variance = nb.var();
458    /// assert!((variance - 38.888889).abs() < 1e-6);
459    /// ```
460    pub fn var(&self) -> F {
461        // Variance = r * (1-p) / p^2
462        let one = F::one();
463        let q = one - self.p; // q = 1 - p
464        self.r * q / (self.p * self.p)
465    }
466
467    /// Calculate the standard deviation of the distribution
468    ///
469    /// # Returns
470    ///
471    /// * The standard deviation of the distribution
472    ///
473    /// # Examples
474    ///
475    /// ```
476    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
477    ///
478    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
479    /// let std_dev = nb.std();
480    /// assert!((std_dev - 6.236).abs() < 1e-3);
481    /// ```
482    pub fn std(&self) -> F {
483        // Std = sqrt(variance)
484        self.var().sqrt()
485    }
486
487    /// Calculate the skewness of the distribution
488    ///
489    /// # Returns
490    ///
491    /// * The skewness of the distribution
492    ///
493    /// # Examples
494    ///
495    /// ```
496    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
497    ///
498    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
499    /// let skewness = nb.skewness();
500    /// assert!((skewness - 0.909).abs() < 1e-3);
501    /// ```
502    pub fn skewness(&self) -> F {
503        // Skewness = (2-p) / sqrt(r * (1-p))
504        let one = F::one();
505        let two = F::from(2.0).expect("Failed to convert constant to float");
506
507        let q = one - self.p; // q = 1 - p
508        (two - self.p) / (self.r * q).sqrt()
509    }
510
511    /// Calculate the kurtosis of the distribution
512    ///
513    /// # Returns
514    ///
515    /// * The excess kurtosis of the distribution
516    ///
517    /// # Examples
518    ///
519    /// ```
520    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
521    ///
522    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
523    /// let kurtosis = nb.kurtosis();
524    /// assert!((kurtosis - 1.226).abs() < 1e-3);
525    /// ```
526    pub fn kurtosis(&self) -> F {
527        // Excess Kurtosis = 6/r + (p^2)/(r*(1-p))
528        let one = F::one();
529        let six = F::from(6.0).expect("Failed to convert constant to float");
530
531        let q = one - self.p; // q = 1 - p
532        six / self.r + (self.p * self.p) / (self.r * q)
533    }
534
535    /// Calculate the entropy of the distribution
536    ///
537    /// # Returns
538    ///
539    /// * The entropy value (approximation for non-integer r)
540    ///
541    /// # Examples
542    ///
543    /// ```
544    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
545    ///
546    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
547    /// let entropy = nb.entropy();
548    /// assert!(entropy > 0.0);
549    /// ```
550    pub fn entropy(&self) -> F {
551        // For integer r, approximate using the mean and variance
552        // This is a reasonable approximation for large r
553        let half = F::from(0.5).expect("Failed to convert constant to float");
554        let two_pi_e = F::from(2.0 * std::f64::consts::PI * std::f64::consts::E)
555            .expect("Failed to convert to float");
556
557        // If r is large, use normal approximation
558        if self.r >= F::from(10.0).expect("Failed to convert constant to float") {
559            let variance = self.var();
560            return half * (two_pi_e * variance).ln();
561        }
562
563        // For smaller r, compute entropy directly for some values
564        // (This is an approximation for the general case)
565        let one = F::one();
566        let q = one - self.p; // q = 1 - p
567        let mean = self.mean();
568
569        if Self::is_integer(self.r) {
570            // r * ln(1/p) - mean * ln(q)
571            return self.r * (-self.p.ln()) - mean * q.ln();
572        }
573
574        // For non-integer r, use mean and variance to approximate
575        half * (two_pi_e * self.var()).ln()
576    }
577
578    /// Calculate the mode of the distribution
579    ///
580    /// # Returns
581    ///
582    /// * The mode of the distribution (floor of (r-1)*(1-p)/p for r>1, 0 for r≤1)
583    ///
584    /// # Examples
585    ///
586    /// ```
587    /// use scirs2_stats::distributions::negative_binomial::NegativeBinomial;
588    ///
589    /// let nb = NegativeBinomial::new(5.0f64, 0.3).expect("Operation failed");
590    /// let mode = nb.mode();
591    /// assert_eq!(mode, 9.0);
592    /// ```
593    pub fn mode(&self) -> F {
594        let zero = F::zero();
595        let one = F::one();
596
597        // For r ≤ 1, the mode is 0
598        if self.r <= one {
599            return zero;
600        }
601
602        // For r > 1, mode = floor((r-1)*(1-p)/p)
603        let q = one - self.p; // q = 1 - p
604        ((self.r - one) * q / self.p).floor()
605    }
606
607    // Helper method to check if a value is an integer
608    fn is_integer(value: F) -> bool {
609        value == value.floor()
610    }
611
612    // Helper method to calculate binomial coefficient C(n,k)
613    fn binom_coef(&self, n: usize, k: usize) -> F {
614        // For small values, calculate directly
615        if n <= 20 {
616            let mut result: u64 = 1;
617            for i in 0..k {
618                result = result * (n - i) as u64 / (i + 1) as u64;
619            }
620            F::from(result).expect("Failed to convert to float")
621        } else {
622            // For larger values, use ln_gamma for numerical stability
623            self.ln_binom_coef(n, k).exp()
624        }
625    }
626
627    // Helper method to calculate log of binomial coefficient ln(C(n,k))
628    fn ln_binom_coef(&self, n: usize, k: usize) -> F {
629        let n_f64 = n as f64;
630        let k_f64 = k as f64;
631
632        // ln(C(n,k)) = ln_gamma(n+1) - ln_gamma(k+1) - ln_gamma(n-k+1)
633        let result = ln_gamma(n_f64 + 1.0) - ln_gamma(k_f64 + 1.0) - ln_gamma(n_f64 - k_f64 + 1.0);
634
635        F::from(result).expect("Failed to convert to float")
636    }
637}
638
639/// Create a Negative Binomial distribution with the given parameters.
640///
641/// This is a convenience function to create a Negative Binomial distribution with
642/// the given number of successes and success probability.
643///
644/// # Arguments
645///
646/// * `r` - Number of successes to achieve (r > 0)
647/// * `p` - Success probability (0 < p ≤ 1)
648///
649/// # Returns
650///
651/// * A Negative Binomial distribution object
652///
653/// # Examples
654///
655/// ```
656/// use scirs2_stats::distributions;
657///
658/// let nb = distributions::nbinom(5.0f64, 0.3).expect("Operation failed");
659/// let pmf_at_7 = nb.pmf(7.0);
660/// assert!((pmf_at_7 - 0.0660).abs() < 1e-4);
661/// ```
662#[allow(dead_code)]
663pub fn nbinom<F>(r: F, p: F) -> StatsResult<NegativeBinomial<F>>
664where
665    F: Float + NumCast + std::fmt::Display,
666{
667    NegativeBinomial::new(r, p)
668}
669
670/// Implementation of SampleableDistribution for NegativeBinomial
671impl<F: Float + NumCast + std::fmt::Display> SampleableDistribution<F> for NegativeBinomial<F> {
672    fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
673        self.rvs(size)
674    }
675}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680    use approx::assert_relative_eq;
681
682    #[test]
683    fn test_negative_binomial_creation() {
684        // Valid parameters
685        let nb1 = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
686        assert_eq!(nb1.r, 5.0);
687        assert_eq!(nb1.p, 0.3);
688
689        let nb2 = NegativeBinomial::new(1.0, 1.0).expect("Operation failed");
690        assert_eq!(nb2.r, 1.0);
691        assert_eq!(nb2.p, 1.0);
692
693        // Invalid r values
694        assert!(NegativeBinomial::<f64>::new(0.0, 0.3).is_err());
695        assert!(NegativeBinomial::<f64>::new(-1.0, 0.3).is_err());
696
697        // Invalid p values
698        assert!(NegativeBinomial::<f64>::new(5.0, 0.0).is_err());
699        assert!(NegativeBinomial::<f64>::new(5.0, -0.1).is_err());
700        assert!(NegativeBinomial::<f64>::new(5.0, 1.1).is_err());
701    }
702
703    #[test]
704    fn test_negative_binomial_pmf() {
705        // NegativeBinomial(5, 0.3) - integer r
706        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
707
708        // PMF values for different k
709        assert_relative_eq!(nb.pmf(0.0), 0.00243, epsilon = 1e-5);
710        assert_relative_eq!(nb.pmf(3.0), 0.02917, epsilon = 1e-5);
711        assert_relative_eq!(nb.pmf(7.0), 0.06604, epsilon = 1e-5);
712        assert_relative_eq!(nb.pmf(12.0), 0.06121, epsilon = 1e-5);
713
714        // PMF should be 0 for negative k
715        assert_eq!(nb.pmf(-1.0), 0.0);
716
717        // PMF should be 0 for non-integer k
718        assert_eq!(nb.pmf(3.5), 0.0);
719
720        // Special case: NegativeBinomial(1, 0.3) - same as geometric
721        let nb_geom = NegativeBinomial::new(1.0, 0.3).expect("Operation failed");
722        assert_relative_eq!(nb_geom.pmf(0.0), 0.3, epsilon = 1e-10);
723        assert_relative_eq!(nb_geom.pmf(1.0), 0.3 * 0.7, epsilon = 1e-10);
724        assert_relative_eq!(nb_geom.pmf(2.0), 0.3 * 0.7 * 0.7, epsilon = 1e-10);
725
726        // Special case: NegativeBinomial(r, 1.0)
727        let nb_p1 = NegativeBinomial::new(5.0, 1.0).expect("Operation failed");
728        assert_eq!(nb_p1.pmf(0.0), 1.0);
729        assert_eq!(nb_p1.pmf(1.0), 0.0);
730
731        // Test with non-integer r
732        let nb_non_int = NegativeBinomial::new(2.5, 0.3).expect("Operation failed");
733        assert!(nb_non_int.pmf(0.0) > 0.0);
734        assert!(nb_non_int.pmf(3.0) > 0.0);
735    }
736
737    #[test]
738    fn test_negative_binomial_log_pmf() {
739        // NegativeBinomial(5, 0.3)
740        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
741
742        // Check log_pmf against pmf
743        for k in 0..15 {
744            let k_f = k as f64;
745            let pmf = nb.pmf(k_f);
746            let log_pmf = nb.log_pmf(k_f);
747
748            if pmf > 0.0 {
749                assert_relative_eq!(log_pmf, pmf.ln(), epsilon = 1e-5);
750            } else {
751                assert!(log_pmf.is_infinite() && log_pmf.is_sign_negative());
752            }
753        }
754
755        // log_pmf should be -infinity for negative k or non-integer k
756        assert!(nb.log_pmf(-1.0).is_infinite() && nb.log_pmf(-1.0).is_sign_negative());
757        assert!(nb.log_pmf(3.5).is_infinite() && nb.log_pmf(3.5).is_sign_negative());
758    }
759
760    #[test]
761    fn test_negative_binomial_cdf() {
762        // NegativeBinomial(5, 0.3)
763        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
764
765        // CDF for k < 0 should be 0
766        assert_eq!(nb.cdf(-1.0), 0.0);
767
768        // CDF values for different k
769        assert_relative_eq!(nb.cdf(0.0), 0.00243, epsilon = 1e-5);
770        assert_relative_eq!(nb.cdf(3.0), 0.05796, epsilon = 1e-5);
771        assert_relative_eq!(nb.cdf(7.0), 0.27634, epsilon = 1e-5);
772        assert_relative_eq!(nb.cdf(12.0), 0.61131, epsilon = 1e-5);
773
774        // Non-integer k should use floor of k
775        assert_relative_eq!(nb.cdf(7.5), nb.cdf(7.0), epsilon = 1e-10);
776
777        // Special case: NegativeBinomial(r, 1.0)
778        let nb_p1 = NegativeBinomial::new(5.0, 1.0).expect("Operation failed");
779        assert_eq!(nb_p1.cdf(0.0), 1.0);
780        assert_eq!(nb_p1.cdf(1.0), 1.0);
781    }
782
783    #[test]
784    fn test_negative_binomial_ppf() {
785        // NegativeBinomial(5, 0.3)
786        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
787
788        // Verify that ppf is the inverse of cdf for some test points
789        let test_points = vec![0.1, 0.3, 0.5, 0.7, 0.9];
790        for &p in &test_points {
791            let k = nb.ppf(p).expect("Operation failed");
792
793            // For discrete distributions, we check that CDF(k-1) < p ≤ CDF(k)
794            if k > 0.0 {
795                let k_minus_1 = k - 1.0;
796                let cdf_k_minus_1 = nb.cdf(k_minus_1);
797                let cdf_k = nb.cdf(k);
798                assert!(cdf_k_minus_1 < p && p <= cdf_k);
799            } else {
800                // For k = 0, just check that p ≤ CDF(0)
801                let cdf_k = nb.cdf(k);
802                assert!(p <= cdf_k);
803            }
804        }
805
806        // Test edge cases
807        assert_eq!(nb.ppf(0.0).expect("Operation failed"), 0.0);
808        assert!(nb.ppf(1.0).expect("Operation failed").is_infinite());
809
810        // Invalid probability values
811        assert!(nb.ppf(-0.1).is_err());
812        assert!(nb.ppf(1.1).is_err());
813    }
814
815    #[test]
816    fn test_negative_binomial_rvs() {
817        // NegativeBinomial(5, 0.3)
818        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
819
820        // Generate samples
821        let samples = nb.rvs(100).expect("Operation failed");
822
823        // Check number of samples
824        assert_eq!(samples.len(), 100);
825
826        // All samples should be non-negative integers
827        for &s in &samples {
828            assert!(s >= 0.0);
829            assert_eq!(s, s.floor());
830        }
831
832        // Mean should be approximately r(1-p)/p = 5*0.7/0.3 = 11.67
833        let sum: f64 = samples.iter().sum();
834        let mean = sum / samples.len() as f64;
835
836        // The mean could deviate somewhat due to randomness
837        assert!(mean > 8.0 && mean < 15.0);
838    }
839
840    #[test]
841    fn test_negative_binomial_moments() {
842        // Test with r = 5, p = 0.3
843        let nb = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
844
845        // Mean = r(1-p)/p = 5*0.7/0.3 = 11.67
846        assert_relative_eq!(nb.mean(), 11.666667, epsilon = 1e-6);
847
848        // Variance = r(1-p)/p^2 = 5*0.7/0.3^2 = 38.89
849        assert_relative_eq!(nb.var(), 38.888889, epsilon = 1e-6);
850
851        // Standard deviation = sqrt(variance) = sqrt(38.89) ≈ 6.236
852        assert_relative_eq!(nb.std(), 6.236095, epsilon = 1e-6);
853
854        // Skewness = (2-p)/sqrt(r(1-p)) = (2-0.3)/sqrt(5*0.7) ≈ 0.909
855        assert_relative_eq!(nb.skewness(), 0.908688, epsilon = 1e-6);
856
857        // Kurtosis = 6/r + p^2/(r(1-p)) = 6/5 + 0.3^2/(5*0.7) ≈ 1.226
858        assert_relative_eq!(nb.kurtosis(), 1.225714, epsilon = 1e-6);
859
860        // Mode is integer for these parameters: floor((r-1)(1-p)/p) = floor(4*0.7/0.3) = 9
861        assert_eq!(nb.mode(), 9.0);
862    }
863
864    #[test]
865    fn test_negative_binomial_edge_cases() {
866        // Test with r = 1, p = 0.3 (should be equivalent to Geometric(0.3))
867        let nb_geom = NegativeBinomial::new(1.0, 0.3).expect("Operation failed");
868
869        // Mean = (1-p)/p = 0.7/0.3 = 2.33...
870        assert_relative_eq!(nb_geom.mean(), 2.333333, epsilon = 1e-6);
871
872        // Variance = (1-p)/p^2 = 0.7/0.09 = 7.77...
873        assert_relative_eq!(nb_geom.var(), 7.777778, epsilon = 1e-6);
874
875        // Mode should be 0 for r = 1
876        assert_eq!(nb_geom.mode(), 0.0);
877
878        // Test with large r
879        let nb_large = NegativeBinomial::new(100.0, 0.3).expect("Operation failed");
880
881        // Mean = r(1-p)/p = 100*0.7/0.3 = 233.33...
882        assert_relative_eq!(nb_large.mean(), 233.33333, epsilon = 1e-5);
883
884        // Test with p close to 1
885        let nb_p_high = NegativeBinomial::new(5.0, 0.99).expect("Operation failed");
886
887        // Mean = r(1-p)/p = 5*0.01/0.99 = 0.0505...
888        assert_relative_eq!(nb_p_high.mean(), 0.050505, epsilon = 1e-6);
889    }
890
891    #[test]
892    fn test_negative_binomial_compare_with_binomial() {
893        // NegativeBinomial with integer r should use binomial coefficient calculation
894        let nb_int = NegativeBinomial::new(5.0, 0.3).expect("Operation failed");
895
896        // PMF values for different k
897        let pmf_0 = nb_int.pmf(0.0);
898        let pmf_3 = nb_int.pmf(3.0);
899        let pmf_7 = nb_int.pmf(7.0);
900
901        // Theoretical values
902        // For k=0: C(5+0-1, 0) * 0.3^5 * 0.7^0 = C(4, 0) * 0.3^5 = 0.3^5 ≈ 0.00243
903        // For k=3: C(5+3-1, 3) * 0.3^5 * 0.7^3 = C(7, 3) * 0.3^5 * 0.7^3 ≈ 0.02917
904        // For k=7: C(5+7-1, 7) * 0.3^5 * 0.7^7 = C(11, 7) * 0.3^5 * 0.7^7 ≈ 0.06604
905        assert_relative_eq!(pmf_0, 0.00243, epsilon = 1e-5);
906        assert_relative_eq!(pmf_3, 0.02917, epsilon = 1e-5);
907        assert_relative_eq!(pmf_7, 0.06604, epsilon = 1e-5);
908    }
909
910    #[test]
911    fn test_non_integer_r() {
912        // Test with non-integer r
913        let nb_non_int = NegativeBinomial::new(2.5, 0.3).expect("Operation failed");
914
915        // PMF values for different k
916        // These values are approximated using gamma function approach
917        let pmf_0 = nb_non_int.pmf(0.0);
918        let pmf_3 = nb_non_int.pmf(3.0);
919        let pmf_7 = nb_non_int.pmf(7.0);
920
921        // Since we're using gamma approximation, just check the values are positive
922        assert!(pmf_0 > 0.0);
923        assert!(pmf_3 > 0.0);
924        assert!(pmf_7 > 0.0);
925
926        // Check that the distribution properties are consistent
927        let mean = nb_non_int.mean();
928        let var = nb_non_int.var();
929
930        // Mean = r(1-p)/p = 2.5*0.7/0.3 = 5.83...
931        assert_relative_eq!(mean, 5.833333, epsilon = 1e-6);
932
933        // Variance = r(1-p)/p^2 = 2.5*0.7/0.3^2 = 19.44...
934        assert_relative_eq!(var, 19.444444, epsilon = 1e-6);
935    }
936}