nu_plugin_vulkan_matrix 0.1.2

GPU matrix operations for Nushell using Vulkan
use cubecl::wgpu::WgpuDeviceKind;
use ndarray::{ArrayD, IxDyn, s};
use nu_plugin_vulkan_matrix::{
    gpu::{download_array, open_device, upload_view},
    layout,
};

fn gpu() -> &'static nu_plugin_vulkan_matrix::gpu::Gpu {
    static GPU: std::sync::OnceLock<nu_plugin_vulkan_matrix::gpu::Gpu> = std::sync::OnceLock::new();
    GPU.get_or_init(|| open_device(WgpuDeviceKind::DiscreteGpu(0)).unwrap())
}

#[test]
fn rejects_invalid_empty_shapes_and_incomplete_strides() {
    let gpu = gpu();
    let empty = upload_view(gpu, ArrayD::<f64>::zeros(IxDyn(&[0])).view()).unwrap();
    for shape in [[0, usize::MAX, 2], [0, isize::MAX as usize + 1, 1]] {
        assert!(layout::reshape(&empty, &shape).is_err());
    }
    let mut tensor = upload_view(gpu, ndarray::array![[1.0_f64, 2.0]].into_dyn().view()).unwrap();
    tensor.metadata.strides.remove(1);
    assert!(layout::reshape(&tensor, &[2]).is_err());
}

#[test]
fn layouts_match_ndarray_without_changing_the_input() {
    let gpu = gpu();
    for shape in [&[][..], &[6], &[2, 3], &[2, 1, 3], &[2, 0, 3]] {
        let a = ArrayD::from_shape_fn(IxDyn(shape), |idx| {
            use ndarray::Dimension;
            idx.slice().iter().fold(0., |v, &i| v * 10. + i as f64)
        });
        let tensor = upload_view(gpu, a.view()).unwrap();
        let transposed = layout::transpose(&tensor);
        assert_eq!(
            transposed.handle.memory.descriptor().id,
            tensor.handle.memory.descriptor().id
        );
        assert_eq!(
            download_array::<f64>(gpu, &transposed).unwrap(),
            a.clone().reversed_axes()
        );
        let flat = layout::reshape(&tensor, &[a.len()]).unwrap();
        assert_eq!(
            flat.handle.memory.descriptor().id,
            tensor.handle.memory.descriptor().id
        );
        assert_eq!(
            download_array::<f64>(gpu, &flat).unwrap(),
            a.clone().into_shape_with_order(IxDyn(&[a.len()])).unwrap()
        );
        let expected = a
            .clone()
            .reversed_axes()
            .into_shape_with_order(IxDyn(&[a.len()]));
        assert_eq!(
            layout::reshape(&transposed, &[a.len()]).is_ok(),
            expected.is_ok()
        );
        assert_eq!(download_array::<f64>(gpu, &tensor).unwrap(), a);
        assert!(layout::reshape(&tensor, &[a.len() + 1]).is_err());
    }
    let a = ArrayD::from_shape_vec(IxDyn(&[2, 3, 4]), (0..24).map(|n| n as f32).collect()).unwrap();
    let negative = a.slice(s![.., ..;-1, ..;2]).into_dyn();
    let tensor = upload_view(gpu, negative.clone()).unwrap();
    assert_eq!(
        download_array::<f32>(gpu, &layout::transpose(&tensor)).unwrap(),
        negative.reversed_axes()
    );
    assert!(layout::reshape(&tensor, &[usize::MAX, 2]).is_err());
}