use tenferro_tensor::{DynRank, Host, TypedTensor};
use super::{scalar_binary_into, scalar_fold, AddOp, SubOp};
use strided_kernel::Exec;
fn tensor(values: &[f64]) -> TypedTensor<f64, DynRank, Host> {
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![values.len()], values.to_vec())
.unwrap()
}
#[test]
fn elementwise_rejects_a_destination_of_the_wrong_shape() {
let lhs = tensor(&[1.0, 2.0]);
let rhs = tensor(&[1.0, 2.0]);
let mut destination = tensor(&[0.0]);
let error =
scalar_binary_into::<f64, AddOp>("add", &mut destination, &lhs, &rhs, Exec::serial())
.expect_err("destination shape must match");
assert_eq!(destination.as_slice(), &[0.0]);
assert_eq!(
error.kind(),
tenferro_tensor::ErrorKind::Validation(tenferro_tensor_core::ValidationKind::ShapeMismatch)
);
}
#[test]
fn elementwise_rejects_operands_of_different_shapes() {
let lhs = tensor(&[1.0, 2.0]);
let rhs = tensor(&[1.0]);
let mut destination = tensor(&[0.0, 0.0]);
let error =
scalar_binary_into::<f64, AddOp>("add", &mut destination, &lhs, &rhs, Exec::serial())
.expect_err("operand shapes must match");
assert_eq!(destination.as_slice(), &[0.0, 0.0]);
assert_eq!(
error.kind(),
tenferro_tensor::ErrorKind::Validation(tenferro_tensor_core::ValidationKind::ShapeMismatch)
);
}
#[test]
fn elementwise_handles_an_empty_destination() {
let lhs: TypedTensor<f64, DynRank, Host> =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![0], Vec::new()).unwrap();
let rhs: TypedTensor<f64, DynRank, Host> =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![0], Vec::new()).unwrap();
let mut destination: TypedTensor<f64, DynRank, Host> =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![0], Vec::new()).unwrap();
scalar_binary_into::<f64, AddOp>("add", &mut destination, &lhs, &rhs, Exec::serial()).unwrap();
assert!(destination.as_slice().is_empty());
}
#[test]
fn fold_uses_the_caller_operation_and_initial_value() {
let values = tensor(&[1.0, 2.0, 3.0]);
assert_eq!(
scalar_fold::<f64, AddOp>("sum", &values, 0.0, Exec::serial()).unwrap(),
6.0
);
assert_eq!(
scalar_fold::<f64, AddOp>("sum", &values, 10.0, Exec::serial()).unwrap(),
16.0
);
assert_eq!(
scalar_fold::<f64, SubOp>("diff", &values, 0.0, Exec::serial()).unwrap(),
-6.0
);
}
#[test]
fn integer_arithmetic_through_the_shared_entry_point_wraps() {
use crate::scalar_ops::{scalar_binary_into, scalar_fold, AddOp, MulOp, SubOp};
use tenferro_tensor::{DynRank, Host, TypedTensor};
let maximum = TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![i32::MAX])
.expect("shape");
let one =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![1]).expect("shape");
let mut destination =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![0]).expect("shape");
scalar_binary_into::<i32, AddOp>("add", &mut destination, &maximum, &one, Exec::serial())
.expect("addition");
assert_eq!(destination.as_slice(), &[i32::MIN]);
let minimum = TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![i32::MIN])
.expect("shape");
scalar_binary_into::<i32, SubOp>("sub", &mut destination, &minimum, &one, Exec::serial())
.expect("subtraction");
assert_eq!(destination.as_slice(), &[i32::MAX]);
let large = TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![i64::MAX])
.expect("shape");
let two =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![2]).expect("shape");
let mut destination =
TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![1], vec![0]).expect("shape");
scalar_binary_into::<i64, MulOp>("mul", &mut destination, &large, &two, Exec::serial())
.expect("product");
assert_eq!(destination.as_slice(), &[i64::MAX.wrapping_mul(2)]);
let total = scalar_fold::<i32, AddOp>(
"sum",
&TypedTensor::<_, DynRank, Host>::from_host_vec_col_major(vec![2], vec![i32::MAX, 1])
.expect("shape"),
0,
Exec::serial(),
)
.expect("reduction");
assert_eq!(total, i32::MIN);
}