use crate::depth::{modified_epigraph_index_1d, modified_hypograph_index_1d};
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 half_region_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 matrix".to_string(),
actual: format!("{nobj}x{m}"),
});
}
if nori < 2 {
return Err(FdarError::InvalidDimension {
parameter: "data_ori",
expected: "at least 2 reference curves for half-region depth".to_string(),
actual: format!("{nori}"),
});
}
let depths: Vec<f64> = iter_maybe_parallel!(0..nobj)
.map(|i| {
let mut ei_count = 0.0_f64; let mut hi_count = 0.0_f64; for j in 0..nori {
let mut j_above = true; let mut j_below = true; for t in 0..m {
let x_j = data_ori[(j, t)];
let x_i = data_obj[(i, t)];
if x_j > x_i {
j_below = false;
}
if x_j < x_i {
j_above = false;
}
if !j_above && !j_below {
break;
}
}
if j_above {
ei_count += 1.0;
}
if j_below {
hi_count += 1.0;
}
}
f64::min(ei_count, hi_count) / nori as f64
})
.collect();
Ok(depths)
}
#[must_use = "expensive computation whose result should not be discarded"]
pub fn modified_half_region_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 matrix".to_string(),
actual: format!("{nobj}x{m}"),
});
}
let mei = modified_epigraph_index_1d(data_obj, data_ori);
let mhi = modified_hypograph_index_1d(data_obj, data_ori)?;
let depths: Vec<f64> = mei
.iter()
.zip(mhi.iter())
.map(|(&e, &h)| f64::min(e, h))
.collect();
Ok(depths)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::depth::dispatch::{functional_depth, DepthMethod};
use crate::matrix::FdMatrix;
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 hrd_range_and_length() {
let n = 8usize;
let data = sample(n, 20);
let hrd = half_region_depth_1d(&data, &data).unwrap();
assert_eq!(hrd.len(), n);
for &v in &hrd {
assert!((0.0..=1.0 + 1e-12).contains(&v), "HRD out of range: {v}");
}
}
#[test]
fn hrd_equals_min_of_ei_and_hi() {
let data = sample(7, 15);
let ei = crate::depth::epigraph_index_1d(&data, &data).unwrap();
let hi = crate::depth::hypograph_index_1d(&data, &data).unwrap();
let hrd = half_region_depth_1d(&data, &data).unwrap();
for i in 0..hrd.len() {
assert!((hrd[i] - f64::min(ei[i], hi[i])).abs() < 1e-12);
}
}
#[test]
fn hrd_ranks_central_deepest_and_extremes_shallow() {
let n = 9usize;
let data = sample(n, 25);
let hrd = half_region_depth_1d(&data, &data).unwrap();
let mut deepest = 0usize;
for i in 1..n {
if hrd[i] > hrd[deepest] {
deepest = i;
}
}
assert_eq!(deepest, n / 2, "central curve should be deepest by HRD");
assert!(hrd[0] <= hrd[deepest] && hrd[n - 1] <= hrd[deepest]);
}
#[test]
fn mhrd_ranks_central_deepest_and_outlier_shallow() {
let outlier_idx = 3usize;
let data = sample_with_outlier(8, 20, outlier_idx);
let mhrd = modified_half_region_depth_1d(&data, &data).unwrap();
let mut deepest = 0usize;
for i in 1..mhrd.len() {
if mhrd[i] > mhrd[deepest] {
deepest = i;
}
}
assert_ne!(
deepest, outlier_idx,
"magnitude outlier must not be deepest"
);
assert!(
mhrd[outlier_idx] < mhrd[deepest],
"outlier MHRD {} should be below the deepest curve's {}",
mhrd[outlier_idx],
mhrd[deepest]
);
assert!(
mhrd[outlier_idx] < 0.2,
"magnitude outlier should be shallow (MHRD ≈ 1/n), got {}",
mhrd[outlier_idx]
);
}
#[test]
fn mhrd_equals_min_of_mei_and_mhi() {
let data = sample(6, 12);
let mei = crate::depth::modified_epigraph_index_1d(&data, &data);
let mhi = crate::depth::modified_hypograph_index_1d(&data, &data).unwrap();
let mhrd = modified_half_region_depth_1d(&data, &data).unwrap();
for i in 0..mhrd.len() {
assert!((mhrd[i] - f64::min(mei[i], mhi[i])).abs() < 1e-12);
}
}
#[test]
fn dispatch_round_trip() {
let data = sample(6, 12);
let hrd = functional_depth(&data, DepthMethod::HalfRegion).unwrap();
assert_eq!(hrd, half_region_depth_1d(&data, &data).unwrap());
let mhrd = functional_depth(&data, DepthMethod::ModifiedHalfRegion).unwrap();
assert_eq!(mhrd, modified_half_region_depth_1d(&data, &data).unwrap());
}
#[test]
fn empty_and_single_curve_return_err() {
let empty = FdMatrix::from_column_major(vec![], 0, 0).unwrap();
assert!(half_region_depth_1d(&empty, &empty).is_err());
assert!(modified_half_region_depth_1d(&empty, &empty).is_err());
let single = sample(1, 8);
assert!(half_region_depth_1d(&single, &single).is_err());
assert!(modified_half_region_depth_1d(&single, &single).is_ok());
assert!(functional_depth(&single, DepthMethod::HalfRegion).is_err());
assert!(functional_depth(&single, DepthMethod::ModifiedHalfRegion).is_err());
}
}