use crate::error::FdarError;
use crate::iter_maybe_parallel;
use crate::matrix::FdMatrix;
#[cfg(feature = "parallel")]
use rayon::iter::ParallelIterator;
fn column_ranks(data: &FdMatrix, col: usize) -> Vec<f64> {
let n = data.nrows();
let mut indexed: Vec<(f64, usize)> = (0..n).map(|i| (data[(i, col)], i)).collect();
indexed.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let mut ranks = vec![0.0_f64; n];
let mut k = 0;
while k < n {
let mut j = k + 1;
while j < n && indexed[j].0 == indexed[k].0 {
j += 1;
}
let avg_rank = (k as f64 + 1.0 + j as f64) / 2.0;
for item in &indexed[k..j] {
ranks[item.1] = avg_rank;
}
k = j;
}
ranks
}
#[must_use = "expensive computation whose result should not be discarded"]
pub fn extreme_rank_length_depth_1d(
data_obj: &FdMatrix,
data_ori: &FdMatrix,
) -> Result<Vec<f64>, FdarError> {
let (n, m) = (data_ori.nrows(), data_ori.ncols());
if n == 0 || m == 0 || data_obj.nrows() == 0 || data_obj.ncols() == 0 {
return Err(FdarError::InvalidDimension {
parameter: "data_ori",
expected: "non-empty matrix".to_string(),
actual: format!("{n}x{m}"),
});
}
if data_obj.ncols() != m {
return Err(FdarError::InvalidDimension {
parameter: "data_obj",
expected: format!("same number of columns as data_ori ({m})"),
actual: format!("{}", data_obj.ncols()),
});
}
if n < 2 {
return Err(FdarError::InvalidDimension {
parameter: "data_ori",
expected: "at least 2 curves for extreme-rank-length depth".to_string(),
actual: format!("{n}"),
});
}
let mut r = vec![vec![0.0_f64; m]; n];
for t in 0..m {
let ranks = column_ranks(data_ori, t);
for i in 0..n {
r[i][t] = f64::min(ranks[i], (n + 1) as f64 - ranks[i]);
}
}
let mut sorted_r: Vec<Vec<f64>> = r
.into_iter()
.map(|mut row| {
row.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
row
})
.collect();
let sorted_r = std::mem::take(&mut sorted_r);
const EPS: f64 = 1e-12;
let depths: Vec<f64> = iter_maybe_parallel!(0..n)
.map(|i| {
let mut not_more_extreme = 0.0_f64;
for j in 0..n {
let more_extreme = sorted_r[i]
.iter()
.zip(sorted_r[j].iter())
.find(|(a, b)| (**a - **b).abs() > EPS)
.map(|(a, b)| a < b)
.unwrap_or(false);
if !more_extreme {
not_more_extreme += 1.0;
}
}
not_more_extreme / n as f64
})
.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_and_deepest_is_one() {
let n = 9usize;
let data = sample(n, 25);
let erl = extreme_rank_length_depth_1d(&data, &data).unwrap();
assert_eq!(erl.len(), n);
for &v in &erl {
assert!(v > 0.0 && v <= 1.0 + 1e-12, "ERL out of range: {v}");
}
let max = erl.iter().cloned().fold(f64::MIN, f64::max);
assert!(
(max - 1.0).abs() < 1e-12,
"deepest ERL should be 1.0, got {max}"
);
}
#[test]
fn central_deepest_and_outlier_among_shallowest() {
let outlier_idx = 3usize;
let data = sample_with_outlier(9, 25, outlier_idx);
let erl = extreme_rank_length_depth_1d(&data, &data).unwrap();
let mut deepest = 0usize;
for i in 1..erl.len() {
if erl[i] > erl[deepest] {
deepest = i;
}
}
assert!(
(erl[deepest] - 1.0).abs() < 1e-12,
"median curve should have ERL 1.0, got {}",
erl[deepest]
);
assert_ne!(deepest, outlier_idx, "outlier must not be deepest");
assert!(
erl[outlier_idx] <= 2.0 / 9.0 + 1e-12,
"outlier should be among the most extreme, got {}",
erl[outlier_idx]
);
}
#[test]
fn dispatch_round_trip() {
let data = sample(6, 12);
let got = functional_depth(&data, DepthMethod::ExtremeRankLength).unwrap();
assert_eq!(got, extreme_rank_length_depth_1d(&data, &data).unwrap());
}
#[test]
fn empty_and_single_curve_return_err() {
let empty = FdMatrix::from_column_major(vec![], 0, 0).unwrap();
assert!(extreme_rank_length_depth_1d(&empty, &empty).is_err());
let one = sample(1, 8);
assert!(extreme_rank_length_depth_1d(&one, &one).is_err()); assert!(functional_depth(&one, DepthMethod::ExtremeRankLength).is_err());
}
}