Skip to main content

fdars_core/depth/
mod.rs

1//! Depth measures for functional data.
2//!
3//! This module provides various depth measures for assessing the centrality
4//! of functional observations within a reference sample.
5
6use crate::matrix::FdMatrix;
7use crate::maybe_par_chunks_mut_enumerate;
8use rand::prelude::*;
9use rand_distr::StandardNormal;
10
11pub mod band;
12pub mod fraiman_muniz;
13pub mod modal;
14pub mod random_projection;
15pub mod random_tukey;
16pub mod rpd;
17pub mod spatial;
18
19#[cfg(test)]
20mod tests;
21
22// Re-export all public functions
23pub use band::{band_1d, modified_band_1d, modified_epigraph_index_1d};
24pub use fraiman_muniz::{fraiman_muniz_1d, fraiman_muniz_2d};
25pub use modal::{modal_1d, modal_2d};
26pub use random_projection::{
27    random_projection_1d, random_projection_1d_seeded, random_projection_2d,
28};
29pub use random_tukey::{random_tukey_1d, random_tukey_1d_seeded, random_tukey_2d};
30pub use rpd::{rpd_depth_1d, rpd_depth_1d_seeded};
31pub use spatial::{
32    functional_spatial_1d, functional_spatial_2d, kernel_functional_spatial_1d,
33    kernel_functional_spatial_2d,
34};
35
36// ---------------------------------------------------------------------------
37// Shared helpers
38// ---------------------------------------------------------------------------
39
40/// Generate `nproj` unit-norm random projection vectors of dimension `m`.
41///
42/// Returns a flat buffer of length `nproj * m` where projection `p` occupies
43/// `[p*m .. (p+1)*m]`.
44///
45/// If `seed` is Some, uses a deterministic RNG seeded from the given value.
46pub(super) fn generate_random_projections(nproj: usize, m: usize, seed: Option<u64>) -> Vec<f64> {
47    let mut rng: Box<dyn RngCore> = match seed {
48        Some(s) => Box::new(StdRng::seed_from_u64(s)),
49        None => Box::new(rand::thread_rng()),
50    };
51    let mut projections = vec![0.0; nproj * m];
52    for p_idx in 0..nproj {
53        let base = p_idx * m;
54        let mut norm_sq = 0.0;
55        for t in 0..m {
56            let v: f64 = rng.sample(StandardNormal);
57            projections[base + t] = v;
58            norm_sq += v * v;
59        }
60        let inv_norm = 1.0 / norm_sq.sqrt();
61        for t in 0..m {
62            projections[base + t] *= inv_norm;
63        }
64    }
65    projections
66}
67
68/// Project each reference curve onto each projection direction and sort.
69///
70/// Returns a flat buffer of length `nproj * nori` where the sorted projections
71/// for direction `p` occupy `[p*nori .. (p+1)*nori]`.
72pub(super) fn project_and_sort_reference(
73    data_ori: &FdMatrix,
74    projections: &[f64],
75    nproj: usize,
76    nori: usize,
77    m: usize,
78) -> Vec<f64> {
79    let mut sorted = vec![0.0; nproj * nori];
80    maybe_par_chunks_mut_enumerate!(sorted, nori, |(p_idx, spo): (usize, &mut [f64])| {
81        let proj = &projections[p_idx * m..(p_idx + 1) * m];
82        for j in 0..nori {
83            let mut dot = 0.0;
84            for t in 0..m {
85                dot += data_ori[(j, t)] * proj[t];
86            }
87            spo[j] = dot;
88        }
89        spo.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
90    });
91    sorted
92}
93
94/// Shared implementation for random projection-based depth measures.
95///
96/// Generates `nproj` random projections, projects both object and reference
97/// curves to scalars, and computes univariate depth via binary-search ranking.
98/// The `aggregate` and `finalize` closures control how per-projection depths
99/// are combined (e.g. average for RP depth, minimum for Tukey depth).
100pub(super) fn random_depth_core(
101    data_obj: &FdMatrix,
102    data_ori: &FdMatrix,
103    nproj: usize,
104    seed: Option<u64>,
105    init: f64,
106    aggregate: impl Fn(f64, f64) -> f64 + Sync,
107    finalize: impl Fn(f64, usize) -> f64 + Sync,
108) -> Vec<f64> {
109    use crate::iter_maybe_parallel;
110    #[cfg(feature = "parallel")]
111    use rayon::iter::ParallelIterator;
112
113    let nobj = data_obj.nrows();
114    let nori = data_ori.nrows();
115    let m = data_obj.ncols();
116
117    if nobj == 0 || nori == 0 || m == 0 || nproj == 0 {
118        return Vec::new();
119    }
120
121    let projections = generate_random_projections(nproj, m, seed);
122    let sorted_proj_ori = project_and_sort_reference(data_ori, &projections, nproj, nori, m);
123    let denom = nori as f64 + 1.0;
124
125    iter_maybe_parallel!(0..nobj)
126        .map(|i| {
127            let mut acc = init;
128            for p_idx in 0..nproj {
129                let proj = &projections[p_idx * m..(p_idx + 1) * m];
130                let sorted_ori = &sorted_proj_ori[p_idx * nori..(p_idx + 1) * nori];
131
132                let mut proj_i = 0.0;
133                for t in 0..m {
134                    proj_i += data_obj[(i, t)] * proj[t];
135                }
136
137                let below = sorted_ori.partition_point(|&v| v < proj_i);
138                let above = nori - sorted_ori.partition_point(|&v| v <= proj_i);
139                let depth = (below.min(above) as f64 + 1.0) / denom;
140                acc = aggregate(acc, depth);
141            }
142            finalize(acc, nproj)
143        })
144        .collect()
145}