ruda-tensor 0.21.78

Ruda tensor operations, backend contracts, and framework-facing primitives.
macro_rules! readback_cases {
    ($backend:ty) => {
        use ruda_tensor::api::{Bool, DType, DataError, Int, Tensor, TensorData, TensorReadError};
        type B = $backend;

        #[test]
        fn resident_readback_access_policy() {
            let device = Default::default();
            let tensor = Tensor::<B, 1>::from_floats([1.0, 2.0], &device);
            let mut data = tensor.try_to_data().unwrap();
            assert_eq!(data.bytes.read(ruda_core::bytes::Reader::new().no_copy()).unwrap().len(), 8);
            data.bytes.write(ruda_core::bytes::Writer::new()).unwrap()[..4].copy_from_slice(&42.0f32.to_ne_bytes());
            assert_eq!(data.try_into_vec::<f32>().unwrap(), [42.0, 2.0]);
            assert_eq!(tensor.try_to_vec_as::<f32>().unwrap(), [1.0, 2.0]);
        }

        #[test]
        fn borrowed_read_preserves_tensor() {
            let device = Default::default();
            let tensor = Tensor::<B, 1>::from_floats([-2.0, 0.0, 3.5], &device);
            assert_eq!(tensor.try_to_data().unwrap().to_vec::<f32>().unwrap(), [-2.0, 0.0, 3.5]);
            assert_eq!(tensor.try_to_vec_as::<i32>().unwrap(), [-2, 0, 3]);
            assert_eq!(tensor.try_to_vec_as::<f64>().unwrap(), [-2.0, 0.0, 3.5]);
            assert_eq!(tensor.into_data().to_vec::<f32>().unwrap(), [-2.0, 0.0, 3.5]);
        }

        #[test]
        fn borrowed_and_owned_typed_data() {
            let device = Default::default();
            let tensor = Tensor::<B, 2>::from_floats([[1.0, 2.0], [3.0, 4.0]], &device);
            for data in [tensor.to_data_as::<f64>(), tensor.try_to_data_as::<f64>().unwrap(),
                         tensor.clone().into_data_as::<f64>(), tensor.try_into_data_as::<f64>().unwrap()] {
                assert_eq!(data.dtype, DType::F64);
                assert_eq!(data.shape.as_slice(), [2, 2]);
                assert_eq!(data.try_into_vec::<f64>().unwrap(), [1.0, 2.0, 3.0, 4.0]);
            }
        }

        #[test]
        fn borrowed_and_owned_runtime_dtype() {
            let device = Default::default();
            let tensor = Tensor::<B, 1>::from_floats([1.5, -2.5], &device);
            for data in [tensor.to_data_dtype(DType::I32), tensor.try_to_data_dtype(DType::I32).unwrap(),
                         tensor.clone().into_data_dtype(DType::I32), tensor.try_into_data_dtype(DType::I32).unwrap()] {
                assert_eq!(data.dtype, DType::I32);
                assert_eq!(data.try_into_vec::<i32>().unwrap(), [1, -2]);
            }
        }

        #[test]
        fn owned_vector_and_noncontiguous_layout() {
            let device = Default::default();
            let tensor = Tensor::<B, 2>::from_floats([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], &device).swap_dims(0, 1);
            assert_eq!(tensor.try_into_vec_as::<i64>().unwrap(), [1, 4, 2, 5, 3, 6]);
        }

        #[test]
        fn boolean_and_integer_readback() {
            let device = Default::default();
            let boolean = Tensor::<B, 1, Bool>::from_data([true, false, true], &device);
            assert_eq!(boolean.try_to_vec_as::<bool>().unwrap(), [true, false, true]);
            assert_eq!(boolean.try_into_vec_as::<u32>().unwrap(), [1, 0, 1]);
            let integer = Tensor::<B, 1, Int>::from_data([-3, 0, 7], &device);
            assert_eq!(integer.try_to_vec_as::<f64>().unwrap(), [-3.0, 0.0, 7.0]);
            assert_eq!(integer.try_into_vec_as::<bool>().unwrap(), [true, false, true]);
        }

        #[test]
        fn empty_readback_keeps_shape() {
            let device = Default::default();
            let tensor = Tensor::<B, 2>::empty([0, 3], &device);
            let data = tensor.try_to_data_as::<f64>().unwrap();
            assert_eq!(data.shape.as_slice(), [0, 3]);
            assert!(data.try_into_vec::<f64>().unwrap().is_empty());
            assert!(tensor.try_into_vec_as::<i32>().unwrap().is_empty());
        }

        #[test]
        fn typed_scalar_sync_and_async() {
            let device = Default::default();
            let tensor = Tensor::<B, 2, Int>::from_data([[7]], &device);
            assert_eq!(tensor.clone().try_into_scalar_as::<f64>().unwrap(), 7.0);
            assert_eq!(ruda_tensor::api::try_read_sync(tensor.try_into_scalar_async::<i64>()).unwrap().unwrap(), 7);
        }

        #[test]
        fn invalid_scalar_shapes_return_errors() {
            let device = Default::default();
            for values in [vec![], vec![1.0, 2.0]] {
                let expected = values.len();
                let tensor = Tensor::<B, 1>::from_data(TensorData::new(values, [expected]), &device);
                assert!(matches!(tensor.clone().try_into_scalar_as::<f32>(),
                    Err(TensorReadError::InvalidShape { expected: 1, actual }) if actual == expected));
                assert!(matches!(ruda_tensor::api::try_read_sync(tensor.try_into_scalar_async::<f32>()).unwrap(),
                    Err(TensorReadError::InvalidShape { expected: 1, actual }) if actual == expected));
            }
        }

        #[test]
        fn unsupported_quantized_target_is_data_error() {
            let device = Default::default();
            let tensor = Tensor::<B, 1>::from_floats([1.0], &device);
            let target = DType::QFloat(Default::default());
            assert!(matches!(tensor.try_to_data_dtype(target),
                Err(TensorReadError::Data(DataError::UnsupportedConversion { from: DType::F32, to })) if to == target));
            assert!(matches!(tensor.try_into_data_dtype(target),
                Err(TensorReadError::Data(DataError::UnsupportedConversion { from: DType::F32, to })) if to == target));
        }
    };
}