Stop custom type that inherits from SparseArray getting converted to full array

Hello all, I’m very new to julia and trying to essentially make a wrapper for a sparse-type array.

When I set up the type to inherit from AbstractSparseArrays, operations with non-sparse arrays output a full array.

using SparseArrays

struct SparseStruct{T,N} <: AbstractSparseArray{T,Int,N}
	data::AbstractSparseArray{T,Int,N}
	dims::NTuple{N,Int}
end

# forwarding some base methods
Base.size(A::SparseStruct) = A.dims
Base.getindex(A::SparseStruct, I::Any) = getindex(A.data, I)
Base.display(A::SparseStruct) = display(A.data)

a = SparseStruct(sprand(20,20,0.1), (20,20))

println(typeof(a * 2)) # produces full array
println(typeof(a .* sparse([2]))) # produces sparse array

This is different to how a normal sparse array acts:

println(typeof(a.data * 2)) # produces a sparse array.

My assumption was that by inheriting the AbstractSparseArray would lead to SparseArray-like functionality.

Thanks in advance

I believe you would need to implement multiplication for your struct something like:

Base.:*(a::SparseStruct, b::SparseStruct) = SparseStruct(a.data * b.data, a.dims)

You might want to do some error checking that the dims are equal and possibly add a where clause to ensure that T and N are the same for both inputs(if you want that).

1 Like

Thank you for the quick reply.

Your suggestion solved part of the problem, however i ran into a similar problem trying to define broadcasted functions.

Do you have a suggestion for the best way to bootsrap on top of SparseArrays inbuilt broadcasting behaviour?

From Interfaces in the manual it says you’d have to redefine Base.BroadcastingStyle and Base.Similar.

I thought I could try to get piggyback on SparseArrays with

Base.BroadcastStyle(::Type{SparseStruct{T,N}}) where {T,N} = SparseArrays.HigherOrderFns.SparseMatStyle()

But that gave me the following error which I can’t get my head around:

ERROR: MethodError: no method matching _copy(::SparseArrays.HigherOrderFns.var"#3#4"{typeof(*),SparseArrays.HigherOrderFns.var"#17#20"{SparseConnection{Float64,2},SparseArrays.HigherOrderFns.var"#23#26"{Int64}}})

And i’m similarly stuck with trying to redefine Base.similar

Uh, we’re out of my comfort zone, but based on my read of the “Customizing broadcasting” section of:
https://docs.julialang.org/en/v1/manual/interfaces/
What I would do is:

# Your explicit call of a function might be better, I don't know.
Base.BroadcastStyle(::Type{SparseStruct{T, N}) where {T, N}  =
    BroadcastStyle(AbstractSparseArray{T, Int, N})
Base.axes(x::SparseStruct) = axes(x.data)
Base.broadcastable(x::SparseStruct) = broadcastable(x.data)

i’m really unsure about the similar function. My best guess would be:

Base.similar(bc::SparseStruct{T, N}, ::Type{SparseStruct{T, N}) where {T, N}  = 
    SparseStruct(similar(bc.data, typeof(bc.data)), bc.dims)

But that is really a guess.

1 Like

Welcome to Julia! You’re diving straight into the deep end here… and there are some sharks in the pool.

In short: subtyping of AbstractSparseArray isn’t magic and isn’t a fully formed and public API. You’ll probably have the most success in trying to duplicate how SparseMatrixCSC works, but that relies on lots of internally goodness.

4 Likes

Okay, thanks for your advice,
In retrospect this was actually a pretty contrived way to go about what I wanted to do, subtyping a base abstract type initially seemed like a simple option, but you’re right it quickly got much more messy than I’d anticipated.