1use ndarray::Array1;
4use solow_distributions::chi2_sf;
5
6pub fn durbin_watson(resid: &Array1<f64>) -> f64 {
11 let n = resid.len();
12 if n < 2 {
13 return f64::NAN;
14 }
15 let mut num = 0.0;
16 let mut den = resid[0] * resid[0];
17 for i in 1..n {
18 let d = resid[i] - resid[i - 1];
19 num += d * d;
20 den += resid[i] * resid[i];
21 }
22 num / den
23}
24
25fn central_moment(x: &Array1<f64>, mean: f64, k: i32) -> f64 {
28 let n = x.len() as f64;
29 x.iter().map(|&v| (v - mean).powi(k)).sum::<f64>() / n
30}
31
32#[derive(Debug, Clone, Copy)]
34pub struct JarqueBera {
35 pub statistic: f64,
37 pub pvalue: f64,
39 pub skew: f64,
41 pub kurtosis: f64,
43}
44
45pub fn jarque_bera(resid: &Array1<f64>) -> JarqueBera {
51 let n = resid.len() as f64;
52 let mean = resid.sum() / n;
53 let m2 = central_moment(resid, mean, 2);
54 let m3 = central_moment(resid, mean, 3);
55 let m4 = central_moment(resid, mean, 4);
56 let skew = m3 / m2.powf(1.5);
57 let kurtosis = m4 / (m2 * m2); let jb = (n / 6.0) * (skew * skew + 0.25 * (kurtosis - 3.0).powi(2));
59 JarqueBera {
60 statistic: jb,
61 pvalue: chi2_sf(jb, 2.0),
62 skew,
63 kurtosis,
64 }
65}
66
67fn skew_z(skew: f64, n: f64) -> f64 {
71 let y = skew * (((n + 1.0) * (n + 3.0)) / (6.0 * (n - 2.0))).sqrt();
72 let beta2 = 3.0 * (n * n + 27.0 * n - 70.0) * (n + 1.0) * (n + 3.0)
73 / ((n - 2.0) * (n + 5.0) * (n + 7.0) * (n + 9.0));
74 let w2 = -1.0 + (2.0 * (beta2 - 1.0)).sqrt();
75 let delta = 1.0 / (0.5 * w2.ln()).sqrt();
76 let alpha = (2.0 / (w2 - 1.0)).sqrt();
77 let y = if y == 0.0 { 1.0 } else { y };
78 let ya = y / alpha;
79 delta * (ya + (ya * ya + 1.0).sqrt()).ln()
80}
81
82fn kurtosis_z(kurt: f64, n: f64) -> f64 {
87 let e = 3.0 * (n - 1.0) / (n + 1.0);
88 let varb2 = 24.0 * n * (n - 2.0) * (n - 3.0) / ((n + 1.0) * (n + 1.0) * (n + 3.0) * (n + 5.0));
89 let x = (kurt - e) / varb2.sqrt();
90 let sqrtbeta1 = 6.0 * (n * n - 5.0 * n + 2.0) / ((n + 7.0) * (n + 9.0))
91 * (6.0 * (n + 3.0) * (n + 5.0) / (n * (n - 2.0) * (n - 3.0))).sqrt();
92 let a =
93 6.0 + 8.0 / sqrtbeta1 * (2.0 / sqrtbeta1 + (1.0 + 4.0 / (sqrtbeta1 * sqrtbeta1)).sqrt());
94 let term1 = 1.0 - 2.0 / (9.0 * a);
95 let denom = 1.0 + x * (2.0 / (a - 4.0)).sqrt();
96 let term2 = denom.signum() * ((1.0 - 2.0 / a) / denom.abs()).powf(1.0 / 3.0);
97 (term1 - term2) / (2.0 / (9.0 * a)).sqrt()
98}
99
100pub fn omni_normtest(resid: &Array1<f64>) -> (f64, f64) {
107 let n = resid.len();
108 if n < 8 {
109 return (f64::NAN, f64::NAN);
110 }
111 let nf = n as f64;
112 let mean = resid.sum() / nf;
113 let m2 = central_moment(resid, mean, 2);
114 let m3 = central_moment(resid, mean, 3);
115 let m4 = central_moment(resid, mean, 4);
116 let skew = m3 / m2.powf(1.5);
117 let kurt = m4 / (m2 * m2);
118 let zs = skew_z(skew, nf);
119 let zk = kurtosis_z(kurt, nf);
120 let stat = zs * zs + zk * zk;
121 (stat, chi2_sf(stat, 2.0))
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use ndarray::array;
128
129 #[test]
130 fn dw_no_correlation_near_two() {
131 let r = array![1.0, -1.0, 1.0, -1.0, 1.0, -1.0];
133 let dw = durbin_watson(&r);
134 assert!(dw > 3.0, "dw = {dw}");
136 }
137
138 #[test]
139 fn jb_symmetric_low_skew() {
140 let r = array![-2.0, -1.0, 0.0, 1.0, 2.0];
141 let jb = jarque_bera(&r);
142 assert!(jb.skew.abs() < 1e-12);
143 }
144}