use crate::ops::imp::{reshape, slice};
use crate::primitive::{Primitive, ScanOp};
use crate::view::normalize_axis;
use crate::{Array, DType, Error, Result, SliceArg, at_least_float};
fn single(primitive: Primitive, a: Array) -> Result<Array> {
Ok(Array::from_op(primitive, vec![a])?.swap_remove(0))
}
fn along(a: &Array, axis: Option<isize>) -> Result<(Array, usize)> {
match axis {
Some(axis) => Ok((a.clone(), normalize_axis(axis, a.ndim())?)),
None => Ok((reshape(a, &[-1])?, 0)),
}
}
pub(crate) fn sort(a: &Array, axis: Option<isize>) -> Result<Array> {
let (a, axis) = along(a, axis)?;
single(Primitive::Sort { axis }, a)
}
pub(crate) fn argsort(a: &Array, axis: Option<isize>) -> Result<Array> {
let (a, axis) = along(a, axis)?;
single(Primitive::ArgSort { axis }, a)
}
fn kth_index(op: &str, kth: isize, n: usize) -> Result<isize> {
let k = if kth < 0 { kth + n as isize } else { kth };
if (0..n as isize).contains(&k) {
Ok(k)
} else {
Err(Error::InvalidArgument(format!(
"{op}: kth {kth} is out of range for an axis of length {n}"
)))
}
}
pub(crate) fn partition(a: &Array, kth: isize, axis: Option<isize>) -> Result<Array> {
let (a, axis) = along(a, axis)?;
kth_index("partition", kth, a.dims()[axis])?;
single(Primitive::Sort { axis }, a)
}
pub(crate) fn argpartition(a: &Array, kth: isize, axis: Option<isize>) -> Result<Array> {
let (a, axis) = along(a, axis)?;
kth_index("argpartition", kth, a.dims()[axis])?;
single(Primitive::ArgSort { axis }, a)
}
pub(crate) fn topk(a: &Array, k: usize, axis: Option<isize>) -> Result<Array> {
let (a, axis) = along(a, axis)?;
let n = a.dims()[axis];
if k > n {
return Err(Error::InvalidArgument(format!(
"topk: k = {k} exceeds the axis length {n}"
)));
}
if k == n {
return Ok(a);
}
let sorted = single(Primitive::Sort { axis }, a.clone())?;
let mut args = vec![SliceArg::full(); a.ndim()];
args[axis] = SliceArg::new(Some((n - k) as isize), None, 1);
slice(&sorted, &args)
}
fn scan(
op: ScanOp,
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
let (a, axis) = along(a, axis)?;
let a = match op {
ScanOp::Sum if a.dtype() == DType::Bool => a.try_astype(DType::I32)?,
ScanOp::LogAddExp => a.try_astype(at_least_float(a.dtype()))?,
_ => a,
};
let narrow = a.dtype();
let wide = match narrow {
DType::U8 | DType::U16 => Some(DType::U32),
DType::I8 | DType::I16 => Some(DType::I32),
_ => None,
};
let a = match wide {
Some(w) => a.try_astype(w)?,
None => a,
};
let out = single(
Primitive::Scan {
op,
axis,
reverse,
inclusive,
},
a,
)?;
match wide {
Some(_) => out.try_astype(narrow),
None => Ok(out),
}
}
pub(crate) fn cumsum(
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
scan(ScanOp::Sum, a, axis, reverse, inclusive)
}
pub(crate) fn cumprod(
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
scan(ScanOp::Prod, a, axis, reverse, inclusive)
}
pub(crate) fn cummax(
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
scan(ScanOp::Max, a, axis, reverse, inclusive)
}
pub(crate) fn cummin(
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
scan(ScanOp::Min, a, axis, reverse, inclusive)
}
pub(crate) fn logcumsumexp(
a: &Array,
axis: Option<isize>,
reverse: bool,
inclusive: bool,
) -> Result<Array> {
scan(ScanOp::LogAddExp, a, axis, reverse, inclusive)
}