use crate::{Tensor, TensorElement};
use torsh_core::error::Result;
pub trait TensorConvenience<T: TensorElement> {
#[allow(non_snake_case)]
fn T(&self) -> Result<Tensor<T>>;
#[allow(non_snake_case)]
fn mT(&self) -> Result<Tensor<T>>;
#[allow(non_snake_case)]
fn H(&self) -> Result<Tensor<T>>;
fn t(&self) -> Result<Tensor<T>>;
fn m_t(&self) -> Result<Tensor<T>>;
fn h(&self) -> Result<Tensor<T>>;
fn detach(&self) -> Tensor<T>;
fn clone_tensor(&self) -> Result<Tensor<T>>;
fn is_contiguous(&self) -> bool;
fn contiguous(&self) -> Result<Tensor<T>>;
fn numel(&self) -> usize;
fn size(&self) -> Vec<usize>;
fn is_empty(&self) -> bool;
fn is_scalar(&self) -> bool;
fn item(&self) -> T;
fn to_scalar(&self) -> Result<T>;
}
impl<T: TensorElement + Copy + torsh_core::FloatElement> TensorConvenience<T> for Tensor<T> {
#[allow(non_snake_case)]
fn T(&self) -> Result<Tensor<T>> {
if self.shape().dims().len() == 2 {
self.transpose(0, 1)
} else if self.shape().dims().len() == 1 {
Ok(self.clone())
} else {
let ndim = self.shape().dims().len();
if ndim >= 2 {
self.transpose((ndim - 2) as i32, (ndim - 1) as i32)
} else {
Ok(self.clone())
}
}
}
#[allow(non_snake_case)]
fn mT(&self) -> Result<Tensor<T>> {
self.T()
}
#[allow(non_snake_case)]
fn H(&self) -> Result<Tensor<T>> {
let transposed = self.T()?;
Ok(transposed)
}
fn t(&self) -> Result<Tensor<T>> {
self.T()
}
fn m_t(&self) -> Result<Tensor<T>> {
self.T()
}
fn h(&self) -> Result<Tensor<T>> {
self.H()
}
fn detach(&self) -> Tensor<T> {
self.clone()
}
fn clone_tensor(&self) -> Result<Tensor<T>> {
Ok(self.detach())
}
fn is_contiguous(&self) -> bool {
let shape_ref = self.shape();
if shape_ref.dims().is_empty() {
return true; }
match &self.strides {
None => true,
Some(strides) => {
let expected = self.compute_default_strides();
strides == &expected
}
}
}
fn contiguous(&self) -> Result<Tensor<T>> {
if self.is_contiguous() {
Ok(self.clone())
} else {
self.clone_tensor()
}
}
fn numel(&self) -> usize {
self.shape().dims().iter().product()
}
fn size(&self) -> Vec<usize> {
self.shape().dims().to_vec()
}
fn is_empty(&self) -> bool {
self.numel() == 0
}
fn is_scalar(&self) -> bool {
self.shape().dims().is_empty()
}
fn item(&self) -> T {
if self.numel() != 1 {
panic!("Can only call item() on tensors with one element");
}
let data = self
.to_vec()
.expect("tensor to vec conversion should succeed");
data[0]
}
fn to_scalar(&self) -> Result<T> {
let squeezed = self.squeeze_all()?;
squeezed.item()
}
}
pub trait TensorShapeConvenience<T: TensorElement> {
fn unsqueeze_at(&self, dim: i32) -> Result<Tensor<T>>;
fn squeeze_all(&self) -> Result<Tensor<T>>;
fn flatten(&self) -> Result<Tensor<T>>;
fn flatten_from(&self, start_dim: i32) -> Result<Tensor<T>>;
fn unflatten(&self, dim: i32, sizes: &[usize]) -> Result<Tensor<T>>;
}
impl<T: TensorElement + Copy> TensorShapeConvenience<T> for Tensor<T> {
fn unsqueeze_at(&self, dim: i32) -> Result<Tensor<T>> {
self.unsqueeze(dim)
}
fn squeeze_all(&self) -> Result<Tensor<T>> {
let mut result = self.clone();
let shape_ref = self.shape();
let dims = shape_ref.dims();
for (i, &size) in dims.iter().enumerate().rev() {
if size == 1 {
result = result.squeeze(i as i32)?;
}
}
Ok(result)
}
fn flatten(&self) -> Result<Tensor<T>> {
let total_elements = self.numel();
self.reshape(&[total_elements as i32])
}
fn flatten_from(&self, start_dim: i32) -> Result<Tensor<T>> {
let shape_ref = self.shape();
let shape = shape_ref.dims();
let ndim = shape.len() as i32;
let start_dim = if start_dim < 0 {
ndim + start_dim
} else {
start_dim
};
if start_dim < 0 || start_dim >= ndim {
return Err(torsh_core::error::TorshError::InvalidArgument(format!(
"Invalid start_dim {start_dim} for tensor with {ndim} dimensions"
)));
}
let mut new_shape = Vec::new();
for &dim in shape.iter().take(start_dim as usize) {
new_shape.push(dim);
}
let flattened_size: usize = shape[start_dim as usize..].iter().product();
new_shape.push(flattened_size);
let new_shape_i32: Vec<i32> = new_shape.iter().map(|&x| x as i32).collect();
self.reshape(&new_shape_i32)
}
fn unflatten(&self, dim: i32, sizes: &[usize]) -> Result<Tensor<T>> {
let shape_ref = self.shape();
let shape = shape_ref.dims();
let ndim = shape.len() as i32;
let dim = if dim < 0 { ndim + dim } else { dim };
if dim < 0 || dim >= ndim {
return Err(torsh_core::error::TorshError::InvalidArgument(format!(
"Invalid dim {dim} for tensor with {ndim} dimensions"
)));
}
let expected_size = shape[dim as usize];
let actual_size: usize = sizes.iter().product();
if expected_size != actual_size {
return Err(torsh_core::error::TorshError::InvalidArgument(format!(
"Sizes {actual_size} don't multiply to dimension size {expected_size}"
)));
}
let mut new_shape = Vec::new();
for &dim_size in shape.iter().take(dim as usize) {
new_shape.push(dim_size);
}
new_shape.extend_from_slice(sizes);
for &dim_size in shape.iter().skip(dim as usize + 1) {
new_shape.push(dim_size);
}
let new_shape_i32: Vec<i32> = new_shape.iter().map(|&x| x as i32).collect();
self.reshape(&new_shape_i32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transpose_shortcuts() {
let tensor = crate::creation::tensor_2d_arrays(&[[1.0f32, 2.0], [3.0, 4.0]])
.expect("tensor creation failed");
let transposed = tensor.T().expect("T() failed");
assert_eq!(transposed.shape().dims(), &[2, 2]);
let mt_transposed = tensor.mT().expect("mT() failed");
assert_eq!(mt_transposed.shape().dims(), &[2, 2]);
let hermitian = tensor.H().expect("H() failed");
assert_eq!(hermitian.shape().dims(), &[2, 2]);
}
#[test]
fn test_tensor_properties() {
let tensor = crate::creation::tensor_2d_arrays(&[[1.0f32, 2.0], [3.0, 4.0]])
.expect("tensor creation failed");
assert_eq!(tensor.numel(), 4);
assert_eq!(tensor.shape().dims(), &[2, 2]);
assert!(!tensor.is_empty());
assert!(!tensor.is_scalar());
assert!(tensor.is_contiguous());
let scalar = crate::creation::tensor_scalar(42.0f32).expect("scalar creation failed");
assert!(scalar.is_scalar());
assert_eq!(scalar.item().expect("item retrieval failed"), 42.0);
}
#[test]
fn test_shape_convenience() {
let tensor = crate::creation::zeros::<f32>(&[4])
.expect("zeros creation failed")
.reshape(&[2, 1, 2])
.expect("reshape failed");
let squeezed = tensor.squeeze_all().expect("squeeze_all failed");
assert_eq!(squeezed.shape().dims(), &[2, 2]);
let flattened = tensor.flatten().expect("flatten failed");
assert_eq!(flattened.shape().dims(), &[4]);
let flat_from_1 = tensor.flatten_from(1).expect("flatten_from failed");
assert_eq!(flat_from_1.shape().dims(), &[2, 2]);
}
#[test]
fn test_detach() {
let tensor =
crate::creation::tensor_1d(&[1.0f32, 2.0, 3.0]).expect("tensor creation failed");
let detached = tensor.detach();
assert_eq!(tensor.shape().dims(), detached.shape().dims());
assert_eq!(
tensor.data().expect("data retrieval failed"),
detached.data().expect("detached data retrieval failed")
);
}
#[test]
fn test_fluent_api() {
use crate::TensorFluentExt;
let tensor =
crate::creation::tensor_1d(&[1.0f32, 2.0, 3.0, 4.0]).expect("tensor creation failed");
let result = tensor
.fluent()
.add_scalar(1.0) .mul_scalar(2.0) .sub_scalar(1.0) .unwrap()
.unwrap();
let expected = vec![3.0, 5.0, 7.0, 9.0];
let actual = result.to_vec().expect("to_vec failed");
for (exp, act) in expected.iter().zip(actual.iter()) {
assert!((exp - act).abs() < f32::EPSILON);
}
}
#[test]
fn test_fluent_api_operations() {
use crate::TensorFluentExt;
let tensor1 =
crate::creation::tensor_1d(&[1.0f32, 2.0, 3.0, 4.0]).expect("tensor1 creation failed");
let tensor2 =
crate::creation::tensor_1d(&[2.0f32, 2.0, 2.0, 2.0]).expect("tensor2 creation failed");
let result = tensor1
.fluent()
.add(&tensor2) .mul_scalar(0.5) .sum() .unwrap()
.unwrap();
let actual = result.to_vec().expect("to_vec failed");
assert!((actual[0] - 9.0).abs() < f32::EPSILON);
}
#[test]
fn test_fluent_api_mathematical_operations() {
use crate::TensorFluentExt;
let tensor =
crate::creation::tensor_1d(&[1.0f32, 2.0, 3.0, 4.0]).expect("tensor creation failed");
let result = tensor
.fluent()
.relu() .pow(2.0) .sigmoid() .unwrap()
.unwrap();
let actual = result.to_vec().expect("to_vec failed");
for val in actual.iter() {
assert!(*val > 0.0 && *val < 1.0);
}
}
}
pub trait TensorFluentExt<T: TensorElement> {
fn fluent(self) -> FluentTensor<T>;
}
pub struct FluentTensor<T: TensorElement> {
tensor: Tensor<T>,
}
impl<T: TensorElement> TensorFluentExt<T> for Tensor<T> {
fn fluent(self) -> FluentTensor<T> {
FluentTensor { tensor: self }
}
}
impl<
T: TensorElement
+ Copy
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ num_traits::Zero,
> FluentTensor<T>
{
pub fn tensor(self) -> Tensor<T> {
self.tensor
}
pub fn unwrap(self) -> Result<Tensor<T>> {
Ok(self.tensor)
}
pub fn add_scalar(mut self, scalar: T) -> Self {
if let Ok(result) = self.tensor.add_scalar(scalar) {
self.tensor = result;
}
self
}
pub fn mul_scalar(mut self, scalar: T) -> Self {
if let Ok(result) = self.tensor.mul_scalar(scalar) {
self.tensor = result;
}
self
}
pub fn sub_scalar(mut self, scalar: T) -> Self {
if let Ok(result) = self.tensor.sub_scalar(scalar) {
self.tensor = result;
}
self
}
pub fn div_scalar(mut self, scalar: T) -> Self {
if let Ok(result) = self.tensor.div_scalar(scalar) {
self.tensor = result;
}
self
}
pub fn add(mut self, other: &Tensor<T>) -> Self {
if let Ok(result) = self.tensor.add_op(other) {
self.tensor = result;
}
self
}
pub fn mul(mut self, other: &Tensor<T>) -> Self {
if let Ok(result) = self.tensor.mul_op(other) {
self.tensor = result;
}
self
}
pub fn sub(mut self, other: &Tensor<T>) -> Self {
if let Ok(result) = self.tensor.sub(other) {
self.tensor = result;
}
self
}
pub fn div(mut self, other: &Tensor<T>) -> Self {
if let Ok(result) = self.tensor.div(other) {
self.tensor = result;
}
self
}
pub fn reshape(mut self, shape: &[i32]) -> Self {
if let Ok(result) = self.tensor.reshape(shape) {
self.tensor = result;
}
self
}
pub fn transpose(mut self, dim0: i32, dim1: i32) -> Self {
if let Ok(result) = self.tensor.transpose(dim0, dim1) {
self.tensor = result;
}
self
}
pub fn t(mut self) -> Self {
if let Ok(result) = self.tensor.t() {
self.tensor = result;
}
self
}
pub fn sum(mut self) -> Self {
if let Ok(result) = self.tensor.sum() {
self.tensor = result;
}
self
}
pub fn sum_dim(mut self, dims: &[i32], keepdim: bool) -> Self {
if let Ok(result) = self.tensor.sum_dim(dims, keepdim) {
self.tensor = result;
}
self
}
pub fn squeeze(mut self, dim: i32) -> Self {
if let Ok(result) = self.tensor.squeeze(dim) {
self.tensor = result;
}
self
}
pub fn unsqueeze(mut self, dim: i32) -> Self {
if let Ok(result) = self.tensor.unsqueeze(dim) {
self.tensor = result;
}
self
}
}
impl<T: TensorElement + Copy + num_traits::Float> FluentTensor<T> {
pub fn relu(mut self) -> Self {
if let Ok(result) = self.tensor.relu() {
self.tensor = result;
}
self
}
pub fn sigmoid(mut self) -> Self
where
T: torsh_core::dtype::FloatElement,
{
if let Ok(result) = self.tensor.sigmoid() {
self.tensor = result;
}
self
}
pub fn tanh(mut self) -> Self
where
T: torsh_core::dtype::FloatElement,
{
if let Ok(result) = self.tensor.tanh() {
self.tensor = result;
}
self
}
pub fn exp(mut self) -> Self
where
T: torsh_core::dtype::FloatElement,
{
if let Ok(result) = self.tensor.exp() {
self.tensor = result;
}
self
}
pub fn log(mut self) -> Self
where
T: torsh_core::dtype::FloatElement,
{
if let Ok(result) = self.tensor.log() {
self.tensor = result;
}
self
}
pub fn pow(mut self, exponent: T) -> Self
where
T: torsh_core::dtype::FloatElement + Into<f32>,
{
if let Ok(result) = self.tensor.pow(exponent) {
self.tensor = result;
}
self
}
}
impl<T: TensorElement + Copy> FluentTensor<T>
where
T: num_traits::Float + std::iter::Sum,
{
pub fn matmul(mut self, other: &Tensor<T>) -> Self {
if let Ok(result) = self.tensor.matmul(other) {
self.tensor = result;
}
self
}
}
impl<
T: TensorElement
+ Copy
+ num_traits::FromPrimitive
+ std::ops::Div<Output = T>
+ num_traits::Zero
+ num_traits::One,
> FluentTensor<T>
{
pub fn mean(mut self, dims: Option<&[usize]>, keepdim: bool) -> Self {
if let Ok(result) = self.tensor.mean(dims, keepdim) {
self.tensor = result;
}
self
}
}