-
-
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
Rework the broadcast API and document it #23939
Conversation
Looks like this would also fix #22180 by the way? |
Regarding the Documenter error:
Maybe you need to export |
3007181
to
4ac2424
Compare
4ac2424
to
85ec999
Compare
OK, I think this is done. @Sacha0, aside from the core API changes this pretty significantly reworks broadcasting esp for sparse arrays, so I'd love it if you'd take a look. The biggest change is that it no longer needs an ambiguity-resolving layer or "second pass" at computation of output type--- |
I was going to strike out the "docs don't build" comment above, since that works now too. (I'd just forgotten to add those functions to the stdlib section of the manual.) |
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.
Nice design, as usual @timholy !
E.g. I think I can see this working fine with StaticArrays given I implement a static unit range (I'd have to try).
base/broadcast.jl
Outdated
See [`Broadcast.Result`](@ref) and [`Broadcast.rule`](@ref). | ||
""" | ||
Base.similar(f, r::Result{BottomArray{N}}, As...) where N = similar(Array{eltype(r)}, indices(r)) | ||
# In cases of conflict we fall back on Array |
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.
Nice
base/broadcast.jl
Outdated
|
||
# When ElType is not concrete, use narrowing. Use the first element of each input to determine | ||
# the starting output eltype; the _broadcast! method will widen `dest` as needed to | ||
# accomodate later values. |
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.
Silly question: Is it possible that this ends up in the situation where the element type is too narrow? E.g. For eltypes like Union{T,Null}
might the final array end up with eltype T
or Null
? (This might prevent push!
on a vector when you have broadcasted it - no problem in the functional case, though).
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.
If I understand your question correctly, you can definitely get that, but it's not unique to this PR. (I think this PR doesn't alter the element-type behavior whatsoever.) julia 0.6:
julia> a = Real[1, 2]
2-element Array{Real,1}:
1
2
julia> b = map(identity, a)
2-element Array{Int64,1}:
1
2
julia> push!(b, 3.2)
ERROR: InexactError()
Stacktrace:
[1] convert(::Type{Int64}, ::Float64) at ./float.jl:679
[2] push!(::Array{Int64,1}, ::Float64) at ./array.jl:617
julia> b = a .+ 1
2-element Array{Int64,1}:
2
3
The reasoning here is that inference is unfortunately likely to give Any
occasionally for something that doesn't have to be that wide. So it's good to narrow the outputs where possible to keep instability from propagating.
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.
Ok thanks for clarifying.
I'm wondering if we'll need to be "smarter" about union types in the future, now that they are faster.
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.
Yes, that's a common behavior already. That could be problematic if you expected to set a value to null
in the resulting array. I'm not sure what could be done about this, as we can't know what are the possible types without relying on inference, which we want to avoid. And for things like map(isnull, x)
we definitely don't want an Array{Union{T, Null}}
result.
See also #23940.
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.
The good news is that there's broadcast!
, so in the end you don't give anything up by forced narrowing.
doc/src/manual/interfaces.md
Outdated
Broadcast.rule(::Type{<:AbstractArray{T,N}}) where {T,N} = BottomArray{N} | ||
Broadcast.rule(::Type{A}, ::Type{BottomArray{N}}) where {A<:AbstractArray,N} = A | ||
|
||
If you want to leverage this `AbstractArray` types, it makes sense to have the unary `rule` return an |
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.
Just noticed I left a sentence unfinished here. Will fix before merging, but I'll wait to see if there are any more requested changes, just to avoid jamming CI or erasing the record of the successful tests.
@nanosoldier |
Yes, completely orthogonal. |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan |
OK, looks likely that some of those |
Maybe to that PR, but I started another (currently secret) branch that is better than it and may depend quite heavily on this 🙂 (I don't mean to be cryptic, the branch just triggered some work on other issues first). |
base/broadcast.jl
Outdated
longest(V1::Val, V2::Val) = longest(ntuple(identity, V1), ntuple(identity, V2)) | ||
longest(t1::Tuple, t2::Tuple) = (true, longest(Base.tail(t1), Base.tail(t2))...) | ||
longest(::Tuple{}, t2::Tuple) = (true, longest((), Base.tail(t2))...) | ||
longest(t1::Tuple, ::Tuple{}) = (true, longest(Base.tail(t1), ())...) |
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.
use nothing
as a sentinel value
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.
Can you explain? I need to check for Tuple{}
anyway when one tuple is longer than the other. I could add nothing
as a new sentinel, but I'm not sure I understand why.
base/broadcast.jl
Outdated
# we defer to any case where the result of `rule` is known. | ||
result_join(::Type{S}, ::Type{T}, ::Type{Unknown}, ::Type{Unknown}) where {S,T} = Unknown | ||
result_join(::Type{S}, ::Type{T}, ::Type{Unknown}, ::Type{U}) where {S,T,U} = U | ||
result_join(::Type{S}, ::Type{T}, ::Type{U}, ::Type{Unknown}) where {S,T,U} = U |
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.
result_join(::Type, ::Type, ::Type{U}, ::Type{Unknown}) where {U} = U
and similarly for the ones above?
base/sparse/higherorderfns.jl
Outdated
# dense vectors/matrices, and PromoteToSparse collections continue in the promote-to-sparse funnel | ||
FunnelToSparseBC = Union{Type{Any},Type{Vector},Type{Matrix},Type{PromoteToSparse},Type{AbstractSparseArray}} | ||
promote_spcontainertype(::FunnelToSparseBC, ::FunnelToSparseBC) = PromoteToSparse | ||
Broadcast.rule(::Type{SPVM}, ::Type{Tuple}) = PromoteToSparse |
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.
Does this rule result in combinations involving tuples being sent to sparse broadcast?
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.
Yes, somehow I misinterpreted the current rules as diverting them to your "second stage" 1-2 dimensional analysis. Will fix (and add a test).
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.
In my view, sparse vectors should have precedence over tuples for broadcasting purposes. The problem IIUC is that the sparse broadcast implementations work only (for understandable practicality reasons) with combinations of sparse structures that share the same storage behavior. A possible way around this would be to create a wrapper for tuples that behaves as an SparseVector
for sparse broadcasting purposes so this works.
Of course that can be done in another PR and with this new API in place, should much easier than before.
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.
... create a wrapper ... that behaves as an SparseVector for sparse broadcasting purposes so this works.
Yes, writing a common iteration interface for all types that participate in sparse broadcast is the direction I have in mind broadly. Such an interface would enable removal of the sparsifystructured
layer and associated allocation. Very much a post-1.0 project though :).
The problem IIUC is that the sparse broadcast implementations work only (for understandable practicality reasons) with combinations of sparse structures that share the same storage behavior.
Rather, including tuple
s in sparse broadcast is as simple as adding an appropriate sparsifystructured
method and changing the couple of rules that Tim inadvertently changed in this pull request. tuple
s do not presently participate because the utility is not clear. Though given Vector
s participate, perhaps tuple
s should as well :).
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.
tuples
do not presently participate because the utility is not clear. Though givenVectors
participate, perhaps tuples should as well
Given they "behave" as vectors in combination with arrays, I think they should behave as Vectors
also in combination with sparse arrays. I imagine it would be unexpected for some people to see them not working with sparse structures.
There have been requests asking even for general iterables with shapes to participate broadcast
and I don't see why general iterables with shape, including tuples, should be excluded (given a clear broadcasting precedence is well established).
base/sparse/higherorderfns.jl
Outdated
Broadcast.rule(::Type{SPVM}, ::Type{Broadcast.BottomVector}) = PromoteToSparse | ||
Broadcast.rule(::Type{SPVM}, ::Type{Broadcast.BottomMatrix}) = PromoteToSparse | ||
Broadcast.rule(::Type{PromoteToSparse}, ::Type{Broadcast.Scalar}) = PromoteToSparse | ||
Broadcast.rule(::Type{PromoteToSparse}, ::Type{Tuple}) = PromoteToSparse |
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.
Likewise here, does this rule result in combinations involving tuples being sent to sparse broadcast?
doc/src/manual/interfaces.md
Outdated
Base.similar(f, r::Broadcast.Result{ContainerType}, As...) | ||
``` | ||
|
||
`f` is the operation being performed, `ContainerType` signals the resulting container type |
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.
Should there be an "and" following the comma, or should the period terminating this sentence instead be a comma, or am I misreading these sentences? :)
85ec999
to
47a906c
Compare
@nanosoldier |
3ce4cb0
to
f875cf9
Compare
The problem turned out to be an unused type parameter in lines like Lines 176 to 177 in c94e502
I'm still a little reluctant to do the more general rename: what if it turns out that for purposes such as concatenation, etc, we want slightly different rules? I'd feel happier about generalizing the name if there were a proof-of-principle demonstrating that we can leverage this machinery for novel purposes without modification---but until then, I think it's better to use a too-narrow name than a too-broad one. However, I am willing to make the change if there is a strong consensus. In any event, I think this is ready. |
Build error on Travis is unrelated (libopenblas couldn't be loaded) |
The Appveyor failure also looks unrelated. I may merge this early next week in case it paves the way for #23692. |
doc/src/manual/interfaces.md
Outdated
It is worth noting that you do not need to (and should not) define both argument orders | ||
of this call; defining one is sufficient no matter what order the user supplies the arguments in. | ||
|
||
For `AbstractArray` types, defining a BroadcastStyle supersedes the fallback choice, |
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.
"a BroadcastStyle" -> "a BroadcastStyle
"?
``` | ||
|
||
Whenever you subtype `AbstractArrayStyle`, you also need to define rules for combining | ||
dimensionalities, by creating a constructor for your style that takes a `Val(N)` argument. |
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.
Nicely done! :)
test/broadcast.jl
Outdated
Base.setindex!(A::ArrayData, v::Any, i::Integer...) = setindex!(A.data, v, i...) | ||
Base.size(A::ArrayData) = size(A.data) | ||
Base.broadcast_similar(f, ::Broadcast.ArrayStyle{A}, ::Type{T}, inds::Tuple, As...) where {A,T} = | ||
A(Array{T}(length.(inds))) |
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.
Array{T}(length.(inds))
-> Array{T}(uninitialized, length.(inds))
in anticipation of conflict? :)
f875cf9
to
776139b
Compare
These broadcast styles were introduced in response to #23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. #23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle.
These broadcast styles were introduced in response to #23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. #23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle.
These broadcast styles were introduced in response to #23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. #23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle.
* Remove kludgy VectorStyle and MatrixStyle These broadcast styles were introduced in response to #23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. #23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle. * Add a graceful deprecation
* Remove kludgy VectorStyle and MatrixStyle These broadcast styles were introduced in response to JuliaLang/julia#23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. JuliaLang/julia#23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle. * Add a graceful deprecation
* Remove kludgy VectorStyle and MatrixStyle These broadcast styles were introduced in response to JuliaLang/julia#23939 (comment) as a way to limit the "greediness" of Sparse's broadcasting implementation -- sparse only wanted to allow known combinations of array types (including Array but not any AbstractArray). The idea was to allow us to gradually improve the sparse broadcast implementation over 1.x in a non-breaking manner. Unfortunately, these special styles for Array make defining new styles in the heirarchy a bit of a pain (ref. JuliaLang/julia#23939 (comment)), and it was making my life harder in getting the 1.0 breaking changes in. This commit removes these special broadcast styles in favor of just having Sparse identify the cases itself and re-dispatch back into the default implementation in the cases it doesn't know how to handle. * Add a graceful deprecation
This is an attempt towards developing an API for broadcasting that is relatively easy to extend. Overall I'm pretty happy with it. For most custom array types, only two methods (
Broadcast.rule
and a broadcast-specificsimilar
) need to be defined. Probably the best place to start reviewing this is with the documentation, or to compare the new way against the old one.(I don't understand why the docs don't build locally, any ideas?)There are two jobs left to do:
sparse/higherorderfunctions.jl
with the new APII think it makes sense to first see whether people like this API before tackling either one. The existing sparse-broadcasting logic looks pretty complicated. I'd hope this may simplify it somewhat, but the proof will be in the pudding.
Fixes #20740, #22180 (edited).