use crate::{
DType, RT, Tensor, ZyxError,
kernel::BOp,
shape::{Dim, UAxis, into_axes, into_axis},
tensor::Axis,
};
#[derive(Clone, Copy)]
pub enum ReduceOp {
Sum,
Mean,
None,
Var,
Std,
Max,
Min,
Prod,
}
impl Tensor {
fn inverse(&self) -> Tensor {
let dtype = self.dtype();
if dtype.is_float() {
-self
} else if dtype.is_int() {
self.bitnot()
} else {
!self
}
}
pub(crate) fn reduce_impl<const KEEPDIM: bool>(
&self,
op: ReduceOp,
axes: impl IntoIterator<Item = Axis>,
dtype: Option<DType>,
correction: Dim,
) -> Result<Tensor, ZyxError> {
fn reduce_acc_dtype(dtype: DType) -> DType {
if dtype.is_uint() {
return dtype.least_upper_dtype(DType::U32);
}
if dtype.is_int() || dtype == DType::Bool {
return dtype.least_upper_dtype(DType::I32);
}
dtype.least_upper_dtype(DType::F32)
}
let shape = self.resolve_shape();
let shape_dims = self.shape();
let rank = shape.len();
let x_dtype = self.dtype();
let axes: Vec<_> = axes.into_iter().collect();
let axes_vec: Vec<UAxis> = into_axes(axes.clone(), rank)?;
let mut tensor = match op {
ReduceOp::Sum => {
let x = if let Some(dtype) = dtype {
self.cast(dtype)
} else {
self.cast(reduce_acc_dtype(x_dtype))
};
Tensor { id: RT.lock().reduce(x.id, axes_vec.clone(), BOp::Add)? }
}
ReduceOp::Max => {
let x = if let Some(dtype) = dtype {
self.cast(dtype)
} else {
self.cast(reduce_acc_dtype(x_dtype))
};
Tensor { id: RT.lock().reduce(x.id, axes_vec.clone(), BOp::Max)? }
}
ReduceOp::Prod => {
let x = if let Some(dtype) = dtype {
self.cast(dtype)
} else {
self.cast(reduce_acc_dtype(x_dtype))
};
Tensor { id: RT.lock().reduce(x.id, axes_vec.clone(), BOp::Mul)? }
}
ReduceOp::Min => {
if let Some(dtype) = dtype {
self.inverse().max_dtype(axes, dtype)?.inverse()
} else {
self.inverse().max(axes)?.inverse()
}
}
ReduceOp::Mean => {
let mut n_t = Tensor::from(1i64);
for &a in &axes_vec {
n_t = n_t * shape_dims[a].clone();
}
let x = if let Some(dtype) = dtype {
self.sum_dtype(axes, dtype)?
} else {
self.sum(axes)?
};
x / n_t.cast(x_dtype)
}
ReduceOp::Var => {
if let Some(dtype) = dtype {
let x = self - self.mean_keepdim_dtype(axes.clone(), dtype)?;
let shape_dims: Vec<Dim> = axes_vec.iter().map(|&a| shape[a]).collect();
let d =
Axis::try_from(shape_dims.iter().product::<Dim>() as u64).unwrap() - Axis::try_from(correction).unwrap();
(x.clone() * x).sum_dtype(axes, dtype)? / Tensor::from(d).cast(x_dtype)
} else {
let x = self - self.mean_keepdim(axes.clone())?;
let shape_dims: Vec<Dim> = axes_vec.iter().map(|&a| shape[a]).collect();
let d =
Axis::try_from(shape_dims.iter().product::<Dim>() as u64).unwrap() - Axis::try_from(correction).unwrap();
(x.clone() * x).sum(axes)? / Tensor::from(d).cast(x_dtype)
}
}
ReduceOp::Std => {
if let Some(dtype) = dtype {
self.var_dtype(axes, dtype)?.sqrt()
} else {
self.var(axes)?.sqrt()
}
}
ReduceOp::None => self.clone(),
};
if dtype.is_none() && x_dtype != tensor.dtype() {
tensor = tensor.cast(x_dtype);
}
if KEEPDIM {
let mut dims: Vec<Tensor> = Vec::with_capacity(rank);
for (a, d) in shape_dims.iter().enumerate() {
if axes_vec.contains(&(a as UAxis)) {
dims.push(Tensor::from(1i64));
} else {
dims.push(d.clone());
}
}
tensor = tensor.reshape(dims)?;
}
Ok(tensor)
}
}
impl Tensor {
#[must_use]
pub fn sum_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Sum, [], None, 1).unwrap()
}
#[must_use]
pub fn sum_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Sum, [], None, 1).unwrap()
}
pub fn sum(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Sum, axes, None, 1)
}
pub fn sum_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Sum, axes, None, 1)
}
#[must_use]
pub fn sum_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Sum, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn sum_all_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Sum, [], Some(dtype), 1).unwrap()
}
pub fn sum_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Sum, axes, Some(dtype), 1)
}
pub fn sum_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Sum, axes, Some(dtype), 1)
}
}
impl Tensor {
#[must_use]
pub fn mean_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Mean, [], None, 1).unwrap()
}
#[must_use]
pub fn mean_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Mean, [], None, 1).unwrap()
}
pub fn mean(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Mean, axes, None, 1)
}
pub fn mean_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Mean, axes, None, 1)
}
#[must_use]
pub fn mean_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Mean, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn mean_all_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Mean, [], Some(dtype), 1).unwrap()
}
pub fn mean_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Mean, axes, Some(dtype), 1)
}
pub fn mean_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Mean, axes, Some(dtype), 1)
}
}
impl Tensor {
#[must_use]
pub fn max_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Max, [], None, 1).unwrap()
}
#[must_use]
pub fn max_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Max, [], None, 1).unwrap()
}
pub fn max(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Max, axes, None, 1)
}
pub fn max_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Max, axes, None, 1)
}
#[must_use]
pub fn max_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Max, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn max_all_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Max, [], Some(dtype), 1).unwrap()
}
pub fn max_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Max, axes, Some(dtype), 1)
}
pub fn max_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Max, axes, Some(dtype), 1)
}
}
impl Tensor {
#[must_use]
pub fn min_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Min, [], None, 1).unwrap()
}
#[must_use]
pub fn min_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Min, [], None, 1).unwrap()
}
pub fn min(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Min, axes, None, 1)
}
pub fn min_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Min, axes, None, 1)
}
#[must_use]
pub fn min_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Min, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn min_all_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Min, [], Some(dtype), 1).unwrap()
}
pub fn min_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Min, axes, Some(dtype), 1)
}
pub fn min_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Min, axes, Some(dtype), 1)
}
}
impl Tensor {
#[must_use]
pub fn prod_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Prod, [], None, 1).unwrap()
}
#[must_use]
pub fn prod_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Prod, [], None, 1).unwrap()
}
pub fn prod(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Prod, axes, None, 1)
}
pub fn prod_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Prod, axes, None, 1)
}
#[must_use]
pub fn prod_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Prod, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn prod_all_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Prod, [], Some(dtype), 1).unwrap()
}
pub fn prod_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Prod, axes, Some(dtype), 1)
}
pub fn prod_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Prod, axes, Some(dtype), 1)
}
}
impl Tensor {
#[must_use]
pub fn var_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Var, [], None, 1).unwrap()
}
#[must_use]
pub fn var_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Var, [], None, 1).unwrap()
}
#[must_use]
pub fn var_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Var, [], Some(dtype), 1).unwrap()
}
pub fn var(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Var, axes, None, 1)
}
pub fn var_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Var, axes, None, 1)
}
pub fn var_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Var, axes, Some(dtype), 1)
}
pub fn var_correction(&self, axes: impl IntoIterator<Item = Axis>, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Var, axes, None, correction)
}
pub fn var_all_correction(&self, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Var, [], None, correction)
}
#[must_use]
pub fn var_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Var, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn var_all_keepdim_correction(&self, correction: Dim) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Var, [], None, correction).unwrap()
}
#[must_use]
pub fn var_all_dtype_correction(&self, dtype: DType, correction: Dim) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Var, [], Some(dtype), correction).unwrap()
}
pub fn var_axes_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Var, axes, Some(dtype), 1)
}
pub fn var_keepdim_correction(&self, axes: impl IntoIterator<Item = Axis>, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Var, axes, None, correction)
}
pub fn var_dtype_correction(
&self,
axes: impl IntoIterator<Item = Axis>,
dtype: DType,
correction: Dim,
) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Var, axes, Some(dtype), correction)
}
#[must_use]
pub fn var_all_keepdim_dtype_correction(&self, dtype: DType, correction: Dim) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Var, [], Some(dtype), correction).unwrap()
}
pub fn var_keepdim_dtype_correction(
&self,
axes: impl IntoIterator<Item = Axis>,
dtype: DType,
correction: Dim,
) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Var, axes, Some(dtype), correction)
}
}
impl Tensor {
#[must_use]
pub fn std_all(&self) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Std, [], None, 1).unwrap()
}
#[must_use]
pub fn std_all_keepdim(&self) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Std, [], None, 1).unwrap()
}
#[must_use]
pub fn std_all_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Std, [], Some(dtype), 1).unwrap()
}
pub fn std(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Std, axes, None, 1)
}
pub fn std_keepdim(&self, axes: impl IntoIterator<Item = Axis>) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Std, axes, None, 1)
}
pub fn std_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Std, axes, Some(dtype), 1)
}
pub fn std_correction(&self, axes: impl IntoIterator<Item = Axis>, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Std, axes, None, correction)
}
pub fn std_all_correction(&self, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Std, [], None, correction)
}
#[must_use]
pub fn std_keepdim_dtype(&self, dtype: DType) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Std, [], Some(dtype), 1).unwrap()
}
#[must_use]
pub fn std_all_keepdim_correction(&self, correction: Dim) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Std, [], None, correction).unwrap()
}
#[must_use]
pub fn std_all_dtype_correction(&self, dtype: DType, correction: Dim) -> Tensor {
self.reduce_impl::<false>(ReduceOp::Std, [], Some(dtype), correction).unwrap()
}
pub fn std_axes_keepdim_dtype(&self, axes: impl IntoIterator<Item = Axis>, dtype: DType) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Std, axes, Some(dtype), 1)
}
pub fn std_keepdim_correction(&self, axes: impl IntoIterator<Item = Axis>, correction: Dim) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Std, axes, None, correction)
}
pub fn std_dtype_correction(
&self,
axes: impl IntoIterator<Item = Axis>,
dtype: DType,
correction: Dim,
) -> Result<Tensor, ZyxError> {
self.reduce_impl::<false>(ReduceOp::Std, axes, Some(dtype), correction)
}
#[must_use]
pub fn std_all_keepdim_dtype_correction(&self, dtype: DType, correction: Dim) -> Tensor {
self.reduce_impl::<true>(ReduceOp::Std, [], Some(dtype), correction).unwrap()
}
pub fn std_keepdim_dtype_correction(
&self,
axes: impl IntoIterator<Item = Axis>,
dtype: DType,
correction: Dim,
) -> Result<Tensor, ZyxError> {
self.reduce_impl::<true>(ReduceOp::Std, axes, Some(dtype), correction)
}
#[allow(clippy::missing_panics_doc)]
pub fn cumsum(&self, axis: Axis) -> Result<Tensor, ZyxError> {
self.cum_reduce(axis, BOp::Add)
}
#[allow(clippy::missing_panics_doc)]
pub fn cummax(&self, axis: Axis) -> Result<Tensor, ZyxError> {
self.cum_reduce(axis, BOp::Max)
}
#[allow(clippy::missing_panics_doc)]
pub fn cumprod(&self, axis: Axis) -> Result<Tensor, ZyxError> {
self.cum_reduce(axis, BOp::Mul)
}
fn cum_reduce(&self, axis: Axis, rop: BOp) -> Result<Tensor, ZyxError> {
let shape = self.resolve_shape();
let uaxis = into_axis(axis, shape.len())?;
let pl_sz = i64::try_from(shape[uaxis] - 1).unwrap();
let mut x = self.transpose(axis, -1)?;
x = x.rpad_zeros([(pl_sz, 0i64)])?;
x = x.pool([shape[uaxis]], [1i64], [1i64])?;
x = match rop {
BOp::Add => x.sum([-1])?,
BOp::Max => x.max([-1])?,
BOp::Mul => x.prod([-1])?,
_ => unreachable!(),
};
x = x.transpose(axis, -1)?;
Ok(x)
}
}