numra_stats/distributions/
f_dist.rs1use numra_core::Scalar;
8use numra_special::{betainc, lgamma};
9use rand::RngCore;
10
11use super::gamma_dist::GammaDist;
12use super::ContinuousDistribution;
13
14#[derive(Clone, Debug)]
16pub struct FDist<S: Scalar> {
17 pub df1: S,
18 pub df2: S,
19}
20
21impl<S: Scalar> FDist<S> {
22 pub fn new(df1: S, df2: S) -> Self {
23 Self { df1, df2 }
24 }
25}
26
27impl<S: Scalar> ContinuousDistribution<S> for FDist<S> {
28 fn pdf(&self, x: S) -> S {
29 if x <= S::ZERO {
30 return S::ZERO;
31 }
32 let half = S::HALF;
33 let d1 = self.df1;
34 let d2 = self.df2;
35 let log_pdf = half * d1 * (d1 * x / (d1 * x + d2)).ln()
36 + half * d2 * (d2 / (d1 * x + d2)).ln()
37 - x.ln()
38 - lbeta(d1 * half, d2 * half);
39 log_pdf.exp()
40 }
41
42 fn cdf(&self, x: S) -> S {
43 if x <= S::ZERO {
44 return S::ZERO;
45 }
46 let half = S::HALF;
47 let d1 = self.df1;
48 let d2 = self.df2;
49 let z = d1 * x / (d1 * x + d2);
50 betainc(d1 * half, d2 * half, z)
51 }
52
53 fn quantile(&self, p: S) -> S {
54 if p <= S::ZERO {
55 return S::ZERO;
56 }
57 if p >= S::ONE {
58 return S::INFINITY;
59 }
60 let mut x = S::ONE; for _ in 0..100 {
63 let f_val = self.cdf(x) - p;
64 let f_prime = self.pdf(x);
65 if f_prime.to_f64().abs() < 1e-300 {
66 break;
67 }
68 let step = f_val / f_prime;
69 x -= step;
70 if x <= S::ZERO {
71 x = S::from_f64(1e-10);
72 }
73 if step.to_f64().abs() < 1e-12 * x.to_f64().abs() {
74 break;
75 }
76 }
77 x
78 }
79
80 fn mean(&self) -> S {
81 let two = S::TWO;
82 if self.df2 > two {
83 self.df2 / (self.df2 - two)
84 } else {
85 S::INFINITY
86 }
87 }
88
89 fn variance(&self) -> S {
90 let two = S::TWO;
91 let four = S::from_f64(4.0);
92 if self.df2 > four {
93 let d1 = self.df1;
94 let d2 = self.df2;
95 two * d2 * d2 * (d1 + d2 - two) / (d1 * (d2 - two) * (d2 - two) * (d2 - four))
96 } else {
97 S::INFINITY
98 }
99 }
100
101 fn sample(&self, rng: &mut dyn RngCore) -> S {
102 let half = S::HALF;
104 let g1 = GammaDist::new(self.df1 * half, half);
105 let g2 = GammaDist::new(self.df2 * half, half);
106 let x1 = g1.sample(rng);
107 let x2 = g2.sample(rng);
108 (x1 / self.df1) / (x2 / self.df2)
109 }
110}
111
112fn lbeta<S: Scalar>(a: S, b: S) -> S {
113 lgamma(a) + lgamma(b) - lgamma(a + b)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_f_dist_mean() {
122 let f = FDist::new(5.0_f64, 10.0);
123 assert!((f.mean() - 10.0 / 8.0).abs() < 1e-12);
124 }
125
126 #[test]
127 fn test_f_dist_cdf_at_zero() {
128 let f = FDist::new(3.0_f64, 5.0);
129 assert!(f.cdf(0.0).abs() < 1e-10);
130 }
131
132 #[test]
133 fn test_f_dist_quantile_roundtrip() {
134 let f = FDist::new(5.0_f64, 10.0);
135 for &p in &[0.1, 0.5, 0.9] {
136 let x = f.quantile(p);
137 let p2 = f.cdf(x);
138 assert!((p - p2).abs() < 1e-5, "p={}, p2={}", p, p2);
139 }
140 }
141}