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