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) -> Vec<f64> {
89 let mut rng = rand::thread_rng();
90 let mut projections = vec![0.0; nproj * m];
91 for p_idx in 0..nproj {
92 let base = p_idx * m;
93 let mut norm_sq = 0.0;
94 for t in 0..m {
95 let v: f64 = rng.sample(StandardNormal);
96 projections[base + t] = v;
97 norm_sq += v * v;
98 }
99 let inv_norm = 1.0 / norm_sq.sqrt();
100 for t in 0..m {
101 projections[base + t] *= inv_norm;
102 }
103 }
104 projections
105}
106
107fn project_and_sort_reference(
112 data_ori: &FdMatrix,
113 projections: &[f64],
114 nproj: usize,
115 nori: usize,
116 m: usize,
117) -> Vec<f64> {
118 let mut sorted = vec![0.0; nproj * nori];
119 for p_idx in 0..nproj {
120 let proj = &projections[p_idx * m..(p_idx + 1) * m];
121 let spo = &mut sorted[p_idx * nori..(p_idx + 1) * nori];
122 for j in 0..nori {
123 let mut dot = 0.0;
124 for t in 0..m {
125 dot += data_ori[(j, t)] * proj[t];
126 }
127 spo[j] = dot;
128 }
129 spo.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
130 }
131 sorted
132}
133
134fn random_depth_core(
141 data_obj: &FdMatrix,
142 data_ori: &FdMatrix,
143 nproj: usize,
144 init: f64,
145 aggregate: impl Fn(f64, f64) -> f64 + Sync,
146 finalize: impl Fn(f64, usize) -> f64 + Sync,
147) -> Vec<f64> {
148 let nobj = data_obj.nrows();
149 let nori = data_ori.nrows();
150 let m = data_obj.ncols();
151
152 if nobj == 0 || nori == 0 || m == 0 || nproj == 0 {
153 return Vec::new();
154 }
155
156 let projections = generate_random_projections(nproj, m);
157 let sorted_proj_ori = project_and_sort_reference(data_ori, &projections, nproj, nori, m);
158 let denom = nori as f64 + 1.0;
159
160 iter_maybe_parallel!(0..nobj)
161 .map(|i| {
162 let mut acc = init;
163 for p_idx in 0..nproj {
164 let proj = &projections[p_idx * m..(p_idx + 1) * m];
165 let sorted_ori = &sorted_proj_ori[p_idx * nori..(p_idx + 1) * nori];
166
167 let mut proj_i = 0.0;
168 for t in 0..m {
169 proj_i += data_obj[(i, t)] * proj[t];
170 }
171
172 let below = sorted_ori.partition_point(|&v| v < proj_i);
173 let above = nori - sorted_ori.partition_point(|&v| v <= proj_i);
174 let depth = (below.min(above) as f64 + 1.0) / denom;
175 acc = aggregate(acc, depth);
176 }
177 finalize(acc, nproj)
178 })
179 .collect()
180}
181
182pub fn random_projection_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
192 random_depth_core(
193 data_obj,
194 data_ori,
195 nproj,
196 0.0,
197 |acc, d| acc + d,
198 |acc, n| acc / n as f64,
199 )
200}
201
202pub fn random_projection_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
204 random_projection_1d(data_obj, data_ori, nproj)
205}
206
207pub fn random_tukey_1d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
211 random_depth_core(
212 data_obj,
213 data_ori,
214 nproj,
215 f64::INFINITY,
216 f64::min,
217 |acc, _| acc,
218 )
219}
220
221pub fn random_tukey_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, nproj: usize) -> Vec<f64> {
223 random_tukey_1d(data_obj, data_ori, nproj)
224}
225
226pub fn functional_spatial_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
228 let nobj = data_obj.nrows();
229 let nori = data_ori.nrows();
230 let n_points = data_obj.ncols();
231
232 if nobj == 0 || nori == 0 || n_points == 0 {
233 return Vec::new();
234 }
235
236 iter_maybe_parallel!(0..nobj)
237 .map(|i| {
238 let mut sum_unit = vec![0.0; n_points];
239
240 for j in 0..nori {
241 let mut norm_sq = 0.0;
243 for t in 0..n_points {
244 let d = data_ori[(j, t)] - data_obj[(i, t)];
245 norm_sq += d * d;
246 }
247
248 let norm = norm_sq.sqrt();
249 if norm > 1e-10 {
250 let inv_norm = 1.0 / norm;
252 for t in 0..n_points {
253 sum_unit[t] += (data_ori[(j, t)] - data_obj[(i, t)]) * inv_norm;
254 }
255 }
256 }
257
258 let mut avg_norm_sq = 0.0;
259 for t in 0..n_points {
260 let avg = sum_unit[t] / nori as f64;
261 avg_norm_sq += avg * avg;
262 }
263
264 1.0 - avg_norm_sq.sqrt()
265 })
266 .collect()
267}
268
269pub fn functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
271 functional_spatial_1d(data_obj, data_ori)
272}
273
274fn kernel_pair_contribution(j: usize, k: usize, m1: &FdMatrix, m2: &[f64]) -> Option<f64> {
276 let denom_j_sq = 2.0 - 2.0 * m2[j];
277 if denom_j_sq < 1e-20 {
278 return None;
279 }
280 let denom_k_sq = 2.0 - 2.0 * m2[k];
281 if denom_k_sq < 1e-20 {
282 return None;
283 }
284 let denom = denom_j_sq.sqrt() * denom_k_sq.sqrt();
285 if denom <= 1e-20 {
286 return None;
287 }
288 let m_ijk = (1.0 + m1[(j, k)] - m2[j] - m2[k]) / denom;
289 if m_ijk.is_finite() {
290 Some(m_ijk)
291 } else {
292 None
293 }
294}
295
296fn kfsd_accumulate(m2: &[f64], m1: &FdMatrix, nori: usize) -> (f64, usize) {
303 let mut total_sum = 0.0;
304 let mut valid_count = 0;
305
306 for j in 0..nori {
308 if let Some(val) = kernel_pair_contribution(j, j, m1, m2) {
309 total_sum += val;
310 valid_count += 1;
311 }
312 }
313
314 for j in 0..nori {
316 for k in (j + 1)..nori {
317 if let Some(val) = kernel_pair_contribution(j, k, m1, m2) {
318 total_sum += 2.0 * val;
319 valid_count += 2;
320 }
321 }
322 }
323
324 (total_sum, valid_count)
325}
326
327fn kfsd_weighted(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64, weights: &[f64]) -> Vec<f64> {
330 let nobj = data_obj.nrows();
331 let nori = data_ori.nrows();
332 let n_points = data_obj.ncols();
333 let h_sq = h * h;
334
335 let m1_upper: Vec<(usize, usize, f64)> = iter_maybe_parallel!(0..nori)
337 .flat_map(|j| {
338 ((j + 1)..nori)
339 .map(|k| {
340 let mut sum = 0.0;
341 for t in 0..n_points {
342 let diff = data_ori[(j, t)] - data_ori[(k, t)];
343 sum += weights[t] * diff * diff;
344 }
345 (j, k, (-sum / h_sq).exp())
346 })
347 .collect::<Vec<_>>()
348 })
349 .collect();
350
351 let mut m1 = FdMatrix::zeros(nori, nori);
352 for j in 0..nori {
353 m1[(j, j)] = 1.0;
354 }
355 for (j, k, kval) in m1_upper {
356 m1[(j, k)] = kval;
357 m1[(k, j)] = kval;
358 }
359
360 let nori_f64 = nori as f64;
361
362 iter_maybe_parallel!(0..nobj)
363 .map(|i| {
364 let m2: Vec<f64> = (0..nori)
365 .map(|j| {
366 let mut sum = 0.0;
367 for t in 0..n_points {
368 let diff = data_obj[(i, t)] - data_ori[(j, t)];
369 sum += weights[t] * diff * diff;
370 }
371 (-sum / h_sq).exp()
372 })
373 .collect();
374
375 let (total_sum, valid_count) = kfsd_accumulate(&m2, &m1, nori);
376
377 if valid_count > 0 && total_sum >= 0.0 {
378 1.0 - total_sum.sqrt() / nori_f64
379 } else if total_sum < 0.0 {
380 1.0
381 } else {
382 0.0
383 }
384 })
385 .collect()
386}
387
388pub fn kernel_functional_spatial_1d(
392 data_obj: &FdMatrix,
393 data_ori: &FdMatrix,
394 argvals: &[f64],
395 h: f64,
396) -> Vec<f64> {
397 let nobj = data_obj.nrows();
398 let nori = data_ori.nrows();
399 let n_points = data_obj.ncols();
400
401 if nobj == 0 || nori == 0 || n_points == 0 {
402 return Vec::new();
403 }
404
405 let weights = simpsons_weights(argvals);
406 kfsd_weighted(data_obj, data_ori, h, &weights)
407}
408
409pub fn kernel_functional_spatial_2d(data_obj: &FdMatrix, data_ori: &FdMatrix, h: f64) -> Vec<f64> {
411 let nobj = data_obj.nrows();
412 let nori = data_ori.nrows();
413 let n_points = data_obj.ncols();
414
415 if nobj == 0 || nori == 0 || n_points == 0 {
416 return Vec::new();
417 }
418
419 let weights = vec![1.0; n_points];
420 kfsd_weighted(data_obj, data_ori, h, &weights)
421}
422
423pub fn band_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
427 if data_obj.nrows() == 0 || data_ori.nrows() < 2 || data_obj.ncols() == 0 {
428 return Vec::new();
429 }
430 let state = FullReferenceState::from_reference(data_ori);
431 let streaming = StreamingBd::new(state);
432 streaming.depth_batch(data_obj)
433}
434
435pub fn modified_band_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
439 if data_obj.nrows() == 0 || data_ori.nrows() < 2 || data_obj.ncols() == 0 {
440 return Vec::new();
441 }
442 let state = SortedReferenceState::from_reference(data_ori);
443 let streaming = StreamingMbd::new(state);
444 streaming.depth_batch(data_obj)
445}
446
447pub fn modified_epigraph_index_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Vec<f64> {
451 let nobj = data_obj.nrows();
452 let nori = data_ori.nrows();
453 let n_points = data_obj.ncols();
454
455 if nobj == 0 || nori == 0 || n_points == 0 {
456 return Vec::new();
457 }
458
459 iter_maybe_parallel!(0..nobj)
460 .map(|i| {
461 let mut total = 0.0;
462
463 for j in 0..nori {
464 let mut count = 0.0;
465
466 for t in 0..n_points {
467 let xi = data_obj[(i, t)];
468 let xj = data_ori[(j, t)];
469
470 if xi < xj {
471 count += 1.0;
472 } else if (xi - xj).abs() < 1e-12 {
473 count += 0.5;
474 }
475 }
476
477 total += count / n_points as f64;
478 }
479
480 total / nori as f64
481 })
482 .collect()
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488 use std::f64::consts::PI;
489
490 fn uniform_grid(n: usize) -> Vec<f64> {
491 (0..n).map(|i| i as f64 / (n - 1) as f64).collect()
492 }
493
494 fn generate_centered_data(n: usize, m: usize) -> FdMatrix {
495 let argvals = uniform_grid(m);
496 let mut data = vec![0.0; n * m];
497 for i in 0..n {
498 let offset = (i as f64 - n as f64 / 2.0) / (n as f64);
499 for j in 0..m {
500 data[i + j * n] = (2.0 * PI * argvals[j]).sin() + offset;
501 }
502 }
503 FdMatrix::from_column_major(data, n, m).unwrap()
504 }
505
506 #[test]
509 fn test_fraiman_muniz() {
510 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);
513 assert_eq!(depths.len(), 2);
514 }
515
516 #[test]
517 fn test_fraiman_muniz_central_deeper() {
518 let n = 20;
519 let m = 30;
520 let data = generate_centered_data(n, m);
521 let depths = fraiman_muniz_1d(&data, &data, true);
522
523 let central_depth = depths[n / 2];
525 let edge_depth = depths[0];
526 assert!(
527 central_depth > edge_depth,
528 "Central curve should be deeper: {} > {}",
529 central_depth,
530 edge_depth
531 );
532 }
533
534 #[test]
535 fn test_fraiman_muniz_range() {
536 let n = 15;
537 let m = 20;
538 let data = generate_centered_data(n, m);
539 let depths = fraiman_muniz_1d(&data, &data, true);
540
541 for d in &depths {
542 assert!(*d >= 0.0 && *d <= 1.0, "Depth should be in [0, 1]");
543 }
544 }
545
546 #[test]
547 fn test_fraiman_muniz_invalid() {
548 let empty = FdMatrix::zeros(0, 0);
549 assert!(fraiman_muniz_1d(&empty, &empty, true).is_empty());
550 }
551
552 #[test]
555 fn test_modal_central_deeper() {
556 let n = 20;
557 let m = 30;
558 let data = generate_centered_data(n, m);
559 let depths = modal_1d(&data, &data, 0.5);
560
561 let central_depth = depths[n / 2];
562 let edge_depth = depths[0];
563 assert!(central_depth > edge_depth, "Central curve should be deeper");
564 }
565
566 #[test]
567 fn test_modal_positive() {
568 let n = 10;
569 let m = 20;
570 let data = generate_centered_data(n, m);
571 let depths = modal_1d(&data, &data, 0.5);
572
573 for d in &depths {
574 assert!(*d > 0.0, "Modal depth should be positive");
575 }
576 }
577
578 #[test]
579 fn test_modal_invalid() {
580 let empty = FdMatrix::zeros(0, 0);
581 assert!(modal_1d(&empty, &empty, 0.5).is_empty());
582 }
583
584 #[test]
587 fn test_rp_depth_range() {
588 let n = 15;
589 let m = 20;
590 let data = generate_centered_data(n, m);
591 let depths = random_projection_1d(&data, &data, 50);
592
593 for d in &depths {
594 assert!(*d >= 0.0 && *d <= 1.0, "RP depth should be in [0, 1]");
595 }
596 }
597
598 #[test]
599 fn test_rp_depth_invalid() {
600 let empty = FdMatrix::zeros(0, 0);
601 assert!(random_projection_1d(&empty, &empty, 10).is_empty());
602 }
603
604 #[test]
607 fn test_random_tukey_range() {
608 let n = 15;
609 let m = 20;
610 let data = generate_centered_data(n, m);
611 let depths = random_tukey_1d(&data, &data, 50);
612
613 for d in &depths {
614 assert!(*d >= 0.0 && *d <= 1.0, "Tukey depth should be in [0, 1]");
615 }
616 }
617
618 #[test]
619 fn test_random_tukey_invalid() {
620 let empty = FdMatrix::zeros(0, 0);
621 assert!(random_tukey_1d(&empty, &empty, 10).is_empty());
622 }
623
624 #[test]
627 fn test_functional_spatial_range() {
628 let n = 15;
629 let m = 20;
630 let data = generate_centered_data(n, m);
631 let depths = functional_spatial_1d(&data, &data);
632
633 for d in &depths {
634 assert!(*d >= 0.0 && *d <= 1.0, "Spatial depth should be in [0, 1]");
635 }
636 }
637
638 #[test]
639 fn test_functional_spatial_invalid() {
640 let empty = FdMatrix::zeros(0, 0);
641 assert!(functional_spatial_1d(&empty, &empty).is_empty());
642 }
643
644 #[test]
647 fn test_band_depth_central_deeper() {
648 let n = 10;
649 let m = 20;
650 let data = generate_centered_data(n, m);
651 let depths = band_1d(&data, &data);
652
653 let central_depth = depths[n / 2];
655 let edge_depth = depths[0];
656 assert!(
657 central_depth >= edge_depth,
658 "Central curve should have higher band depth"
659 );
660 }
661
662 #[test]
663 fn test_band_depth_range() {
664 let n = 10;
665 let m = 20;
666 let data = generate_centered_data(n, m);
667 let depths = band_1d(&data, &data);
668
669 for d in &depths {
670 assert!(*d >= 0.0 && *d <= 1.0, "Band depth should be in [0, 1]");
671 }
672 }
673
674 #[test]
675 fn test_band_depth_invalid() {
676 let empty = FdMatrix::zeros(0, 0);
677 assert!(band_1d(&empty, &empty).is_empty());
678 let single = FdMatrix::from_column_major(vec![1.0, 2.0], 1, 2).unwrap();
679 assert!(band_1d(&single, &single).is_empty()); }
681
682 #[test]
685 fn test_modified_band_depth_range() {
686 let n = 10;
687 let m = 20;
688 let data = generate_centered_data(n, m);
689 let depths = modified_band_1d(&data, &data);
690
691 for d in &depths {
692 assert!(*d >= 0.0 && *d <= 1.0, "MBD should be in [0, 1]");
693 }
694 }
695
696 #[test]
697 fn test_modified_band_depth_invalid() {
698 let empty = FdMatrix::zeros(0, 0);
699 assert!(modified_band_1d(&empty, &empty).is_empty());
700 }
701
702 #[test]
705 fn test_mei_range() {
706 let n = 15;
707 let m = 20;
708 let data = generate_centered_data(n, m);
709 let mei = modified_epigraph_index_1d(&data, &data);
710
711 for d in &mei {
712 assert!(*d >= 0.0 && *d <= 1.0, "MEI should be in [0, 1]");
713 }
714 }
715
716 #[test]
717 fn test_mei_invalid() {
718 let empty = FdMatrix::zeros(0, 0);
719 assert!(modified_epigraph_index_1d(&empty, &empty).is_empty());
720 }
721
722 #[test]
725 fn test_kfsd_1d_range() {
726 let n = 10;
727 let m = 20;
728 let argvals = uniform_grid(m);
729 let data = generate_centered_data(n, m);
730 let depths = kernel_functional_spatial_1d(&data, &data, &argvals, 0.5);
731
732 assert_eq!(depths.len(), n);
733 for d in &depths {
734 assert!(
735 *d >= -0.01 && *d <= 1.01,
736 "KFSD depth should be near [0, 1], got {}",
737 d
738 );
739 assert!(d.is_finite(), "KFSD depth should be finite");
740 }
741
742 let central_depth = depths[n / 2];
744 let edge_depth = depths[0];
745 assert!(
746 central_depth > edge_depth,
747 "Central KFSD depth {} should be > edge depth {}",
748 central_depth,
749 edge_depth
750 );
751 }
752
753 #[test]
754 fn test_kfsd_1d_identical() {
755 let n = 5;
757 let m = 10;
758 let argvals = uniform_grid(m);
759 let data_vec: Vec<f64> = (0..n * m)
760 .map(|i| (2.0 * PI * (i % m) as f64 / (m - 1) as f64).sin())
761 .collect();
762 let data = FdMatrix::from_column_major(data_vec, n, m).unwrap();
763
764 let depths = kernel_functional_spatial_1d(&data, &data, &argvals, 0.5);
767
768 assert_eq!(depths.len(), n);
769 for d in &depths {
770 assert!(
771 d.is_finite(),
772 "KFSD depth should be finite for identical curves"
773 );
774 }
775 }
776
777 #[test]
778 fn test_kfsd_1d_invalid() {
779 let argvals = uniform_grid(10);
780 let empty = FdMatrix::zeros(0, 0);
781 assert!(kernel_functional_spatial_1d(&empty, &empty, &argvals, 0.5).is_empty());
782 let empty_obj = FdMatrix::zeros(0, 0);
783 assert!(kernel_functional_spatial_1d(&empty_obj, &empty_obj, &argvals, 0.5).is_empty());
784 }
785
786 #[test]
789 fn test_kfsd_2d_range() {
790 let n = 8;
791 let m = 15;
792 let data = generate_centered_data(n, m);
793 let depths = kernel_functional_spatial_2d(&data, &data, 0.5);
794
795 assert_eq!(depths.len(), n);
796 for d in &depths {
797 assert!(
798 *d >= -0.01 && *d <= 1.01,
799 "KFSD 2D depth should be near [0, 1], got {}",
800 d
801 );
802 assert!(d.is_finite(), "KFSD 2D depth should be finite");
803 }
804 }
805
806 #[test]
809 fn test_fraiman_muniz_2d_delegates() {
810 let n = 10;
811 let m = 15;
812 let data = generate_centered_data(n, m);
813 let depths_1d = fraiman_muniz_1d(&data, &data, true);
814 let depths_2d = fraiman_muniz_2d(&data, &data, true);
815 assert_eq!(depths_1d, depths_2d);
816 }
817
818 #[test]
819 fn test_modal_2d_delegates() {
820 let n = 10;
821 let m = 15;
822 let data = generate_centered_data(n, m);
823 let depths_1d = modal_1d(&data, &data, 0.5);
824 let depths_2d = modal_2d(&data, &data, 0.5);
825 assert_eq!(depths_1d, depths_2d);
826 }
827
828 #[test]
829 fn test_functional_spatial_2d_delegates() {
830 let n = 10;
831 let m = 15;
832 let data = generate_centered_data(n, m);
833 let depths_1d = functional_spatial_1d(&data, &data);
834 let depths_2d = functional_spatial_2d(&data, &data);
835 assert_eq!(depths_1d, depths_2d);
836 }
837
838 #[test]
839 fn test_random_projection_2d_returns_valid() {
840 let n = 10;
841 let m = 15;
842 let data = generate_centered_data(n, m);
843 let depths = random_projection_2d(&data, &data, 20);
844 assert_eq!(depths.len(), n);
845 for d in &depths {
846 assert!(*d >= 0.0 && *d <= 1.0, "RP 2D depth should be in [0, 1]");
847 }
848 }
849
850 fn golden_data() -> FdMatrix {
854 let n = 5;
855 let m = 10;
856 let argvals: Vec<f64> = (0..m).map(|i| i as f64 / (m - 1) as f64).collect();
857 let mut data = vec![0.0; n * m];
858 for i in 0..n {
859 let offset = (i as f64 - n as f64 / 2.0) / (n as f64);
860 for j in 0..m {
861 data[i + j * n] = (2.0 * PI * argvals[j]).sin() + offset;
862 }
863 }
864 FdMatrix::from_column_major(data, n, m).unwrap()
865 }
866
867 #[test]
868 fn test_fm_golden_values_scaled() {
869 let data = golden_data();
870 let depths = fraiman_muniz_1d(&data, &data, true);
871 let expected = [0.4, 0.8, 0.8, 0.4, 0.0];
872 assert_eq!(depths.len(), expected.len());
873 for (d, e) in depths.iter().zip(expected.iter()) {
874 assert!(
875 (d - e).abs() < 1e-10,
876 "FM scaled golden mismatch: got {}, expected {}",
877 d,
878 e
879 );
880 }
881 }
882
883 #[test]
884 fn test_fm_golden_values_unscaled() {
885 let data = golden_data();
886 let depths = fraiman_muniz_1d(&data, &data, false);
887 let expected = [0.2, 0.4, 0.4, 0.2, 0.0];
888 assert_eq!(depths.len(), expected.len());
889 for (d, e) in depths.iter().zip(expected.iter()) {
890 assert!(
891 (d - e).abs() < 1e-10,
892 "FM unscaled golden mismatch: got {}, expected {}",
893 d,
894 e
895 );
896 }
897 }
898
899 #[test]
900 fn test_mbd_golden_values() {
901 let data = golden_data();
902 let depths = modified_band_1d(&data, &data);
903 let expected = [0.4, 0.7, 0.8, 0.7, 0.4];
904 assert_eq!(depths.len(), expected.len());
905 for (d, e) in depths.iter().zip(expected.iter()) {
906 assert!(
907 (d - e).abs() < 1e-10,
908 "MBD golden mismatch: got {}, expected {}",
909 d,
910 e
911 );
912 }
913 }
914}