Skip to main content

scirs2_stats/distributions/
geometric.rs

1//! Geometric distribution functions
2//!
3//! This module provides functionality for the Geometric 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, Geometric as RandGeometric};
10
11/// Geometric distribution structure
12///
13/// The Geometric distribution is a discrete probability distribution that models
14/// the number of failures before the first success in a sequence of independent
15/// Bernoulli trials, each with success probability p.
16pub struct Geometric<F: Float> {
17    /// Success probability (0 < p ≤ 1)
18    pub p: F,
19    /// Random number generator
20    rand_distr: RandGeometric,
21}
22
23impl<F: Float + NumCast + std::fmt::Display> Geometric<F> {
24    /// Create a new Geometric distribution with given success probability
25    ///
26    /// # Arguments
27    ///
28    /// * `p` - Success probability (0 < p ≤ 1)
29    ///
30    /// # Returns
31    ///
32    /// * A new Geometric distribution instance
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use scirs2_stats::distributions::geometric::Geometric;
38    ///
39    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
40    /// ```
41    pub fn new(p: F) -> StatsResult<Self> {
42        // Validate parameters
43        if p <= F::zero() || p > F::one() {
44            return Err(StatsError::DomainError(
45                "Success probability must be between 0 and 1, exclusive of 0".to_string(),
46            ));
47        }
48
49        // Create RNG for Geometric distribution
50        let p_f64 = <f64 as scirs2_core::numeric::NumCast>::from(p).expect("Operation failed");
51        let rand_distr = match RandGeometric::new(p_f64) {
52            Ok(distr) => distr,
53            Err(_) => {
54                return Err(StatsError::ComputationError(
55                    "Failed to create Geometric distribution for sampling".to_string(),
56                ))
57            }
58        };
59
60        Ok(Geometric { p, rand_distr })
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 first success)
68    ///
69    /// # Returns
70    ///
71    /// * The value of the PMF at the given point
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use scirs2_stats::distributions::geometric::Geometric;
77    ///
78    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
79    /// let pmf_at_2 = geom.pmf(2.0);
80    /// assert!((pmf_at_2 - 0.147).abs() < 1e-3);
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().expect("Operation failed");
92
93        // PMF = p * (1 - p)^k
94        let q = one - self.p; // q = 1 - p
95        self.p * q.powf(F::from(k_usize).expect("Failed to convert to float"))
96    }
97
98    /// Calculate the log of the probability mass function (log-PMF) at a given point
99    ///
100    /// # Arguments
101    ///
102    /// * `k` - The point at which to evaluate the log-PMF (number of failures before first success)
103    ///
104    /// # Returns
105    ///
106    /// * The value of the log-PMF at the given point
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use scirs2_stats::distributions::geometric::Geometric;
112    ///
113    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
114    /// let log_pmf_at_2 = geom.log_pmf(2.0);
115    /// assert!((log_pmf_at_2 - (-1.9164)) < 1e-4);
116    /// ```
117    pub fn log_pmf(&self, k: F) -> F {
118        let zero = F::zero();
119        let neg_infinity = F::neg_infinity();
120
121        // Check if k is a non-negative integer
122        if k < zero || !Self::is_integer(k) {
123            return neg_infinity;
124        }
125
126        let k_usize = k.to_usize().expect("Operation failed");
127        let k_f = F::from(k_usize).expect("Failed to convert to float");
128
129        // log-PMF = ln(p) + k * ln(1 - p)
130        let one = F::one();
131        let q = one - self.p; // q = 1 - p
132
133        self.p.ln() + k_f * q.ln()
134    }
135
136    /// Calculate the cumulative distribution function (CDF) at a given point
137    ///
138    /// # Arguments
139    ///
140    /// * `k` - The point at which to evaluate the CDF
141    ///
142    /// # Returns
143    ///
144    /// * The value of the CDF at the given point
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use scirs2_stats::distributions::geometric::Geometric;
150    ///
151    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
152    /// let cdf_at_2 = geom.cdf(2.0);
153    /// assert!((cdf_at_2 - 0.657).abs() < 1e-3);
154    /// ```
155    pub fn cdf(&self, k: F) -> F {
156        let zero = F::zero();
157        let one = F::one();
158
159        // CDF = 0 for k < 0
160        if k < zero {
161            return zero;
162        }
163
164        // Floor k to handle non-integer values
165        let k_floor = k.floor();
166        let k_int = k_floor.to_usize().expect("Operation failed");
167
168        // Calculate CDF = 1 - (1 - p)^(k+1)
169        let q = one - self.p; // q = 1 - p
170        one - q.powf(F::from(k_int + 1).expect("Failed to convert to float"))
171    }
172
173    /// Inverse of the cumulative distribution function (quantile function)
174    ///
175    /// # Arguments
176    ///
177    /// * `p` - Probability value (between 0 and 1)
178    ///
179    /// # Returns
180    ///
181    /// * The value k such that CDF(k) = p
182    ///
183    /// # Examples
184    ///
185    /// ```
186    /// use scirs2_stats::distributions::geometric::Geometric;
187    ///
188    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
189    /// let quant = geom.ppf(0.5).expect("Operation failed");
190    /// assert_eq!(quant, 1.0);
191    /// ```
192    pub fn ppf(&self, pval: F) -> StatsResult<F> {
193        let zero = F::zero();
194        let one = F::one();
195
196        if pval < zero || pval > one {
197            return Err(StatsError::DomainError(
198                "Probability must be between 0 and 1".to_string(),
199            ));
200        }
201
202        // Special cases
203        if pval <= zero {
204            return Ok(zero);
205        }
206        if pval >= one {
207            return Ok(F::infinity());
208        }
209
210        // Handle case when p = 1.0 separately
211        if self.p == one {
212            return Ok(zero); // When p=1, X is always 0
213        }
214
215        // For actual calculation, we need to find the smallest k where CDF(k) >= p_val
216        // We could use the formula: k = ceiling(log(1-p_val) / log(1-p)) - 1
217        // But we'll use a simpler iterative approach instead
218
219        // Calculate the quantile manually to avoid formula issues
220        let mut k = zero;
221        while self.cdf(k) < pval {
222            k = k + one;
223        }
224
225        Ok(k)
226    }
227
228    /// Generate random samples from the distribution
229    ///
230    /// # Arguments
231    ///
232    /// * `size` - Number of samples to generate
233    ///
234    /// # Returns
235    ///
236    /// * Vector of random samples
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use scirs2_stats::distributions::geometric::Geometric;
242    ///
243    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
244    /// let samples = geom.rvs(10).expect("Operation failed");
245    /// assert_eq!(samples.len(), 10);
246    /// ```
247    pub fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
248        let mut rng = thread_rng();
249        let mut samples = Vec::with_capacity(size);
250
251        for _ in 0..size {
252            // Generate random Geometric sample
253            let sample = self.rand_distr.sample(&mut rng);
254            let sample_f = F::from(sample).expect("Failed to convert to float");
255            samples.push(sample_f);
256        }
257
258        Ok(samples)
259    }
260
261    /// Calculate the mean of the distribution
262    ///
263    /// # Returns
264    ///
265    /// * The mean of the distribution
266    ///
267    /// # Examples
268    ///
269    /// ```
270    /// use scirs2_stats::distributions::geometric::Geometric;
271    ///
272    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
273    /// let mean = geom.mean();
274    /// assert!((mean - 2.333333).abs() < 1e-6);
275    /// ```
276    pub fn mean(&self) -> F {
277        // Mean = (1-p)/p
278        let one = F::one();
279        let q = one - self.p; // q = 1 - p
280        q / self.p
281    }
282
283    /// Calculate the variance of the distribution
284    ///
285    /// # Returns
286    ///
287    /// * The variance of the distribution
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use scirs2_stats::distributions::geometric::Geometric;
293    ///
294    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
295    /// let variance = geom.var();
296    /// assert!((variance - 7.777778).abs() < 1e-6);
297    /// ```
298    pub fn var(&self) -> F {
299        // Variance = (1-p)/p^2
300        let one = F::one();
301        let q = one - self.p; // q = 1 - p
302        q / (self.p * self.p)
303    }
304
305    /// Calculate the standard deviation of the distribution
306    ///
307    /// # Returns
308    ///
309    /// * The standard deviation of the distribution
310    ///
311    /// # Examples
312    ///
313    /// ```
314    /// use scirs2_stats::distributions::geometric::Geometric;
315    ///
316    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
317    /// let std_dev = geom.std();
318    /// assert!((std_dev - 2.788867).abs() < 1e-6);
319    /// ```
320    pub fn std(&self) -> F {
321        // Std = sqrt(variance)
322        self.var().sqrt()
323    }
324
325    /// Calculate the skewness of the distribution
326    ///
327    /// # Returns
328    ///
329    /// * The skewness of the distribution
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use scirs2_stats::distributions::geometric::Geometric;
335    ///
336    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
337    /// let skewness = geom.skewness();
338    /// assert!((skewness - 2.0318891).abs() < 1e-6);
339    /// ```
340    pub fn skewness(&self) -> F {
341        // Skewness = (2-p) / sqrt(1-p)
342        let one = F::one();
343        let two = F::from(2.0).expect("Failed to convert constant to float");
344
345        let q = one - self.p; // q = 1 - p
346        (two - self.p) / q.sqrt()
347    }
348
349    /// Calculate the kurtosis of the distribution
350    ///
351    /// # Returns
352    ///
353    /// * The excess kurtosis of the distribution
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use scirs2_stats::distributions::geometric::Geometric;
359    ///
360    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
361    /// let kurtosis = geom.kurtosis();
362    /// assert!((kurtosis - 6.9) < 1e-1);
363    /// ```
364    pub fn kurtosis(&self) -> F {
365        // Excess Kurtosis = 6 + p^2/(1-p)
366        let one = F::one();
367        let six = F::from(6.0).expect("Failed to convert constant to float");
368
369        let q = one - self.p; // q = 1 - p
370        six + (self.p * self.p) / q
371    }
372
373    /// Calculate the entropy of the distribution
374    ///
375    /// # Returns
376    ///
377    /// * The entropy value
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use scirs2_stats::distributions::geometric::Geometric;
383    ///
384    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
385    /// let entropy = geom.entropy();
386    /// assert!((entropy - 2.937588) < 1e-6);
387    /// ```
388    pub fn entropy(&self) -> F {
389        // Entropy = -[(1-p)*ln(1-p) + p*ln(p)] / p
390        let one = F::one();
391        let q = one - self.p; // q = 1 - p
392
393        -(q * q.ln() + self.p * self.p.ln()) / self.p
394    }
395
396    /// Calculate the median of the distribution
397    ///
398    /// # Returns
399    ///
400    /// * The median of the distribution
401    ///
402    /// # Examples
403    ///
404    /// ```
405    /// use scirs2_stats::distributions::geometric::Geometric;
406    ///
407    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
408    /// let median = geom.median();
409    /// assert_eq!(median, 1.0);
410    /// ```
411    pub fn median(&self) -> F {
412        // Median = ceiling(ln(0.5)/ln(1-p)) - 1
413        let half = F::from(0.5).expect("Failed to convert constant to float");
414        let one = F::one();
415        let q = one - self.p; // q = 1 - p
416
417        (half.ln() / q.ln()).ceil() - one
418    }
419
420    /// Calculate the mode of the distribution
421    ///
422    /// # Returns
423    ///
424    /// * The mode of the distribution
425    ///
426    /// # Examples
427    ///
428    /// ```
429    /// use scirs2_stats::distributions::geometric::Geometric;
430    ///
431    /// let geom = Geometric::new(0.3f64).expect("Operation failed");
432    /// let mode = geom.mode();
433    /// assert_eq!(mode, 0.0);
434    /// ```
435    pub fn mode(&self) -> F {
436        // Mode = 0 (always for geometric distribution)
437        F::zero()
438    }
439
440    // Helper method to check if a value is an integer
441    fn is_integer(value: F) -> bool {
442        value == value.floor()
443    }
444}
445
446/// Create a Geometric distribution with the given parameter.
447///
448/// This is a convenience function to create a Geometric distribution with
449/// the given success probability.
450///
451/// # Arguments
452///
453/// * `p` - Success probability (0 < p ≤ 1)
454///
455/// # Returns
456///
457/// * A Geometric distribution object
458///
459/// # Examples
460///
461/// ```
462/// use scirs2_stats::distributions;
463///
464/// let g = distributions::geom(0.3f64).expect("Operation failed");
465/// let pmf_at_2 = g.pmf(2.0);
466/// assert!((pmf_at_2 - 0.147).abs() < 1e-3);
467/// ```
468#[allow(dead_code)]
469pub fn geom<F>(p: F) -> StatsResult<Geometric<F>>
470where
471    F: Float + NumCast + std::fmt::Display,
472{
473    Geometric::new(p)
474}
475
476/// Implementation of SampleableDistribution for Geometric
477impl<F: Float + NumCast + std::fmt::Display> SampleableDistribution<F> for Geometric<F> {
478    fn rvs(&self, size: usize) -> StatsResult<Vec<F>> {
479        self.rvs(size)
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486    use approx::assert_relative_eq;
487
488    #[test]
489    fn test_geometric_creation() {
490        // Valid p values
491        let geom1 = Geometric::new(0.3).expect("Operation failed");
492        assert_eq!(geom1.p, 0.3);
493
494        let geom2 = Geometric::new(1.0).expect("Operation failed");
495        assert_eq!(geom2.p, 1.0);
496
497        // Invalid p values
498        assert!(Geometric::<f64>::new(0.0).is_err());
499        assert!(Geometric::<f64>::new(-0.1).is_err());
500        assert!(Geometric::<f64>::new(1.1).is_err());
501    }
502
503    #[test]
504    fn test_geometric_pmf() {
505        // Geometric(0.3)
506        let geom = Geometric::new(0.3).expect("Operation failed");
507
508        // PMF values for different k
509        assert_relative_eq!(geom.pmf(0.0), 0.3, epsilon = 1e-10);
510        assert_relative_eq!(geom.pmf(1.0), 0.3 * 0.7, epsilon = 1e-10);
511        assert_relative_eq!(geom.pmf(2.0), 0.3 * 0.7 * 0.7, epsilon = 1e-10);
512        assert_relative_eq!(geom.pmf(3.0), 0.3 * 0.7 * 0.7 * 0.7, epsilon = 1e-10);
513
514        // PMF should be 0 for negative k
515        assert_eq!(geom.pmf(-1.0), 0.0);
516
517        // PMF should be 0 for non-integer k
518        assert_eq!(geom.pmf(1.5), 0.0);
519
520        // Special case: Geometric(1.0)
521        let geom_p1 = Geometric::new(1.0).expect("Operation failed");
522        assert_eq!(geom_p1.pmf(0.0), 1.0);
523        assert_eq!(geom_p1.pmf(1.0), 0.0);
524        assert_eq!(geom_p1.pmf(2.0), 0.0);
525    }
526
527    #[test]
528    fn test_geometric_log_pmf() {
529        // Geometric(0.3)
530        let geom = Geometric::new(0.3).expect("Operation failed");
531
532        // Check log_pmf against pmf
533        for k in 0..5 {
534            let k_f = k as f64;
535            let pmf = geom.pmf(k_f);
536            let log_pmf = geom.log_pmf(k_f);
537
538            if pmf > 0.0 {
539                assert_relative_eq!(log_pmf, pmf.ln(), epsilon = 1e-10);
540            } else {
541                assert!(log_pmf.is_infinite() && log_pmf.is_sign_negative());
542            }
543        }
544
545        // log_pmf should be -infinity for negative k or non-integer k
546        assert!(geom.log_pmf(-1.0).is_infinite() && geom.log_pmf(-1.0).is_sign_negative());
547        assert!(geom.log_pmf(1.5).is_infinite() && geom.log_pmf(1.5).is_sign_negative());
548    }
549
550    #[test]
551    fn test_geometric_cdf() {
552        // Geometric(0.3)
553        let geom = Geometric::new(0.3).expect("Operation failed");
554
555        // CDF for k = 0 should be 1 - (1-p)^1 = 1 - 0.7^1 = 0.3
556        assert_relative_eq!(geom.cdf(0.0), 0.3, epsilon = 1e-10);
557
558        // CDF for k = 1 should be 1 - (1-p)^2 = 1 - 0.7^2 = 1 - 0.49 = 0.51
559        assert_relative_eq!(geom.cdf(1.0), 0.51, epsilon = 1e-10);
560
561        // CDF for k = 2 should be 1 - (1-p)^3 = 1 - 0.7^3 = 1 - 0.343 = 0.657
562        assert_relative_eq!(geom.cdf(2.0), 0.657, epsilon = 1e-10);
563
564        // CDF for k = 3 should be 1 - (1-p)^4 = 1 - 0.7^4 = 1 - 0.2401 = 0.7599
565        assert_relative_eq!(geom.cdf(3.0), 0.7599, epsilon = 1e-10);
566
567        // CDF should be 0 for k < 0
568        assert_eq!(geom.cdf(-1.0), 0.0);
569
570        // Non-integer k should use floor of k
571        assert_relative_eq!(geom.cdf(2.5), geom.cdf(2.0), epsilon = 1e-10);
572
573        // Special case: Geometric(1.0)
574        let geom_p1 = Geometric::new(1.0).expect("Operation failed");
575        assert_eq!(geom_p1.cdf(0.0), 1.0);
576        assert_eq!(geom_p1.cdf(1.0), 1.0);
577    }
578
579    #[test]
580    fn test_geometric_ppf() {
581        // Geometric(0.3)
582        let geom = Geometric::new(0.3).expect("Operation failed");
583
584        // Verify the ppf against the cdf
585        for k in 0..10 {
586            let k_f = k as f64;
587            let cdf_k = geom.cdf(k_f);
588
589            // For any value p such that CDF(k-1) < p <= CDF(k),
590            // the inverse CDF (ppf) should return k
591            if k > 0 {
592                let cdf_k_minus_1 = geom.cdf(k_f - 1.0);
593                let p_mid = (cdf_k + cdf_k_minus_1) / 2.0; // A value between CDF(k-1) and CDF(k)
594                assert_eq!(geom.ppf(p_mid).expect("Operation failed"), k_f);
595            } else {
596                // For k=0, just test a small value of p
597                assert_eq!(geom.ppf(cdf_k / 2.0).expect("Operation failed"), k_f);
598            }
599        }
600
601        // Test edge cases
602        assert_eq!(geom.ppf(0.0).expect("Operation failed"), 0.0);
603        assert!(geom.ppf(1.0).expect("Operation failed").is_infinite()); // For p=1, the result should be infinity
604
605        // The PPF should be the inverse of the CDF
606        let test_values = vec![0.1, 0.3, 0.5, 0.7, 0.9];
607        for p in test_values {
608            let k = geom.ppf(p).expect("Operation failed");
609
610            // Special case for p = 1.0 which gives infinity
611            if p == 1.0 {
612                continue;
613            }
614
615            // For discrete distributions, we check that F(k-1) < p ≤ F(k)
616            if k > 0.0 {
617                let k_minus_1 = k - 1.0;
618                let cdf_k_minus_1 = geom.cdf(k_minus_1);
619                let cdf_k = geom.cdf(k);
620                assert!(cdf_k_minus_1 < p && p <= cdf_k);
621            } else {
622                // For k = 0, just check that p ≤ F(0)
623                let cdf_k = geom.cdf(k);
624                assert!(p <= cdf_k);
625            }
626        }
627
628        // Invalid probability values
629        assert!(geom.ppf(-0.1).is_err());
630        assert!(geom.ppf(1.1).is_err());
631    }
632
633    #[test]
634    fn test_geometric_rvs() {
635        // Geometric(0.3)
636        let geom = Geometric::new(0.3).expect("Operation failed");
637
638        // Generate samples
639        let samples = geom.rvs(100).expect("Operation failed");
640
641        // Check number of samples
642        assert_eq!(samples.len(), 100);
643
644        // All samples should be non-negative integers
645        for &s in &samples {
646            assert!(s >= 0.0);
647            assert_eq!(s, s.floor());
648        }
649
650        // Mean should be approximately (1-p)/p = 0.7/0.3 = 2.333...
651        let sum: f64 = samples.iter().sum();
652        let mean = sum / samples.len() as f64;
653
654        // The mean could deviate somewhat due to randomness
655        assert!(mean > 1.0 && mean < 4.0);
656    }
657
658    #[test]
659    fn test_geometric_moments() {
660        // Test with p = 0.3
661        let geom = Geometric::new(0.3).expect("Operation failed");
662
663        // Mean = (1-p)/p = 0.7/0.3 = 2.333...
664        assert_relative_eq!(geom.mean(), 2.333333, epsilon = 1e-6);
665
666        // Variance = (1-p)/p^2 = 0.7/(0.3^2) = 0.7/0.09 = 7.777...
667        assert_relative_eq!(geom.var(), 7.777778, epsilon = 1e-6);
668
669        // Standard deviation = sqrt(variance) = sqrt(7.777...) ≈ 2.789...
670        assert_relative_eq!(geom.std(), 2.788867, epsilon = 1e-6);
671
672        // Skewness = (2-p)/sqrt(1-p) = (2-0.3)/sqrt(0.7) = 1.7/sqrt(0.7) ≈ 2.031...
673        assert_relative_eq!(geom.skewness(), 2.031889, epsilon = 1e-6);
674
675        // Kurtosis = 6 + p^2/(1-p) = 6 + 0.3^2/0.7 = 6 + 0.09/0.7 ≈ 6.129...
676        assert_relative_eq!(geom.kurtosis(), 6.128571, epsilon = 1e-6);
677
678        // Entropy is more complex, we'll just check it's positive
679        assert!(geom.entropy() > 0.0);
680
681        // Mode is always 0 for geometric distribution
682        assert_eq!(geom.mode(), 0.0);
683
684        // Median = ceiling(ln(0.5)/ln(1-p)) - 1 = ceiling(ln(0.5)/ln(0.7)) - 1
685        // ln(0.5) ≈ -0.693, ln(0.7) ≈ -0.357, so ln(0.5)/ln(0.7) ≈ 1.94, ceiling(1.94) = 2, 2-1 = 1
686        assert_eq!(geom.median(), 1.0);
687    }
688
689    #[test]
690    fn test_geometric_edge_cases() {
691        // Test with p = 1.0 (special case, only k=0 has non-zero probability)
692        let geom_p1 = Geometric::new(1.0).expect("Operation failed");
693
694        // PMF is 1 for k=0, 0 elsewhere
695        assert_eq!(geom_p1.pmf(0.0), 1.0);
696        assert_eq!(geom_p1.pmf(1.0), 0.0);
697
698        // CDF is 1 for all k >= 0
699        assert_eq!(geom_p1.cdf(0.0), 1.0);
700        assert_eq!(geom_p1.cdf(1.0), 1.0);
701
702        // Mean = (1-p)/p = 0/1 = 0
703        assert_eq!(geom_p1.mean(), 0.0);
704
705        // Variance = (1-p)/p^2 = 0/1 = 0
706        assert_eq!(geom_p1.var(), 0.0);
707
708        // PPF is 0 for all p < 1, infinity for p=1
709        assert_eq!(geom_p1.ppf(0.5).expect("Operation failed"), 0.0);
710        assert!(geom_p1.ppf(1.0).expect("Operation failed").is_infinite());
711    }
712}