nu_plugin_vulkan_matrix 0.1.2

GPU matrix operations for Nushell using Vulkan
plugin use vulkan_matrix

def check [condition: bool, message: string] {
    if not $condition { error make {msg: $message} }
}
def native-lists [value: any]: nothing -> any { $value | matrix into-nu }
def native [value: any]: nothing -> any {
    let output = native-lists $value
    if ($value.shape | length) == 1 { $output | get 0 } else { $output }
}
def cpu-array [value: any] { $value | into matrix }
def gpu [value: any]: nothing -> any { $value | vulkan matrix into-nu }
def fails [run: closure] { try { do $run | ignore; false } catch { true } }

for dtype in [f64 f32] {
    for dimensions in [[3] [2 3] [2 1 3]] {
        let first = $dimensions | first
        let rest = $dimensions | skip 1
        let a = vulkan matrix zeros $first ...$rest --device d0 --dtype $dtype
        check ($a.shape == $dimensions and $a.dtype == $dtype) 'zeros metadata'
        check ((gpu $a) == (native (matrix zeros $first ...$rest))) 'native zeros'
    }
    for n in [1 3 4] {
        let a = vulkan matrix identity $n --device d0 --dtype $dtype
        check ((gpu $a) == (native (matrix identity $n))) 'native identity'
    }
    let values = [[2.0 4.0 8.0] [16.0 32.0 64.0]]
    let a = $values | vulkan matrix from-nu --device d0 --dtype $dtype
    let n = $values | into matrix
    let b = [[2.0 4.0 2.0] [4.0 2.0 4.0]] | vulkan matrix from-nu --device d0 --dtype $dtype
    let nb = [[2.0 4.0 2.0] [4.0 2.0 4.0]] | into matrix
    check ((gpu ($a | vulkan matrix transpose)) == (native ($n | matrix transpose))) 'transpose'
    check ((gpu ($a | vulkan matrix reshape 3 2)) == (native ($n | matrix reshape 3 2))) 'reshape'
    check ((gpu ($a | vulkan matrix reshape --flatten)) == (native ($n | matrix reshape --flatten))) 'flatten'
    check ((gpu ($a | vulkan matrix add $b)) == (native ($n | matrix add $nb))) 'add matrix'
    check ((gpu ($a | vulkan matrix subtract $b)) == (native ($n | matrix subtract $nb))) 'subtract matrix'
    check ((gpu ($a | vulkan matrix add 2)) == (native ($n | matrix add 2))) 'add scalar'
    check ((gpu ($a | vulkan matrix subtract 2)) == (native ($n | matrix subtract 2))) 'subtract scalar'
    check ((gpu ($a | vulkan matrix scale 2)) == (native ($n | matrix scale 2))) 'scale'
    check ((gpu ($a + $b)) == (native ($n + $nb))) 'operator + matrix'
    check ((gpu ($a - $b)) == (native ($n - $nb))) 'operator - matrix'
    check ((gpu ($a * $b)) == (native ($n * $nb))) 'operator * elementwise'
    check ((gpu ($a / $b)) == (native ($n / $nb))) 'operator / elementwise'
    check ((gpu ($a + 2)) == (native ($n + 2))) 'operator + scalar'
    check ((gpu ($a - 2)) == (native ($n - 2))) 'operator - scalar'
    check ((gpu ($a * 2)) == (native ($n * 2))) 'operator * scalar'
    check ((gpu ($a / 2)) == (native ($n / 2))) 'operator / scalar'
    let row = [1.0 2.0 3.0] | vulkan matrix from-nu --device d0 --dtype $dtype
    let nrow = cpu-array [[1.0 2.0 3.0]] | matrix reshape 3
    check ((gpu ($a | vulkan matrix add --broadcast $row)) == (native ($n | matrix add --broadcast $nrow))) 'RHS broadcast'
    check ((gpu ($a | vulkan matrix subtract --broadcast $row)) == (native ($n | matrix subtract --broadcast $nrow))) 'RHS subtract broadcast'
    check (fails { $row | vulkan matrix add --broadcast $a }) 'broadcast direction'
    check (fails { $a | vulkan matrix add $row }) 'explicit broadcast only'
    check (fails { $a + $row }) 'operator shape mismatch'
    check (fails { $a | vulkan matrix reshape 5 }) 'reshape count'
    check (fails { $a | vulkan matrix reshape 0 6 }) 'reshape dimensions'
    check (fails { $a | vulkan matrix reshape }) 'reshape dimensions missing'
    check ((fails { $a | vulkan matrix transpose | vulkan matrix reshape 6 }) == (fails { $n | matrix transpose | matrix reshape 6 })) 'native nonstandard reshape error'
    let t = $a | vulkan matrix transpose
    let one = vulkan matrix identity 2 --device d0 --dtype $dtype
    check ((gpu ($t | vulkan matrix multiply $one)) == (native ($n | matrix transpose | matrix multiply (matrix identity 2)))) 'transposed multiply'
    let chain = $a | vulkan matrix reshape 6 1 | vulkan matrix transpose | vulkan matrix reshape 2 3 | vulkan matrix multiply (vulkan matrix identity 3 --device d0 --dtype $dtype)
    let expected = $n | matrix reshape 6 1 | matrix transpose | matrix reshape 2 3 | matrix multiply (matrix identity 3)
    check ((gpu $chain) == (native $expected)) 'transpose reshape multiply chain'
    check ($chain.dtype == $dtype) 'chain dtype'
    check ((gpu ($a | vulkan matrix reshape 2 1 3 | vulkan matrix transpose)) == (native ($n | matrix reshape 2 1 3 | matrix transpose))) 'N-D transpose'
    check ((gpu $a) == $values) 'branch input unchanged'
}

let survivor = do {
    let a = [[1.0 2.0] [3.0 4.0]] | vulkan matrix from-nu --device d0
    $a | vulkan matrix transpose
}
check ((gpu $survivor) == [[1.0 3.0] [2.0 4.0]]) 'layout survives source drop'
let a64 = [1.0 2.0] | vulkan matrix from-nu --device d0
let a32 = [1.0 2.0] | vulkan matrix from-nu --device d0 --dtype f32
check (fails { $a64 + $a32 }) 'dtype mismatch'
check (fails { $a64 | vulkan matrix add true }) 'invalid RHS'
check (fails { vulkan matrix zeros 0 --device d0 }) 'zeros dimension'
check (fails { vulkan matrix identity -1 --device d0 }) 'identity dimension'
check (fails { vulkan matrix zeros 2 }) 'required device'
check (fails { vulkan matrix identity 2 --device cpu }) 'no CPU fallback'
check (fails { vulkan matrix zeros 2 --device d0 --dtype f16 }) 'invalid dtype'
let scalar = 3.0 | vulkan matrix from-nu --device d0
check ((gpu ($scalar * 2)) == 6.0) 'scalar input'
let empty = [] | vulkan matrix from-nu --device d0 --shape [2 0 3]
check (($empty | vulkan matrix scale 2).shape == [2 0 3]) 'empty arithmetic shape'
check ((gpu ($empty | vulkan matrix reshape --flatten)) == []) 'empty flatten'
for dtype in [f64 f32] {
    let a = [-0.0 inf -inf NaN] | vulkan matrix from-nu --device d0 --dtype $dtype
    let b = gpu ($a * 1)
    check (($b.0 | to msgpack | encode hex) == CB8000000000000000) 'arithmetic negative zero'
    check ($b.1 == inf and $b.2 == -inf and ($b.3 | into string) == 'NaN') 'arithmetic Inf/NaN'
}
print 'layout and elementwise Nu tests passed'