@@ -329,4 +329,133 @@ nB = 2
329329 @test ref_cuda_sparse. colPtr == cuda_spdiagm. colPtr
330330 end
331331 end
332+
333+ @testset " getindex with boolean masks" begin
334+ A = sprand (elty, m, n, 0.4 )
335+ rowmask = rand (Bool, m)
336+ colmask = rand (Bool, n)
337+ S_cpu = A[rowmask, colmask]
338+
339+ rowmask_d = CuVector (rowmask)
340+ colmask_d = CuVector (colmask)
341+
342+ # test slicing of CSC format
343+ A_csc = CuSparseMatrixCSC (A)
344+ S_csc = A_csc[rowmask_d, colmask_d]
345+ @test S_csc isa CuSparseMatrixCSC
346+ @test S_cpu ≈ collect (S_csc)
347+
348+ # test slicing of CSR format
349+ # Conversion between CSC and CSR is broken in many ways on CUDA 12.0,
350+ # therefore we construct the CSR matrix manually from the transposed CSC.
351+ Aᵀ_csc = CuSparseMatrixCSC (Transpose (A))
352+ A_csr = CuSparseMatrixCSR {eltype(A), Int32} (
353+ copy (Aᵀ_csc. colPtr), # rowPtr is the same as colPtr of the transposed CSC
354+ copy (Aᵀ_csc. rowVal), # colVal is the same as rowVal of the transposed CSC
355+ copy (Aᵀ_csc. nzVal), # nzVal is unchanged by transposition
356+ size (A)
357+ )
358+ # collect calls CSR→CSC conversion again which is broken, so we test on scalar level
359+ CUDA. @allowscalar for i in eachindex (A, A_csr)
360+ @test A[i] ≈ A_csr[i]
361+ end
362+ S_csr = A_csr[rowmask_d, colmask_d]
363+ @test S_csr isa CuSparseMatrixCSR
364+ CUDA. @allowscalar for i in eachindex (S_cpu, S_csr)
365+ @test S_cpu[i] ≈ S_csr[i]
366+ end
367+
368+ # wrong mask size: throws BoundsError for both too-long and too-short, matching the behaviour of dense Array.
369+ @test_throws BoundsError A_csc[CuVector (trues (m + 1 )), colmask_d]
370+ @test_throws BoundsError A_csc[rowmask_d, CuVector (trues (n + 1 ))]
371+ @test_throws BoundsError A_csc[CuVector (trues (m - 1 )), colmask_d]
372+ @test_throws BoundsError A_csc[rowmask_d, CuVector (trues (n - 1 ))]
373+ @test_throws BoundsError A_csr[CuVector (trues (m + 1 )), colmask_d]
374+ @test_throws BoundsError A_csr[rowmask_d, CuVector (trues (n + 1 ))]
375+ @test_throws BoundsError A_csr[CuVector (trues (m - 1 )), colmask_d]
376+ @test_throws BoundsError A_csr[rowmask_d, CuVector (trues (n - 1 ))]
377+
378+ # empty mask (all zeros): cumsum gives all-zero rowmap/colmap, new_m or new_n = 0,
379+ # both kernels are guarded by `new_m > 0 && new_n > 0`, so nothing executes.
380+ # new_rowPtr collapses to [1] (or all-ones), nnz = 0. Same as CPU SparseArrays.
381+ S_empty_rows_csc = A_csc[CuVector (falses (m)), CuVector (trues (n))]
382+ @test S_empty_rows_csc isa CuSparseMatrixCSC
383+ @test size (S_empty_rows_csc) == (0 , n)
384+ @test nnz (S_empty_rows_csc) == 0
385+
386+ S_empty_cols_csc = A_csc[CuVector (trues (m)), CuVector (falses (n))]
387+ @test S_empty_cols_csc isa CuSparseMatrixCSC
388+ @test size (S_empty_cols_csc) == (m, 0 )
389+ @test nnz (S_empty_cols_csc) == 0
390+
391+ S_empty_rows_csr = A_csr[CuVector (falses (m)), CuVector (trues (n))]
392+ @test S_empty_rows_csr isa CuSparseMatrixCSR
393+ @test size (S_empty_rows_csr) == (0 , n)
394+ @test nnz (S_empty_rows_csr) == 0
395+
396+ S_empty_cols_csr = A_csr[CuVector (trues (m)), CuVector (falses (n))]
397+ @test S_empty_cols_csr isa CuSparseMatrixCSR
398+ @test size (S_empty_cols_csr) == (m, 0 )
399+ @test nnz (S_empty_cols_csr) == 0
400+
401+ S_empty_both_csc = A_csc[CuVector (falses (m)), CuVector (falses (n))]
402+ @test S_empty_both_csc isa CuSparseMatrixCSC
403+ @test size (S_empty_both_csc) == (0 , 0 )
404+ @test nnz (S_empty_both_csc) == 0
405+
406+ S_empty_both_csr = A_csr[CuVector (falses (m)), CuVector (falses (n))]
407+ @test S_empty_both_csr isa CuSparseMatrixCSR
408+ @test size (S_empty_both_csr) == (0 , 0 )
409+ @test nnz (S_empty_both_csr) == 0
410+
411+ # all-ones mask: rowmap = 1:m, colmap = 1:n, both kernels run unfiltered.
412+ # Result should equal the full matrix. Same as CPU SparseArrays.
413+ S_all_csc = A_csc[CuVector (trues (m)), CuVector (trues (n))]
414+ @test S_all_csc isa CuSparseMatrixCSC
415+ @test collect (S_all_csc) ≈ Matrix (A)
416+
417+ S_all_csr = A_csr[CuVector (trues (m)), CuVector (trues (n))]
418+ @test S_all_csr isa CuSparseMatrixCSR
419+ CUDA. @allowscalar for i in eachindex (A, S_all_csr)
420+ @test A[i] ≈ S_all_csr[i]
421+ end
422+
423+ # zero-dimension matrix: accessing rowmap[end] / colmap[end] on an empty CuVector
424+ # would crash without the `m > 0` / `n > 0` guard in the implementation.
425+ A_zero_rows_csr = CuSparseMatrixCSR (spzeros (elty, 0 , n))
426+ S_zr = A_zero_rows_csr[CuVector {Bool} ([]), CuVector (trues (n))]
427+ @test S_zr isa CuSparseMatrixCSR
428+ @test size (S_zr) == (0 , n)
429+ @test nnz (S_zr) == 0
430+
431+ A_zero_cols_csr = CuSparseMatrixCSR (spzeros (elty, m, 0 ))
432+ S_zc = A_zero_cols_csr[CuVector (trues (m)), CuVector {Bool} ([])]
433+ @test S_zc isa CuSparseMatrixCSR
434+ @test size (S_zc) == (m, 0 )
435+ @test nnz (S_zc) == 0
436+
437+ A_zero_both_csr = CuSparseMatrixCSR (spzeros (elty, 0 , 0 ))
438+ S_zb = A_zero_both_csr[CuVector {Bool} ([]), CuVector {Bool} ([])]
439+ @test S_zb isa CuSparseMatrixCSR
440+ @test size (S_zb) == (0 , 0 )
441+ @test nnz (S_zb) == 0
442+
443+ A_zero_rows_csc = CuSparseMatrixCSC (spzeros (elty, 0 , n))
444+ S_zr_csc = A_zero_rows_csc[CuVector {Bool} ([]), CuVector (trues (n))]
445+ @test S_zr_csc isa CuSparseMatrixCSC
446+ @test size (S_zr_csc) == (0 , n)
447+ @test nnz (S_zr_csc) == 0
448+
449+ A_zero_cols_csc = CuSparseMatrixCSC (spzeros (elty, m, 0 ))
450+ S_zc_csc = A_zero_cols_csc[CuVector (trues (m)), CuVector {Bool} ([])]
451+ @test S_zc_csc isa CuSparseMatrixCSC
452+ @test size (S_zc_csc) == (m, 0 )
453+ @test nnz (S_zc_csc) == 0
454+
455+ A_zero_both_csc = CuSparseMatrixCSC (spzeros (elty, 0 , 0 ))
456+ S_zb_csc = A_zero_both_csc[CuVector {Bool} ([]), CuVector {Bool} ([])]
457+ @test S_zb_csc isa CuSparseMatrixCSC
458+ @test size (S_zb_csc) == (0 , 0 )
459+ @test nnz (S_zb_csc) == 0
460+ end
332461end
0 commit comments