Skip to main content

fdars_core/streaming_depth/
mbd.rs

1//! Streaming Modified Band Depth (MBD) estimator.
2
3use crate::iter_maybe_parallel;
4use crate::matrix::FdMatrix;
5#[cfg(feature = "parallel")]
6use rayon::iter::ParallelIterator;
7
8use super::sorted_ref::SortedReferenceState;
9use super::{c2, StreamingDepth};
10
11/// Rank-based Modified Band Depth estimator.
12///
13/// Uses the combinatorial identity: at time t with `b` values strictly below
14/// x(t) and `a` strictly above,
15///
16/// > pairs containing x(t) = C(N,2) - C(b,2) - C(a,2)
17///
18/// MBD(x) = (1 / (C(N,2) x T)) x sum_t [C(N,2) - C(b_t,2) - C(a_t,2)]
19///
20/// Per-query complexity: **O(T x log N)** instead of O(N^2 x T).
21#[derive(Debug, Clone, PartialEq)]
22pub struct StreamingMbd {
23    state: SortedReferenceState,
24}
25
26impl StreamingMbd {
27    pub fn new(state: SortedReferenceState) -> Self {
28        Self { state }
29    }
30
31    /// Compute MBD for a single row-layout curve using rank formula.
32    #[inline]
33    fn mbd_one_inner(&self, curve: &[f64]) -> f64 {
34        let n = self.state.nori;
35        if n < 2 {
36            return 0.0;
37        }
38        let cn2 = c2(n);
39        let t_len = self.state.n_points;
40        let mut total = 0usize;
41        for t in 0..t_len {
42            let (below, above) = self.state.rank_at(t, curve[t]);
43            total += cn2 - c2(below) - c2(above);
44        }
45        total as f64 / (cn2 as f64 * t_len as f64)
46    }
47
48    /// Compute MBD for row `row` of `data` without allocating a temporary Vec.
49    #[inline]
50    fn mbd_one_from_row(&self, data: &FdMatrix, row: usize) -> f64 {
51        let n = self.state.nori;
52        if n < 2 {
53            return 0.0;
54        }
55        let cn2 = c2(n);
56        let t_len = self.state.n_points;
57        let mut total = 0usize;
58        for t in 0..t_len {
59            let (below, above) = self.state.rank_at(t, data[(row, t)]);
60            total += cn2 - c2(below) - c2(above);
61        }
62        total as f64 / (cn2 as f64 * t_len as f64)
63    }
64}
65
66impl StreamingDepth for StreamingMbd {
67    fn depth_one(&self, curve: &[f64]) -> f64 {
68        self.mbd_one_inner(curve)
69    }
70
71    fn depth_batch(&self, data_obj: &FdMatrix) -> Vec<f64> {
72        let nobj = data_obj.nrows();
73        if nobj == 0 || self.state.n_points == 0 || self.state.nori < 2 {
74            return vec![0.0; nobj];
75        }
76        iter_maybe_parallel!(0..nobj)
77            .map(|i| self.mbd_one_from_row(data_obj, i))
78            .collect()
79    }
80
81    fn n_points(&self) -> usize {
82        self.state.n_points
83    }
84
85    fn n_reference(&self) -> usize {
86        self.state.nori
87    }
88}