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).

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.

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.

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.