From 7f33ba456bd273866079d2c375d69100124a6665 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 14:17:01 -0400 Subject: [PATCH 01/15] Fix slicing of GradedArrays --- src/abstractblocksparsearray/arraylayouts.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index 5b6e80a7..edffeb29 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -62,7 +62,10 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax # TODO: Use `similar`? blocktype_a = blocktype(parent(a)) a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) - a_dest .= a + for I in SparseArraysBase.eachstoredindex(blocks(a)) + b = Block(Tuple(I)) + a_dest[b] = blocks(a)[Tuple(I)...] + end return a_dest end From 0082d2557db5d0de92c2887d416a7ebf76038504 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 14:48:52 -0400 Subject: [PATCH 02/15] Adustments to slicing --- src/abstractblocksparsearray/arraylayouts.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index edffeb29..b8329c19 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -64,7 +64,7 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) for I in SparseArraysBase.eachstoredindex(blocks(a)) b = Block(Tuple(I)) - a_dest[b] = blocks(a)[Tuple(I)...] + a_dest[b] = copy(blocks(a)[Tuple(I)...]) end return a_dest end From 006cae4cd6f1d93bc9f2049b596f8e8b9a2f9123 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 15:37:08 -0400 Subject: [PATCH 03/15] More progress --- .../BlockSparseArraysTensorAlgebraExt.jl | 23 +++++++++--------- src/abstractblocksparsearray/arraylayouts.jl | 24 ++++++++++++------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl index 25384e05..917119df 100644 --- a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl +++ b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl @@ -41,17 +41,11 @@ function TensorAlgebra.matricize( key(I) = Block(Tuple(I)) value(I) = matricize(reshaped_blocks_a[I], length_codomain) Is = eachstoredindex(reshaped_blocks_a) - bs = if isempty(Is) - # Catch empty case and make sure the type is constrained properly. - # This seems to only be necessary in Julia versions below v1.11, - # try removing it when we drop support for those versions. - keytype = Base.promote_op(key, eltype(Is)) - valtype = Base.promote_op(value, eltype(Is)) - valtype′ = !isconcretetype(valtype) ? AbstractMatrix{eltype(a)} : valtype - Dict{keytype, valtype′}() - else - Dict(key(I) => value(I) for I in Is) - end + # Constrain key/value types explicitly so empty cases are still typed. + keytype = Base.promote_op(key, eltype(Is)) + valtype = Base.promote_op(value, eltype(Is)) + valtype′ = !isconcretetype(valtype) ? AbstractMatrix{eltype(a)} : valtype + bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) return blocksparse(bs, ax) end @@ -73,7 +67,12 @@ function TensorAlgebra.unmatricize( ) return unmatricize(reshaped_blocks_m[I], block_axes_I) end - bs = Dict(key(I) => value(I) for I in eachstoredindex(reshaped_blocks_m)) + Is = eachstoredindex(reshaped_blocks_m) + # Constrain key/value types explicitly so empty cases are still typed. + keytype = Base.promote_op(key, eltype(Is)) + valtype = Base.promote_op(value, eltype(Is)) + valtype′ = !isconcretetype(valtype) ? AbstractArray{eltype(m), length(ax)} : valtype + bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) return blocksparse(bs, ax) end diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index b8329c19..76d9a4ab 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -62,20 +62,26 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax # TODO: Use `similar`? blocktype_a = blocktype(parent(a)) a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) - for I in SparseArraysBase.eachstoredindex(blocks(a)) - b = Block(Tuple(I)) - a_dest[b] = copy(blocks(a)[Tuple(I)...]) - end + a_dest .= a return a_dest end -function _similar(arraytype::Type{<:AbstractArray}, size::Tuple) - return similar(arraytype, size) +function _similar(arraytype::Type{<:AbstractArray{T, N}}, size::Tuple) where {T, N} + if isconcretetype(arraytype) + try + return similar(arraytype, size) + catch err + if !(err isa MethodError) + rethrow() + end + end + end + return similar(Array{T, N}, size) end function _similar( ::Type{<:SubArray{<:Any, <:Any, <:ArrayType}}, size::Tuple ) where {ArrayType} - return similar(ArrayType, size) + return _similar(ArrayType, size) end # Materialize a SubArray view. @@ -83,6 +89,8 @@ function ArrayLayouts.sub_materialize( layout::BlockLayout{<:SparseLayout}, a, axes::Tuple{Vararg{Base.OneTo}} ) a_dest = _similar(blocktype(a), length.(axes)) - a_dest .= a + for I in CartesianIndices(a_dest) + @inbounds a_dest[I] = a[I] + end return a_dest end From 92f88e861b0e5c360dcf0f384446c7c9f9e8d780 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 17:35:35 -0400 Subject: [PATCH 04/15] Simplify --- Project.toml | 2 +- src/abstractblocksparsearray/arraylayouts.jl | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Project.toml b/Project.toml index ed57ad63..0b15d3ad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BlockSparseArrays" uuid = "2c9a651f-6452-4ace-a6ac-809f4280fbb4" -version = "0.10.34" +version = "0.10.35" authors = ["ITensor developers and contributors"] [workspace] diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index 76d9a4ab..f519798e 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -67,16 +67,7 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax end function _similar(arraytype::Type{<:AbstractArray{T, N}}, size::Tuple) where {T, N} - if isconcretetype(arraytype) - try - return similar(arraytype, size) - catch err - if !(err isa MethodError) - rethrow() - end - end - end - return similar(Array{T, N}, size) + return isconcretetype(arraytype) ? similar(arraytype, size) : similar(Array{T, N}, size) end function _similar( ::Type{<:SubArray{<:Any, <:Any, <:ArrayType}}, size::Tuple From 29fb6e8fb4102bf32fd0ffba10c08cbc8c0caa6f Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 17:44:53 -0400 Subject: [PATCH 05/15] Bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0b15d3ad..74289e35 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BlockSparseArrays" uuid = "2c9a651f-6452-4ace-a6ac-809f4280fbb4" -version = "0.10.35" +version = "0.10.36" authors = ["ITensor developers and contributors"] [workspace] From 6994d05ab4c4a1d22d58c72d79d84cd4f7f41ac5 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 17:48:28 -0400 Subject: [PATCH 06/15] Fix version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 74289e35..0b15d3ad 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "BlockSparseArrays" uuid = "2c9a651f-6452-4ace-a6ac-809f4280fbb4" -version = "0.10.36" +version = "0.10.35" authors = ["ITensor developers and contributors"] [workspace] From 7ba392b5e73c34f7c27cd0f6b27a742d91a1b180 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 19:08:48 -0400 Subject: [PATCH 07/15] More slicing improvements --- src/abstractblocksparsearray/arraylayouts.jl | 12 ++++- .../blocksparsearrayinterface.jl | 3 +- test/test_abstract_blocktype.jl | 46 ++++++------------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index f519798e..10b29c80 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -1,5 +1,5 @@ using ArrayLayouts: ArrayLayouts, DualLayout, MemoryLayout, MulAdd -using BlockArrays: BlockLayout +using BlockArrays: Block, BlockLayout, blocks using SparseArraysBase: SparseLayout using TypeParameterAccessors: parenttype, similartype @@ -62,7 +62,15 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax # TODO: Use `similar`? blocktype_a = blocktype(parent(a)) a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) - a_dest .= a + for I in CartesianIndices(blocks(a_dest)) + b = Block(Tuple(I)) + block_a = @view a[b] + if !iszero(block_a) + block_dest = similar(a_dest[b]) + copyto!(block_dest, block_a) + a_dest[b] = block_dest + end + end return a_dest end diff --git a/src/blocksparsearrayinterface/blocksparsearrayinterface.jl b/src/blocksparsearrayinterface/blocksparsearrayinterface.jl index 417afb5c..4b2375e0 100644 --- a/src/blocksparsearrayinterface/blocksparsearrayinterface.jl +++ b/src/blocksparsearrayinterface/blocksparsearrayinterface.jl @@ -442,8 +442,7 @@ end # TODO: Define `getstoredindex`, `getunstoredindex` instead. function Base.getindex(a::SparseSubArrayBlocks{<:Any, N}, I::Vararg{Int, N}) where {N} - # TODO: Should this be defined as `@view a.array[Block(I)]` instead? - return @view a.array[Block(I)] + return BlockArrays.viewblock(a.array, Block(I)) ## parent_blocks = @view blocks(parent(a.array))[blockrange(a)...] ## parent_block = parent_blocks[I...] diff --git a/test/test_abstract_blocktype.jl b/test/test_abstract_blocktype.jl index 94d57505..6a05561b 100644 --- a/test/test_abstract_blocktype.jl +++ b/test/test_abstract_blocktype.jl @@ -56,36 +56,22 @@ arrayts = (Array, JLArray) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(randn(elt, 2, 2)) for f in (eig_full, eig_trunc) - if arrayt === Array - d, v = f(a) - @test a * v ≈ v * d - else - @test_broken f(a) - end - end - if arrayt === Array - d = eig_vals(a) - @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) - else - @test_broken eig_vals(a) + res = f(a) + d, v = res[1:2] + @test a * v ≈ v * d end + d = eig_vals(a) + @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(parent(hermitianpart(randn(elt, 2, 2)))) for f in (eigh_full, eigh_trunc) - if arrayt === Array - d, v = f(a) - @test a * v ≈ v * d - else - @test_broken f(a) - end - end - if arrayt === Array - d = eigh_vals(a) - @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) - else - @test_broken eigh_vals(a) + res = f(a) + d, v = res[1:2] + @test a * v ≈ v * d end + d = eigh_vals(a) + @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(randn(elt, 2, 2)) @@ -96,7 +82,7 @@ arrayts = (Array, JLArray) @test isisometric(u; side = :left) else # TODO: Fix comparison with UniformScaling on GPU. - @test_broken isisometric(u; side = :left) + @test isisometric(u; side = :left) end end for f in (right_orth, right_polar, lq_compact, lq_full) @@ -106,15 +92,11 @@ arrayts = (Array, JLArray) @test isisometric(u; side = :right) else # TODO: Fix comparison with UniformScaling on GPU. - @test_broken isisometric(u; side = :right) + @test isisometric(u; side = :right) end end for f in (svd_compact, svd_full, svd_trunc) - if arrayt ≢ Array && (f ≡ svd_full || f ≡ svd_trunc) - @test_broken f(a) - else - u, s, v = f(a) - @test u * s * v ≈ a - end + u, s, v = f(a) + @test u * s * v ≈ a end end From 4e59d57aedf824d7e29245f000bc656b2cf8ade7 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 19:26:45 -0400 Subject: [PATCH 08/15] Mark some tests as fixed --- test/test_map.jl | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/test_map.jl b/test/test_map.jl index fcd9f364..8aefed5f 100644 --- a/test/test_map.jl +++ b/test/test_map.jl @@ -98,16 +98,14 @@ arrayts = (Array, JLArray) @test iszero(storedlength(a)) @test iszero(b) @test iszero(storedlength(b)) - # TODO: Broken on GPU. - @test iszero(c) broken = arrayt ≠ Array + @test iszero(c) @test iszero(storedlength(c)) @allowscalar a[5, 7] = 1 @test !iszero(a) @test storedlength(a) == 3 * 4 @test !iszero(b) @test storedlength(b) == 3 * 4 - # TODO: Broken on GPU. - @test !iszero(c) broken = arrayt ≠ Array + @test !iszero(c) @test storedlength(c) == 3 * 4 d = @view a[1:4, 1:6] @test iszero(d) @@ -407,12 +405,7 @@ arrayts = (Array, JLArray) a[Block(1, 1)] = dev(randn(elt, 2, 2)) a[Block(2, 2)] = dev(randn(elt, 3, 3)) I = (:, [2, 4]) - if arrayt === Array - @test Array(a[I...]) == Array(a)[I...] - else - # TODO: Broken on GPU, fix this. - @test_broken a[I...] - end + @test Array(a[I...]) == Array(a)[I...] a = BlockSparseArray{elt}(undef, [2, 3], [2, 3]) @views for b in [Block(1, 1), Block(2, 2)] From 07d6155f27f9d9adb78ee7e592350e91c3ba982f Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 19:57:31 -0400 Subject: [PATCH 09/15] Fix tests --- test/test_abstract_blocktype.jl | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/test_abstract_blocktype.jl b/test/test_abstract_blocktype.jl index 6a05561b..b2783b47 100644 --- a/test/test_abstract_blocktype.jl +++ b/test/test_abstract_blocktype.jl @@ -1,6 +1,7 @@ using Adapt: adapt using BlockArrays: Block using BlockSparseArrays: BlockSparseMatrix, blockstoredlength +using GPUArraysCore: allowscalar using JLArrays: JLArray using LinearAlgebra: hermitianpart, norm using MatrixAlgebraKit: eig_full, eig_trunc, eig_vals, eigh_full, eigh_trunc, eigh_vals, @@ -14,6 +15,7 @@ arrayts = (Array, JLArray) @testset "Abstract block type (arraytype=$arrayt, eltype=$elt)" for arrayt in arrayts, elt in elts + arrayt === Array || allowscalar(false) dev = adapt(arrayt) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) @@ -56,22 +58,30 @@ arrayts = (Array, JLArray) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(randn(elt, 2, 2)) for f in (eig_full, eig_trunc) - res = f(a) - d, v = res[1:2] - @test a * v ≈ v * d + @test begin + res = f(a) + d, v = res[1:2] + a * v ≈ v * d + end broken = arrayt ≠ Array end - d = eig_vals(a) - @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) + @test begin + d = eig_vals(a) + sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) + end broken = arrayt ≠ Array a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(parent(hermitianpart(randn(elt, 2, 2)))) for f in (eigh_full, eigh_trunc) - res = f(a) - d, v = res[1:2] - @test a * v ≈ v * d + @test begin + res = f(a) + d, v = res[1:2] + a * v ≈ v * d + end broken = arrayt ≠ Array end - d = eigh_vals(a) - @test sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) + @test begin + d = eigh_vals(a) + sort(Vector(d); by = abs) ≈ sort(eig_vals(Matrix(a)); by = abs) + end broken = arrayt ≠ Array a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) a[Block(1, 1)] = dev(randn(elt, 2, 2)) From c80b0cccfb6fa6770d3ff2e6658d56852aa287b3 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 20:10:56 -0400 Subject: [PATCH 10/15] Fix more tests --- src/abstractblocksparsearray/arraylayouts.jl | 4 ++-- test/test_abstract_blocktype.jl | 14 ++------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index 10b29c80..6c7a060e 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -64,8 +64,8 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) for I in CartesianIndices(blocks(a_dest)) b = Block(Tuple(I)) - block_a = @view a[b] - if !iszero(block_a) + if isstored(a, b) + block_a = @view a[b] block_dest = similar(a_dest[b]) copyto!(block_dest, block_a) a_dest[b] = block_dest diff --git a/test/test_abstract_blocktype.jl b/test/test_abstract_blocktype.jl index b2783b47..7807916c 100644 --- a/test/test_abstract_blocktype.jl +++ b/test/test_abstract_blocktype.jl @@ -88,22 +88,12 @@ arrayts = (Array, JLArray) for f in (left_orth, left_polar, qr_compact, qr_full) u, c = f(a) @test u * c ≈ a - if arrayt ≡ Array - @test isisometric(u; side = :left) - else - # TODO: Fix comparison with UniformScaling on GPU. - @test isisometric(u; side = :left) - end + @test isisometric(u; side = :left) end for f in (right_orth, right_polar, lq_compact, lq_full) c, u = f(a) @test c * u ≈ a - if arrayt ≡ Array - @test isisometric(u; side = :right) - else - # TODO: Fix comparison with UniformScaling on GPU. - @test isisometric(u; side = :right) - end + @test isisometric(u; side = :right) end for f in (svd_compact, svd_full, svd_trunc) u, s, v = f(a) From e296bd2a25e11fb736fbad998f0655aad8b8ace8 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 22:45:45 -0400 Subject: [PATCH 11/15] Remove use of global allowscalar in tests --- test/test_abstract_blocktype.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_abstract_blocktype.jl b/test/test_abstract_blocktype.jl index 7807916c..a3f68e1c 100644 --- a/test/test_abstract_blocktype.jl +++ b/test/test_abstract_blocktype.jl @@ -1,7 +1,6 @@ using Adapt: adapt using BlockArrays: Block using BlockSparseArrays: BlockSparseMatrix, blockstoredlength -using GPUArraysCore: allowscalar using JLArrays: JLArray using LinearAlgebra: hermitianpart, norm using MatrixAlgebraKit: eig_full, eig_trunc, eig_vals, eigh_full, eigh_trunc, eigh_vals, @@ -15,7 +14,6 @@ arrayts = (Array, JLArray) @testset "Abstract block type (arraytype=$arrayt, eltype=$elt)" for arrayt in arrayts, elt in elts - arrayt === Array || allowscalar(false) dev = adapt(arrayt) a = BlockSparseMatrix{elt, AbstractMatrix{elt}}(undef, [2, 3], [2, 3]) From f55069afd1eccd9e67a2e6b335da46dffd787125 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 23:06:18 -0400 Subject: [PATCH 12/15] Fix tests --- test/test_map.jl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/test_map.jl b/test/test_map.jl index 8aefed5f..fcd9f364 100644 --- a/test/test_map.jl +++ b/test/test_map.jl @@ -98,14 +98,16 @@ arrayts = (Array, JLArray) @test iszero(storedlength(a)) @test iszero(b) @test iszero(storedlength(b)) - @test iszero(c) + # TODO: Broken on GPU. + @test iszero(c) broken = arrayt ≠ Array @test iszero(storedlength(c)) @allowscalar a[5, 7] = 1 @test !iszero(a) @test storedlength(a) == 3 * 4 @test !iszero(b) @test storedlength(b) == 3 * 4 - @test !iszero(c) + # TODO: Broken on GPU. + @test !iszero(c) broken = arrayt ≠ Array @test storedlength(c) == 3 * 4 d = @view a[1:4, 1:6] @test iszero(d) @@ -405,7 +407,12 @@ arrayts = (Array, JLArray) a[Block(1, 1)] = dev(randn(elt, 2, 2)) a[Block(2, 2)] = dev(randn(elt, 3, 3)) I = (:, [2, 4]) - @test Array(a[I...]) == Array(a)[I...] + if arrayt === Array + @test Array(a[I...]) == Array(a)[I...] + else + # TODO: Broken on GPU, fix this. + @test_broken a[I...] + end a = BlockSparseArray{elt}(undef, [2, 3], [2, 3]) @views for b in [Block(1, 1), Block(2, 2)] From a44bb7d3942636d3b814b5e041c798a105f43975 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Mon, 9 Mar 2026 23:29:50 -0400 Subject: [PATCH 13/15] Cleanup --- .../BlockSparseArraysTensorAlgebraExt.jl | 31 +++++++++++++------ src/abstractblocksparsearray/arraylayouts.jl | 18 +++++------ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl index 917119df..c13734f1 100644 --- a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl +++ b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl @@ -41,11 +41,17 @@ function TensorAlgebra.matricize( key(I) = Block(Tuple(I)) value(I) = matricize(reshaped_blocks_a[I], length_codomain) Is = eachstoredindex(reshaped_blocks_a) - # Constrain key/value types explicitly so empty cases are still typed. - keytype = Base.promote_op(key, eltype(Is)) - valtype = Base.promote_op(value, eltype(Is)) - valtype′ = !isconcretetype(valtype) ? AbstractMatrix{eltype(a)} : valtype - bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) + bs = if isempty(Is) + # Catch empty case and make sure the type is constrained properly. + # This seems to only be necessary in Julia versions below v1.11, + # try removing it when we drop support for those versions. + keytype = Base.promote_op(key, eltype(Is)) + valtype = Base.promote_op(value, eltype(Is)) + valtype′ = !isconcretetype(valtype) ? AbstractMatrix{eltype(a)} : valtype + Dict{keytype, valtype′}() + else + Dict(key(I) => value(I) for I in Is) + end return blocksparse(bs, ax) end @@ -68,11 +74,16 @@ function TensorAlgebra.unmatricize( return unmatricize(reshaped_blocks_m[I], block_axes_I) end Is = eachstoredindex(reshaped_blocks_m) - # Constrain key/value types explicitly so empty cases are still typed. - keytype = Base.promote_op(key, eltype(Is)) - valtype = Base.promote_op(value, eltype(Is)) - valtype′ = !isconcretetype(valtype) ? AbstractArray{eltype(m), length(ax)} : valtype - bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) + bs = if isempty(Is) + # Constrain key/value types explicitly so empty cases are still typed. + keytype = Base.promote_op(key, eltype(Is)) + valtype = Base.promote_op(value, eltype(Is)) + valtype′ = + !isconcretetype(valtype) ? AbstractArray{eltype(m), length(ax)} : valtype + bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) + else + Dict(key(I) => value(I) for I in eachstoredindex(reshaped_blocks_m)) + end return blocksparse(bs, ax) end diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index 6c7a060e..921488e9 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -1,6 +1,6 @@ using ArrayLayouts: ArrayLayouts, DualLayout, MemoryLayout, MulAdd using BlockArrays: Block, BlockLayout, blocks -using SparseArraysBase: SparseLayout +using SparseArraysBase: SparseLayout, eachstoredindex using TypeParameterAccessors: parenttype, similartype function ArrayLayouts.MemoryLayout(arraytype::Type{<:AnyAbstractBlockSparseArray}) @@ -62,14 +62,12 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax # TODO: Use `similar`? blocktype_a = blocktype(parent(a)) a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) - for I in CartesianIndices(blocks(a_dest)) + for I in eachstoredindex(blocks(a)) b = Block(Tuple(I)) - if isstored(a, b) - block_a = @view a[b] - block_dest = similar(a_dest[b]) - copyto!(block_dest, block_a) - a_dest[b] = block_dest - end + block_a = @view a[b] + block_dest = similar(a_dest[b]) + copyto!(block_dest, block_a) + a_dest[b] = block_dest end return a_dest end @@ -88,8 +86,6 @@ function ArrayLayouts.sub_materialize( layout::BlockLayout{<:SparseLayout}, a, axes::Tuple{Vararg{Base.OneTo}} ) a_dest = _similar(blocktype(a), length.(axes)) - for I in CartesianIndices(a_dest) - @inbounds a_dest[I] = a[I] - end + copyto!(a_dest, a) return a_dest end From d627bb104e52b2242b135196640e42a12016b835 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Tue, 10 Mar 2026 07:19:41 -0400 Subject: [PATCH 14/15] Fix tests with non-ideal loop in sub_materialize --- src/abstractblocksparsearray/arraylayouts.jl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/abstractblocksparsearray/arraylayouts.jl b/src/abstractblocksparsearray/arraylayouts.jl index 921488e9..edd87da5 100644 --- a/src/abstractblocksparsearray/arraylayouts.jl +++ b/src/abstractblocksparsearray/arraylayouts.jl @@ -1,6 +1,6 @@ using ArrayLayouts: ArrayLayouts, DualLayout, MemoryLayout, MulAdd using BlockArrays: Block, BlockLayout, blocks -using SparseArraysBase: SparseLayout, eachstoredindex +using SparseArraysBase: SparseLayout using TypeParameterAccessors: parenttype, similartype function ArrayLayouts.MemoryLayout(arraytype::Type{<:AnyAbstractBlockSparseArray}) @@ -62,12 +62,14 @@ function ArrayLayouts.sub_materialize(layout::BlockLayout{<:SparseLayout}, a, ax # TODO: Use `similar`? blocktype_a = blocktype(parent(a)) a_dest = BlockSparseArray{eltype(a), length(axes), blocktype_a}(undef, axes) - for I in eachstoredindex(blocks(a)) + for I in CartesianIndices(blocks(a_dest)) b = Block(Tuple(I)) - block_a = @view a[b] - block_dest = similar(a_dest[b]) - copyto!(block_dest, block_a) - a_dest[b] = block_dest + if isstored(a, b) + block_a = @view a[b] + block_dest = similar(a_dest[b]) + copyto!(block_dest, block_a) + a_dest[b] = block_dest + end end return a_dest end From 79ebb14f1dea4506226a9146cfbf9f1645408fe5 Mon Sep 17 00:00:00 2001 From: mtfishman Date: Tue, 10 Mar 2026 10:13:37 -0400 Subject: [PATCH 15/15] Small simplification in blockless unmatricize --- .../BlockSparseArraysTensorAlgebraExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl index c13734f1..c5b74c7c 100644 --- a/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl +++ b/ext/BlockSparseArraysTensorAlgebraExt/BlockSparseArraysTensorAlgebraExt.jl @@ -80,7 +80,7 @@ function TensorAlgebra.unmatricize( valtype = Base.promote_op(value, eltype(Is)) valtype′ = !isconcretetype(valtype) ? AbstractArray{eltype(m), length(ax)} : valtype - bs = Dict{keytype, valtype′}(key(I) => value(I) for I in Is) + bs = Dict{keytype, valtype′}() else Dict(key(I) => value(I) for I in eachstoredindex(reshaped_blocks_m)) end