Custom broadcasting

After spending quite a bit of time in the Customizing broadcasting section of the doc, reading a few questions here and trawling through the source code of StaticArrays.jl I’m throwing the sponge. (Once I’ve figured this out, I’ll try to help make this broadcasting section of the docs more accessible for the lay person like me…).

My specs are pretty simple:

struct MyType{R} <: AbstractVector{R}
  data::Matrix{R}
  metadata
end

I’d like to be able to do what’s required so that a user can do f.(x) where x is an instance of MyType. This is pretty much identical to the ArrayAndChar example from the docs with one difference: data is a Matrix, elements of MyType are the rows. Something like

Base.length(m::MyType) = size(m.data, 1)
Base.getindex(m::MyType, i::Integer) = m.data[i,:]
Base.getindex(m::MyType, I::AbstractVector{<:Integer}) = MyType(m.data[I, :], m.metadata)

What I’d like to do is that f.(x) applies f to rows of x.data. I don’t need operations etc, just this broadcasting rule. As a toy example, I’d like to be able to do maximum.(x) and get a vector of maxes i.e. similar as the [maximum(x[i]) for i in 1:length(x)]. (In practice I’d like f to make use of the metadata but that doesn’t seem to be the hard bit)

After thoroughly confusing myself between the custom similar and copyto!, I admit I don’t understand what’s going on, help would be very welcome, thanks!

3 Likes

Your type definition does not agree with your specification. Either you want

struct MyType{RowType} <: AbstractVector{RowType}
  data::Vector{RowType}
  metadata
end

or you want

struct MyType{ElType} <: AbstractVector{Vector{ElType}}
  data::Matrix{ElType}
  metadata
end

In the second case, you might further want to replace Vector{ElType} with a view for efficiency, but that is another layer of complexity which we can get into later.

4 Likes

With this definition:

and a method for size, simple broadcasting should work without any customising:

Base.size(x::MyType) = (size(x.data, 1),)
Base.getindex(x::MyType, i::Integer) = x.data[i,:]
m42 = MyType(rand(Int8,4,2), nothing) 
maximum.(m42) # works

How is metadata going to be used?

1 Like

Thanks a lot for your answers, I understand the original mistake. There may be a further spec I should have mentioned.

What I’d like is that the data is either a Vector{R} or a Matrix{R}. In the first case the type should be subtype of AbstractVector{R}, in the second AbstractVector{Vector{R}} as per @ettersi’s comment. I understand from both your comments that if this subtyping is done properly I wouldn’t have to bother with similar and copyto!.

In non-valid code I guess I’d like to have something like

struct MyType{R,E<:Union{R,Vector{R}}} <: AbstractVector{E}
  data::Array{R, E == R ? 1 : 2}
  metadata
end

Edit: the following seems to do what I want

struct MyType3{R,D,E<:Union{R,Vector{R}}} <: AbstractVector{E}
  data::Array{R,D}
  MyType3(m::Matrix{R}) where R = new{R,2,Vector{R}}(m)
  MyType3(v::Vector{R}) where R = new{R,1,R}(v)
end

mt2 = MyType3(rand(5, 2));
mt1 = MyType3(rand(5));

Base.size(m::MyType3) = (size(m.data,1),)
Base.length(m::MyType3) = size(m.data,1)

Base.getindex(m::MyType3{R,1}, i::Int) where R = m.data[i]
Base.getindex(m::MyType3{R,2}, i::Int) where R = m.data[i,:]

mt2[1]

mt1[1]

foo(x) = sum(x.^2)

foo.(mt1)
foo.(mt2)

I am completely new to this topic and happened to find the discussions here.

I have a question regarding subtypes here: with the <: syntax, are we saying that our custom type is a subtype of AbstractVector{R} or AbstractVector{Vector{ElType}}? And why do we need to explicitly write this, otherwise the custom broadcasting won’t work?

In a more general context, do we need to specify the abstract supertypes for our custom types?

No, you don’t need to subtype anything in order to participate in or customize broadcasting. But if you do, you must behave as we’d expect that abstract type to behave.

Subtyping <: AbstractArray gives you lots of behaviors “for free,” but you must obey the rules for an AbstractArray in order for those behaviors to work. Whether you use <: AbstractVector{R} or AbstractVector{Vector{ElType}} or something different entirely all comes down to how you want your type to behave.

3 Likes