1use crate::helpers::simpsons_weights;
7use crate::iter_maybe_parallel;
8use crate::matrix::FdMatrix;
9use crate::streaming_depth::{
10 FullReferenceState, SortedReferenceState, StreamingBd, StreamingDepth, StreamingFraimanMuniz,
11 StreamingMbd,
12};
13use rand::prelude::*;
14use rand_distr::StandardNormal;
15#[cfg(feature = "parallel")]
16use rayon::iter::ParallelIterator;
17
18pub fn fraiman_muniz_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, scale: bool) -> Vec<f64> {
28 if data_obj.nrows() == 0 || data_ori.nrows() == 0 || data_obj.ncols() == 0 {
29 return Vec::new();
30 }
31 let state = SortedReferenceState::from_reference(data_ori);
32 let streaming = StreamingFraimanMuniz::new(state, scale);
33 streaming.depth_batch(data_obj)
34}
35
36pub fn fraiman_muniz_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, scale: bool) -> Vec<f64> {
38 fraiman_muniz_1d(data_obj, data_ori, scale)
40}
41
42pub fn modal_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> Vec<f64> {
51 let nobj = data_obj.nrows();
52 let nori = data_ori.nrows();
53 let n_points = data_obj.ncols();
54
55 if nobj == 0 || nori == 0 || n_points == 0 {
56 return Vec::new();
57 }
58
59 iter_maybe_parallel!(0..nobj)
60 .map(|i| {
61 let mut depth = 0.0;
62
63 for j in 0..nori {
64 let mut dist_sq = 0.0;
65 for t in 0..n_points {
66 let diff = data_obj[(i, t)] - data_ori[(j, t)];
67 dist_sq += diff * diff;
68 }
69 let dist = (dist_sq / n_points as f64).sqrt();
70 let kernel_val = (-0.5 * (dist / h).powi(2)).exp();
71 depth += kernel_val;
72 }
73
74 depth / nori as f64
75 })
76 .collect()
77}
78
79pub fn modal_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> Vec<f64> {
81 modal_1d(data_obj, data_ori, h)
82}
83
84fn generate_random_projections(nproj: usize, m: usize, seed: Option<u64>) -> Vec<f64> {
91 let mut rng: Box<dyn RngCore> = match seed {
92 Some(s) => Box::new(StdRng::seed_from_u64(s)),
93 None => Box::new(rand::thread_rng()),
94 };
95 let mut projections = vec![0.0; nproj * m];
96 for p_idx in 0..nproj {
97 let base = p_idx * m;
98 let mut norm_sq = 0.0;
99 for t in 0..m {
100 let v: f64 = rng.sample(StandardNormal);
101 projections[base + t] = v;
102 norm_sq += v * v;
103 }
104 let inv_norm = 1.0 / norm_sq.sqrt();
105 for t in 0..m {
106 projections[base + t] *= inv_norm;
107 }
108 }
109 projections
110}
111
112fn project_and_sort_reference(
117 data_ori: &FdMatrix,
118 projections: &[f64],
119 nproj: usize,
120 nori: usize,
121 m: usize,
122) -> Vec<f64> {
123 let mut sorted = vec![0.0; nproj * nori];
124 for p_idx in 0..nproj {
125 let proj = &projections[p_idx * m..(p_idx + 1) * m];
126 let spo = &mut sorted[p_idx * nori..(p_idx + 1) * nori];
127 for j in 0..nori {
128 let mut dot = 0.0;
129 for t in 0..m {
130 dot += data_ori[(j, t)] * proj[t];
131 }
132 spo[j] = dot;
133 }
134 spo.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
135 }
136 sorted
137}
138
139fn random_depth_core(
146 data_obj: &FdMatrix,
147 data_ori: &FdMatrix,
148 nproj: usize,
149 seed: Option<u64>,
150 init: f64,
151 aggregate: impl Fn(f64, f64) -> f64 + Sync,
152 finalize: impl Fn(f64, usize) -> f64 + Sync,
153) -> Vec<f64> {
154 let nobj = data_obj.nrows();
155 let nori = data_ori.nrows();
156 let m = data_obj.ncols();
157
158 if nobj == 0 || nori == 0 || m == 0 || nproj == 0 {
159 return Vec::new();
160 }
161
162 let projections = generate_random_projections(nproj, m, seed);
163 let sorted_proj_ori = project_and_sort_reference(data_ori, &projections, nproj, nori, m);
164 let denom = nori as f64 + 1.0;
165
166 iter_maybe_parallel!(0..nobj)
167 .map(|i| {
168 let mut acc = init;
169 for p_idx in 0..nproj {
170 let proj = &projections[p_idx * m..(p_idx + 1) * m];
171 let sorted_ori = &sorted_proj_ori[p_idx * nori..(p_idx + 1) * nori];
172
173 let mut proj_i = 0.0;
174 for t in 0..m {
175 proj_i += data_obj[(i, t)] * proj[t];
176 }
177
178 let below = sorted_ori.partition_point(|&v| v < proj_i);
179 let above = nori - sorted_ori.partition_point(|&v| v <= proj_i);
180 let depth = (below.min(above) as f64 + 1.0) / denom;
181 acc = aggregate(acc, depth);
182 }
183 finalize(acc, nproj)
184 })
185 .collect()
186}
187
188pub fn random_projection_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
198 random_projection_1d_seeded(data_obj, data_ori, nproj, None)
199}
200
201pub fn random_projection_1d_seeded(
203 data_obj: &FdMatrix,
204 data_ori: &FdMatrix,
205 nproj: usize,
206 seed: Option<u64>,
207) -> Vec<f64> {
208 random_depth_core(
209 data_obj,
210 data_ori,
211 nproj,
212 seed,
213 0.0,
214 |acc, d| acc + d,
215 |acc, n| acc / n as f64,
216 )
217}
218
219pub fn random_projection_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
221 random_projection_1d(data_obj, data_ori, nproj)
222}
223
224pub fn random_tukey_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
228 random_tukey_1d_seeded(data_obj, data_ori, nproj, None)
229}
230
231pub fn random_tukey_1d_seeded(
233 data_obj: &FdMatrix,
234 data_ori: &FdMatrix,
235 nproj: usize,
236 seed: Option<u64>,
237) -> Vec<f64> {
238 random_depth_core(
239 data_obj,
240 data_ori,
241 nproj,
242 seed,
243 f64::INFINITY,
244 f64::min,
245 |acc, _| acc,
246 )
247}
248
249pub fn random_tukey_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
251 random_tukey_1d(data_obj, data_ori, nproj)
252}
253
254pub fn functional_spatial_1d(
263 data_obj: &FdMatrix,
264 data_ori: &FdMatrix,
265 argvals: Option<&[f64]>,
266) -> Vec<f64> {
267 let nobj = data_obj.nrows();
268 let nori = data_ori.nrows();
269 let n_points = data_obj.ncols();
270
271 if nobj == 0 || nori == 0 || n_points == 0 {
272 return Vec::new();
273 }
274
275 let default_argvals: Vec<f64>;
277 let weights = if let Some(av) = argvals {
278 simpsons_weights(av)
279 } else {
280 default_argvals = (0..n_points)
281 .map(|i| i as f64 / (n_points - 1).max(1) as f64)
282 .collect();
283 simpsons_weights(&default_argvals)
284 };
285
286 iter_maybe_parallel!(0..nobj)
287 .map(|i| {
288 let mut sum_unit = vec![0.0; n_points];
289
290 for j in 0..nori {
291 let mut norm_sq = 0.0;
293 for t in 0..n_points {
294 let d = data_ori[(j, t)] - data_obj[(i, t)];
295 norm_sq += weights[t] * d * d;
296 }
297
298 let norm = norm_sq.sqrt();
299 if norm > 1e-10 {
300 let inv_norm = 1.0 / norm;
301 for t in 0..n_points {
302 sum_unit[t] += (data_ori[(j, t)] - data_obj[(i, t)]) * inv_norm;
303 }
304 }
305 }
306
307 let mut avg_norm_sq = 0.0;
309 for t in 0..n_points {
310 let avg = sum_unit[t] / nori as f64;
311 avg_norm_sq += weights[t] * avg * avg;
312 }
313
314 1.0 - avg_norm_sq.sqrt()
315 })
316 .collect()
317}
318
319pub fn functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
321 functional_spatial_1d(data_obj, data_ori, None)
322}
323
324fn kernel_pair_contribution(j: usize, k: usize, m1: &FdMatrix, m2: &[f64]) -> Option<f64> {
326 let denom_j_sq = 2.0 - 2.0 * m2[j];
327 if denom_j_sq < 1e-20 {
328 return None;
329 }
330 let denom_k_sq = 2.0 - 2.0 * m2[k];
331 if denom_k_sq < 1e-20 {
332 return None;
333 }
334 let denom = denom_j_sq.sqrt() * denom_k_sq.sqrt();
335 if denom <= 1e-20 {
336 return None;
337 }
338 let m_ijk = (1.0 + m1[(j, k)] - m2[j] - m2[k]) / denom;
339 if m_ijk.is_finite() {
340 Some(m_ijk)
341 } else {
342 None
343 }
344}
345
346fn kfsd_accumulate(m2: &[f64], m1: &FdMatrix, nori: usize) -> (f64, usize) {
353 let mut total_sum = 0.0;
354 let mut valid_count = 0;
355
356 for j in 0..nori {
358 if let Some(val) = kernel_pair_contribution(j, j, m1, m2) {
359 total_sum += val;
360 valid_count += 1;
361 }
362 }
363
364 for j in 0..nori {
366 for k in (j + 1)..nori {
367 if let Some(val) = kernel_pair_contribution(j, k, m1, m2) {
368 total_sum += 2.0 * val;
369 valid_count += 2;
370 }
371 }
372 }
373
374 (total_sum, valid_count)
375}
376
377fn kfsd_weighted(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64, weights: &[f64]) -> Vec<f64> {
380 let nobj = data_obj.nrows();
381 let nori = data_ori.nrows();
382 let n_points = data_obj.ncols();
383 let h_sq = h * h;
384
385 let m1_upper: Vec<(usize, usize, f64)> = iter_maybe_parallel!(0..nori)
387 .flat_map(|j| {
388 ((j + 1)..nori)
389 .map(|k| {
390 let mut sum = 0.0;
391 for t in 0..n_points {
392 let diff = data_ori[(j, t)] - data_ori[(k, t)];
393 sum += weights[t] * diff * diff;
394 }
395 (j, k, (-sum / h_sq).exp())
396 })
397 .collect::<Vec<_>>()
398 })
399 .collect();
400
401 let mut m1 = FdMatrix::zeros(nori, nori);
402 for j in 0..nori {
403 m1[(j, j)] = 1.0;
404 }
405 for (j, k, kval) in m1_upper {
406 m1[(j, k)] = kval;
407 m1[(k, j)] = kval;
408 }
409
410 let nori_f64 = nori as f64;
411
412 iter_maybe_parallel!(0..nobj)
413 .map(|i| {
414 let m2: Vec<f64> = (0..nori)
415 .map(|j| {
416 let mut sum = 0.0;
417 for t in 0..n_points {
418 let diff = data_obj[(i, t)] - data_ori[(j, t)];
419 sum += weights[t] * diff * diff;
420 }
421 (-sum / h_sq).exp()
422 })
423 .collect();
424
425 let (total_sum, valid_count) = kfsd_accumulate(&m2, &m1, nori);
426
427 if valid_count > 0 && total_sum >= 0.0 {
428 1.0 - total_sum.sqrt() / nori_f64
429 } else if total_sum < 0.0 {
430 1.0
431 } else {
432 0.0
433 }
434 })
435 .collect()
436}
437
438pub fn kernel_functional_spatial_1d(
442 data_obj: &FdMatrix,
443 data_ori: &FdMatrix,
444 argvals: &[f64],
445 h: f64,
446) -> Vec<f64> {
447 let nobj = data_obj.nrows();
448 let nori = data_ori.nrows();
449 let n_points = data_obj.ncols();
450
451 if nobj == 0 || nori == 0 || n_points == 0 {
452 return Vec::new();
453 }
454
455 let weights = simpsons_weights(argvals);
456 kfsd_weighted(data_obj, data_ori, h, &weights)
457}
458
459pub fn kernel_functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> 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 == 0 || n_points == 0 {
466 return Vec::new();
467 }
468
469 let weights = vec![1.0; n_points];
470 kfsd_weighted(data_obj, data_ori, h, &weights)
471}
472
473pub fn band_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
477 if data_obj.nrows() == 0 || data_ori.nrows() < 2 || data_obj.ncols() == 0 {
478 return Vec::new();
479 }
480 let state = FullReferenceState::from_reference(data_ori);
481 let streaming = StreamingBd::new(state);
482 streaming.depth_batch(data_obj)
483}
484
485pub fn modified_band_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
489 if data_obj.nrows() == 0 || data_ori.nrows() < 2 || data_obj.ncols() == 0 {
490 return Vec::new();
491 }
492 let state = SortedReferenceState::from_reference(data_ori);
493 let streaming = StreamingMbd::new(state);
494 streaming.depth_batch(data_obj)
495}
496
497pub fn modified_epigraph_index_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
502 let nobj = data_obj.nrows();
503 let nori = data_ori.nrows();
504 let n_points = data_obj.ncols();
505
506 if nobj == 0 || nori == 0 || n_points == 0 {
507 return Vec::new();
508 }
509
510 iter_maybe_parallel!(0..nobj)
511 .map(|i| {
512 let mut total = 0.0;
513
514 for j in 0..nori {
515 let mut count = 0.0;
516
517 for t in 0..n_points {
518 let xi = data_obj[(i, t)];
519 let xj = data_ori[(j, t)];
520
521 if xi <= xj {
523 count += 1.0;
524 }
525 }
526
527 total += count / n_points as f64;
528 }
529
530 total / nori as f64
531 })
532 .collect()
533}
534
535#[cfg(test)]
536mod tests {
537 use super::*;
538 use std::f64::consts::PI;
539
540 fn uniform_grid(n: usize) -> Vec<f64> {
541 (0..n).map(|i| i as f64 / (n - 1) as f64).collect()
542 }
543
544 fn generate_centered_data(n: usize, m: usize) -> FdMatrix {
545 let argvals = uniform_grid(m);
546 let mut data = vec![0.0; n * m];
547 for i in 0..n {
548 let offset = (i as f64 - n as f64 / 2.0) / (n as f64);
549 for j in 0..m {
550 data[i + j * n] = (2.0 * PI * argvals[j]).sin() + offset;
551 }
552 }
553 FdMatrix::from_column_major(data, n, m).unwrap()
554 }
555
556 #[test]
559 fn test_fraiman_muniz() {
560 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);
563 assert_eq!(depths.len(), 2);
564 }
565
566 #[test]
567 fn test_fraiman_muniz_central_deeper() {
568 let n = 20;
569 let m = 30;
570 let data = generate_centered_data(n, m);
571 let depths = fraiman_muniz_1d(&data, &data, true);
572
573 let central_depth = depths[n / 2];
575 let edge_depth = depths[0];
576 assert!(
577 central_depth > edge_depth,
578 "Central curve should be deeper: {} > {}",
579 central_depth,
580 edge_depth
581 );
582 }
583
584 #[test]
585 fn test_fraiman_muniz_range() {
586 let n = 15;
587 let m = 20;
588 let data = generate_centered_data(n, m);
589 let depths = fraiman_muniz_1d(&data, &data, true);
590
591 for d in &depths {
592 assert!(*d >= 0.0 && *d <= 1.0, "Depth should be in [0, 1]");
593 }
594 }
595
596 #[test]
597 fn test_fraiman_muniz_invalid() {
598 let empty = FdMatrix::zeros(0, 0);
599 assert!(fraiman_muniz_1d(&empty, &empty, true).is_empty());
600 }
601
602 #[test]
605 fn test_modal_central_deeper() {
606 let n = 20;
607 let m = 30;
608 let data = generate_centered_data(n, m);
609 let depths = modal_1d(&data, &data, 0.5);
610
611 let central_depth = depths[n / 2];
612 let edge_depth = depths[0];
613 assert!(central_depth > edge_depth, "Central curve should be deeper");
614 }
615
616 #[test]
617 fn test_modal_positive() {
618 let n = 10;
619 let m = 20;
620 let data = generate_centered_data(n, m);
621 let depths = modal_1d(&data, &data, 0.5);
622
623 for d in &depths {
624 assert!(*d > 0.0, "Modal depth should be positive");
625 }
626 }
627
628 #[test]
629 fn test_modal_invalid() {
630 let empty = FdMatrix::zeros(0, 0);
631 assert!(modal_1d(&empty, &empty, 0.5).is_empty());
632 }
633
634 #[test]
637 fn test_rp_depth_range() {
638 let n = 15;
639 let m = 20;
640 let data = generate_centered_data(n, m);
641 let depths = random_projection_1d(&data, &data, 50);
642
643 for d in &depths {
644 assert!(*d >= 0.0 && *d <= 1.0, "RP depth should be in [0, 1]");
645 }
646 }
647
648 #[test]
649 fn test_rp_depth_invalid() {
650 let empty = FdMatrix::zeros(0, 0);
651 assert!(random_projection_1d(&empty, &empty, 10).is_empty());
652 }
653
654 #[test]
657 fn test_random_tukey_range() {
658 let n = 15;
659 let m = 20;
660 let data = generate_centered_data(n, m);
661 let depths = random_tukey_1d(&data, &data, 50);
662
663 for d in &depths {
664 assert!(*d >= 0.0 && *d <= 1.0, "Tukey depth should be in [0, 1]");
665 }
666 }
667
668 #[test]
669 fn test_random_tukey_invalid() {
670 let empty = FdMatrix::zeros(0, 0);
671 assert!(random_tukey_1d(&empty, &empty, 10).is_empty());
672 }
673
674 #[test]
677 fn test_functional_spatial_range() {
678 let n = 15;
679 let m = 20;
680 let data = generate_centered_data(n, m);
681 let depths = functional_spatial_1d(&data, &data, None);
682
683 for d in &depths {
684 assert!(*d >= 0.0 && *d <= 1.0, "Spatial depth should be in [0, 1]");
685 }
686 }
687
688 #[test]
689 fn test_functional_spatial_invalid() {
690 let empty = FdMatrix::zeros(0, 0);
691 assert!(functional_spatial_1d(&empty, &empty, None).is_empty());
692 }
693
694 #[test]
697 fn test_band_depth_central_deeper() {
698 let n = 10;
699 let m = 20;
700 let data = generate_centered_data(n, m);
701 let depths = band_1d(&data, &data);
702
703 let central_depth = depths[n / 2];
705 let edge_depth = depths[0];
706 assert!(
707 central_depth >= edge_depth,
708 "Central curve should have higher band depth"
709 );
710 }
711
712 #[test]
713 fn test_band_depth_range() {
714 let n = 10;
715 let m = 20;
716 let data = generate_centered_data(n, m);
717 let depths = band_1d(&data, &data);
718
719 for d in &depths {
720 assert!(*d >= 0.0 && *d <= 1.0, "Band depth should be in [0, 1]");
721 }
722 }
723
724 #[test]
725 fn test_band_depth_invalid() {
726 let empty = FdMatrix::zeros(0, 0);
727 assert!(band_1d(&empty, &empty).is_empty());
728 let single = FdMatrix::from_column_major(vec![1.0, 2.0], 1, 2).unwrap();
729 assert!(band_1d(&single, &single).is_empty()); }
731
732 #[test]
735 fn test_modified_band_depth_range() {
736 let n = 10;
737 let m = 20;
738 let data = generate_centered_data(n, m);
739 let depths = modified_band_1d(&data, &data);
740
741 for d in &depths {
742 assert!(*d >= 0.0 && *d <= 1.0, "MBD should be in [0, 1]");
743 }
744 }
745
746 #[test]
747 fn test_modified_band_depth_invalid() {
748 let empty = FdMatrix::zeros(0, 0);
749 assert!(modified_band_1d(&empty, &empty).is_empty());
750 }
751
752 #[test]
755 fn test_mei_range() {
756 let n = 15;
757 let m = 20;
758 let data = generate_centered_data(n, m);
759 let mei = modified_epigraph_index_1d(&data, &data);
760
761 for d in &mei {
762 assert!(*d >= 0.0 && *d <= 1.0, "MEI should be in [0, 1]");
763 }
764 }
765
766 #[test]
767 fn test_mei_invalid() {
768 let empty = FdMatrix::zeros(0, 0);
769 assert!(modified_epigraph_index_1d(&empty, &empty).is_empty());
770 }
771
772 #[test]
775 fn test_kfsd_1d_range() {
776 let n = 10;
777 let m = 20;
778 let argvals = uniform_grid(m);
779 let data = generate_centered_data(n, m);
780 let depths = kernel_functional_spatial_1d(&data, &data, &argvals, 0.5);
781
782 assert_eq!(depths.len(), n);
783 for d in &depths {
784 assert!(
785 *d >= -0.01 && *d <= 1.01,
786 "KFSD depth should be near [0, 1], got {}",
787 d
788 );
789 assert!(d.is_finite(), "KFSD depth should be finite");
790 }
791
792 let central_depth = depths[n / 2];
794 let edge_depth = depths[0];
795 assert!(
796 central_depth > edge_depth,
797 "Central KFSD depth {} should be > edge depth {}",
798 central_depth,
799 edge_depth
800 );
801 }
802
803 #[test]
804 fn test_kfsd_1d_identical() {
805 let n = 5;
807 let m = 10;
808 let argvals = uniform_grid(m);
809 let data_vec: Vec<f64> = (0..n * m)
810 .map(|i| (2.0 * PI * (i % m) as f64 / (m - 1) as f64).sin())
811 .collect();
812 let data = FdMatrix::from_column_major(data_vec, n, m).unwrap();
813
814 let depths = kernel_functional_spatial_1d(&data, &data, &argvals, 0.5);
817
818 assert_eq!(depths.len(), n);
819 for d in &depths {
820 assert!(
821 d.is_finite(),
822 "KFSD depth should be finite for identical curves"
823 );
824 }
825 }
826
827 #[test]
828 fn test_kfsd_1d_invalid() {
829 let argvals = uniform_grid(10);
830 let empty = FdMatrix::zeros(0, 0);
831 assert!(kernel_functional_spatial_1d(&empty, &empty, &argvals, 0.5).is_empty());
832 let empty_obj = FdMatrix::zeros(0, 0);
833 assert!(kernel_functional_spatial_1d(&empty_obj, &empty_obj, &argvals, 0.5).is_empty());
834 }
835
836 #[test]
839 fn test_kfsd_2d_range() {
840 let n = 8;
841 let m = 15;
842 let data = generate_centered_data(n, m);
843 let depths = kernel_functional_spatial_2d(&data, &data, 0.5);
844
845 assert_eq!(depths.len(), n);
846 for d in &depths {
847 assert!(
848 *d >= -0.01 && *d <= 1.01,
849 "KFSD 2D depth should be near [0, 1], got {}",
850 d
851 );
852 assert!(d.is_finite(), "KFSD 2D depth should be finite");
853 }
854 }
855
856 #[test]
859 fn test_fraiman_muniz_2d_delegates() {
860 let n = 10;
861 let m = 15;
862 let data = generate_centered_data(n, m);
863 let depths_1d = fraiman_muniz_1d(&data, &data, true);
864 let depths_2d = fraiman_muniz_2d(&data, &data, true);
865 assert_eq!(depths_1d, depths_2d);
866 }
867
868 #[test]
869 fn test_modal_2d_delegates() {
870 let n = 10;
871 let m = 15;
872 let data = generate_centered_data(n, m);
873 let depths_1d = modal_1d(&data, &data, 0.5);
874 let depths_2d = modal_2d(&data, &data, 0.5);
875 assert_eq!(depths_1d, depths_2d);
876 }
877
878 #[test]
879 fn test_functional_spatial_2d_delegates() {
880 let n = 10;
881 let m = 15;
882 let data = generate_centered_data(n, m);
883 let depths_1d = functional_spatial_1d(&data, &data, None);
884 let depths_2d = functional_spatial_2d(&data, &data);
885 assert_eq!(depths_1d, depths_2d);
886 }
887
888 #[test]
889 fn test_random_projection_2d_returns_valid() {
890 let n = 10;
891 let m = 15;
892 let data = generate_centered_data(n, m);
893 let depths = random_projection_2d(&data, &data, 20);
894 assert_eq!(depths.len(), n);
895 for d in &depths {
896 assert!(*d >= 0.0 && *d <= 1.0, "RP 2D depth should be in [0, 1]");
897 }
898 }
899
900 fn golden_data() -> FdMatrix {
904 let n = 5;
905 let m = 10;
906 let argvals: Vec<f64> = (0..m).map(|i| i as f64 / (m - 1) as f64).collect();
907 let mut data = vec![0.0; n * m];
908 for i in 0..n {
909 let offset = (i as f64 - n as f64 / 2.0) / (n as f64);
910 for j in 0..m {
911 data[i + j * n] = (2.0 * PI * argvals[j]).sin() + offset;
912 }
913 }
914 FdMatrix::from_column_major(data, n, m).unwrap()
915 }
916
917 #[test]
918 fn test_fm_golden_values_scaled() {
919 let data = golden_data();
920 let depths = fraiman_muniz_1d(&data, &data, true);
921 let expected = [0.4, 0.8, 0.8, 0.4, 0.0];
922 assert_eq!(depths.len(), expected.len());
923 for (d, e) in depths.iter().zip(expected.iter()) {
924 assert!(
925 (d - e).abs() < 1e-10,
926 "FM scaled golden mismatch: got {}, expected {}",
927 d,
928 e
929 );
930 }
931 }
932
933 #[test]
934 fn test_fm_golden_values_unscaled() {
935 let data = golden_data();
936 let depths = fraiman_muniz_1d(&data, &data, false);
937 let expected = [0.2, 0.4, 0.4, 0.2, 0.0];
938 assert_eq!(depths.len(), expected.len());
939 for (d, e) in depths.iter().zip(expected.iter()) {
940 assert!(
941 (d - e).abs() < 1e-10,
942 "FM unscaled golden mismatch: got {}, expected {}",
943 d,
944 e
945 );
946 }
947 }
948
949 #[test]
950 fn test_mbd_golden_values() {
951 let data = golden_data();
952 let depths = modified_band_1d(&data, &data);
953 let expected = [0.4, 0.7, 0.8, 0.7, 0.4];
954 assert_eq!(depths.len(), expected.len());
955 for (d, e) in depths.iter().zip(expected.iter()) {
956 assert!(
957 (d - e).abs() < 1e-10,
958 "MBD golden mismatch: got {}, expected {}",
959 d,
960 e
961 );
962 }
963 }
964
965 #[test]
966 fn test_nan_fm_no_panic() {
967 let n = 5;
968 let m = 10;
969 let mut data_vec = vec![0.0; n * m];
970 data_vec[3] = f64::NAN;
971 let data = FdMatrix::from_column_major(data_vec, n, m).unwrap();
972 let depths = fraiman_muniz_1d(&data, &data, true);
973 assert_eq!(depths.len(), n);
974 }
975
976 #[test]
977 fn test_nan_mbd_no_panic() {
978 let n = 5;
979 let m = 10;
980 let mut data_vec = vec![0.0; n * m];
981 data_vec[3] = f64::NAN;
982 let data = FdMatrix::from_column_major(data_vec, n, m).unwrap();
983 let depths = modified_band_1d(&data, &data);
984 assert_eq!(depths.len(), n);
985 }
986
987 #[test]
988 fn test_n1_depths() {
989 let data = FdMatrix::from_column_major(vec![1.0, 2.0, 3.0], 1, 3).unwrap();
990 let fm = fraiman_muniz_1d(&data, &data, true);
991 assert_eq!(fm.len(), 1);
992 let modal = modal_1d(&data, &data, 0.5);
993 assert_eq!(modal.len(), 1);
994 let spatial = functional_spatial_1d(&data, &data, None);
995 assert_eq!(spatial.len(), 1);
996 }
997
998 #[test]
999 fn test_n2_band_depth() {
1000 let data = FdMatrix::from_column_major(vec![1.0, 2.0, 3.0, 4.0], 2, 2).unwrap();
1002 let depths = band_1d(&data, &data);
1003 assert_eq!(depths.len(), 2);
1004 for &d in &depths {
1005 assert!((0.0..=1.0).contains(&d));
1006 }
1007 }
1008
1009 #[test]
1010 fn test_inf_spatial_depth() {
1011 let n = 3;
1012 let m = 5;
1013 let mut data_vec = vec![1.0; n * m];
1014 data_vec[0] = f64::INFINITY;
1015 let data = FdMatrix::from_column_major(data_vec, n, m).unwrap();
1016 let depths = functional_spatial_1d(&data, &data, None);
1017 assert_eq!(depths.len(), n);
1018 }
1020}