oxiphysics_core/statistics/
functions_2.rs1#[allow(unused_imports)]
6use super::functions::*;
7#[allow(unused_imports)]
8use super::types::*;
9#[cfg(test)]
10mod tests_new_statistics {
11 use super::*;
12 #[test]
13 fn test_mad_known_value() {
14 let data = [1.0, 2.0, 3.0, 4.0, 5.0];
15 assert!(
16 (mad(&data) - 1.0).abs() < 1e-10,
17 "MAD should be 1, got {}",
18 mad(&data)
19 );
20 }
21 #[test]
22 fn test_mad_empty() {
23 assert_eq!(mad(&[]), 0.0);
24 }
25 #[test]
26 fn test_mad_constant_data() {
27 let data = vec![5.0; 10];
28 assert_eq!(mad(&data), 0.0);
29 }
30 #[test]
31 fn test_mad_robust_to_outliers() {
32 let clean = [1.0, 2.0, 3.0, 4.0, 5.0];
33 let with_outlier = [1.0, 2.0, 3.0, 4.0, 5.0, 1000.0];
34 let mad_clean = mad(&clean);
35 let mad_outlier = mad(&with_outlier);
36 assert!(
37 (mad_clean - mad_outlier).abs() < 2.0,
38 "MAD should be robust to outliers"
39 );
40 }
41 #[test]
42 fn test_huber_estimator_symmetric_data() {
43 let data: Vec<f64> = (-50..=50).map(|i| i as f64).collect();
44 let mu = huber_m_estimator(&data, 1.345, 100);
45 assert!(
46 (mu - 0.0).abs() < 1.0,
47 "Huber estimate of symmetric data should be ~0, got {mu}"
48 );
49 }
50 #[test]
51 fn test_huber_estimator_empty() {
52 assert_eq!(huber_m_estimator(&[], 1.345, 100), 0.0);
53 }
54 #[test]
55 fn test_huber_estimator_robust() {
56 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 100.0];
57 let mu = huber_m_estimator(&data, 1.345, 100);
58 let m = mean(&data);
59 assert!(
60 mu < m,
61 "Huber should be more robust than mean for data with outlier"
62 );
63 }
64 #[test]
65 fn test_tukey_biweight_symmetric() {
66 let data: Vec<f64> = (-30..=30).map(|i| i as f64).collect();
67 let mu = tukey_biweight_estimator(&data, 4.685, 100);
68 assert!(
69 (mu - 0.0).abs() < 1.0,
70 "Tukey biweight of symmetric data ~0, got {mu}"
71 );
72 }
73 #[test]
74 fn test_tukey_biweight_empty() {
75 assert_eq!(tukey_biweight_estimator(&[], 4.685, 100), 0.0);
76 }
77 #[test]
78 fn test_tukey_biweight_downweights_outliers() {
79 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 1000.0];
80 let mu_tukey = tukey_biweight_estimator(&data, 4.685, 100);
81 let mu_mean = mean(&data);
82 assert!(
83 mu_tukey < mu_mean,
84 "Tukey should downweight outlier: tukey={mu_tukey}, mean={mu_mean}"
85 );
86 }
87 #[test]
88 fn test_empirical_cdf_length() {
89 let data = [1.0, 2.0, 3.0, 4.0, 5.0];
90 let ecdf = empirical_cdf(&data);
91 assert_eq!(ecdf.len(), 5);
92 }
93 #[test]
94 fn test_empirical_cdf_last_is_one() {
95 let data = [3.0, 1.0, 4.0, 1.0, 5.0, 9.0];
96 let ecdf = empirical_cdf(&data);
97 assert!(
98 (ecdf.last().unwrap().1 - 1.0).abs() < 1e-12,
99 "last ECDF value should be 1"
100 );
101 }
102 #[test]
103 fn test_empirical_cdf_monotone() {
104 let data = [3.0, 1.0, 4.0, 1.0, 5.0];
105 let ecdf = empirical_cdf(&data);
106 for w in ecdf.windows(2) {
107 assert!(w[1].1 >= w[0].1, "ECDF should be non-decreasing");
108 }
109 }
110 #[test]
111 fn test_ecdf_at_known() {
112 let data = [1.0, 2.0, 3.0, 4.0, 5.0];
113 let f3 = ecdf_at(&data, 3.0);
114 assert!((f3 - 0.6).abs() < 1e-12, "F(3) = 0.6, got {f3}");
115 }
116 #[test]
117 fn test_ecdf_at_empty() {
118 assert_eq!(ecdf_at(&[], 1.0), 0.0);
119 }
120 #[test]
121 fn test_ecdf_at_below_min() {
122 let data = [2.0, 3.0, 4.0];
123 assert_eq!(ecdf_at(&data, 1.0), 0.0, "ECDF below min should be 0");
124 }
125 #[test]
126 fn test_ecdf_at_above_max() {
127 let data = [2.0, 3.0, 4.0];
128 assert!(
129 (ecdf_at(&data, 10.0) - 1.0).abs() < 1e-12,
130 "ECDF above max should be 1"
131 );
132 }
133 #[test]
134 fn test_anderson_darling_uniform_vs_uniform() {
135 let data: Vec<f64> = (1..=20).map(|i| i as f64 / 20.0).collect();
136 let a2 = anderson_darling_statistic(&data, |x| x.clamp(0.0, 1.0));
137 assert!(a2.is_finite(), "A² should be finite, got {a2}");
138 }
139 #[test]
140 fn test_anderson_darling_non_negative() {
141 let data: Vec<f64> = (0..50).map(|i| i as f64 / 50.0).collect();
142 let a2 = anderson_darling_statistic(&data, |x| x.clamp(0.0, 1.0));
143 assert!(
144 a2 >= 0.0 || a2.is_nan() || a2.is_infinite(),
145 "A² should be non-negative for typical data, got {a2}"
146 );
147 }
148 #[test]
149 fn test_anderson_darling_empty() {
150 let a2 = anderson_darling_statistic(&[], |x| x);
151 assert_eq!(a2, 0.0);
152 }
153 #[test]
154 fn test_kruskal_wallis_identical_groups() {
155 let g1 = [1.0, 2.0, 3.0];
156 let g2 = [1.0, 2.0, 3.0];
157 let h = kruskal_wallis_h(&[&g1, &g2]);
158 assert!(h.abs() < 0.1, "identical groups should give H~0, got {h}");
159 }
160 #[test]
161 fn test_kruskal_wallis_separated_groups() {
162 let g1: Vec<f64> = (0..10).map(|i| i as f64).collect();
163 let g2: Vec<f64> = (100..110).map(|i| i as f64).collect();
164 let h = kruskal_wallis_h(&[&g1, &g2]);
165 assert!(
166 h > 10.0,
167 "well-separated groups should give large H, got {h}"
168 );
169 }
170 #[test]
171 fn test_kruskal_wallis_two_groups_only_one() {
172 let g1 = [1.0, 2.0];
173 let h = kruskal_wallis_h(&[&g1]);
174 assert_eq!(h, 0.0, "single group should return 0");
175 }
176 #[test]
177 fn test_kruskal_wallis_three_groups() {
178 let g1 = [1.0, 2.0, 3.0];
179 let g2 = [4.0, 5.0, 6.0];
180 let g3 = [7.0, 8.0, 9.0];
181 let h = kruskal_wallis_h(&[&g1, &g2, &g3]);
182 assert!(
183 h > 5.0,
184 "three clearly-separated groups: H should be large, got {h}"
185 );
186 }
187 #[test]
188 fn test_kruskal_wallis_nonnegative() {
189 let g1 = [1.0, 3.0, 5.0, 7.0];
190 let g2 = [2.0, 4.0, 6.0, 8.0];
191 let h = kruskal_wallis_h(&[&g1, &g2]);
192 assert!(h >= 0.0, "H should be non-negative, got {h}");
193 }
194 #[test]
195 fn test_mad_vs_std_for_normal() {
196 let data: Vec<f64> = {
197 let mut v = Vec::new();
198 let mut rng = StatRng::new(42);
199 for _ in 0..10000 {
200 v.push(rng.next_normal());
201 }
202 v
203 };
204 let mad_val = mad(&data);
205 let std_val = std_dev(&data);
206 let mad_scaled = mad_val * 1.4826;
207 assert!(
208 (mad_scaled - std_val).abs() / std_val < 0.05,
209 "MAD*1.4826={mad_scaled} should match std_dev={std_val} within 5%"
210 );
211 }
212 #[test]
213 fn test_huber_vs_mean_clean_data() {
214 let data: Vec<f64> = {
215 let mut v = Vec::new();
216 let mut rng = StatRng::new(99);
217 for _ in 0..1000 {
218 v.push(rng.next_normal() * 2.0 + 5.0);
219 }
220 v
221 };
222 let mu = huber_m_estimator(&data, 1.345, 50);
223 assert!(
224 (mu - 5.0).abs() < 0.3,
225 "Huber on clean data should match mean~5, got {mu}"
226 );
227 }
228 #[test]
229 fn test_ecdf_is_step_function() {
230 let data = vec![1.0, 2.0, 3.0];
231 assert_eq!(ecdf_at(&data, 1.5), ecdf_at(&data, 1.9));
232 }
233 #[test]
234 fn test_anderson_darling_larger_for_bad_fit() {
235 let good: Vec<f64> = (1..=10).map(|i| i as f64 / 10.0).collect();
236 let bad: Vec<f64> = (1..=10).map(|i| i as f64 / 10.0 + 0.5).collect();
237 let a2_good = anderson_darling_statistic(&good, |x| x.clamp(0.0, 1.0));
238 let a2_bad = anderson_darling_statistic(&bad, |x| x.clamp(0.0, 1.0));
239 assert!(
240 a2_good.is_finite(),
241 "good-fit A² should be finite: {a2_good}"
242 );
243 assert!(a2_bad.is_finite(), "bad-fit A² should be finite: {a2_bad}");
244 }
245}