-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Make it easier to extend broadcast! #24992
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,7 +93,8 @@ end | |
# (3) broadcast[!] entry points | ||
broadcast(f::Tf, A::SparseVector) where {Tf} = _noshapecheck_map(f, A) | ||
broadcast(f::Tf, A::SparseMatrixCSC) where {Tf} = _noshapecheck_map(f, A) | ||
function broadcast!(f::Tf, C::SparseVecOrMat) where Tf | ||
|
||
@inline function broadcast!(f::Tf, C::SparseVecOrMat, ::Nothing) where Tf | ||
isempty(C) && return _finishempty!(C) | ||
fofnoargs = f() | ||
if _iszero(fofnoargs) # f() is zero, so empty C | ||
|
@@ -106,14 +107,7 @@ function broadcast!(f::Tf, C::SparseVecOrMat) where Tf | |
end | ||
return C | ||
end | ||
function broadcast!(f::Tf, C::SparseVecOrMat, A::SparseVecOrMat, Bs::Vararg{SparseVecOrMat,N}) where {Tf,N} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turned this into a |
||
_aresameshape(C, A, Bs...) && return _noshapecheck_map!(f, C, A, Bs...) | ||
Base.Broadcast.check_broadcast_indices(axes(C), A, Bs...) | ||
fofzeros = f(_zeros_eltypes(A, Bs...)...) | ||
fpreszeros = _iszero(fofzeros) | ||
return fpreszeros ? _broadcast_zeropres!(f, C, A, Bs...) : | ||
_broadcast_notzeropres!(f, fofzeros, C, A, Bs...) | ||
end | ||
|
||
# the following three similar defs are necessary for type stability in the mixed vector/matrix case | ||
broadcast(f::Tf, A::SparseVector, Bs::Vararg{SparseVector,N}) where {Tf,N} = | ||
_aresameshape(A, Bs...) ? _noshapecheck_map(f, A, Bs...) : _diffshape_broadcast(f, A, Bs...) | ||
|
@@ -1006,28 +1000,30 @@ Broadcast.BroadcastStyle(::SparseMatStyle, ::Broadcast.DefaultArrayStyle{N}) whe | |
broadcast(f, ::PromoteToSparse, ::Nothing, ::Nothing, As::Vararg{Any,N}) where {N} = | ||
broadcast(f, map(_sparsifystructured, As)...) | ||
|
||
# ambiguity resolution | ||
broadcast!(::typeof(identity), dest::SparseVecOrMat, x::Number) = | ||
fill!(dest, x) | ||
broadcast!(f, dest::SparseVecOrMat, x::Number...) = | ||
spbroadcast_args!(f, dest, SPVM, x...) | ||
|
||
# For broadcast! with ::Any inputs, we need a layer of indirection to determine whether | ||
# the inputs can be promoted to SparseVecOrMat. If it's just SparseVecOrMat and scalars, | ||
# we can handle it here, otherwise see below for the promotion machinery. | ||
broadcast!(f, dest::SparseVecOrMat, mixedsrcargs::Vararg{Any,N}) where N = | ||
spbroadcast_args!(f, dest, Broadcast.combine_styles(mixedsrcargs...), mixedsrcargs...) | ||
function spbroadcast_args!(f, dest, ::Type{SPVM}, mixedsrcargs::Vararg{Any,N}) where N | ||
function broadcast!(f::Tf, dest::SparseVecOrMat, ::SPVM, A::SparseVecOrMat, Bs::Vararg{SparseVecOrMat,N}) where {Tf,N} | ||
if f isa typeof(identity) && N == 0 && Base.axes(dest) == Base.axes(A) | ||
return copyto!(dest, A) | ||
end | ||
_aresameshape(dest, A, Bs...) && return _noshapecheck_map!(f, dest, A, Bs...) | ||
Base.Broadcast.check_broadcast_indices(axes(dest), A, Bs...) | ||
fofzeros = f(_zeros_eltypes(A, Bs...)...) | ||
fpreszeros = _iszero(fofzeros) | ||
fpreszeros ? _broadcast_zeropres!(f, dest, A, Bs...) : | ||
_broadcast_notzeropres!(f, fofzeros, dest, A, Bs...) | ||
return dest | ||
end | ||
function broadcast!(f::Tf, dest::SparseVecOrMat, ::SPVM, mixedsrcargs::Vararg{Any,N}) where {Tf,N} | ||
# mixedsrcargs contains nothing but SparseVecOrMat and scalars | ||
parevalf, passedsrcargstup = capturescalars(f, mixedsrcargs) | ||
return broadcast!(parevalf, dest, passedsrcargstup...) | ||
broadcast!(parevalf, dest, passedsrcargstup...) | ||
return dest | ||
end | ||
function spbroadcast_args!(f, dest, ::PromoteToSparse, mixedsrcargs::Vararg{Any,N}) where N | ||
function broadcast!(f::Tf, dest::SparseVecOrMat, ::PromoteToSparse, mixedsrcargs::Vararg{Any,N}) where {Tf,N} | ||
broadcast!(f, dest, map(_sparsifystructured, mixedsrcargs)...) | ||
end | ||
function spbroadcast_args!(f, dest, ::Any, mixedsrcargs::Vararg{Any,N}) where N | ||
# Fallback. From a performance perspective would it be best to densify? | ||
Broadcast._broadcast!(f, dest, mixedsrcargs...) | ||
return dest | ||
end | ||
|
||
_sparsifystructured(M::AbstractMatrix) = SparseMatrixCSC(M) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -404,6 +404,8 @@ perhaps range-types `Ind` of your own design. For more information, see [Arrays | |
| `broadcast(f, As...)` | Complete bypass of broadcasting machinery | | ||
| `broadcast(f, ::DestStyle, ::Nothing, ::Nothing, As...)` | Bypass after container type is computed | | ||
| `broadcast(f, ::DestStyle, ::Type{ElType}, inds::Tuple, As...)` | Bypass after container type, eltype, and indices are computed | | ||
| `broadcast!(f, dest::DestType, ::Nothing, As...)` | Bypass in-place broadcast, specialization on destination type | | ||
| `broadcast!(f, dest, ::BroadcastStyle, As...)` | Bypass in-place broadcast, specialization on `BroadcastStyle` | | ||
|
||
[Broadcasting](@ref) is triggered by an explicit call to `broadcast` or `broadcast!`, or implicitly by | ||
"dot" operations like `A .+ b`. Any `AbstractArray` type supports broadcasting, | ||
|
@@ -591,3 +593,37 @@ yields another `SparseVecStyle`, that its combination with a 2-dimensional array | |
yields a `SparseMatStyle`, and anything of higher dimensionality falls back to the dense arbitrary-dimensional framework. | ||
These rules allow broadcasting to keep the sparse representation for operations that result | ||
in one or two dimensional outputs, but produce an `Array` for any other dimensionality. | ||
|
||
### [Extending `broadcast!`](@id extending-in-place-broadcast) | ||
|
||
Extending `broadcast!` (in-place broadcast) should be done with care, as it is easy to introduce | ||
ambiguities between packages. To avoid these ambiguities, we adhere to the following conventions. | ||
|
||
First, if you want to specialize on the destination type, say `DestType`, then you should | ||
define a method with the following signature: | ||
|
||
```julia | ||
broadcast!(f, dest::DestType, ::Nothing, As...) | ||
``` | ||
|
||
Note that no bounds should be placed on the types of `f` and `As...`. | ||
|
||
Second, if specialized `broadcast!` behavior is desired depending on the input types, | ||
you should write [binary broadcasting rules](@ref writing-binary-broadcasting-rules) to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://docs.julialang.org/en/latest/manual/interfaces/#writing-binary-broadcasting-rules-1 (link seems to be working). |
||
determine a custom `BroadcastStyle` given the input types, say `MyBroadcastStyle`, and you should define a method with the following | ||
signature: | ||
|
||
```julia | ||
broadcast!(f, dest, ::MyBroadcastStyle, As...) | ||
``` | ||
|
||
Note the lack of bounds on `f`, `dest`, and `As...`. | ||
|
||
Third, simultaneously specializing on both the type of `dest` and the `BroadcastStyle` is fine. In this case, | ||
it is also allowed to specialize on the types of the source arguments (`As...`). For example, these method signatures are OK: | ||
|
||
```julia | ||
broadcast!(f, dest::DestType, ::MyBroadcastStyle, As...) | ||
broadcast!(f, dest::DestType, ::MyBroadcastStyle, As::AbstractArray...) | ||
broadcast!(f, dest::DestType, ::Broadcast.Scalar, As::Number...) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@StefanKarpinski, thanks again for seeing the
Void
->Nothing
change through! So much more intuitive :).