diff --git a/Project.toml b/Project.toml index 5180d3a..c102112 100644 --- a/Project.toml +++ b/Project.toml @@ -1,15 +1,15 @@ name = "SparseArraysBase" uuid = "0d5efcca-f356-4864-8770-e1ed8d78f208" authors = ["ITensor developers and contributors"] -version = "0.7.11" +version = "0.8.0" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -DerivableInterfaces = "6c5e35bf-e59e-4898-b73c-732dcc4ba65f" Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" +FunctionImplementations = "7c7cc465-9c6a-495f-bdd1-f42428e86d0c" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MapBroadcast = "ebd9b9da-f48d-417c-9660-449667d60261" @@ -29,13 +29,13 @@ SparseArraysBaseTensorAlgebraExt = ["TensorAlgebra", "SparseArrays"] Accessors = "0.1.41" Adapt = "4.3" ArrayLayouts = "1.11" -DerivableInterfaces = "0.5" Dictionaries = "0.4.3" FillArrays = "1.13" +FunctionImplementations = "0.3.1" GPUArraysCore = "0.2" LinearAlgebra = "1.10" MapBroadcast = "0.1.5" -NamedDimsArrays = "0.11" +NamedDimsArrays = "0.12" Random = "1.10" SparseArrays = "1.10" TensorAlgebra = "0.6.2" diff --git a/docs/Project.toml b/docs/Project.toml index 786b536..92bdbdf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -11,4 +11,4 @@ SparseArraysBase = {path = ".."} Dictionaries = "0.4.4" Documenter = "1.8.1" Literate = "2.20.1" -SparseArraysBase = "0.7.0" +SparseArraysBase = "0.8" diff --git a/examples/Project.toml b/examples/Project.toml index 9dbdee0..e11cac3 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -8,5 +8,5 @@ SparseArraysBase = {path = ".."} [compat] Dictionaries = "0.4.4" -SparseArraysBase = "0.7.0" +SparseArraysBase = "0.8" Test = "<0.0.1, 1" diff --git a/src/SparseArraysBase.jl b/src/SparseArraysBase.jl index 9f75a13..0f5ce2f 100644 --- a/src/SparseArraysBase.jl +++ b/src/SparseArraysBase.jl @@ -17,8 +17,8 @@ export SparseArrayDOK, storedpairs, storedvalues -include("abstractsparsearrayinterface.jl") -include("sparsearrayinterface.jl") +include("abstractsparsearraystyle.jl") +include("sparsearraystyle.jl") include("indexing.jl") include("map.jl") include("wrappers.jl") diff --git a/src/abstractsparsearray.jl b/src/abstractsparsearray.jl index 3f13ccf..c835b48 100644 --- a/src/abstractsparsearray.jl +++ b/src/abstractsparsearray.jl @@ -1,17 +1,25 @@ using Dictionaries: AbstractDictionary abstract type AbstractSparseArray{T, N} <: AbstractArray{T, N} end +const AbstractSparseVector{T} = AbstractSparseArray{T, 1} +const AbstractSparseMatrix{T} = AbstractSparseArray{T, 2} + +using Adapt: WrappedArray +const WrappedAbstractSparseArray{T, N} = + WrappedArray{T, N, AbstractSparseArray, AbstractSparseArray{T, N}} +const AnyAbstractSparseArray{T, N} = Union{ + AbstractSparseArray{T, N}, WrappedAbstractSparseArray{T, N}, +} +const AnyAbstractSparseVector{T} = AnyAbstractSparseArray{T, 1} +const AnyAbstractSparseMatrix{T} = AnyAbstractSparseArray{T, 2} +const AnyAbstractSparseVecOrMat{T} = Union{ + AnyAbstractSparseVector{T}, AnyAbstractSparseMatrix{T}, +} Base.convert(T::Type{<:AbstractSparseArray}, a::AbstractArray) = a isa T ? a : T(a) -using DerivableInterfaces: @array_aliases -# Define AbstractSparseVector, AnyAbstractSparseArray, etc. -@array_aliases AbstractSparseArray - -using DerivableInterfaces: DerivableInterfaces -function DerivableInterfaces.interface(::Type{<:AbstractSparseArray}) - return SparseArrayInterface() -end +using FunctionImplementations: FunctionImplementations +FunctionImplementations.Style(::Type{<:AnyAbstractSparseArray}) = SparseArrayStyle() function Base.copy(a::AnyAbstractSparseArray) return copyto!(similar(a), a) @@ -49,48 +57,58 @@ function Base.similar( return similar_sparsearray(a, T, ax) end -using DerivableInterfaces: @derive - -# TODO: These need to be loaded since `AbstractArrayOps` -# includes overloads of functions from these modules. -# Ideally that wouldn't be needed and can be circumvented -# with `GlobalRef`. using ArrayLayouts: ArrayLayouts using LinearAlgebra: LinearAlgebra -@derive (T = AnyAbstractSparseArray,) begin - Base.getindex(::T, ::Any...) - Base.getindex(::T, ::Int...) - Base.setindex!(::T, ::Any, ::Any...) - Base.setindex!(::T, ::Any, ::Int...) - Base.copy!(::AbstractArray, ::T) - Base.copyto!(::AbstractArray, ::T) - Base.map(::Any, ::T...) - Base.map!(::Any, ::AbstractArray, ::T...) - Base.mapreduce(::Any, ::Any, ::T...; kwargs...) - Base.reduce(::Any, ::T...; kwargs...) - Base.all(::Function, ::T) - Base.all(::T) - Base.iszero(::T) - Base.real(::T) - Base.fill!(::T, ::Any) - DerivableInterfaces.zero!(::T) - Base.zero(::T) - Base.permutedims!(::Any, ::T, ::Any) - Broadcast.BroadcastStyle(::Type{<:T}) - Base.copyto!(::T, ::Broadcast.Broadcasted{Broadcast.DefaultArrayStyle{0}}) - ArrayLayouts.MemoryLayout(::Type{<:T}) - LinearAlgebra.mul!(::AbstractMatrix, ::T, ::T, ::Number, ::Number) -end - -using DerivableInterfaces.Concatenate: concatenate -# We overload `Base._cat` instead of `Base.cat` since it -# is friendlier for invalidations/compile times, see -# https://github.com/ITensor/SparseArraysBase.jl/issues/25. -function Base._cat(dims, a::AnyAbstractSparseArray...) - return concatenate(dims, a...) +Base.getindex(a::AnyAbstractSparseArray, I::Any...) = style(a)(getindex)(a, I...) +Base.getindex(a::AnyAbstractSparseArray, I::Int...) = style(a)(getindex)(a, I...) +Base.setindex!(a::AnyAbstractSparseArray, x, I::Any...) = style(a)(setindex!)(a, x, I...) +Base.setindex!(a::AnyAbstractSparseArray, x, I::Int...) = style(a)(setindex!)(a, x, I...) +Base.copy!(dst::AbstractArray, src::AnyAbstractSparseArray) = style(src)(copy!)(dst, src) +function Base.copyto!(dst::AbstractArray, src::AnyAbstractSparseArray) + return style(src)(copyto!)(dst, src) +end +Base.map(f, as::AnyAbstractSparseArray...) = style(as...)(map)(f, as...) +function Base.map!(f, dst::AbstractArray, as::AnyAbstractSparseArray...) + return style(as...)(map!)(f, dst, as...) +end +function Base.mapreduce(f, op, as::AnyAbstractSparseArray...; kwargs...) + return style(as...)(mapreduce)(f, op, as...; kwargs...) +end +function Base.reduce(f, as::AnyAbstractSparseArray...; kwargs...) + return style(as...)(reduce)(f, as...; kwargs...) +end +Base.all(f::Function, a::AnyAbstractSparseArray) = style(a)(all)(f, a) +Base.all(a::AnyAbstractSparseArray) = style(a)(all)(a) +Base.iszero(a::AnyAbstractSparseArray) = style(a)(iszero)(a) +Base.isreal(a::AnyAbstractSparseArray) = style(a)(isreal)(a) +Base.real(a::AnyAbstractSparseArray) = style(a)(real)(a) +Base.fill!(a::AnyAbstractSparseArray, x) = style(a)(fill!)(a, x) +FunctionImplementations.zero!(a::AnyAbstractSparseArray) = style(a)(zero!)(a) +Base.zero(a::AnyAbstractSparseArray) = style(a)(zero)(a) +function Base.permutedims!(dst, a::AnyAbstractSparseArray, perm) + return style(a)(permutedims!)(dst, a, perm) +end +function LinearAlgebra.mul!( + dst::AbstractMatrix, a1::AnyAbstractSparseArray, a2::AnyAbstractSparseArray, + α::Number, β::Number, + ) + return style(a1, a2)(mul!)(dst, a1, a2, α, β) end +function Base.Broadcast.BroadcastStyle(type::Type{<:AnyAbstractSparseArray}) + return Broadcast.SparseArrayStyle{ndims(type)}() +end + +using ArrayLayouts: ArrayLayouts +ArrayLayouts.MemoryLayout(type::Type{<:AnyAbstractSparseArray}) = SparseLayout() + +using FunctionImplementations.Concatenate: concatenate +# We overload `Base._cat` instead of `Base.cat` since it +# is friendlier for invalidations/compile times, see: +# https://github.com/ITensor/SparseArraysBase.jl/issues/25 +Base._cat(dims, a::AnyAbstractSparseArray...) = concatenate(dims, a...) + # TODO: Use `map(WeakPreserving(f), a)` instead. # Currently that has trouble with type unstable maps, since # the element type becomes abstract and therefore the zero/unstored @@ -247,10 +265,6 @@ function sparserand!( end end -# Catch some cases that aren't getting caught by the current -# DerivableInterfaces.jl logic. -# TODO: Make this more systematic once DerivableInterfaces.jl -# is rewritten. using ArrayLayouts: ArrayLayouts, MemoryLayout using LinearAlgebra: LinearAlgebra, Adjoint function ArrayLayouts.MemoryLayout(::Type{Transpose{T, P}}) where {T, P <: AbstractSparseMatrix} diff --git a/src/abstractsparsearrayinterface.jl b/src/abstractsparsearraystyle.jl similarity index 56% rename from src/abstractsparsearrayinterface.jl rename to src/abstractsparsearraystyle.jl index 32a20d4..45bd02c 100644 --- a/src/abstractsparsearrayinterface.jl +++ b/src/abstractsparsearraystyle.jl @@ -1,7 +1,6 @@ using Base: @_propagate_inbounds_meta -using DerivableInterfaces: - DerivableInterfaces, @derive, @interface, AbstractArrayInterface, zero! using FillArrays: Zeros +using FunctionImplementations: FunctionImplementations function unstored end function eachstoredindex end @@ -52,34 +51,21 @@ function dense(a::AbstractArray) return @allowscalar convert(densetype(a), a) end -# Minimal interface for `SparseArrayInterface`. +# Minimal interface for `SparseArrayStyle`. # Fallbacks for dense/non-sparse arrays. -# TODO: Add `ndims` type parameter, like `Base.Broadcast.AbstractArrayStyle`. -# TODO: This isn't used to define interface functions right now. -# Currently, `@interface` expects an instance, probably it should take a -# type instead so fallback functions can use abstract types. -abstract type AbstractSparseArrayInterface{N} <: AbstractArrayInterface{N} end +using FunctionImplementations: AbstractArrayStyle +abstract type AbstractSparseArrayStyle <: AbstractArrayStyle end -function DerivableInterfaces.combine_interface_rule( - interface1::AbstractSparseArrayInterface, interface2::AbstractSparseArrayInterface +function FunctionImplementations.Style( + style1::AbstractSparseArrayStyle, style2::AbstractSparseArrayStyle ) return error("Rule not defined.") end -function DerivableInterfaces.combine_interface_rule( - interface1::Interface, interface2::Interface - ) where {Interface <: AbstractSparseArrayInterface} - return interface1 -end -function DerivableInterfaces.combine_interface_rule( - interface1::AbstractSparseArrayInterface, interface2::AbstractArrayInterface - ) - return interface1 -end -function DerivableInterfaces.combine_interface_rule( - interface1::AbstractArrayInterface, interface2::AbstractSparseArrayInterface +function FunctionImplementations.Style( + style1::AbstractSparseArrayStyle, style2::AbstractArrayStyle ) - return interface2 + return style1 end to_vec(x) = vec(collect(x)) @@ -106,63 +92,6 @@ Base.size(a::StoredValues) = size(a.storedindices) return setindex!(a.array, value, a.storedindices[I]) end -using DerivableInterfaces: DerivableInterfaces, zero! - -# `zero!` isn't defined in `Base`, but it is defined in `ArrayLayouts` -# and is useful for sparse array logic, since it can be used to empty -# the sparse array storage. -# We use a single function definition to minimize method ambiguities. -@interface interface::AbstractSparseArrayInterface function DerivableInterfaces.zero!( - a::AbstractArray - ) - # More generally, this codepath could be taking if `zero(eltype(a))` - # is defined and the elements are immutable. - f = eltype(a) <: Number ? Returns(zero(eltype(a))) : zero! - @inbounds for I in eachstoredindex(a) - a[I] = f(a[I]) - end - return a -end - -# `f::typeof(norm)`, `op::typeof(max)` used by `norm`. -function reduce_init(f, op, as...) - # TODO: Generalize this. - @assert isone(length(as)) - a = only(as) - ## TODO: Make this more efficient for block sparse - ## arrays, in that case it allocates a block. Maybe - ## it can use `FillArrays.Zeros`. - return f(getunstoredindex(a, first(eachindex(a)))) -end - -@interface ::AbstractSparseArrayInterface function Base.mapreduce( - f, op, as::AbstractArray...; init = reduce_init(f, op, as...), kwargs... - ) - # TODO: Generalize this. - @assert isone(length(as)) - a = only(as) - output = mapreduce(f, op, storedvalues(a); init, kwargs...) - ## TODO: Bring this check back, or make the function more general. - ## f_notstored = apply_notstored(f, a) - ## @assert isequal(op(output, eltype(output)(f_notstored)), output) - return output -end - -abstract type AbstractSparseArrayStyle{N} <: Broadcast.AbstractArrayStyle{N} end - -@derive (T = AbstractSparseArrayStyle,) begin - Base.similar(::Broadcast.Broadcasted{<:T}, ::Type, ::Tuple) - Base.copyto!(::AbstractArray, ::Broadcast.Broadcasted{<:T}) -end - -struct SparseArrayStyle{N} <: AbstractSparseArrayStyle{N} end - -SparseArrayStyle{M}(::Val{N}) where {M, N} = SparseArrayStyle{N}() - -@interface ::AbstractSparseArrayInterface function Broadcast.BroadcastStyle(type::Type) - return SparseArrayStyle{ndims(type)}() -end - using ArrayLayouts: ArrayLayouts, MatMulMatAdd abstract type AbstractSparseLayout <: ArrayLayouts.MemoryLayout end @@ -181,7 +110,7 @@ function mul_indices(I1::CartesianIndex{2}, I2::CartesianIndex{2}) end using LinearAlgebra: mul! -function default_mul!!( +function mul!!( a_dest::AbstractMatrix, a1::AbstractMatrix, a2::AbstractMatrix, @@ -192,20 +121,20 @@ function default_mul!!( return a_dest end -function default_mul!!( +function mul!!( a_dest::Number, a1::Number, a2::Number, α::Number = true, β::Number = false ) return a1 * a2 * α + a_dest * β end # a1 * a2 * α + a_dest * β -function sparse_mul!( +function _mul!_sparse( a_dest::AbstractArray, a1::AbstractArray, a2::AbstractArray, α::Number = true, β::Number = false; - (mul!!) = (default_mul!!), + (mul!!) = (mul!!), ) a_dest .*= β β′ = one(Bool) @@ -227,12 +156,6 @@ end function ArrayLayouts.materialize!( m::MatMulMatAdd{<:AbstractSparseLayout, <:AbstractSparseLayout, <:AbstractSparseLayout} ) - sparse_mul!(m.C, m.A, m.B, m.α, m.β) + _mul!_sparse(m.C, m.A, m.B, m.α, m.β) return m.C end - -struct SparseLayout <: AbstractSparseLayout end - -@interface ::AbstractSparseArrayInterface function ArrayLayouts.MemoryLayout(type::Type) - return SparseLayout() -end diff --git a/src/indexing.jl b/src/indexing.jl index 85fdead..2805675 100644 --- a/src/indexing.jl +++ b/src/indexing.jl @@ -1,9 +1,8 @@ using Base: @_propagate_inbounds_meta +using FunctionImplementations: Implementation, style # Indexing interface # ------------------ -# these definitions are not using @derive since we need the @inline annotation -# to correctly deal with boundschecks and @inbounds """ getstoredindex(A::AbstractArray, I...) -> eltype(A) @@ -12,9 +11,9 @@ Obtain `getindex(A, I...)` with the guarantee that there is a stored entry at th Similar to `Base.getindex`, new definitions should be in line with `IndexStyle(A)`. """ -@inline getstoredindex(A::AbstractArray, I...) = @interface interface(A) getstoredindex( - A, I... -) +@inline function getstoredindex(A::AbstractArray, I...) + return style(A)(getstoredindex)(A, I...) +end """ getunstoredindex(A::AbstractArray, I...) -> eltype(A) @@ -26,9 +25,9 @@ instantiated object. Similar to `Base.getindex`, new definitions should be in line with `IndexStyle(A)`. """ -@inline getunstoredindex(A::AbstractArray, I...) = @interface interface(A) getunstoredindex( - A, I... -) +@inline function getunstoredindex(A::AbstractArray, I...) + return style(A)(getunstoredindex)(A, I...) +end """ isstored(A::AbstractArray, I...) -> Bool @@ -39,7 +38,9 @@ sparse array types might overload this function when appropriate. Similar to `Base.getindex`, new definitions should be in line with `IndexStyle(A)`. """ -@inline isstored(A::AbstractArray, I...) = @interface interface(A) isstored(A, I...) +@inline function isstored(A::AbstractArray, I...) + return style(A)(isstored)(A, I...) +end """ setstoredindex!(A::AbstractArray, v, I...) -> A @@ -48,9 +49,9 @@ Similar to `Base.getindex`, new definitions should be in line with `IndexStyle(A Similar to `Base.setindex!`, new definitions should be in line with `IndexStyle(A)`. """ -@inline setstoredindex!(A::AbstractArray, v, I...) = @interface interface(A) setstoredindex!( - A, v, I... -) +@inline function setstoredindex!(A::AbstractArray, v, I...) + return style(A)(setstoredindex!)(A, v, I...) +end """ setunstoredindex!(A::AbstractArray, v, I...) -> A @@ -59,9 +60,9 @@ Similar to `Base.setindex!`, new definitions should be in line with `IndexStyle( Similar to `Base.setindex!`, new definitions should be in line with `IndexStyle(A)`. """ -@inline setunstoredindex!(A::AbstractArray, v, I...) = @interface interface(A) setunstoredindex!( - A, v, I... -) +@inline function setunstoredindex!(A::AbstractArray, v, I...) + return style(A)(setunstoredindex!)(A, v, I...) +end # Indices interface # ----------------- @@ -109,13 +110,13 @@ to be the same as [`eachstoredindex`](@ref). """ function storedvalues end -@derive (T = AbstractArray,) begin - SparseArraysBase.eachstoredindex(::T...) - SparseArraysBase.eachstoredindex(::IndexStyle, ::T...) - SparseArraysBase.storedlength(::T) - SparseArraysBase.storedpairs(::T) - SparseArraysBase.storedvalues(::T) +eachstoredindex(as::AbstractArray...) = style(as...)(eachstoredindex)(as...) +function eachstoredindex(indexstyle::IndexStyle, as::AbstractArray...) + return style(as...)(eachstoredindex)(indexstyle, as...) end +storedlength(a::AbstractArray) = style(a)(storedlength)(a) +storedpairs(a::AbstractArray) = style(a)(storedpairs)(a) +storedvalues(a::AbstractArray) = style(a)(storedvalues)(a) # canonical indexing # ------------------ @@ -127,7 +128,7 @@ for f in (:isstored, :getunstoredindex, :getstoredindex) _f = Symbol(:_, f) error_if_canonical = Symbol(:error_if_canonical_, f) @eval begin - @interface ::AbstractArrayInterface function $f(A::AbstractArray, I...) + function (::Implementation{typeof($f)})(A::AbstractArray, I...) @_propagate_inbounds_meta style = IndexStyle(A) $error_if_canonical(style, A, I...) @@ -168,11 +169,11 @@ for f in (:isstored, :getunstoredindex, :getstoredindex) end end -for f! in (:setunstoredindex!, :setstoredindex!) +for f! in (:setstoredindex!, :setunstoredindex!) _f! = Symbol(:_, f!) error_if_canonical = Symbol(:error_if_canonical_, f!) @eval begin - @interface ::AbstractArrayInterface function $f!(A::AbstractArray, v, I...) + function (::Implementation{typeof($f!)})(A::AbstractArray, v, I...) @_propagate_inbounds_meta style = IndexStyle(A) $error_if_canonical(style, A, I...) @@ -213,67 +214,79 @@ for f! in (:setunstoredindex!, :setstoredindex!) end end -# AbstractArrayInterface fallback definitions +# AbstractArrayStyle fallback definitions # ------------------------------------------- -@interface ::AbstractArrayInterface function isstored(A::AbstractArray, i::Int, I::Int...) +function (::Implementation{typeof(isstored)})(A::AbstractArray, i::Int, I::Int...) @inline @boundscheck checkbounds(A, i, I...) return true end -@interface ::AbstractArrayInterface function getunstoredindex(A::AbstractArray, I::Int...) +function (::Implementation{typeof(getunstoredindex)})(A::AbstractArray, I::Int...) @inline @boundscheck checkbounds(A, I...) return zero(eltype(A)) end -@interface ::AbstractArrayInterface function getstoredindex(A::AbstractArray, I::Int...) +function (::Implementation{typeof(getstoredindex)})(A::AbstractArray, I::Int...) @inline return getindex(A, I...) end -@interface ::AbstractArrayInterface function setstoredindex!(A::AbstractArray, v, I::Int...) +function (::Implementation{typeof(setstoredindex!)})(A::AbstractArray, v, I::Int...) @inline return setindex!(A, v, I...) end -@interface ::AbstractArrayInterface setunstoredindex!(A::AbstractArray, v, I::Int...) = error( - "setunstoredindex! for $(typeof(A)) is not supported" -) +function (::Implementation{typeof(setunstoredindex!)})(A::AbstractArray, v, I::Int...) + return error("setunstoredindex! for $(typeof(A)) is not supported") +end -@interface ::AbstractArrayInterface eachstoredindex(A::AbstractArray, B::AbstractArray...) = eachstoredindex( - IndexStyle(A, B...), A, B... -) -@interface ::AbstractArrayInterface eachstoredindex(style::IndexStyle, A::AbstractArray, B::AbstractArray...) = eachindex( - style, A, B... -) +function (::Implementation{typeof(eachstoredindex)})(A::AbstractArray, B::AbstractArray...) + return eachstoredindex(IndexStyle(A, B...), A, B...) +end +function (::Implementation{typeof(eachstoredindex)})(style::IndexStyle, A::AbstractArray, B::AbstractArray...) + return eachindex(style, A, B...) +end -@interface ::AbstractArrayInterface storedvalues(A::AbstractArray) = values(A) -@interface ::AbstractArrayInterface storedpairs(A::AbstractArray) = pairs(A) -@interface ::AbstractArrayInterface storedlength(A::AbstractArray) = length(storedvalues(A)) +(::Implementation{typeof(storedvalues)})(a::AbstractArray) = values(a) +(::Implementation{typeof(storedpairs)})(a::AbstractArray) = pairs(a) +(::Implementation{typeof(storedlength)})(a::AbstractArray) = length(storedvalues(a)) # SparseArrayInterface implementations # ------------------------------------ # canonical errors are moved to `isstored`, `getstoredindex` and `getunstoredindex` # so no errors at this level by defining both IndexLinear and IndexCartesian -@interface ::AbstractSparseArrayInterface function Base.getindex( +const getindex_sparse = sparse_style(getindex) +function getindex_sparse( A::AbstractArray{<:Any, N}, I::Vararg{Int, N} ) where {N} @_propagate_inbounds_meta @boundscheck checkbounds(A, I...) # generally isstored requires bounds checking return @inbounds isstored(A, I...) ? getstoredindex(A, I...) : getunstoredindex(A, I...) end -@interface ::AbstractSparseArrayInterface function Base.getindex(A::AbstractArray, I::Int) +function getindex_sparse(A::AbstractArray, I::Int) @_propagate_inbounds_meta @boundscheck checkbounds(A, I) return @inbounds isstored(A, I) ? getstoredindex(A, I) : getunstoredindex(A, I) end # disambiguate vectors -@interface ::AbstractSparseArrayInterface function Base.getindex(A::AbstractVector, I::Int) +function getindex_sparse(A::AbstractVector, I::Int) @_propagate_inbounds_meta @boundscheck checkbounds(A, I) return @inbounds isstored(A, I) ? getstoredindex(A, I) : getunstoredindex(A, I) end +# TODO: Make this more general, use `Base.to_index`. +function getindex_sparse( + a::AbstractArray{<:Any, N}, I::CartesianIndex{N} + ) where {N} + return getindex_sparse(a, Tuple(I)...) +end +using ArrayLayouts: ArrayLayouts +function getindex_sparse(a::AbstractArray, I...) + return ArrayLayouts.layout_getindex(a, I...) +end -@interface ::AbstractSparseArrayInterface function Base.setindex!( +const setindex!_sparse = sparse_style(setindex!) +function setindex!_sparse( A::AbstractArray{<:Any, N}, v, I::Vararg{Int, N} ) where {N} @_propagate_inbounds_meta @@ -284,7 +297,7 @@ end setunstoredindex!(A, v, I...) end end -@interface ::AbstractSparseArrayInterface function Base.setindex!( +function setindex!_sparse( A::AbstractArray, v, I::Int ) @_propagate_inbounds_meta @@ -296,7 +309,7 @@ end end end # disambiguate vectors -@interface ::AbstractSparseArrayInterface function Base.setindex!( +function setindex!_sparse( A::AbstractVector, v, I::Int ) @_propagate_inbounds_meta @@ -307,6 +320,16 @@ end setunstoredindex!(A, v, I) end end +# TODO: Make this more general, use `Base.to_index`. +function setindex!_sparse( + a::AbstractArray{<:Any, N}, value, I::CartesianIndex{N} + ) where {N} + return setindex!(a, value, Tuple(I)...) +end +function setindex!_sparse(a::AbstractArray, value, I...) + map!(identity, @view(a[I...]), value) + return a +end @noinline function error_if_canonical_eachstoredindex(style::IndexStyle, A::AbstractArray) style === IndexStyle(A) && throw(Base.CanonicalIndexError("eachstoredindex", typeof(A))) @@ -314,7 +337,8 @@ end end # required: one implementation for canonical index style -@interface ::AbstractSparseArrayInterface function eachstoredindex( +const eachstoredindex_sparse = sparse_style(eachstoredindex) +function eachstoredindex_sparse( style::IndexStyle, A::AbstractArray ) error_if_canonical_eachstoredindex(style, A) @@ -331,18 +355,20 @@ end end # derived but may be specialized: -@interface ::AbstractSparseArrayInterface function eachstoredindex( +function eachstoredindex_sparse( style::IndexStyle, A::AbstractArray, B::AbstractArray... ) return union(map(Base.Fix1(eachstoredindex, style), (A, B...))...) end -@interface ::AbstractSparseArrayInterface storedvalues(A::AbstractArray) = StoredValues(A) +const storedvalues_sparse = sparse_style(storedvalues) +storedvalues_sparse(A::AbstractArray) = StoredValues(A) # default implementation is a bit tricky here: we don't know if this is the "canonical" # implementation, so we check this and otherwise map back to `_isstored` to canonicalize the # indices -@interface ::AbstractSparseArrayInterface function isstored(A::AbstractArray, I::Int...) +const isstored_sparse = sparse_style(isstored) +function isstored_sparse(A::AbstractArray, I::Int...) @_propagate_inbounds_meta style = IndexStyle(A) # canonical linear indexing @@ -361,7 +387,8 @@ end return _isstored(style, A, Base.to_indices(A, I)...) end -@interface ::AbstractSparseArrayInterface function getunstoredindex( +const getunstoredindex_sparse = sparse_style(getunstoredindex) +function getunstoredindex_sparse( A::AbstractArray, I::Int... ) @_propagate_inbounds_meta @@ -383,8 +410,8 @@ end return _getunstoredindex(style, A, Base.to_indices(A, I)...) end -# make sure we don't call AbstractArrayInterface defaults -@interface ::AbstractSparseArrayInterface function getstoredindex( +const getstoredindex_sparse = sparse_style(getstoredindex) +function getstoredindex_sparse( A::AbstractArray, I::Int... ) @_propagate_inbounds_meta @@ -393,11 +420,14 @@ end return _getstoredindex(style, A, Base.to_indices(A, I)...) end +const setstoredindex!_sparse = sparse_style(setstoredindex!) +const setunstoredindex!_sparse = sparse_style(setunstoredindex!) for f! in (:setstoredindex!, :setunstoredindex!) + f!_sparse = Symbol(f!, :_sparse) _f! = Symbol(:_, f!) error_if_canonical_setstoredindex = Symbol(:error_if_canonical_, f!) @eval begin - @interface ::AbstractSparseArrayInterface function $f!(A::AbstractArray, v, I::Int...) + function $f!_sparse(A::AbstractArray, v, I::Int...) @_propagate_inbounds_meta style = IndexStyle(A) $error_if_canonical_setstoredindex(style, A, I...) @@ -406,10 +436,8 @@ for f! in (:setstoredindex!, :setunstoredindex!) end end -@interface ::AbstractSparseArrayInterface storedlength(A::AbstractArray) = length( - storedvalues(A) -) -@interface ::AbstractSparseArrayInterface function storedpairs(A::AbstractArray) +const storedpairs_sparse = sparse_style(storedpairs) +function storedpairs_sparse(A::AbstractArray) return Iterators.map(I -> (I => A[I]), eachstoredindex(A)) end @@ -426,7 +454,7 @@ for (Tr, Tc) in Iterators.product( ) Tr === Tc === :Integer && continue @eval begin - @interface ::AbstractSparseArrayInterface function Base.getindex( + function getindex_sparse( A::AbstractMatrix, kr::$Tr, jr::$Tc ) Base.@inline # needed to make boundschecks work diff --git a/src/map.jl b/src/map.jl index 83efe6b..d44e337 100644 --- a/src/map.jl +++ b/src/map.jl @@ -67,15 +67,16 @@ end # map(!) # ------ -@interface I::AbstractSparseArrayInterface function Base.map( +const map_sparse = sparse_style(map) +function map_sparse( f, A::AbstractArray, Bs::AbstractArray... ) f_pres = ZeroPreserving(f, A, Bs...) - return map_sparsearray(f_pres, A, Bs...) + return map_sparse(f_pres, A, Bs...) end # This isn't an overload of `Base.map` since that leads to ambiguity errors. -function map_sparsearray(f::ZeroPreserving, A::AbstractArray, Bs::AbstractArray...) +function map_sparse(f::ZeroPreserving, A::AbstractArray, Bs::AbstractArray...) T = Base.Broadcast.combine_eltypes(f.f, (A, Bs...)) C = similar(A, T) # TODO: Instead use: @@ -84,18 +85,19 @@ function map_sparsearray(f::ZeroPreserving, A::AbstractArray, Bs::AbstractArray. # C = similar(A, Unstored(U)) # ``` # though right now `map` doesn't preserve `Zeros` or `BlockZeros`. - return map_sparsearray!(f, C, A, Bs...) + return map!_sparse(f, C, A, Bs...) end -@interface I::AbstractSparseArrayInterface function Base.map!( +const map!_sparse = sparse_style(map!) +function map!_sparse( f, C::AbstractArray, A::AbstractArray, Bs::AbstractArray... ) f_pres = ZeroPreserving(f, A, Bs...) - return map_sparsearray!(f_pres, C, A, Bs...) + return map!_sparse(f_pres, C, A, Bs...) end # This isn't an overload of `Base.map!` since that leads to ambiguity errors. -function map_sparsearray!( +function map!_sparse( f::ZeroPreserving, C::AbstractArray, A::AbstractArray, Bs::AbstractArray... ) checkshape(C, A, Bs...) @@ -121,33 +123,63 @@ end # Derived functions # ----------------- -@interface I::AbstractSparseArrayInterface function Base.copyto!( +const copyto!_sparse = sparse_style(copyto!) +function copyto!_sparse( dest::AbstractArray, src::AbstractArray ) - @interface I map!(identity, dest, src) + map!_sparse(identity, dest, src) return dest end +const permutedims!_sparse = sparse_style(permutedims!) +function permutedims!_sparse( + a_dest::AbstractArray, a_src::AbstractArray, perm + ) + return map!(identity, a_dest, PermutedDimsArray(a_src, perm)) +end + # Only map the stored values of the inputs. function map_stored! end -@interface interface::AbstractArrayInterface function map_stored!( +const map_stored!_sparse = sparse_style(map_stored!) +function map_stored!_sparse( f, a_dest::AbstractArray, as::AbstractArray... ) - @interface interface map!(WeakPreserving(f), a_dest, as...) + map!_sparse(WeakPreserving(f), a_dest, as...) return a_dest end # Only map all values, not just the stored ones. function map_all! end -@interface interface::AbstractArrayInterface function map_all!( +const map_all!_sparse = sparse_style(map_all!) +function map_all!_sparse( f, a_dest::AbstractArray, as::AbstractArray... ) - @interface interface map!(NonPreserving(f), a_dest, as...) + map!_sparse(NonPreserving(f), a_dest, as...) return a_dest end +# TODO: Generalize to multiple inputs. +const reduce_sparse = sparse_style(reduce) +function reduce_sparse(f, a::AbstractArray; kwargs...) + return mapreduce(identity, f, a; kwargs...) +end + +const all_sparse = sparse_style(all) +function all_sparse(a::AbstractArray) + return reduce(&, a; init = true) +end +function all_sparse(f::Function, a::AbstractArray) + return mapreduce(f, &, a; init = true) +end + +const isreal_sparse = sparse_style(isreal) +isreal_sparse(a::AbstractArray) = all(isreal, a) + +const iszero_sparse = sparse_style(iszero) +iszero_sparse(a::AbstractArray) = all(iszero, a) + # Utility functions # ----------------- # shape check similar to checkbounds diff --git a/src/oneelementarray.jl b/src/oneelementarray.jl index e276169..eda8443 100644 --- a/src/oneelementarray.jl +++ b/src/oneelementarray.jl @@ -16,9 +16,8 @@ struct OneElementArray{T, N, I, Unstored <: AbstractArray{T, N}} <: AbstractSpar end end -using DerivableInterfaces: @array_aliases -# Define `OneElementMatrix`, `AnyOneElementArray`, etc. -@array_aliases OneElementArray +const OneElementVector{T} = OneElementArray{T, 1} +const OneElementMatrix{T} = OneElementArray{T, 2} function OneElementArray{T, N}( value, index::NTuple{N, Int}, axes::NTuple{N, AbstractUnitRange} diff --git a/src/sparsearraydok.jl b/src/sparsearraydok.jl index 6441d34..44217d3 100644 --- a/src/sparsearraydok.jl +++ b/src/sparsearraydok.jl @@ -1,5 +1,5 @@ using Accessors: @set -using DerivableInterfaces: DerivableInterfaces, @interface, interface, zero! +using FunctionImplementations: FunctionImplementations, zero! using Dictionaries: Dictionary, IndexError, set! const DOKStorage{T, N} = Dictionary{CartesianIndex{N}, T} @@ -65,15 +65,8 @@ function SparseArrayDOK{T}(::UndefInitializer, ax::Vararg{Any, N}) where {T, N} return SparseArrayDOK{T, N}(undef, ax) end -using DerivableInterfaces: DerivableInterfaces -# This defines the destination type of various operations in DerivableInterfaces.jl. -function Base.similar(::AbstractSparseArrayInterface, T::Type, ax::Tuple) - return similar(SparseArrayDOK{T}, ax) -end - -using DerivableInterfaces: @array_aliases -# Define `SparseMatrixDOK`, `AnySparseArrayDOK`, etc. -@array_aliases SparseArrayDOK +const SparseVectorDOK{T} = SparseArrayDOK{T, 1} +const SparseMatrixDOK{T} = SparseArrayDOK{T, 2} storage(a::SparseArrayDOK) = a.storage @@ -115,7 +108,7 @@ end storedpairs(a::SparseArrayDOK) = pairs(storage(a)) # TODO: Also handle wrappers. -function DerivableInterfaces.zero!(a::SparseArrayDOK) +function FunctionImplementations.zero!(a::SparseArrayDOK) empty!(storage(a)) return a end diff --git a/src/sparsearrayinterface.jl b/src/sparsearrayinterface.jl deleted file mode 100644 index a0307fd..0000000 --- a/src/sparsearrayinterface.jl +++ /dev/null @@ -1,36 +0,0 @@ -using DerivableInterfaces: DerivableInterfaces - -struct SparseArrayInterface{N} <: AbstractSparseArrayInterface{N} end -SparseArrayInterface() = SparseArrayInterface{Any}() -SparseArrayInterface(::Val{N}) where {N} = SparseArrayInterface{N}() -SparseArrayInterface{M}(::Val{N}) where {M, N} = SparseArrayInterface{N}() - -# Fix ambiguity error. -function DerivableInterfaces.combine_interface_rule( - ::SparseArrayInterface{N}, ::SparseArrayInterface{N} - ) where {N} - return SparseArrayInterface{N}() -end -function DerivableInterfaces.combine_interface_rule( - ::SparseArrayInterface, ::SparseArrayInterface - ) - return SparseArrayInterface() -end -function DerivableInterfaces.combine_interface_rule( - interface1::SparseArrayInterface, interface2::AbstractSparseArrayInterface - ) - return interface1 -end -function DerivableInterfaces.combine_interface_rule( - interface1::AbstractSparseArrayInterface, interface2::SparseArrayInterface - ) - return interface2 -end - -# Convenient shorthand to refer to the sparse interface. -# Can turn a function into a sparse function with the syntax `sparse(f)`, -# i.e. `sparse(map)(x -> 2x, randn(2, 2))` while use the sparse -# version of `map`. -# const sparse = SparseArrayInterface() - -DerivableInterfaces.interface(::Type{<:AbstractSparseArrayStyle}) = SparseArrayInterface() diff --git a/src/sparsearrays.jl b/src/sparsearrays.jl index ec03616..e8f7b7c 100644 --- a/src/sparsearrays.jl +++ b/src/sparsearrays.jl @@ -6,7 +6,7 @@ function eachstoredindex(m::AbstractSparseMatrixCSC) return Iterators.map(CartesianIndex, zip(I, J)) end function eachstoredindex(a::Base.ReshapedArray{<:Any, <:Any, <:AbstractSparseMatrixCSC}) - return @interface SparseArrayInterface() eachstoredindex(a) + return eachstoredindex_sparse(a) end function SparseArrays.SparseMatrixCSC{Tv, Ti}(m::AnyAbstractSparseMatrix) where {Tv, Ti} diff --git a/src/sparsearraystyle.jl b/src/sparsearraystyle.jl new file mode 100644 index 0000000..6c5ccb0 --- /dev/null +++ b/src/sparsearraystyle.jl @@ -0,0 +1,114 @@ +using FunctionImplementations: FunctionImplementations + +struct SparseArrayStyle <: AbstractSparseArrayStyle end + +# Convenient shorthand to refer to the sparse style. +# Can turn a function into a sparse function with the syntax `sparse_style(f)`, +# i.e. `sparse_style(map)(x -> 2x, randn(2, 2))` while use the sparse +# version of `map`. +const sparse_style = SparseArrayStyle() + +const fill!_sparse = sparse_style(fill!) +function fill!_sparse(a::AbstractArray, value) + return map!(Returns(value), a, a) +end + +using FunctionImplementations: FunctionImplementations, zero! + +# `zero!` isn't defined in `Base`, but it is defined in `ArrayLayouts` +# and is useful for sparse array logic, since it can be used to empty +# the sparse array storage. +# We use a single function definition to minimize method ambiguities. +const zero!_sparse = sparse_style(zero!) +function zero!_sparse(a::AbstractArray) + # More generally, this codepath could be taking if `zero(eltype(a))` + # is defined and the elements are immutable. + f = eltype(a) <: Number ? Returns(zero(eltype(a))) : zero! + @inbounds for I in eachstoredindex(a) + a[I] = f(a[I]) + end + return a +end + +const zero_sparse = sparse_style(zero) +# Specialized version of `Base.zero` written in terms of `zero!`. +# This is friendlier for sparse arrays since `zero!` makes it easier +# to handle the logic of dropping all elements of the sparse array when possible. +# We use a single function definition to minimize method ambiguities. +function zero_sparse(a::AbstractArray) + # More generally, the first codepath could be taking if `zero(eltype(a))` + # is defined and the elements are immutable. + if eltype(a) <: Number + return zero!(similar(a)) + end + return map(zero, a) +end + +# `f::typeof(norm)`, `op::typeof(max)` used by `norm`. +function reduce_init(f, op, as...) + # TODO: Generalize this. + @assert isone(length(as)) + a = only(as) + ## TODO: Make this more efficient for block sparse + ## arrays, in that case it allocates a block. Maybe + ## it can use `FillArrays.Zeros`. + return f(getunstoredindex(a, first(eachindex(a)))) +end + +# This is defined in this way so we can rely on the Broadcast logic +# for determining the destination of the operation (element type, shape, etc.). +const map_sparse = sparse_style(map) +function map_sparse(f, as::AbstractArray...) + # Broadcasting is used here to determine the destination array but that + # could be done manually here. + return f.(as...) +end + +const mapreduce_sparse = sparse_style(mapreduce) +function mapreduce_sparse( + f, op, as::AbstractArray...; init = reduce_init(f, op, as...), kwargs... + ) + # TODO: Generalize this. + @assert isone(length(as)) + a = only(as) + output = mapreduce(f, op, storedvalues(a); init, kwargs...) + ## TODO: Bring this check back, or make the function more general. + ## f_notstored = apply_notstored(f, a) + ## @assert isequal(op(output, eltype(output)(f_notstored)), output) + return output +end + +# Namespace for Broadcast styles to avoid clashing with FunctionImplementations +# styles. +module Broadcast + abstract type AbstractSparseArrayStyle{N} <: Base.Broadcast.AbstractArrayStyle{N} end + struct SparseArrayStyle{N} <: AbstractSparseArrayStyle{N} end + SparseArrayStyle{M}(::Val{N}) where {M, N} = SparseArrayStyle{N}() +end + +using MapBroadcast: Mapped +# TODO: Look into `SparseArrays.capturescalars`: +# https://github.com/JuliaSparse/SparseArrays.jl/blob/1beb0e4a4618b0399907b0000c43d9f66d34accc/src/higherorderfns.jl#L1092-L1102 +function Base.copyto!( + a_dest::AbstractArray, bc::Base.Broadcast.Broadcasted{<:Broadcast.SparseArrayStyle} + ) + m = Mapped(bc) + map!(m.f, a_dest, m.args...) + return a_dest +end + +function Base.similar( + bc::Base.Broadcast.Broadcasted{<:Broadcast.SparseArrayStyle}, elt::Type, ax + ) + return similar(SparseArrayDOK{elt}, ax) +end + +using ArrayLayouts: ArrayLayouts +const mul!_sparse = sparse_style(mul!) +function mul!_sparse( + a_dest::AbstractVecOrMat, a1::AbstractVecOrMat, a2::AbstractVecOrMat, α::Number, β::Number + ) + return ArrayLayouts.mul!(a_dest, a1, a2, α, β) +end + +struct SparseLayout <: AbstractSparseLayout end diff --git a/src/wrappers.jl b/src/wrappers.jl index 3851114..3ce15a8 100644 --- a/src/wrappers.jl +++ b/src/wrappers.jl @@ -148,30 +148,30 @@ function isstored(a::Transpose, I::Vararg{Int, 2}) return isstored_wrapped(a, I...) end -# TODO: Turn these into `AbstractWrappedSparseArrayInterface` functions? +# TODO: Turn these into `AbstractWrappedSparseArrayStyle` functions? for type in (:Adjoint, :PermutedDimsArray, :ReshapedArray, :SubArray, :Transpose) @eval begin - @interface ::AbstractSparseArrayInterface storedvalues(a::$type) = storedparentvalues(a) - @interface ::AbstractSparseArrayInterface function eachstoredindex(a::$type) + storedvalues_sparse(a::$type) = storedparentvalues(a) + function eachstoredindex_sparse(a::$type) return map(Base.Fix1(parentindex_to_index, a), eachstoredparentindex(a)) end - @interface ::AbstractSparseArrayInterface function eachstoredindex( + function eachstoredindex_sparse( style::IndexStyle, a::$type ) # TODO: Make lazy with `Iterators.map`. return map(Base.Fix1(parentindex_to_index, a), eachstoredparentindex(style, a)) end - @interface ::AbstractSparseArrayInterface function getstoredindex(a::$type, I::Int...) + function getstoredindex_sparse(a::$type, I::Int...) return parentvalue_to_value( a, getstoredindex(parent(a), index_to_parentindex(a, I...)...) ) end - @interface ::AbstractSparseArrayInterface function getunstoredindex(a::$type, I::Int...) + function getunstoredindex_sparse(a::$type, I::Int...) return parentvalue_to_value( a, getunstoredindex(parent(a), index_to_parentindex(a, I...)...) ) end - @interface ::AbstractSparseArrayInterface function setstoredindex!( + function setstoredindex!_sparse( a::$type, value, I::Int... ) setstoredindex!( @@ -179,7 +179,7 @@ for type in (:Adjoint, :PermutedDimsArray, :ReshapedArray, :SubArray, :Transpose ) return a end - @interface ::AbstractSparseArrayInterface function setunstoredindex!( + function setunstoredindex!_sparse( a::$type, value, I::Int... ) setunstoredindex!( @@ -190,31 +190,35 @@ for type in (:Adjoint, :PermutedDimsArray, :ReshapedArray, :SubArray, :Transpose end end +using FunctionImplementations: Style using LinearAlgebra: LinearAlgebra, Diagonal -@interface ::AbstractArrayInterface storedvalues(D::Diagonal) = LinearAlgebra.diag(D) +const diag_style = Style(Diagonal) +const storedvalues_diag = diag_style(storedvalues) +storedvalues_diag(D::AbstractMatrix) = LinearAlgebra.diag(D) # compat with LTS: @static if VERSION ≥ v"1.11" _diagind = LinearAlgebra.diagind else - function _diagind(x::Diagonal, ::IndexCartesian) + function _diagind(x::AbstractMatrix, ::IndexCartesian) return view(CartesianIndices(x), LinearAlgebra.diagind(x)) end end -@interface ::AbstractArrayInterface eachstoredindex(D::Diagonal) = _diagind( - D, IndexCartesian() -) +const eachstoredindex_diag = diag_style(eachstoredindex) +eachstoredindex_diag(D::AbstractMatrix) = _diagind(D, IndexCartesian()) -@interface ::AbstractArrayInterface function isstored(D::Diagonal, i::Int, j::Int) +const isstored_diag = diag_style(isstored) +function isstored_diag(D::AbstractMatrix, i::Int, j::Int) return i == j && checkbounds(Bool, D, i, j) end -@interface ::AbstractArrayInterface function getstoredindex(D::Diagonal, i::Int, j::Int) - return D.diag[i] -end -@interface ::AbstractArrayInterface function getunstoredindex(D::Diagonal, i::Int, j::Int) +const getstoredindex_diag = diag_style(getstoredindex) +getstoredindex_diag(D::AbstractMatrix, i::Int, j::Int) = D.diag[i] +const getunstoredindex_diag = diag_style(getunstoredindex) +function getunstoredindex_diag(D::AbstractMatrix, i::Int, j::Int) return zero(eltype(D)) end -@interface ::AbstractArrayInterface function setstoredindex!(D::Diagonal, v, i::Int, j::Int) +const setstoredindex!_diag = diag_style(setstoredindex!) +function setstoredindex!_diag(D::AbstractMatrix, v, i::Int, j::Int) D.diag[i] = v return D end diff --git a/test/Project.toml b/test/Project.toml index ceeb6e3..761f1bb 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -27,11 +27,11 @@ Dictionaries = "0.4.4" FillArrays = "1.13.0" JLArrays = "0.2.0, 0.3" LinearAlgebra = "<0.0.1, 1" -NamedDimsArrays = "0.11" +NamedDimsArrays = "0.12" Random = "<0.0.1, 1" SafeTestsets = "0.1.0" SparseArrays = "1.10" -SparseArraysBase = "0.7" +SparseArraysBase = "0.8" StableRNGs = "1.0.2" Suppressor = "0.2.8" TensorAlgebra = "0.6"