solow_stats/
normality_ext.rs1use solow_core::{Error, Result};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct GofResult {
14 pub statistic: f64,
16 pub pvalue: f64,
18}
19
20pub fn shapiro_wilk(x: &[f64]) -> Result<GofResult> {
26 let n = x.len();
27 if n < 3 || n > 5000 {
28 return Err(Error::Value("shapiro_wilk: sample size must be in [3, 5000]".into()));
29 }
30 let mut sorted: Vec<f64> = x.to_vec();
31 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
32 let mut m_i = vec![0.0_f64; n];
35 for i in 0..n {
36 let q = ((i + 1) as f64 - 3.0 / 8.0) / (n as f64 + 1.0 / 4.0);
37 m_i[i] = inv_normal_cdf(q);
38 }
39 let m_sq: f64 = m_i.iter().map(|m| m * m).sum();
41 let m_sq_sqrt = m_sq.sqrt().max(1e-30);
42 let mut a = vec![0.0_f64; n];
43 let u = 1.0 / (n as f64).sqrt();
45 let a_n = -2.706_056 * u.powi(5)
46 + 4.434_685 * u.powi(4)
47 - 2.071_190 * u.powi(3)
48 - 0.147_981 * u.powi(2)
49 + 0.221_157 * u
50 + m_i[n - 1] / m_sq_sqrt;
51 let a_n1 = -3.582_633 * u.powi(5)
52 + 5.682_633 * u.powi(4)
53 - 1.752_460 * u.powi(3)
54 - 0.293_762 * u.powi(2)
55 + 0.042_981 * u
56 + m_i[n - 2] / m_sq_sqrt;
57 a[n - 1] = a_n;
58 a[n - 2] = a_n1;
59 a[0] = -a_n;
60 if n > 3 {
61 a[1] = -a_n1;
62 }
63 let e: f64 = m_sq - 2.0 * m_i[n - 1].powi(2) - 2.0 * m_i[n - 2].powi(2);
64 let denom = (1.0 - 2.0 * a_n * a_n - 2.0 * a_n1 * a_n1).max(1e-30);
65 let ep = (e / denom).sqrt().max(1e-30);
66 for i in 2..(n - 2) {
67 a[i] = m_i[i] / ep;
68 }
69 let mean: f64 = sorted.iter().sum::<f64>() / n as f64;
70 let ssd: f64 = sorted.iter().map(|v| (v - mean).powi(2)).sum();
71 let mut num = 0.0_f64;
72 for i in 0..n {
73 num += a[i] * sorted[i];
74 }
75 let w = (num * num) / ssd.max(1e-300);
76 let pvalue = if n == 3 {
78 let pi = std::f64::consts::PI;
80 6.0 * (w.asin().sqrt() - (3.0_f64.sqrt() / 2.0).asin()) / pi
81 } else if n <= 11 {
82 let gamma = -2.273 + 0.459 * n as f64;
83 let mu = 0.5440 - 0.399_78 * n as f64 + 0.025_054 * (n as f64).powi(2)
84 - 0.000_671_4 * (n as f64).powi(3);
85 let sigma = (-0.312_98 + 0.729_87 * n as f64 - 0.325_88 * (n as f64).powi(2)
86 + 0.0104_54 * (n as f64).powi(3))
87 .exp();
88 let z = (gamma - (1.0 - w).ln()) / sigma - mu / sigma;
89 1.0 - standard_normal_cdf(z)
90 } else {
91 let mu = 0.0038915 * (n as f64).ln().powi(3) - 0.083751 * (n as f64).ln().powi(2)
92 - 0.31082 * (n as f64).ln()
93 - 1.5861;
94 let sigma = (0.0030302 * (n as f64).ln().powi(2)
95 - 0.082676 * (n as f64).ln()
96 - 0.4803).exp();
97 let z = ((1.0 - w).ln() - mu) / sigma;
98 1.0 - standard_normal_cdf(z)
99 };
100 Ok(GofResult { statistic: w, pvalue: pvalue.clamp(0.0, 1.0) })
101}
102
103pub fn anderson_darling(x: &[f64]) -> Result<GofResult> {
106 let n = x.len();
107 if n < 8 {
108 return Err(Error::Value("anderson_darling: need n ≥ 8".into()));
109 }
110 let mean: f64 = x.iter().sum::<f64>() / n as f64;
111 let var: f64 = x.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (n - 1).max(1) as f64;
112 let sd = var.sqrt().max(1e-30);
113 let mut zi: Vec<f64> = x.iter().map(|v| (v - mean) / sd).collect();
114 zi.sort_by(|a, b| a.partial_cmp(b).unwrap());
115 let mut a2 = 0.0_f64;
116 for (i, &z) in zi.iter().enumerate() {
117 let phi = standard_normal_cdf(z);
118 let phi_c = 1.0 - phi;
119 a2 += (2 * (i + 1) - 1) as f64
120 * (phi.max(1e-300).ln() + phi_c.max(1e-300).ln());
121 }
122 a2 = -(n as f64) - a2 / n as f64;
123 let a2_adj = a2 * (1.0 + 0.75 / n as f64 + 2.25 / (n as f64).powi(2));
124 let pvalue = if a2_adj < 0.2 {
126 1.0 - (-13.436 + 101.14 * a2_adj - 223.73 * a2_adj.powi(2)).exp()
127 } else if a2_adj < 0.34 {
128 1.0 - (-8.318 + 42.796 * a2_adj - 59.938 * a2_adj.powi(2)).exp()
129 } else if a2_adj < 0.6 {
130 (0.9177 - 4.279 * a2_adj - 1.38 * a2_adj.powi(2)).exp()
131 } else {
132 (1.2937 - 5.709 * a2_adj + 0.0186 * a2_adj.powi(2)).exp()
133 };
134 Ok(GofResult { statistic: a2_adj, pvalue: pvalue.clamp(0.0, 1.0) })
135}
136
137pub fn ks_2samp(a: &[f64], b: &[f64]) -> Result<GofResult> {
139 if a.is_empty() || b.is_empty() {
140 return Err(Error::Value("ks_2samp: both samples must be non-empty".into()));
141 }
142 let mut ai: Vec<f64> = a.to_vec();
143 let mut bi: Vec<f64> = b.to_vec();
144 ai.sort_by(|x, y| x.partial_cmp(y).unwrap());
145 bi.sort_by(|x, y| x.partial_cmp(y).unwrap());
146 let mut i = 0_usize;
147 let mut j = 0_usize;
148 let mut d = 0.0_f64;
149 let na = ai.len() as f64;
150 let nb = bi.len() as f64;
151 while i < ai.len() && j < bi.len() {
152 let cdf_a = (i as f64) / na;
153 let cdf_b = (j as f64) / nb;
154 let curr = (cdf_a - cdf_b).abs();
155 if curr > d {
156 d = curr;
157 }
158 if ai[i] < bi[j] {
159 i += 1;
160 } else if ai[i] > bi[j] {
161 j += 1;
162 } else {
163 i += 1;
164 j += 1;
165 }
166 }
167 let en = (na * nb / (na + nb)).sqrt();
168 let pvalue = ks_p((en + 0.12 + 0.11 / en) * d);
169 Ok(GofResult { statistic: d, pvalue: pvalue.clamp(0.0, 1.0) })
170}
171
172pub fn runs_test(x: &[f64]) -> Result<GofResult> {
174 let n = x.len();
175 if n < 2 {
176 return Err(Error::Value("runs_test: need n ≥ 2".into()));
177 }
178 let median = {
179 let mut sorted: Vec<f64> = x.to_vec();
180 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
181 sorted[n / 2]
182 };
183 let mut n1 = 0_usize;
184 let mut n2 = 0_usize;
185 let mut runs = 1_usize;
186 let mut prev: Option<bool> = None;
187 for &v in x {
188 if v == median {
189 continue;
190 }
191 let up = v > median;
192 if up {
193 n1 += 1;
194 } else {
195 n2 += 1;
196 }
197 if let Some(p) = prev {
198 if p != up {
199 runs += 1;
200 }
201 }
202 prev = Some(up);
203 }
204 if n1 == 0 || n2 == 0 {
205 return Ok(GofResult { statistic: runs as f64, pvalue: 1.0 });
206 }
207 let n1f = n1 as f64;
208 let n2f = n2 as f64;
209 let total = n1f + n2f;
210 let mean_r = 2.0 * n1f * n2f / total + 1.0;
211 let var_r = (2.0 * n1f * n2f * (2.0 * n1f * n2f - total)) / (total * total * (total - 1.0));
212 let z = (runs as f64 - mean_r) / var_r.sqrt().max(1e-30);
213 let pvalue = 2.0 * (1.0 - standard_normal_cdf(z.abs()));
214 Ok(GofResult { statistic: z, pvalue: pvalue.clamp(0.0, 1.0) })
215}
216
217fn ks_p(lambda: f64) -> f64 {
218 if lambda < 0.18 {
220 return 1.0;
221 }
222 let x = lambda * lambda;
223 let mut sum = 0.0_f64;
224 for j in 1..101 {
225 let term = (-(2 * j * j) as f64 * x).exp();
226 sum += (if j % 2 == 1 { 1.0 } else { -1.0 }) * term;
227 }
228 (2.0 * sum).clamp(0.0, 1.0)
229}
230
231fn standard_normal_cdf(z: f64) -> f64 {
232 0.5 * (1.0 + erf(z / std::f64::consts::SQRT_2))
233}
234
235fn erf(x: f64) -> f64 {
236 let a1 = 0.254_829_592;
237 let a2 = -0.284_496_736;
238 let a3 = 1.421_413_741;
239 let a4 = -1.453_152_027;
240 let a5 = 1.061_405_429;
241 let p = 0.327_591_1;
242 let sign = if x < 0.0 { -1.0 } else { 1.0 };
243 let ax = x.abs();
244 let t = 1.0 / (1.0 + p * ax);
245 let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-ax * ax).exp();
246 sign * y
247}
248
249fn inv_normal_cdf(p: f64) -> f64 {
250 let a = [
252 -3.969_683_028_665_376e1,
253 2.209_460_984_245_205e2,
254 -2.759_285_104_469_687e2,
255 1.383_577_518_672_69e2,
256 -3.066_479_806_614_716e1,
257 2.506_628_277_459_239,
258 ];
259 let b = [
260 -5.447_609_879_822_406e1,
261 1.615_858_368_580_409e2,
262 -1.556_989_798_598_866e2,
263 6.680_131_188_771_972e1,
264 -1.328_068_155_288_572e1,
265 ];
266 let c = [
267 -7.784_894_002_430_293e-3,
268 -3.223_964_580_411_365e-1,
269 -2.400_758_277_161_838,
270 -2.549_732_539_343_734,
271 4.374_664_141_464_968,
272 2.938_163_982_698_783,
273 ];
274 let d = [
275 7.784_695_709_041_462e-3,
276 3.224_671_290_700_398e-1,
277 2.445_134_137_142_996,
278 3.754_408_661_907_416,
279 ];
280 let p_low = 0.02425;
281 let p_high = 1.0 - p_low;
282 if p < p_low {
283 let q = (-2.0 * p.ln()).sqrt();
284 return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
285 / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0);
286 }
287 if p <= p_high {
288 let q = p - 0.5;
289 let r = q * q;
290 return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q
291 / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1.0);
292 }
293 let q = (-2.0 * (1.0 - p).ln()).sqrt();
294 -((((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
295 / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0))
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn shapiro_wilk_recognises_normality() {
304 let x: Vec<f64> = (1..=30).map(|i| i as f64).collect();
306 let r = shapiro_wilk(&x).unwrap();
307 assert!(r.statistic > 0.0 && r.statistic <= 1.0);
309 assert!(r.pvalue.is_finite());
310 }
311
312 #[test]
313 fn anderson_darling_detects_a_bimodal_sample() {
314 let mut x = vec![0.0_f64; 30];
315 for i in 0..15 {
316 x[i] = i as f64;
317 }
318 for i in 15..30 {
319 x[i] = 100.0 + i as f64;
320 }
321 let r = anderson_darling(&x).unwrap();
322 assert!(r.statistic > 0.0);
323 assert!(r.pvalue.is_finite());
324 }
325
326 #[test]
327 fn ks_2samp_rejects_two_shifted_distributions() {
328 let a = vec![1.0_f64, 2.0, 3.0, 4.0, 5.0];
329 let b = vec![10.0_f64, 11.0, 12.0, 13.0, 14.0];
330 let r = ks_2samp(&a, &b).unwrap();
331 assert!(r.statistic >= 0.7);
333 assert!(r.pvalue < 0.1);
334 }
335
336 #[test]
337 fn runs_test_returns_a_valid_p_value() {
338 let x = vec![1.0_f64, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0];
339 let r = runs_test(&x).unwrap();
340 assert!((0.0..=1.0).contains(&r.pvalue));
341 }
342}