use crate::{Tensor, TensorElement, FloatElement};
use torsh_core::error::{Result, TorshError};
use torsh_core::shape::Shape;
use scirs2_core::ndarray::{Array, ArrayView, Axis, IxDyn};
use scirs2_core::numeric::{Float, Zero, One, cast::ToPrimitive};
use std::ops::{Add, Mul};
#[cfg(feature = "simd")]
use scirs2_core::simd::{SimdArray, SimdOps};
#[cfg(feature = "parallel")]
use scirs2_core::parallel_ops::{par_chunks, par_reduce};
#[cfg(feature = "profiling")]
use scirs2_core::profiling::profile_section;
impl<T: TensorElement> Tensor<T> {
pub fn sum_all(&self) -> T
where
T: Add<Output = T> + Zero + Copy
{
#[cfg(feature = "profiling")]
let _profile = profile_section!("sum_all");
#[cfg(feature = "gpu")]
{
if self.numel() > 100000 {
if let Ok(sum) = self.gpu_sum_all() {
return sum;
}
}
}
#[cfg(feature = "simd")]
{
if self.numel() > 1000 {
if let Ok(sum) = self.simd_sum_all() {
return sum;
}
}
}
#[cfg(feature = "parallel")]
{
if self.numel() > 100 {
if let Ok(data) = self.data() {
return par_reduce(&data, <T as TensorElement>::zero(), |acc, &val| acc + val);
}
}
}
let mut sum = <T as TensorElement>::zero();
for i in 0..self.numel() {
if let Ok(val) = self.get_item_flat(i) {
sum = sum + val;
}
}
sum
}
#[cfg(feature = "gpu")]
fn gpu_sum_all(&self) -> Result<T>
where
T: scirs2_core::gpu::GpuElement + Add<Output = T> + Zero + Copy,
{
#[cfg(feature = "profiling")]
let _profile = profile_section!("gpu_sum_all");
use scirs2_core::gpu::{GpuContext, GpuBuffer, GpuKernel};
let gpu_context = GpuContext::new()?;
let data = self.data()?;
let gpu_input = GpuBuffer::from_slice(&gpu_context, &data)?;
let kernel = GpuKernel::parallel_sum_reduction(&gpu_context)?;
let sum = kernel.reduce_sum(&gpu_input)?;
Ok(sum)
}
#[cfg(feature = "simd")]
fn simd_sum_all(&self) -> Result<T>
where
T: scirs2_core::simd::SimdElement + Add<Output = T> + Zero + Copy,
{
let data = self.data()?;
let simd_width = T::simd_width();
let mut simd_sum = SimdArray::splat(<T as TensorElement>::zero());
let (simd_data, remainder) = data.split_at(data.len() - (data.len() % simd_width));
for chunk in simd_data.chunks_exact(simd_width) {
let simd_chunk = SimdArray::from_slice(chunk);
simd_sum = simd_sum.add(&simd_chunk);
}
let mut total = simd_sum.horizontal_sum();
for &val in remainder {
total = total + val;
}
Ok(total)
}
pub fn sum(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy
{
match dims {
None => {
let sum_val = self.sum_all();
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Self::from_scalar(sum_val, &shape, self.device())
}
Some(dims) => {
self.reduce_along_dims(dims, keepdim, <T as TensorElement>::zero(), |acc, val| acc + val)
}
}
}
pub fn mean(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy + ToPrimitive,
T: std::ops::Div<f64, Output = T>,
{
let sum_tensor = self.sum(dims, keepdim)?;
let count = match dims {
None => self.numel() as f64,
Some(dims) => {
dims.iter().map(|&dim| self.shape().dims()[dim] as f64).product()
}
};
let mut result = sum_tensor;
for i in 0..result.numel() {
if let Ok(val) = result.get_item_flat(i) {
let mean_val = val / count;
let _ = result.set_item_flat(i, mean_val);
}
}
Ok(result)
}
pub fn min(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: PartialOrd + Copy
{
if self.numel() == 0 {
return Err(TorshError::Other("Cannot find min of empty tensor".to_string()));
}
match dims {
None => {
let mut min_val = self.get_item_flat(0)?;
for i in 1..self.numel() {
let val = self.get_item_flat(i)?;
if val < min_val {
min_val = val;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Self::from_scalar(min_val, &shape, self.device())
}
Some(dims) => {
let first_val = self.get_item_flat(0)?;
self.reduce_along_dims(dims, keepdim, first_val, |acc, val| {
if val < acc { val } else { acc }
})
}
}
}
pub fn max_v2(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: PartialOrd + Copy
{
if self.numel() == 0 {
return Err(TorshError::Other("Cannot find max of empty tensor".to_string()));
}
match dims {
None => {
let mut max_val = self.get_item_flat(0)?;
for i in 1..self.numel() {
let val = self.get_item_flat(i)?;
if val > max_val {
max_val = val;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Self::from_scalar(max_val, &shape, self.device())
}
Some(dims) => {
let first_val = self.get_item_flat(0)?;
self.reduce_along_dims(dims, keepdim, first_val, |acc, val| {
if val > acc { val } else { acc }
})
}
}
}
pub fn argmin(&self, dim: usize, keepdim: bool) -> Result<Tensor<i64>>
where
T: PartialOrd + Copy
{
if dim >= self.ndim() {
return Err(TorshError::Other(format!(
"Dimension {} out of range for {}-dimensional tensor",
dim, self.ndim()
)));
}
let mut result_dims = self.shape().dims().to_vec();
if keepdim {
result_dims[dim] = 1;
} else {
result_dims.remove(dim);
}
let result = Tensor::<i64>::zeros(&result_dims, self.device())?;
Ok(result)
}
pub fn argmax(&self, dim: usize, keepdim: bool) -> Result<Tensor<i64>>
where
T: PartialOrd + Copy
{
if dim >= self.ndim() {
return Err(TorshError::Other(format!(
"Dimension {} out of range for {}-dimensional tensor",
dim, self.ndim()
)));
}
let mut result_dims = self.shape().dims().to_vec();
if keepdim {
result_dims[dim] = 1;
} else {
result_dims.remove(dim);
}
let result = Tensor::<i64>::zeros(&result_dims, self.device())?;
Ok(result)
}
fn reduce_along_dims<F>(&self, dims: &[usize], keepdim: bool, init_val: T, op: F) -> Result<Self>
where
T: Copy,
F: Fn(T, T) -> T + Copy
{
for &dim in dims {
if dim >= self.ndim() {
return Err(TorshError::Other(format!(
"Dimension {} out of range for {}-dimensional tensor",
dim, self.ndim()
)));
}
}
let mut result_dims = self.shape().dims().to_vec();
let mut dims_sorted = dims.to_vec();
dims_sorted.sort_by(|a, b| b.cmp(a));
if keepdim {
for &dim in dims {
result_dims[dim] = 1;
}
} else {
for &dim in &dims_sorted {
result_dims.remove(dim);
}
}
if result_dims.is_empty() {
result_dims.push(1); }
let mut result = Self::from_scalar(init_val, &result_dims, self.device())?;
Ok(result)
}
}
impl<T: FloatElement + Default> Tensor<T> {
pub fn std_v2(&self, dims: Option<&[usize]>, keepdim: bool, unbiased: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy + ToPrimitive,
T: std::ops::Div<f64, Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T>,
{
let var_tensor = self.var_v2(dims, keepdim, unbiased)?;
let mut result = var_tensor;
for i in 0..result.numel() {
if let Ok(val) = result.get_item_flat(i) {
if let Some(val_f64) = <T as TensorElement>::to_f64(&val) {
let std_val = T::from(val_f64.sqrt()).unwrap_or(val);
let _ = result.set_item_flat(i, std_val);
}
}
}
Ok(result)
}
pub fn var_v2(&self, dims: Option<&[usize]>, keepdim: bool, unbiased: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy + ToPrimitive + Default,
T: std::ops::Div<f64, Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T>,
{
let mean_tensor = self.mean(dims, keepdim)?;
let diff = self.sub(&mean_tensor)?;
let squared_diff = diff.mul(&diff)?;
let sum_squared = squared_diff.sum(dims, keepdim)?;
let count = match dims {
None => self.numel() as f64,
Some(dims) => {
dims.iter().map(|&dim| self.shape().dims()[dim] as f64).product()
}
};
let divisor = if unbiased && count > 1.0 {
count - 1.0
} else {
count
};
let mut result = sum_squared;
for i in 0..result.numel() {
if let Ok(val) = result.get_item_flat(i) {
let var_val = val / divisor;
let _ = result.set_item_flat(i, var_val);
}
}
Ok(result)
}
pub fn norm_l1(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy,
{
let mut abs_tensor = self.clone();
for i in 0..abs_tensor.numel() {
if let Ok(val) = abs_tensor.get_item_flat(i) {
if let Some(val_f64) = <T as TensorElement>::to_f64(&val) {
let abs_val = T::from(val_f64.abs()).unwrap_or(val);
let _ = abs_tensor.set_item_flat(i, abs_val);
}
}
}
abs_tensor.sum(dims, keepdim)
}
pub fn norm_l2(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy + Default + std::ops::Mul<Output = T>,
{
let squared = self.mul(self)?;
let sum_squares = squared.sum(dims, keepdim)?;
let mut result = sum_squares;
for i in 0..result.numel() {
if let Ok(val) = result.get_item_flat(i) {
if let Some(val_f64) = <T as TensorElement>::to_f64(&val) {
let norm_val = T::from(val_f64.sqrt()).unwrap_or(val);
let _ = result.set_item_flat(i, norm_val);
}
}
}
Ok(result)
}
pub fn norm_lp(&self, p: f64, dims: Option<&[usize]>, keepdim: bool) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy,
{
if p == 1.0 {
return self.norm_l1(dims, keepdim);
} else if p == 2.0 {
return self.norm_l2(dims, keepdim);
}
let mut powered_tensor = self.clone();
for i in 0..powered_tensor.numel() {
if let Ok(val) = powered_tensor.get_item_flat(i) {
if let Some(val_f64) = <T as TensorElement>::to_f64(&val) {
let powered_val = T::from(val_f64.abs().powf(p)).unwrap_or(val);
let _ = powered_tensor.set_item_flat(i, powered_val);
}
}
}
let sum_powered = powered_tensor.sum(dims, keepdim)?;
let mut result = sum_powered;
for i in 0..result.numel() {
if let Ok(val) = result.get_item_flat(i) {
if let Some(val_f64) = <T as TensorElement>::to_f64(&val) {
let norm_val = T::from(val_f64.powf(1.0 / p)).unwrap_or(val);
let _ = result.set_item_flat(i, norm_val);
}
}
}
Ok(result)
}
pub fn cumsum(&self, dim: usize) -> Result<Self>
where
T: Add<Output = T> + Zero + Copy
{
if dim >= self.ndim() {
return Err(TorshError::Other(format!(
"Dimension {} out of range for {}-dimensional tensor",
dim, self.ndim()
)));
}
let mut result = self.clone();
Ok(result)
}
pub fn cumprod(&self, dim: usize) -> Result<Self>
where
T: Mul<Output = T> + One + Copy
{
if dim >= self.ndim() {
return Err(TorshError::Other(format!(
"Dimension {} out of range for {}-dimensional tensor",
dim, self.ndim()
)));
}
let mut result = self.clone();
Ok(result)
}
pub fn all(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Tensor<bool>>
where
T: PartialEq + Zero + Copy
{
let mut bool_tensor = Tensor::<bool>::zeros(&self.shape().dims(), self.device())?;
for i in 0..self.numel() {
if let Ok(val) = self.get_item_flat(i) {
let is_nonzero = val != <T as TensorElement>::zero();
let _ = bool_tensor.set_item_flat(i, is_nonzero);
}
}
match dims {
None => {
let mut all_true = true;
for i in 0..bool_tensor.numel() {
if let Ok(val) = bool_tensor.get_item_flat(i) {
all_true = all_true && val;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Tensor::<bool>::from_scalar(all_true, &shape, self.device())
}
Some(_dims) => {
Ok(bool_tensor)
}
}
}
pub fn any(&self, dims: Option<&[usize]>, keepdim: bool) -> Result<Tensor<bool>>
where
T: PartialEq + Zero + Copy
{
let mut bool_tensor = Tensor::<bool>::zeros(&self.shape().dims(), self.device())?;
for i in 0..self.numel() {
if let Ok(val) = self.get_item_flat(i) {
let is_nonzero = val != <T as TensorElement>::zero();
let _ = bool_tensor.set_item_flat(i, is_nonzero);
}
}
match dims {
None => {
let mut any_true = false;
for i in 0..bool_tensor.numel() {
if let Ok(val) = bool_tensor.get_item_flat(i) {
any_true = any_true || val;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Tensor::<bool>::from_scalar(any_true, &shape, self.device())
}
Some(_dims) => {
Ok(bool_tensor)
}
}
}
pub fn argmax(&self, dim: Option<isize>, keepdim: bool) -> Result<Tensor<i64>>
where
T: PartialOrd + Copy,
{
match dim {
None => {
let data = self.data()?;
if data.is_empty() {
return Err(TorshError::InvalidArgument(
"argmax of empty tensor".to_string(),
));
}
let mut max_idx = 0;
let mut max_val = data[0];
for (i, &val) in data.iter().enumerate().skip(1) {
if val > max_val {
max_val = val;
max_idx = i;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Tensor::<i64>::from_scalar(max_idx as i64, &shape, self.device())
}
Some(d) => {
let ndim = self.ndim();
let dim = if d < 0 {
(ndim as isize + d) as usize
} else {
d as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
d, ndim
)));
}
let mut output_shape: Vec<usize> = self
.shape()
.dims()
.iter()
.enumerate()
.filter(|(i, _)| *i != dim || keepdim)
.map(|(i, &s)| if i == dim && keepdim { 1 } else { s })
.collect();
if !keepdim {
output_shape = self
.shape()
.dims()
.iter()
.enumerate()
.filter(|(i, _)| *i != dim)
.map(|(_, &s)| s)
.collect();
}
let data = self.data()?;
let shape = self.shape().dims();
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let reduced_size = outer_size * inner_size;
let mut result = vec![0i64; reduced_size];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut max_idx = 0;
let mut max_val = data[outer * dim_size * inner_size + inner];
for d in 1..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
if data[idx] > max_val {
max_val = data[idx];
max_idx = d;
}
}
result[outer * inner_size + inner] = max_idx as i64;
}
}
Tensor::<i64>::from_data(result, output_shape, self.device())
}
}
}
pub fn argmin(&self, dim: Option<isize>, keepdim: bool) -> Result<Tensor<i64>>
where
T: PartialOrd + Copy,
{
match dim {
None => {
let data = self.data()?;
if data.is_empty() {
return Err(TorshError::InvalidArgument(
"argmin of empty tensor".to_string(),
));
}
let mut min_idx = 0;
let mut min_val = data[0];
for (i, &val) in data.iter().enumerate().skip(1) {
if val < min_val {
min_val = val;
min_idx = i;
}
}
let shape = if keepdim {
vec![1; self.ndim()]
} else {
vec![]
};
Tensor::<i64>::from_scalar(min_idx as i64, &shape, self.device())
}
Some(d) => {
let ndim = self.ndim();
let dim = if d < 0 {
(ndim as isize + d) as usize
} else {
d as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
d, ndim
)));
}
let mut output_shape: Vec<usize> = self
.shape()
.dims()
.iter()
.enumerate()
.filter(|(i, _)| *i != dim || keepdim)
.map(|(i, &s)| if i == dim && keepdim { 1 } else { s })
.collect();
if !keepdim {
output_shape = self
.shape()
.dims()
.iter()
.enumerate()
.filter(|(i, _)| *i != dim)
.map(|(_, &s)| s)
.collect();
}
let data = self.data()?;
let shape = self.shape().dims();
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let reduced_size = outer_size * inner_size;
let mut result = vec![0i64; reduced_size];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut min_idx = 0;
let mut min_val = data[outer * dim_size * inner_size + inner];
for d in 1..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
if data[idx] < min_val {
min_val = data[idx];
min_idx = d;
}
}
result[outer * inner_size + inner] = min_idx as i64;
}
}
Tensor::<i64>::from_data(result, output_shape, self.device())
}
}
}
pub fn cumsum(&self, dim: isize) -> Result<Self>
where
T: std::ops::Add<Output = T> + Copy + Default,
{
let ndim = self.ndim();
let dim = if dim < 0 {
(ndim as isize + dim) as usize
} else {
dim as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
dim, ndim
)));
}
let data = self.data()?;
let shape = self.shape().dims();
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let mut result = vec![T::default(); data.len()];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut cumsum = T::default();
for d in 0..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
cumsum = cumsum + data[idx];
result[idx] = cumsum;
}
}
}
Self::from_data(result, shape.to_vec(), self.device())
}
pub fn cumprod(&self, dim: isize) -> Result<Self>
where
T: std::ops::Mul<Output = T> + Copy + num_traits::One,
{
let ndim = self.ndim();
let dim = if dim < 0 {
(ndim as isize + dim) as usize
} else {
dim as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
dim, ndim
)));
}
let data = self.data()?;
let shape = self.shape().dims();
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let mut result = vec![<T as num_traits::One>::one(); data.len()];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut cumprod = <T as num_traits::One>::one();
for d in 0..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
cumprod = cumprod * data[idx];
result[idx] = cumprod;
}
}
}
Self::from_data(result, shape.to_vec(), self.device())
}
pub fn prod(&self) -> Result<T>
where
T: std::ops::Mul<Output = T> + Copy + num_traits::One,
{
let data = self.data()?;
if data.is_empty() {
return Ok(<T as num_traits::One>::one());
}
let result = data
.iter()
.copied()
.reduce(|a, b| a * b)
.unwrap_or_else(<T as num_traits::One>::one);
Ok(result)
}
pub fn median(&self) -> Result<T>
where
T: PartialOrd + Copy,
{
let mut data = self.data()?;
if data.is_empty() {
return Err(TorshError::InvalidArgument(
"median of empty tensor".to_string(),
));
}
data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let len = data.len();
let median = if len % 2 == 0 {
data[len / 2 - 1]
} else {
data[len / 2]
};
Ok(median)
}
pub fn median_dim(&self, dim: isize, keepdim: bool) -> Result<(Self, Tensor<i64>)>
where
T: PartialOrd + Copy + Default,
{
let ndim = self.ndim();
let dim = if dim < 0 {
(ndim as isize + dim) as usize
} else {
dim as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
dim, ndim
)));
}
let shape = self.shape().dims();
let data = self.data()?;
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let mut output_shape = shape.to_vec();
if keepdim {
output_shape[dim] = 1;
} else {
output_shape.remove(dim);
}
let output_size = output_shape.iter().product();
let mut values = vec![T::default(); output_size];
let mut indices = vec![0i64; output_size];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut slice = Vec::with_capacity(dim_size);
let mut indexed_slice = Vec::with_capacity(dim_size);
for d in 0..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
slice.push(data[idx]);
indexed_slice.push((data[idx], d as i64));
}
indexed_slice.sort_by(|a, b| {
a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)
});
let median_idx = if dim_size % 2 == 0 {
dim_size / 2 - 1
} else {
dim_size / 2
};
let (median_val, original_idx) = indexed_slice[median_idx];
let out_idx = if keepdim {
outer * inner_size + inner
} else {
outer * inner_size + inner
};
values[out_idx] = median_val;
indices[out_idx] = original_idx;
}
}
let values_tensor = Self::from_data(values, output_shape.clone(), self.device())?;
let indices_tensor = Tensor::<i64>::from_data(indices, output_shape, self.device())?;
Ok((values_tensor, indices_tensor))
}
pub fn mode(&self) -> Result<T>
where
T: PartialEq + Copy,
{
let data = self.data()?;
if data.is_empty() {
return Err(TorshError::InvalidArgument(
"mode of empty tensor".to_string(),
));
}
let mut frequency_map: std::collections::HashMap<String, (T, usize)> =
std::collections::HashMap::new();
for &val in data.iter() {
let key = format!("{:?}", val);
frequency_map
.entry(key)
.and_modify(|(_, count)| *count += 1)
.or_insert((val, 1));
}
let (mode_val, _) = frequency_map
.values()
.max_by_key(|(_, count)| count)
.ok_or_else(|| TorshError::InvalidArgument("Failed to compute mode".to_string()))?;
Ok(*mode_val)
}
pub fn mode_dim(&self, dim: isize, keepdim: bool) -> Result<(Self, Tensor<i64>)>
where
T: PartialEq + PartialOrd + Copy + Default,
{
let ndim = self.ndim();
let dim = if dim < 0 {
(ndim as isize + dim) as usize
} else {
dim as usize
};
if dim >= ndim {
return Err(TorshError::InvalidArgument(format!(
"Dimension {} out of range for {}-D tensor",
dim, ndim
)));
}
let shape = self.shape().dims();
let data = self.data()?;
let dim_size = shape[dim];
let outer_size: usize = shape[..dim].iter().product();
let inner_size: usize = shape[dim + 1..].iter().product();
let mut output_shape = shape.to_vec();
if keepdim {
output_shape[dim] = 1;
} else {
output_shape.remove(dim);
}
let output_size = output_shape.iter().product();
let mut values = vec![T::default(); output_size];
let mut indices = vec![0i64; output_size];
for outer in 0..outer_size {
for inner in 0..inner_size {
let mut indexed_slice = Vec::with_capacity(dim_size);
for d in 0..dim_size {
let idx = outer * dim_size * inner_size + d * inner_size + inner;
indexed_slice.push((data[idx], d as i64));
}
let mut frequency_map: std::collections::HashMap<String, (T, i64, usize)> =
std::collections::HashMap::new();
for (val, idx) in indexed_slice {
let key = format!("{:?}", val);
frequency_map
.entry(key)
.and_modify(|(_, first_idx, count)| {
*count += 1;
})
.or_insert((val, idx, 1));
}
let (mode_val, mode_idx, _) = frequency_map
.values()
.max_by(|a, b| {
match a.2.cmp(&b.2) {
std::cmp::Ordering::Equal => {
b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)
}
other => other,
}
})
.ok_or_else(|| TorshError::InvalidArgument("Failed to compute mode".to_string()))?;
let out_idx = if keepdim {
outer * inner_size + inner
} else {
outer * inner_size + inner
};
values[out_idx] = *mode_val;
indices[out_idx] = *mode_idx;
}
}
let values_tensor = Self::from_data(values, output_shape.clone(), self.device())?;
let indices_tensor = Tensor::<i64>::from_data(indices, output_shape, self.device())?;
Ok((values_tensor, indices_tensor))
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
#[test]
fn test_argmax_1d() {
let tensor = Tensor::from_data(vec![1.0f32, 5.0, 3.0, 2.0, 4.0], vec![5], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.argmax(None, false).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1i64]); }
#[test]
fn test_argmax_2d_no_dim() {
let tensor = Tensor::from_data(
vec![1.0f32, 5.0, 3.0, 2.0, 7.0, 4.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.argmax(None, false).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![4i64]); }
#[test]
fn test_argmax_2d_with_dim() {
let tensor = Tensor::from_data(
vec![1.0f32, 5.0, 3.0, 2.0, 7.0, 4.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.argmax(Some(1), false).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1i64, 1i64]); assert_eq!(result.shape().dims(), &[2]);
}
#[test]
fn test_argmin_1d() {
let tensor = Tensor::from_data(vec![5.0f32, 1.0, 3.0, 2.0, 4.0], vec![5], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.argmin(None, false).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1i64]); }
#[test]
fn test_argmin_2d_with_dim() {
let tensor = Tensor::from_data(
vec![5.0f32, 1.0, 3.0, 2.0, 7.0, 4.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.argmin(Some(1), false).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1i64, 0i64]); assert_eq!(result.shape().dims(), &[2]);
}
#[test]
fn test_cumsum_1d() {
let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![4], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.cumsum(0).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1.0, 3.0, 6.0, 10.0]);
}
#[test]
fn test_cumsum_2d() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.cumsum(1).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1.0, 3.0, 6.0, 4.0, 9.0, 15.0]);
}
#[test]
fn test_cumprod_1d() {
let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0], vec![4], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.cumprod(0).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1.0, 2.0, 6.0, 24.0]);
}
#[test]
fn test_cumprod_2d() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 3.0, 2.0, 2.0, 2.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.cumprod(1).expect("operation should succeed");
let data = result.data().expect("data retrieval should succeed");
assert_eq!(data, vec![1.0, 2.0, 6.0, 2.0, 4.0, 8.0]);
}
#[test]
fn test_prod_1d() {
let tensor = Tensor::from_data(vec![2.0f32, 3.0, 4.0], vec![3], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.prod().expect("prod should succeed");
assert_eq!(result, 24.0); }
#[test]
fn test_prod_empty() {
let tensor: Tensor<f32> = Tensor::from_data(vec![], vec![0], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.prod().expect("prod should succeed");
assert_eq!(result, 1.0); }
#[test]
fn test_prod_2d() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 3.0, 4.0],
vec![2, 2],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.prod().expect("prod should succeed");
assert_eq!(result, 24.0); }
#[test]
fn test_median_odd() {
let tensor = Tensor::from_data(vec![5.0f32, 1.0, 3.0, 9.0, 2.0], vec![5], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.median().expect("median should succeed");
assert_eq!(result, 3.0); }
#[test]
fn test_median_even() {
let tensor = Tensor::from_data(vec![5.0f32, 1.0, 3.0, 2.0], vec![4], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.median().expect("median should succeed");
assert_eq!(result, 2.0); }
#[test]
fn test_median_single() {
let tensor = Tensor::from_data(vec![42.0f32], vec![1], DeviceType::Cpu).expect("operation should succeed");
let result = tensor.median().expect("median should succeed");
assert_eq!(result, 42.0);
}
#[test]
fn test_median_dim() {
let tensor = Tensor::from_data(
vec![5.0f32, 1.0, 3.0, 9.0, 2.0, 7.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let (values, indices) = tensor.median_dim(1, false).expect("operation should succeed");
let val_data = values.data().expect("data retrieval should succeed");
let idx_data = indices.data().expect("data retrieval should succeed");
assert_eq!(values.shape().dims(), &[2]); assert_eq!(val_data, vec![3.0, 7.0]);
assert_eq!(idx_data, vec![2, 2]);
}
#[test]
fn test_median_dim_keepdim() {
let tensor = Tensor::from_data(
vec![5.0f32, 1.0, 3.0, 9.0, 2.0, 7.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let (values, _) = tensor.median_dim(1, true).expect("operation should succeed");
assert_eq!(values.shape().dims(), &[2, 1]); }
#[test]
fn test_mode_single_mode() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 3.0, 2.0, 2.0, 4.0],
vec![6],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.mode().expect("mode should succeed");
assert_eq!(result, 2.0); }
#[test]
fn test_mode_all_unique() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 3.0, 4.0],
vec![4],
DeviceType::Cpu,
).expect("operation should succeed");
let result = tensor.mode().expect("mode should succeed");
assert!([1.0, 2.0, 3.0, 4.0].contains(&result));
}
#[test]
fn test_mode_dim() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 2.0, 3.0, 3.0, 3.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let (values, indices) = tensor.mode_dim(1, false).expect("operation should succeed");
let val_data = values.data().expect("data retrieval should succeed");
assert_eq!(values.shape().dims(), &[2]);
assert_eq!(val_data, vec![2.0, 3.0]);
}
#[test]
fn test_mode_dim_keepdim() {
let tensor = Tensor::from_data(
vec![1.0f32, 2.0, 2.0, 3.0, 3.0, 3.0],
vec![2, 3],
DeviceType::Cpu,
).expect("operation should succeed");
let (values, _) = tensor.mode_dim(1, true).expect("operation should succeed");
assert_eq!(values.shape().dims(), &[2, 1]); }
}