I previously asked this question and am aware of the section in the manual about customizing broadcasting.
However, rather than defining a new type which behaves like a single array, I would like to define broadcasting recursively for types which contain several arrays (or types which are themselves collections of arrays). That is, imagine we have
struct A{T so that broadcasting is already defined for T}
a::T
b::T
end
If x::A, y::A, calculate z = x .+ y by calling z.a = x.a .+ y.a (etc.).
This would allow me to efficiently express algebra with large models I am building. Any ideas on how to do this?
Extending upon your previous solution, youāll need to now implement broadcasting for your style. You do this by implementing copyto!(dest::A, bc::Broadcast.Broadcasted{MyStyle}). There are two challenges here:
Implementing that method isnāt easy. You need to support any arbitrary combinations of fused expressions and do the indexing computations yourself ā you can make this a bit easier (at the sacrifice of a bit of performance) by using Broadcast.flatten, which transforms the nested expression tree into a single function and a flat list of arguments. Itās still hard and annoying.
My thinking and use case is similar to marius311 in the GitHub link. Essentially, I am just looking for a way to call the already implemented broadcasting rules of the fields of my type, using the dot syntax for my type.
What I got out of this is now:
What I want to do is currently not considered broadcasting even though it could be phrased as such (see marius311ās way of supporting indexing in his Foo type). However, such indexing or flattening would incur a performance hit.
If I implement broadcast, broadcast! methods for my types, I can get what I want at the expense of notational ease and readability.
This could look like
function broadcast!(f, dest::A, x::A, y::A)
@. dest.a = f(x.a, y.a)
@. dest.b = f(x.b, y.b)
end
Since this came up before, I am wondering if this use case could ever be supported by broadcasting. Given that it is easy to implement the broadcast methods in this case, could they be as easily translated to a dot syntax for A?
The trouble is that itās at odds with the definition of broadcast. How should we define broadcast if it doesnāt mean an elementwise operation?
The dot syntax needs to handle arbitrary heterogeneous combinations of arguments with an arbitrary number of nested fused calls. Sure, itās nicely defined for a simple function over a pair of A arguments, but it gets complicated fast when you add in other arrays, scalars, tuples, etc., etc. And since youāre defining broadcast to mean something different than what its generic fallbacks do, if your specialization ever fails to kick in you may get a completely different answer.
I now better understand the difficulty in this. Itās not clear how to deal with heterogeneous combinations of arguments in this case. Thank you for explaining it to me!
Iāve actually been using the method described there quite successfully. For fairly simple expressions which donāt hit the bug mentioned therein, its completely type-stable with no performance overhead, its exactly as if I wrote the broadcast over the member arrays by hand. Even in cases where you do hit that inference bug, you can just write out the flattened expression by hand. E.g. this expression hits that bug
@. y += h*(kā + 2kā + 2kā + kā)/6
but in cases like that you can just write out:
broadcast!((y,h,kā,kā,kā,kā)->(y+h*(kā+2kā+2kā+kā)/6), y, (y,h,kā,kā,kā,kā)...)
and youāre back to Array performance. You could even write a macro to do that for you in fact. It is a tiny bit more of a pain since you have to catch these cases where inference fails, so I do really hope the developers can one day fix #27988, but for now this is OK.
FYI, in terms of automatically broadcasting things on the right hand side, itās kind of easy to get automatic broadcasting for arbitrary (āwell-behavingā) struct. To do this, you can just subtype the following BroadcastableStruct (which is just a subset of BroadcastableCallable I described here):
abstract type BroadcastableStruct end
fieldvalues(obj) = ntuple(i -> getfield(obj, i), fieldcount(typeof(obj)))
# Taken from `Setfield.constructor_of`:
@generated constructor_of(::Type{T}) where T =
getfield(parentmodule(T), nameof(T))
Broadcast.broadcastable(obj::BroadcastableStruct) =
Broadcast.broadcasted(
constructor_of(typeof(obj)),
map(Broadcast.broadcastable, fieldvalues(obj))...)
Of course, you need to make sure that the construction of the struct is āfreeā (or define such one and use it via constructor_of). Itās just a convenient way to use broadcasting so I guess it does not improve any performance, though (I havenāt checked how it works with the inference etc).
I donāt think itās hard to define copyto!(::A{<:AbstractArray{T}}, bc::Broadcasted) for the case āeltypeā of bc is A{T}. But this is a bit more tricky to do at the level of BroadcastableStruct.