|
101 | 101 | end |
102 | 102 | end |
103 | 103 |
|
| 104 | +@testitem "PyDenseArray" setup=[Setup] begin |
| 105 | + using LinearAlgebra |
| 106 | + x = pyimport("array").array("d", pylist(0:5)) |
| 107 | + y = PyDenseArray(x) |
| 108 | + |
| 109 | + # Helper function to create a row-major Float64 array |
| 110 | + function rowmajor(vals, shape) |
| 111 | + arr = pyimport("array").array("d", pylist(vals)) |
| 112 | + bytearr = pybuiltins.bytearray(arr.tobytes()) |
| 113 | + |
| 114 | + pybuiltins.memoryview(bytearr).cast("d", pylist(shape)) |
| 115 | + end |
| 116 | + |
| 117 | + @testset "construct" begin |
| 118 | + @test y isa PyDenseArray{Float64,1,true} |
| 119 | + @test y isa StridedVector{Float64} |
| 120 | + @test Py(y) === x |
| 121 | + @test PyDenseArray{Float64,1,true}(x) isa PyDenseArray{Float64,1,true} |
| 122 | + @test PyDenseArray(PyArray(x)) isa PyDenseArray{Float64,1,true} |
| 123 | + @test PyDenseArray(pybytes(b"abc")) isa PyDenseArray{UInt8,1,false} |
| 124 | + |
| 125 | + @test pyconvert(PyDenseArray, x) isa PyDenseArray{Float64,1,true} |
| 126 | + @test pyconvert(PyDenseArray{Float64,1}, x) isa PyDenseArray{Float64,1,true} |
| 127 | + # Defaults are unchanged |
| 128 | + @test pyconvert(Any, x) isa PyArray |
| 129 | + @test pyconvert(DenseArray, x) isa Array |
| 130 | + |
| 131 | + @test_throws Exception PyDenseArray{Int}(x) |
| 132 | + @test_throws Exception PyDenseArray{Float64,1,false}(x) |
| 133 | + # Non-contiguous |
| 134 | + strided = pybuiltins.memoryview(x)[pyslice(nothing, nothing, 2)] |
| 135 | + @test_throws Exception PyDenseArray(strided) |
| 136 | + @test_throws Exception pyconvert(PyDenseArray, strided) |
| 137 | + |
| 138 | + if Setup.devdeps |
| 139 | + np = pyimport("numpy") |
| 140 | + # Object arrays have eltype Py, which is not the buffer eltype |
| 141 | + @test_throws Exception PyDenseArray(np.array(pylist([1, "a"]), dtype = np.object_)) |
| 142 | + end |
| 143 | + end |
| 144 | + |
| 145 | + @testset "shape" begin |
| 146 | + # Row-major data is reversed |
| 147 | + c = PyDenseArray(rowmajor(0:5, [2, 3])) |
| 148 | + @test size(c) == (3, 2) |
| 149 | + @test strides(c) == (1, 3) |
| 150 | + @test c == transpose(PyArray(Py(c))) |
| 151 | + |
| 152 | + # Arrays with dimensions of size 1 are not |
| 153 | + @test size(PyDenseArray(rowmajor(0:3, [1, 4]))) == (1, 4) |
| 154 | + |
| 155 | + # Nor is column-major data |
| 156 | + if Setup.devdeps |
| 157 | + np = pyimport("numpy") |
| 158 | + f = PyDenseArray(np.asfortranarray(np.arange(6.0).reshape(2, 3))) |
| 159 | + @test size(f) == (2, 3) |
| 160 | + @test f == PyArray(Py(f)) |
| 161 | + end |
| 162 | + end |
| 163 | + |
| 164 | + @testset "indexing" begin |
| 165 | + @test Base.IndexStyle(y) === Base.IndexLinear() |
| 166 | + @test length(y) == 6 |
| 167 | + @test pointer(y) == pointer(PyArray(x)) |
| 168 | + @test pointer(y, 2) == pointer(y) + sizeof(Float64) # requires elsize() |
| 169 | + @test y[2] == 1.0 |
| 170 | + @test_throws BoundsError y[7] |
| 171 | + |
| 172 | + y[2] = 42 |
| 173 | + @test pyeq(Bool, x[1], 42.0) |
| 174 | + @test_throws Exception PyDenseArray(pybytes(b"abc"))[1] = 0x00 |
| 175 | + end |
| 176 | + |
| 177 | + @testset "strided dispatch" begin |
| 178 | + # dot() has a BLAS method for StridedVector{Float64} |
| 179 | + @test which(dot, (typeof(y), typeof(y))) == |
| 180 | + which(dot, (Vector{Float64}, Vector{Float64})) |
| 181 | + @test which(dot, (typeof(y), typeof(y))) != |
| 182 | + which(dot, (typeof(PyArray(x)), typeof(PyArray(x)))) |
| 183 | + |
| 184 | + a = PyDenseArray(rowmajor(0:5, [2, 3])) # 3×2 |
| 185 | + b = PyDenseArray(rowmajor(0:5, [3, 2])) # 2×3 |
| 186 | + @test mul!(zeros(3, 3), a, b) ≈ Matrix(a) * Matrix(b) |
| 187 | + @test view(a, :, 1:2) isa StridedArray |
| 188 | + @test copy(a) isa Matrix{Float64} |
| 189 | + end |
| 190 | + |
| 191 | + @testset "serialize" begin |
| 192 | + using Serialization: serialize, deserialize |
| 193 | + arrays = Any[x] |
| 194 | + |
| 195 | + if Setup.devdeps |
| 196 | + np = pyimport("numpy") |
| 197 | + push!(arrays, np.arange(6.0).reshape(2, 3)) |
| 198 | + end |
| 199 | + |
| 200 | + for a in arrays |
| 201 | + c = PyDenseArray(a) |
| 202 | + io = IOBuffer() |
| 203 | + serialize(io, c) |
| 204 | + seekstart(io) |
| 205 | + c2 = deserialize(io) |
| 206 | + @test typeof(c2) == typeof(c) |
| 207 | + @test c2 == c |
| 208 | + end |
| 209 | + end |
| 210 | +end |
| 211 | + |
104 | 212 | @testitem "PyDict" begin |
105 | 213 | x = pydict(["foo" => 12]) |
106 | 214 | y = PyDict(x) |
|
0 commit comments