use crate::error::FdarError;
use crate::iter_maybe_parallel;
use crate::matrix::FdMatrix;
#[cfg(feature = "parallel")]
use rayon::iter::ParallelIterator;
#[must_use = "expensive computation whose result should not be discarded"]
pub fn linfinity_depth_1d(data_obj: &FdMatrix, data_ori: &FdMatrix) -> Result<Vec<f64>, FdarError> {
let (nobj, nori, m) = (data_obj.nrows(), data_ori.nrows(), data_obj.ncols());
if nobj == 0 || nori == 0 || m == 0 {
return Err(FdarError::InvalidDimension {
parameter: "data_obj",
expected: "non-empty matrices".to_string(),
actual: format!("{nobj}x{m}"),
});
}
if data_ori.ncols() != m {
return Err(FdarError::InvalidDimension {
parameter: "data_ori",
expected: format!("same number of columns as data_obj ({m})"),
actual: format!("{}", data_ori.ncols()),
});
}
let depths: Vec<f64> = iter_maybe_parallel!(0..nobj)
.map(|i| {
let mut total = 0.0_f64;
for j in 0..nori {
let mut sup = 0.0_f64;
for t in 0..m {
let d = (data_obj[(i, t)] - data_ori[(j, t)]).abs();
if d > sup {
sup = d;
}
}
total += sup;
}
let mean_dist = total / nori as f64;
1.0 / (1.0 + mean_dist)
})
.collect();
Ok(depths)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::depth::dispatch::{functional_depth, DepthMethod};
fn sample(n: usize, m: usize) -> FdMatrix {
let mut col_major = vec![0.0; n * m];
for i in 0..n {
for t in 0..m {
let x = t as f64 / (m as f64 - 1.0);
col_major[i + t * n] = (x * std::f64::consts::PI).sin() + 0.05 * i as f64;
}
}
FdMatrix::from_column_major(col_major, n, m).unwrap()
}
fn sample_with_outlier(n: usize, m: usize, outlier_idx: usize) -> FdMatrix {
let mut col_major = vec![0.0; n * m];
for i in 0..n {
for t in 0..m {
let x = t as f64 / (m as f64 - 1.0);
let base = (x * std::f64::consts::PI).sin();
let val = if i == outlier_idx {
base + 100.0
} else {
base + 0.01 * i as f64
};
col_major[i + t * n] = val;
}
}
FdMatrix::from_column_major(col_major, n, m).unwrap()
}
#[test]
fn depths_in_unit_interval() {
let data = sample(8, 20);
let ld = linfinity_depth_1d(&data, &data).unwrap();
assert_eq!(ld.len(), 8);
for &v in &ld {
assert!(v > 0.0 && v <= 1.0 + 1e-12, "L∞ depth out of range: {v}");
}
}
#[test]
fn closest_to_sample_is_deepest_and_outlier_shallow() {
let outlier_idx = 3usize;
let data = sample_with_outlier(8, 20, outlier_idx);
let ld = linfinity_depth_1d(&data, &data).unwrap();
let mut shallowest = 0usize;
let mut deepest = 0usize;
for i in 1..ld.len() {
if ld[i] < ld[shallowest] {
shallowest = i;
}
if ld[i] > ld[deepest] {
deepest = i;
}
}
assert_eq!(
shallowest, outlier_idx,
"outlier should be shallowest by L∞ depth"
);
assert_ne!(deepest, outlier_idx);
assert!(ld[deepest] > ld[outlier_idx]);
}
#[test]
fn monotone_decreasing_in_mean_distance() {
let n = 9usize;
let data = sample(n, 25);
let ld = linfinity_depth_1d(&data, &data).unwrap();
let mut deepest = 0usize;
for i in 1..n {
if ld[i] > ld[deepest] {
deepest = i;
}
}
assert_eq!(
deepest,
n / 2,
"central curve should be deepest by L∞ depth"
);
}
#[test]
fn single_curve_self_depth_is_one() {
let one = sample(1, 8);
let ld = linfinity_depth_1d(&one, &one).unwrap();
assert_eq!(ld.len(), 1);
assert!(
(ld[0] - 1.0).abs() < 1e-12,
"n=1 self-depth should be 1.0, got {}",
ld[0]
);
}
#[test]
fn dispatch_round_trip_and_empty_err() {
let data = sample(6, 12);
let got = functional_depth(&data, DepthMethod::LInfinity).unwrap();
assert_eq!(got, linfinity_depth_1d(&data, &data).unwrap());
let empty = FdMatrix::from_column_major(vec![], 0, 0).unwrap();
assert!(linfinity_depth_1d(&empty, &empty).is_err());
}
}