qualia_core_db/solvers/statistics/distributions/
special.rs1const EPS: f64 = 1e-15;
19const ITMAX: usize = 300;
20const FPMIN: f64 = 1e-300;
22
23pub fn ln_gamma(x: f64) -> f64 {
27 const G: f64 = 7.0;
29 const C: [f64; 9] = [
30 0.999_999_999_999_809_93,
31 676.520_368_121_885_1,
32 -1_259.139_216_722_402_8,
33 771.323_428_777_653_13,
34 -176.615_029_162_140_59,
35 12.507_343_278_686_905,
36 -0.138_571_095_265_720_12,
37 9.984_369_578_019_571_6e-6,
38 1.505_632_735_149_311_6e-7,
39 ];
40 if x < 0.5 {
41 let pi = std::f64::consts::PI;
43 return (pi / (pi * x).sin()).ln() - ln_gamma(1.0 - x);
44 }
45 let x = x - 1.0;
46 let mut a = C[0];
47 let t = x + G + 0.5;
48 for (i, &ci) in C.iter().enumerate().skip(1) {
49 a += ci / (x + i as f64);
50 }
51 0.5 * (2.0 * std::f64::consts::PI).ln() + (x + 0.5) * t.ln() - t + a.ln()
52}
53
54pub fn gamma(x: f64) -> f64 {
56 ln_gamma(x).exp()
57 * if x < 0.5 && (x.floor() == x) {
58 f64::NAN
59 } else {
60 1.0
61 }
62}
63
64pub fn gammp(a: f64, x: f64) -> f64 {
67 debug_assert!(a > 0.0);
68 if x <= 0.0 {
69 return 0.0;
70 }
71 if x < a + 1.0 {
72 gser(a, x)
73 } else {
74 1.0 - gcf(a, x)
75 }
76}
77
78pub fn gammq(a: f64, x: f64) -> f64 {
80 1.0 - gammp(a, x)
81}
82
83fn gser(a: f64, x: f64) -> f64 {
85 let mut ap = a;
86 let mut sum = 1.0 / a;
87 let mut del = sum;
88 for _ in 0..ITMAX {
89 ap += 1.0;
90 del *= x / ap;
91 sum += del;
92 if del.abs() < sum.abs() * EPS {
93 break;
94 }
95 }
96 sum * (-x + a * x.ln() - ln_gamma(a)).exp()
97}
98
99fn gcf(a: f64, x: f64) -> f64 {
101 let mut b = x + 1.0 - a;
102 let mut c = 1.0 / FPMIN;
103 let mut d = 1.0 / b;
104 let mut h = d;
105 for i in 1..=ITMAX {
106 let an = -(i as f64) * (i as f64 - a);
107 b += 2.0;
108 d = an * d + b;
109 if d.abs() < FPMIN {
110 d = FPMIN;
111 }
112 c = b + an / c;
113 if c.abs() < FPMIN {
114 c = FPMIN;
115 }
116 d = 1.0 / d;
117 let del = d * c;
118 h *= del;
119 if (del - 1.0).abs() < EPS {
120 break;
121 }
122 }
123 (-x + a * x.ln() - ln_gamma(a)).exp() * h
124}
125
126pub fn betai(a: f64, b: f64, x: f64) -> f64 {
129 debug_assert!(a > 0.0 && b > 0.0);
130 if x <= 0.0 {
131 return 0.0;
132 }
133 if x >= 1.0 {
134 return 1.0;
135 }
136 let bt = (ln_gamma(a + b) - ln_gamma(a) - ln_gamma(b) + a * x.ln() + b * (1.0 - x).ln()).exp();
137 if x < (a + 1.0) / (a + b + 2.0) {
138 bt * betacf(a, b, x) / a
139 } else {
140 1.0 - bt * betacf(b, a, 1.0 - x) / b
141 }
142}
143
144fn betacf(a: f64, b: f64, x: f64) -> f64 {
146 let qab = a + b;
147 let qap = a + 1.0;
148 let qam = a - 1.0;
149 let mut c = 1.0;
150 let mut d = 1.0 - qab * x / qap;
151 if d.abs() < FPMIN {
152 d = FPMIN;
153 }
154 d = 1.0 / d;
155 let mut h = d;
156 for m in 1..=ITMAX {
157 let m = m as f64;
158 let m2 = 2.0 * m;
159 let aa = m * (b - m) * x / ((qam + m2) * (a + m2));
161 d = 1.0 + aa * d;
162 if d.abs() < FPMIN {
163 d = FPMIN;
164 }
165 c = 1.0 + aa / c;
166 if c.abs() < FPMIN {
167 c = FPMIN;
168 }
169 d = 1.0 / d;
170 h *= d * c;
171 let aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
173 d = 1.0 + aa * d;
174 if d.abs() < FPMIN {
175 d = FPMIN;
176 }
177 c = 1.0 + aa / c;
178 if c.abs() < FPMIN {
179 c = FPMIN;
180 }
181 d = 1.0 / d;
182 let del = d * c;
183 h *= del;
184 if (del - 1.0).abs() < EPS {
185 break;
186 }
187 }
188 h
189}
190
191pub fn erf(x: f64) -> f64 {
193 if x == 0.0 {
194 0.0
195 } else if x > 0.0 {
196 gammp(0.5, x * x)
197 } else {
198 -gammp(0.5, x * x)
199 }
200}
201
202pub fn erfc(x: f64) -> f64 {
204 1.0 - erf(x)
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 const TOL: f64 = 1e-9;
211
212 #[test]
213 fn ln_gamma_known_values() {
214 assert!((ln_gamma(1.0)).abs() < TOL); assert!((ln_gamma(2.0)).abs() < TOL); assert!((ln_gamma(5.0) - 24.0_f64.ln()).abs() < 1e-9); assert!((ln_gamma(0.5) - std::f64::consts::PI.sqrt().ln()).abs() < 1e-9);
219 }
220
221 #[test]
222 fn erf_known_values() {
223 assert!((erf(0.0)).abs() < TOL);
224 assert!((erf(1.0) - 0.842_700_792_949_715).abs() < 1e-9);
225 assert!((erf(-1.0) + 0.842_700_792_949_715).abs() < 1e-9);
226 assert!((erf(2.0) - 0.995_322_265_018_953).abs() < 1e-9);
227 assert!((erfc(0.0) - 1.0).abs() < TOL);
228 }
229
230 #[test]
231 fn gammp_is_exponential_cdf_for_a_one() {
232 for &x in &[0.5, 1.0, 2.5, 5.0] {
234 assert!((gammp(1.0, x) - (1.0 - (-x).exp())).abs() < 1e-10, "x={x}");
235 }
236 assert!((gammp(1.0, 0.0)).abs() < TOL);
237 }
238
239 #[test]
240 fn gammp_gammq_complementary() {
241 for &(a, x) in &[(0.5, 0.3), (2.0, 1.0), (3.5, 7.0), (10.0, 4.0)] {
242 assert!((gammp(a, x) + gammq(a, x) - 1.0).abs() < 1e-12);
243 }
244 }
245
246 #[test]
247 fn betai_endpoints_and_symmetry() {
248 for &x in &[0.0, 0.25, 0.5, 0.9, 1.0] {
250 assert!((betai(1.0, 1.0, x) - x).abs() < 1e-10, "x={x}");
251 }
252 for &(a, b, x) in &[(2.0, 3.0, 0.4), (0.5, 2.5, 0.7), (5.0, 1.5, 0.2)] {
254 assert!(
255 (betai(a, b, x) - (1.0 - betai(b, a, 1.0 - x))).abs() < 1e-10,
256 "{a},{b},{x}"
257 );
258 }
259 }
260}