mod common;
use candela::{Dimension, FloatLikeTensorElement, Tensor, arange, ones, s};
use common::{assert_approx_eq, tensor_of};
use rstest::rstest;
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_scalar_sub_was_adding<T: FloatLikeTensorElement>(#[case] _t: T) {
let t = Tensor::from_scalar(T::from_f64(10.0), &[4]);
assert_approx_eq((t - T::from_f64(3.0)).materialize().data(), &[7.0; 4]);
}
#[test]
fn regression_scalar_sub_from_ones() {
let t = ones!(&[4]);
assert_eq!((t - 3.0).materialize().data(), &vec![-2.0; 4]);
}
#[test]
fn regression_not_same_shape_error_shows_both_shapes() {
let a = ones!(&[3, 4]);
let b = ones!(&[3, 5]);
let result = std::panic::catch_unwind(|| {
let _ = (a + b).materialize();
});
assert!(result.is_err(), "expected a panic for shape mismatch");
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_sub_ordering_with_reusable_rhs<T: FloatLikeTensorElement>(#[case] _t: T) {
let a = Tensor::from_scalar(T::from_f64(10.0), &[4]);
let b = Tensor::from_scalar(T::from_f64(3.0), &[4]);
let b_node = b + T::from_f64(0.0);
let result = (a - b_node).materialize();
assert_approx_eq(result.data(), &[7.0; 4]); }
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn bug_redirect_timing_independent_consumer_before_as_contiguous<T: FloatLikeTensorElement>(
#[case] _t: T,
) {
let t = tensor_of::<T>(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
let transposed = t.transpose(); let contiguous = transposed.as_contiguous(); let shifted = &transposed + T::from_f64(1.0);
let result = (&contiguous + &shifted).materialize();
assert_approx_eq(result.data(), &[3.0, 7.0, 5.0, 9.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_matmul_input_order<T: FloatLikeTensorElement>(#[case] _t: T) {
let identity = Tensor::<T>::eye(2, 2);
let b = tensor_of::<T>(&[1.0, 2.0, 3.0, 4.0], &[2, 2]);
let result = identity.matmul(&b).unwrap().materialize();
assert_eq!(result.data(), b.data());
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_buffer_reuse_chain_correctness<T: FloatLikeTensorElement>(#[case] _t: T) {
let t: Tensor<T> = arange!(6);
let result = (t * T::from_f64(2.0) - T::from_f64(1.0)).materialize();
assert_approx_eq(result.data(), &[-1.0, 1.0, 3.0, 5.0, 7.0, 9.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_matmul_rhs_single_column<T: FloatLikeTensorElement>(#[case] _t: T) {
let a = tensor_of::<T>(&[1.0, 2.0], &[1, 2]);
let b = tensor_of::<T>(&[3.0, 4.0], &[2, 1]);
let c = a.matmul(&b).unwrap().materialize();
assert_eq!(c.shape(), &[1, 1]);
assert_eq!(c.data(), &[T::from_f64(11.0)]);
}
#[test]
fn regression_matmul_1d_2d_shape_mismatch() {
let a = Tensor::from_slice(&[3.0_f64, 4.0], &[2]);
let b = Tensor::from_slice(&[1.0_f64, 2.0], &[1, 2]);
let result = a.matmul(&b);
assert!(matches!(result, Err(candela::OpError::CannotMatMul(_, _))));
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_cached_node_consumed_by_reusable_op<T: FloatLikeTensorElement>(#[case] _t: T) {
let t: Tensor<T> = arange!(4);
let cached = (t + T::from_f64(1.0)).cache();
let result = (&cached + T::from_f64(0.0)).materialize();
assert_approx_eq(result.data(), &[1.0, 2.0, 3.0, 4.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_scalar_op_offset_slice<T: FloatLikeTensorElement>(#[case] _t: T) {
let t: Tensor<T> = arange!(5); let sliced = t.slice(s![2..4]).unwrap(); let result = (sliced + T::from_f64(1.0)).materialize();
assert_approx_eq(result.data(), &[3.0, 4.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_scalar_op_offset_row_block<T: FloatLikeTensorElement>(#[case] _t: T) {
let t = tensor_of::<T>(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0], &[2, 3]);
let sliced = t.slice(s![1..2, ..]).unwrap();
let result = (sliced * T::from_f64(10.0)).materialize();
assert_approx_eq(result.data(), &[30.0, 40.0, 50.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_fused_scalar_offset_slice<T: FloatLikeTensorElement>(#[case] _t: T) {
let t: Tensor<T> = arange!(5);
let sliced = t.slice(s![2..4]).unwrap(); let result = (sliced * T::from_f64(2.0) + T::from_f64(1.0)).materialize();
assert_approx_eq(result.data(), &[5.0, 7.0]);
}
#[rstest]
#[case::f64(0.0f64)]
#[case::f32(0.0f32)]
fn regression_scalar_op_offset_slice_feeds_consumer<T: FloatLikeTensorElement>(#[case] _t: T) {
let t: Tensor<T> = arange!(5);
let sliced = t.slice(s![2..4]).unwrap(); let shifted = sliced + T::from_f64(1.0); let result = (&shifted + &shifted).materialize(); assert_approx_eq(result.data(), &[6.0, 8.0]);
}
#[test]
#[should_panic(expected = "rank >= 1")]
fn tensor_from_slice_empty_shape_panics() {
let _ = Tensor::from_slice(&[1.0_f64], &[]);
}
#[test]
#[should_panic(expected = "rank >= 1")]
fn tensor_from_scalar_empty_shape_panics() {
let _ = Tensor::from_scalar(0.0_f32, &[]);
}
#[test]
#[should_panic(expected = "rank >= 1")]
fn layout_from_shape_empty_panics() {
let _ = candela::Layout::new(&[]);
}