1use solow_distributions::special::lgamma;
18use solow_distributions::{norm_cdf, norm_pdf};
19
20const N_INNER: usize = 100;
22const N_OUTER: usize = 80;
24
25fn gauss_legendre(n: usize) -> (Vec<f64>, Vec<f64>) {
30 let mut x = vec![0.0; n];
31 let mut w = vec![0.0; n];
32 let m = n.div_ceil(2);
33 let nf = n as f64;
34 for i in 0..m {
35 let mut z = (std::f64::consts::PI * (i as f64 + 0.75) / (nf + 0.5)).cos();
37 let mut pp = 0.0;
38 for _ in 0..100 {
39 let mut p1 = 1.0;
41 let mut p2 = 0.0;
42 for j in 0..n {
43 let p3 = p2;
44 p2 = p1;
45 let jf = j as f64;
46 p1 = ((2.0 * jf + 1.0) * z * p2 - jf * p3) / (jf + 1.0);
47 }
48 pp = nf * (z * p1 - p2) / (z * z - 1.0);
50 let z1 = z;
51 z = z1 - p1 / pp;
52 if (z - z1).abs() <= 1e-15 {
53 break;
54 }
55 }
56 x[i] = -z;
57 x[n - 1 - i] = z;
58 let wt = 2.0 / ((1.0 - z * z) * pp * pp);
59 w[i] = wt;
60 w[n - 1 - i] = wt;
61 }
62 (x, w)
63}
64
65#[inline]
67fn phi_cdf(z: f64) -> f64 {
68 norm_cdf(z)
69}
70
71fn range_cdf(w: f64, k: f64, nodes: &(Vec<f64>, Vec<f64>)) -> f64 {
73 if w <= 0.0 {
74 return 0.0;
75 }
76 let a = -8.0;
78 let b = 8.0 + w;
79 let half = 0.5 * (b - a);
80 let mid = 0.5 * (b + a);
81 let (x, wt) = nodes;
82 let mut acc = 0.0;
83 for i in 0..x.len() {
84 let z = half * x[i] + mid;
85 let inner = phi_cdf(z) - phi_cdf(z - w);
86 acc += wt[i] * norm_pdf(z) * inner.powf(k - 1.0);
87 }
88 k * half * acc
89}
90
91pub fn srange_cdf(q: f64, k: f64, df: f64) -> f64 {
93 if q <= 0.0 {
94 return 0.0;
95 }
96 let inner = gauss_legendre(N_INNER);
97 if !df.is_finite() {
98 return range_cdf(q, k, &inner);
99 }
100 let outer = gauss_legendre(N_OUTER);
101 let logc = (df / 2.0) * df.ln() - (df / 2.0 - 1.0) * std::f64::consts::LN_2 - lgamma(df / 2.0);
103 let a = 1e-9;
104 let b = 1.0 + 10.0 / df.sqrt();
105 let half = 0.5 * (b - a);
106 let mid = 0.5 * (b + a);
107 let (x, wt) = &outer;
108 let mut acc = 0.0;
109 for i in 0..x.len() {
110 let u = half * x[i] + mid;
111 let f_u = (logc + (df - 1.0) * u.ln() - df * u * u / 2.0).exp();
112 acc += wt[i] * f_u * range_cdf(q * u, k, &inner);
113 }
114 let v = half * acc;
115 v.clamp(0.0, 1.0)
116}
117
118pub fn srange_sf(q: f64, k: f64, df: f64) -> f64 {
120 1.0 - srange_cdf(q, k, df)
121}
122
123pub fn srange_ppf(p: f64, k: f64, df: f64) -> f64 {
128 if p <= 0.0 {
129 return 0.0;
130 }
131 if p >= 1.0 {
132 return f64::INFINITY;
133 }
134 let mut lo = 0.0;
135 let mut hi = 100.0;
136 for _ in 0..200 {
137 let mid = 0.5 * (lo + hi);
138 if srange_cdf(mid, k, df) < p {
139 lo = mid;
140 } else {
141 hi = mid;
142 }
143 if hi - lo <= 1e-12 * (1.0 + hi) {
144 break;
145 }
146 }
147 0.5 * (lo + hi)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn gauss_legendre_integrates_polynomial() {
156 let (x, w) = gauss_legendre(2);
158 let val: f64 = x.iter().zip(&w).map(|(&xi, &wi)| wi * xi * xi).sum();
159 assert!((val - 2.0 / 3.0).abs() < 1e-12);
160 }
161
162 #[test]
163 fn cdf_is_monotone_and_bounded() {
164 let a = srange_cdf(2.0, 3.0, 20.0);
165 let b = srange_cdf(4.0, 3.0, 20.0);
166 assert!(a > 0.0 && a < b && b < 1.0);
167 }
168
169 #[test]
170 fn ppf_inverts_cdf() {
171 let q = srange_ppf(0.95, 4.0, 30.0);
172 let p = srange_cdf(q, 4.0, 30.0);
173 assert!((p - 0.95).abs() < 1e-8);
174 }
175}