1use crate::helpers::simpsons_weights;
7use crate::iter_maybe_parallel;
8use rand::prelude::*;
9use rand_distr::StandardNormal;
10#[cfg(feature = "parallel")]
11use rayon::iter::ParallelIterator;
12
13pub fn fraiman_muniz_1d(
26 data_obj: &[f64],
27 data_ori: &[f64],
28 nobj: usize,
29 nori: usize,
30 n_points: usize,
31 scale: bool,
32) -> Vec<f64> {
33 if nobj == 0 || nori == 0 || n_points == 0 {
34 return Vec::new();
35 }
36
37 let scale_factor = if scale { 2.0 } else { 1.0 };
38
39 iter_maybe_parallel!(0..nobj)
40 .map(|i| {
41 let mut depth_sum = 0.0;
42
43 for t in 0..n_points {
44 let x_t = data_obj[i + t * nobj];
45 let mut le_count = 0;
46
47 for j in 0..nori {
48 let y_t = data_ori[j + t * nori];
49 if y_t <= x_t {
50 le_count += 1;
51 }
52 }
53
54 let fn_x = le_count as f64 / nori as f64;
55 let univariate_depth = fn_x.min(1.0 - fn_x) * scale_factor;
56 depth_sum += univariate_depth;
57 }
58
59 depth_sum / n_points as f64
60 })
61 .collect()
62}
63
64pub fn fraiman_muniz_2d(
66 data_obj: &[f64],
67 data_ori: &[f64],
68 nobj: usize,
69 nori: usize,
70 n_points: usize,
71 scale: bool,
72) -> Vec<f64> {
73 fraiman_muniz_1d(data_obj, data_ori, nobj, nori, n_points, scale)
75}
76
77pub fn modal_1d(
89 data_obj: &[f64],
90 data_ori: &[f64],
91 nobj: usize,
92 nori: usize,
93 n_points: usize,
94 h: f64,
95) -> Vec<f64> {
96 if nobj == 0 || nori == 0 || n_points == 0 {
97 return Vec::new();
98 }
99
100 iter_maybe_parallel!(0..nobj)
101 .map(|i| {
102 let mut depth = 0.0;
103
104 for j in 0..nori {
105 let mut dist_sq = 0.0;
106 for t in 0..n_points {
107 let diff = data_obj[i + t * nobj] - data_ori[j + t * nori];
108 dist_sq += diff * diff;
109 }
110 let dist = (dist_sq / n_points as f64).sqrt();
111 let kernel_val = (-0.5 * (dist / h).powi(2)).exp();
112 depth += kernel_val;
113 }
114
115 depth / nori as f64
116 })
117 .collect()
118}
119
120pub fn modal_2d(
122 data_obj: &[f64],
123 data_ori: &[f64],
124 nobj: usize,
125 nori: usize,
126 n_points: usize,
127 h: f64,
128) -> Vec<f64> {
129 modal_1d(data_obj, data_ori, nobj, nori, n_points, h)
130}
131
132pub fn random_projection_1d(
145 data_obj: &[f64],
146 data_ori: &[f64],
147 nobj: usize,
148 nori: usize,
149 n_points: usize,
150 nproj: usize,
151) -> Vec<f64> {
152 if nobj == 0 || nori == 0 || n_points == 0 || nproj == 0 {
153 return Vec::new();
154 }
155
156 let mut rng = rand::thread_rng();
157 let projections: Vec<Vec<f64>> = (0..nproj)
158 .map(|_| {
159 let mut proj: Vec<f64> = (0..n_points).map(|_| rng.sample(StandardNormal)).collect();
160 let norm: f64 = proj.iter().map(|x| x * x).sum::<f64>().sqrt();
161 proj.iter_mut().for_each(|x| *x /= norm);
162 proj
163 })
164 .collect();
165
166 iter_maybe_parallel!(0..nobj)
167 .map(|i| {
168 let mut total_depth = 0.0;
169
170 for proj in &projections {
171 let mut proj_i = 0.0;
172 for t in 0..n_points {
173 proj_i += data_obj[i + t * nobj] * proj[t];
174 }
175
176 let mut proj_ori: Vec<f64> = (0..nori)
177 .map(|j| {
178 let mut p = 0.0;
179 for t in 0..n_points {
180 p += data_ori[j + t * nori] * proj[t];
181 }
182 p
183 })
184 .collect();
185
186 proj_ori.sort_by(|a, b| a.partial_cmp(b).unwrap());
187
188 let below = proj_ori.iter().filter(|&&x| x < proj_i).count();
189 let above = proj_ori.iter().filter(|&&x| x > proj_i).count();
190 let depth = (below.min(above) as f64 + 1.0) / (nori as f64 + 1.0);
191
192 total_depth += depth;
193 }
194
195 total_depth / nproj as f64
196 })
197 .collect()
198}
199
200pub fn random_projection_2d(
202 data_obj: &[f64],
203 data_ori: &[f64],
204 nobj: usize,
205 nori: usize,
206 n_points: usize,
207 nproj: usize,
208) -> Vec<f64> {
209 random_projection_1d(data_obj, data_ori, nobj, nori, n_points, nproj)
210}
211
212pub fn random_tukey_1d(
216 data_obj: &[f64],
217 data_ori: &[f64],
218 nobj: usize,
219 nori: usize,
220 n_points: usize,
221 nproj: usize,
222) -> Vec<f64> {
223 if nobj == 0 || nori == 0 || n_points == 0 || nproj == 0 {
224 return Vec::new();
225 }
226
227 let mut rng = rand::thread_rng();
228 let projections: Vec<Vec<f64>> = (0..nproj)
229 .map(|_| {
230 let mut proj: Vec<f64> = (0..n_points).map(|_| rng.sample(StandardNormal)).collect();
231 let norm: f64 = proj.iter().map(|x| x * x).sum::<f64>().sqrt();
232 proj.iter_mut().for_each(|x| *x /= norm);
233 proj
234 })
235 .collect();
236
237 iter_maybe_parallel!(0..nobj)
238 .map(|i| {
239 let mut min_depth = f64::INFINITY;
240
241 for proj in &projections {
242 let mut proj_i = 0.0;
243 for t in 0..n_points {
244 proj_i += data_obj[i + t * nobj] * proj[t];
245 }
246
247 let proj_ori: Vec<f64> = (0..nori)
248 .map(|j| {
249 let mut p = 0.0;
250 for t in 0..n_points {
251 p += data_ori[j + t * nori] * proj[t];
252 }
253 p
254 })
255 .collect();
256
257 let below = proj_ori.iter().filter(|&&x| x < proj_i).count();
258 let above = proj_ori.iter().filter(|&&x| x > proj_i).count();
259 let depth = (below.min(above) as f64 + 1.0) / (nori as f64 + 1.0);
260
261 min_depth = min_depth.min(depth);
262 }
263
264 min_depth
265 })
266 .collect()
267}
268
269pub fn random_tukey_2d(
271 data_obj: &[f64],
272 data_ori: &[f64],
273 nobj: usize,
274 nori: usize,
275 n_points: usize,
276 nproj: usize,
277) -> Vec<f64> {
278 random_tukey_1d(data_obj, data_ori, nobj, nori, n_points, nproj)
279}
280
281pub fn functional_spatial_1d(
283 data_obj: &[f64],
284 data_ori: &[f64],
285 nobj: usize,
286 nori: usize,
287 n_points: usize,
288) -> Vec<f64> {
289 if nobj == 0 || nori == 0 || n_points == 0 {
290 return Vec::new();
291 }
292
293 iter_maybe_parallel!(0..nobj)
294 .map(|i| {
295 let mut sum_unit = vec![0.0; n_points];
296
297 for j in 0..nori {
298 let mut direction = vec![0.0; n_points];
299 let mut norm_sq = 0.0;
300
301 for t in 0..n_points {
302 direction[t] = data_ori[j + t * nori] - data_obj[i + t * nobj];
303 norm_sq += direction[t] * direction[t];
304 }
305
306 let norm = norm_sq.sqrt();
307 if norm > 1e-10 {
308 for t in 0..n_points {
309 sum_unit[t] += direction[t] / norm;
310 }
311 }
312 }
313
314 let mut avg_norm_sq = 0.0;
315 for t in 0..n_points {
316 let avg = sum_unit[t] / nori as f64;
317 avg_norm_sq += avg * avg;
318 }
319
320 1.0 - avg_norm_sq.sqrt()
321 })
322 .collect()
323}
324
325pub fn functional_spatial_2d(
327 data_obj: &[f64],
328 data_ori: &[f64],
329 nobj: usize,
330 nori: usize,
331 n_points: usize,
332) -> Vec<f64> {
333 functional_spatial_1d(data_obj, data_ori, nobj, nori, n_points)
334}
335
336fn kernel_pair_contribution(j: usize, k: usize, m1: &[Vec<f64>], m2: &[f64]) -> Option<f64> {
338 let denom_j_sq = 2.0 - 2.0 * m2[j];
339 if denom_j_sq < 1e-20 {
340 return None;
341 }
342 let denom_k_sq = 2.0 - 2.0 * m2[k];
343 if denom_k_sq < 1e-20 {
344 return None;
345 }
346 let denom = denom_j_sq.sqrt() * denom_k_sq.sqrt();
347 if denom <= 1e-20 {
348 return None;
349 }
350 let m_ijk = (1.0 + m1[j][k] - m2[j] - m2[k]) / denom;
351 if m_ijk.is_finite() {
352 Some(m_ijk)
353 } else {
354 None
355 }
356}
357
358fn kfsd_accumulate(m2: &[f64], m1: &[Vec<f64>], nori: usize) -> (f64, usize) {
361 let mut total_sum = 0.0;
362 let mut valid_count = 0;
363
364 for j in 0..nori {
365 for k in 0..nori {
366 if let Some(val) = kernel_pair_contribution(j, k, m1, m2) {
367 total_sum += val;
368 valid_count += 1;
369 }
370 }
371 }
372
373 (total_sum, valid_count)
374}
375
376fn kfsd_weighted(
379 data_obj: &[f64],
380 data_ori: &[f64],
381 nobj: usize,
382 nori: usize,
383 n_points: usize,
384 h: f64,
385 weights: &[f64],
386) -> Vec<f64> {
387 let h_sq = h * h;
388
389 let m1_upper: Vec<(usize, usize, f64)> = iter_maybe_parallel!(0..nori)
391 .flat_map(|j| {
392 ((j + 1)..nori)
393 .map(|k| {
394 let mut sum = 0.0;
395 for t in 0..n_points {
396 let diff = data_ori[j + t * nori] - data_ori[k + t * nori];
397 sum += weights[t] * diff * diff;
398 }
399 (j, k, (-sum / h_sq).exp())
400 })
401 .collect::<Vec<_>>()
402 })
403 .collect();
404
405 let mut m1 = vec![vec![0.0; nori]; nori];
406 for j in 0..nori {
407 m1[j][j] = 1.0;
408 }
409 for (j, k, kval) in m1_upper {
410 m1[j][k] = kval;
411 m1[k][j] = kval;
412 }
413
414 let nori_f64 = nori as f64;
415
416 iter_maybe_parallel!(0..nobj)
417 .map(|i| {
418 let m2: Vec<f64> = (0..nori)
419 .map(|j| {
420 let mut sum = 0.0;
421 for t in 0..n_points {
422 let diff = data_obj[i + t * nobj] - data_ori[j + t * nori];
423 sum += weights[t] * diff * diff;
424 }
425 (-sum / h_sq).exp()
426 })
427 .collect();
428
429 let (total_sum, valid_count) = kfsd_accumulate(&m2, &m1, nori);
430
431 if valid_count > 0 && total_sum >= 0.0 {
432 1.0 - total_sum.sqrt() / nori_f64
433 } else if total_sum < 0.0 {
434 1.0
435 } else {
436 0.0
437 }
438 })
439 .collect()
440}
441
442fn curve_inside_band(
444 data_obj: &[f64],
445 data_ori: &[f64],
446 nobj: usize,
447 nori: usize,
448 n_points: usize,
449 i: usize,
450 j: usize,
451 k: usize,
452) -> bool {
453 for t in 0..n_points {
454 let x_t = data_obj[i + t * nobj];
455 let y_j_t = data_ori[j + t * nori];
456 let y_k_t = data_ori[k + t * nori];
457 if x_t < y_j_t.min(y_k_t) || x_t > y_j_t.max(y_k_t) {
458 return false;
459 }
460 }
461 true
462}
463
464fn proportion_inside_band(
466 data_obj: &[f64],
467 data_ori: &[f64],
468 nobj: usize,
469 nori: usize,
470 n_points: usize,
471 i: usize,
472 j: usize,
473 k: usize,
474) -> f64 {
475 let mut count = 0usize;
476 for t in 0..n_points {
477 let x_t = data_obj[i + t * nobj];
478 let y_j_t = data_ori[j + t * nori];
479 let y_k_t = data_ori[k + t * nori];
480 if x_t >= y_j_t.min(y_k_t) && x_t <= y_j_t.max(y_k_t) {
481 count += 1;
482 }
483 }
484 count as f64 / n_points as f64
485}
486
487pub fn kernel_functional_spatial_1d(
491 data_obj: &[f64],
492 data_ori: &[f64],
493 nobj: usize,
494 nori: usize,
495 n_points: usize,
496 argvals: &[f64],
497 h: f64,
498) -> Vec<f64> {
499 if nobj == 0 || nori == 0 || n_points == 0 {
500 return Vec::new();
501 }
502
503 let weights = simpsons_weights(argvals);
504 kfsd_weighted(data_obj, data_ori, nobj, nori, n_points, h, &weights)
505}
506
507pub fn kernel_functional_spatial_2d(
509 data_obj: &[f64],
510 data_ori: &[f64],
511 nobj: usize,
512 nori: usize,
513 n_points: usize,
514 h: f64,
515) -> Vec<f64> {
516 if nobj == 0 || nori == 0 || n_points == 0 {
517 return Vec::new();
518 }
519
520 let weights = vec![1.0; n_points];
521 kfsd_weighted(data_obj, data_ori, nobj, nori, n_points, h, &weights)
522}
523
524pub fn band_1d(
528 data_obj: &[f64],
529 data_ori: &[f64],
530 nobj: usize,
531 nori: usize,
532 n_points: usize,
533) -> Vec<f64> {
534 if nobj == 0 || nori < 2 || n_points == 0 {
535 return Vec::new();
536 }
537
538 let n_pairs = (nori * (nori - 1)) / 2;
539
540 iter_maybe_parallel!(0..nobj)
541 .map(|i| {
542 let mut count_in_band = 0usize;
543
544 for j in 0..nori {
545 for k in (j + 1)..nori {
546 if curve_inside_band(data_obj, data_ori, nobj, nori, n_points, i, j, k) {
547 count_in_band += 1;
548 }
549 }
550 }
551
552 count_in_band as f64 / n_pairs as f64
553 })
554 .collect()
555}
556
557pub fn modified_band_1d(
561 data_obj: &[f64],
562 data_ori: &[f64],
563 nobj: usize,
564 nori: usize,
565 n_points: usize,
566) -> Vec<f64> {
567 if nobj == 0 || nori < 2 || n_points == 0 {
568 return Vec::new();
569 }
570
571 let n_pairs = (nori * (nori - 1)) / 2;
572
573 iter_maybe_parallel!(0..nobj)
574 .map(|i| {
575 let mut total_proportion = 0.0;
576
577 for j in 0..nori {
578 for k in (j + 1)..nori {
579 total_proportion +=
580 proportion_inside_band(data_obj, data_ori, nobj, nori, n_points, i, j, k);
581 }
582 }
583
584 total_proportion / n_pairs as f64
585 })
586 .collect()
587}
588
589pub fn modified_epigraph_index_1d(
593 data_obj: &[f64],
594 data_ori: &[f64],
595 nobj: usize,
596 nori: usize,
597 n_points: usize,
598) -> Vec<f64> {
599 if nobj == 0 || nori == 0 || n_points == 0 {
600 return Vec::new();
601 }
602
603 iter_maybe_parallel!(0..nobj)
604 .map(|i| {
605 let mut total = 0.0;
606
607 for j in 0..nori {
608 let mut count = 0.0;
609
610 for t in 0..n_points {
611 let xi = data_obj[i + t * nobj];
612 let xj = data_ori[j + t * nori];
613
614 if xi < xj {
615 count += 1.0;
616 } else if (xi - xj).abs() < 1e-12 {
617 count += 0.5;
618 }
619 }
620
621 total += count / n_points as f64;
622 }
623
624 total / nori as f64
625 })
626 .collect()
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use std::f64::consts::PI;
633
634 fn uniform_grid(n: usize) -> Vec<f64> {
635 (0..n).map(|i| i as f64 / (n - 1) as f64).collect()
636 }
637
638 fn generate_centered_data(n: usize, m: usize) -> Vec<f64> {
639 let argvals = uniform_grid(m);
640 let mut data = vec![0.0; n * m];
641 for i in 0..n {
642 let offset = (i as f64 - n as f64 / 2.0) / (n as f64);
643 for j in 0..m {
644 data[i + j * n] = (2.0 * PI * argvals[j]).sin() + offset;
645 }
646 }
647 data
648 }
649
650 #[test]
653 fn test_fraiman_muniz() {
654 let data = vec![1.0, 1.0, 2.0, 2.0]; let depths = fraiman_muniz_1d(&data, &data, 2, 2, 2, true);
657 assert_eq!(depths.len(), 2);
658 }
659
660 #[test]
661 fn test_fraiman_muniz_central_deeper() {
662 let n = 20;
663 let m = 30;
664 let data = generate_centered_data(n, m);
665 let depths = fraiman_muniz_1d(&data, &data, n, n, m, true);
666
667 let central_depth = depths[n / 2];
669 let edge_depth = depths[0];
670 assert!(
671 central_depth > edge_depth,
672 "Central curve should be deeper: {} > {}",
673 central_depth,
674 edge_depth
675 );
676 }
677
678 #[test]
679 fn test_fraiman_muniz_range() {
680 let n = 15;
681 let m = 20;
682 let data = generate_centered_data(n, m);
683 let depths = fraiman_muniz_1d(&data, &data, n, n, m, true);
684
685 for d in &depths {
686 assert!(*d >= 0.0 && *d <= 1.0, "Depth should be in [0, 1]");
687 }
688 }
689
690 #[test]
691 fn test_fraiman_muniz_invalid() {
692 assert!(fraiman_muniz_1d(&[], &[], 0, 0, 0, true).is_empty());
693 }
694
695 #[test]
698 fn test_modal_central_deeper() {
699 let n = 20;
700 let m = 30;
701 let data = generate_centered_data(n, m);
702 let depths = modal_1d(&data, &data, n, n, m, 0.5);
703
704 let central_depth = depths[n / 2];
705 let edge_depth = depths[0];
706 assert!(central_depth > edge_depth, "Central curve should be deeper");
707 }
708
709 #[test]
710 fn test_modal_positive() {
711 let n = 10;
712 let m = 20;
713 let data = generate_centered_data(n, m);
714 let depths = modal_1d(&data, &data, n, n, m, 0.5);
715
716 for d in &depths {
717 assert!(*d > 0.0, "Modal depth should be positive");
718 }
719 }
720
721 #[test]
722 fn test_modal_invalid() {
723 assert!(modal_1d(&[], &[], 0, 0, 0, 0.5).is_empty());
724 }
725
726 #[test]
729 fn test_rp_depth_range() {
730 let n = 15;
731 let m = 20;
732 let data = generate_centered_data(n, m);
733 let depths = random_projection_1d(&data, &data, n, n, m, 50);
734
735 for d in &depths {
736 assert!(*d >= 0.0 && *d <= 1.0, "RP depth should be in [0, 1]");
737 }
738 }
739
740 #[test]
741 fn test_rp_depth_invalid() {
742 assert!(random_projection_1d(&[], &[], 0, 0, 0, 10).is_empty());
743 }
744
745 #[test]
748 fn test_random_tukey_range() {
749 let n = 15;
750 let m = 20;
751 let data = generate_centered_data(n, m);
752 let depths = random_tukey_1d(&data, &data, n, n, m, 50);
753
754 for d in &depths {
755 assert!(*d >= 0.0 && *d <= 1.0, "Tukey depth should be in [0, 1]");
756 }
757 }
758
759 #[test]
760 fn test_random_tukey_invalid() {
761 assert!(random_tukey_1d(&[], &[], 0, 0, 0, 10).is_empty());
762 }
763
764 #[test]
767 fn test_functional_spatial_range() {
768 let n = 15;
769 let m = 20;
770 let data = generate_centered_data(n, m);
771 let depths = functional_spatial_1d(&data, &data, n, n, m);
772
773 for d in &depths {
774 assert!(*d >= 0.0 && *d <= 1.0, "Spatial depth should be in [0, 1]");
775 }
776 }
777
778 #[test]
779 fn test_functional_spatial_invalid() {
780 assert!(functional_spatial_1d(&[], &[], 0, 0, 0).is_empty());
781 }
782
783 #[test]
786 fn test_band_depth_central_deeper() {
787 let n = 10;
788 let m = 20;
789 let data = generate_centered_data(n, m);
790 let depths = band_1d(&data, &data, n, n, m);
791
792 let central_depth = depths[n / 2];
794 let edge_depth = depths[0];
795 assert!(
796 central_depth >= edge_depth,
797 "Central curve should have higher band depth"
798 );
799 }
800
801 #[test]
802 fn test_band_depth_range() {
803 let n = 10;
804 let m = 20;
805 let data = generate_centered_data(n, m);
806 let depths = band_1d(&data, &data, n, n, m);
807
808 for d in &depths {
809 assert!(*d >= 0.0 && *d <= 1.0, "Band depth should be in [0, 1]");
810 }
811 }
812
813 #[test]
814 fn test_band_depth_invalid() {
815 assert!(band_1d(&[], &[], 0, 0, 0).is_empty());
816 assert!(band_1d(&[1.0, 2.0], &[1.0, 2.0], 1, 1, 2).is_empty()); }
818
819 #[test]
822 fn test_modified_band_depth_range() {
823 let n = 10;
824 let m = 20;
825 let data = generate_centered_data(n, m);
826 let depths = modified_band_1d(&data, &data, n, n, m);
827
828 for d in &depths {
829 assert!(*d >= 0.0 && *d <= 1.0, "MBD should be in [0, 1]");
830 }
831 }
832
833 #[test]
834 fn test_modified_band_depth_invalid() {
835 assert!(modified_band_1d(&[], &[], 0, 0, 0).is_empty());
836 }
837
838 #[test]
841 fn test_mei_range() {
842 let n = 15;
843 let m = 20;
844 let data = generate_centered_data(n, m);
845 let mei = modified_epigraph_index_1d(&data, &data, n, n, m);
846
847 for d in &mei {
848 assert!(*d >= 0.0 && *d <= 1.0, "MEI should be in [0, 1]");
849 }
850 }
851
852 #[test]
853 fn test_mei_invalid() {
854 assert!(modified_epigraph_index_1d(&[], &[], 0, 0, 0).is_empty());
855 }
856
857 #[test]
860 fn test_kfsd_1d_range() {
861 let n = 10;
862 let m = 20;
863 let argvals = uniform_grid(m);
864 let data = generate_centered_data(n, m);
865 let depths = kernel_functional_spatial_1d(&data, &data, n, n, m, &argvals, 0.5);
866
867 assert_eq!(depths.len(), n);
868 for d in &depths {
869 assert!(
870 *d >= -0.01 && *d <= 1.01,
871 "KFSD depth should be near [0, 1], got {}",
872 d
873 );
874 assert!(d.is_finite(), "KFSD depth should be finite");
875 }
876
877 let central_depth = depths[n / 2];
879 let edge_depth = depths[0];
880 assert!(
881 central_depth > edge_depth,
882 "Central KFSD depth {} should be > edge depth {}",
883 central_depth,
884 edge_depth
885 );
886 }
887
888 #[test]
889 fn test_kfsd_1d_identical() {
890 let n = 5;
892 let m = 10;
893 let argvals = uniform_grid(m);
894 let data: Vec<f64> = (0..n * m)
895 .map(|i| (2.0 * PI * (i % m) as f64 / (m - 1) as f64).sin())
896 .collect();
897
898 let depths = kernel_functional_spatial_1d(&data, &data, n, n, m, &argvals, 0.5);
901
902 assert_eq!(depths.len(), n);
903 for d in &depths {
904 assert!(
905 d.is_finite(),
906 "KFSD depth should be finite for identical curves"
907 );
908 }
909 }
910
911 #[test]
912 fn test_kfsd_1d_invalid() {
913 let argvals = uniform_grid(10);
914 assert!(kernel_functional_spatial_1d(&[], &[], 0, 0, 0, &argvals, 0.5).is_empty());
915 assert!(kernel_functional_spatial_1d(&[], &[], 0, 5, 10, &argvals, 0.5).is_empty());
916 }
917
918 #[test]
921 fn test_kfsd_2d_range() {
922 let n = 8;
923 let m = 15;
924 let data = generate_centered_data(n, m);
925 let depths = kernel_functional_spatial_2d(&data, &data, n, n, m, 0.5);
926
927 assert_eq!(depths.len(), n);
928 for d in &depths {
929 assert!(
930 *d >= -0.01 && *d <= 1.01,
931 "KFSD 2D depth should be near [0, 1], got {}",
932 d
933 );
934 assert!(d.is_finite(), "KFSD 2D depth should be finite");
935 }
936 }
937
938 #[test]
941 fn test_fraiman_muniz_2d_delegates() {
942 let n = 10;
943 let m = 15;
944 let data = generate_centered_data(n, m);
945 let depths_1d = fraiman_muniz_1d(&data, &data, n, n, m, true);
946 let depths_2d = fraiman_muniz_2d(&data, &data, n, n, m, true);
947 assert_eq!(depths_1d, depths_2d);
948 }
949
950 #[test]
951 fn test_modal_2d_delegates() {
952 let n = 10;
953 let m = 15;
954 let data = generate_centered_data(n, m);
955 let depths_1d = modal_1d(&data, &data, n, n, m, 0.5);
956 let depths_2d = modal_2d(&data, &data, n, n, m, 0.5);
957 assert_eq!(depths_1d, depths_2d);
958 }
959
960 #[test]
961 fn test_functional_spatial_2d_delegates() {
962 let n = 10;
963 let m = 15;
964 let data = generate_centered_data(n, m);
965 let depths_1d = functional_spatial_1d(&data, &data, n, n, m);
966 let depths_2d = functional_spatial_2d(&data, &data, n, n, m);
967 assert_eq!(depths_1d, depths_2d);
968 }
969
970 #[test]
971 fn test_random_projection_2d_returns_valid() {
972 let n = 10;
973 let m = 15;
974 let data = generate_centered_data(n, m);
975 let depths = random_projection_2d(&data, &data, n, n, m, 20);
976 assert_eq!(depths.len(), n);
977 for d in &depths {
978 assert!(*d >= 0.0 && *d <= 1.0, "RP 2D depth should be in [0, 1]");
979 }
980 }
981}