use std::cmp::Ordering;
use ndarray::{
Array, ArrayBase, ArrayD, ArrayView1, ArrayViewMut1, AsArray, Axis, Dimension, IxDyn, ViewRepr,
};
use rayon::prelude::*;
use crate::copy::copy_into_flat;
use crate::prelude::*;
#[inline]
pub fn linear_percentile<'a, T, A, D>(
data: A,
percentile: f64,
axis: Option<usize>,
epsilon: Option<f64>,
threads: Option<usize>,
) -> Result<ArrayD<f64>, ImgalError>
where
A: AsArray<'a, T, D>,
D: Dimension,
T: 'a + AsNumeric,
{
let data: ArrayBase<ViewRepr<&'a T>, D> = data.into();
if data.is_empty() {
return Err(ImgalError::InvalidParameterEmptyArray { param_name: "data" });
}
let per_arr = match axis {
Some(ax) => {
if ax >= data.ndim() {
return Err(ImgalError::InvalidAxis {
axis_idx: ax,
dim_len: data.ndim(),
});
}
let mut shape = data.shape().to_vec();
shape.remove(ax);
let mut arr = ArrayD::<f64>::zeros(IxDyn(&shape));
let lanes = data.lanes(Axis(ax));
let lin_per_calc = |ln: ArrayView1<T>, pr: &mut f64| {
let mut ln = Array::from_vec(ln.to_vec());
*pr = linear_percentile_1d(ln.view_mut(), percentile, epsilon);
};
par!(threads,
seq_exp: lanes.into_iter().zip(arr.iter_mut())
.for_each(|(ln, pr)| lin_per_calc(ln, pr)),
par_exp: lanes.into_iter().zip(arr.iter_mut()).par_bridge()
.for_each(|(ln, pr)| lin_per_calc(ln, pr)));
arr
}
None => {
let mut arr = copy_into_flat(&data, threads);
let per = linear_percentile_1d(arr.view_mut(), percentile, epsilon);
Array::from_vec(vec![per]).into_dyn()
}
};
Ok(per_arr)
}
fn linear_percentile_1d<T>(mut data: ArrayViewMut1<T>, percentile: f64, epsilon: Option<f64>) -> f64
where
T: AsNumeric,
{
let epsilon = epsilon.unwrap_or(1e-12);
let p_clamp = percentile.clamp(0.0, 100.0);
match data.as_slice_mut() {
Some(val_arr) => {
let p = p_clamp / 100.0;
let h = (val_arr.len() as f64 - 1.0) * p;
let j = h.floor() as usize;
let gamma = h - j as f64;
if gamma.abs() < epsilon {
val_arr
.select_nth_unstable_by(j, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
return val_arr[j].to_f64();
}
val_arr.select_nth_unstable_by(j, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
let v_j = val_arr[j].to_f64();
val_arr
.select_nth_unstable_by(j + 1, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
let v_j1 = val_arr[j + 1].to_f64();
(1.0 - gamma) * v_j + gamma * v_j1
}
None => 0.0,
}
}