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!

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.

1 Like

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)