-
-
Notifications
You must be signed in to change notification settings - Fork 58
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
ArrayPartition violates the documentation of eltype #397
Comments
I don't think this is quite true, although I'm not sure what the real rule is: julia> ap = ArrayPartition([[1,2]], [3,4], [(5,6)])
([[1, 2]], [3, 4], [(5, 6)])
julia> eltype(ap)
Int64
julia> [typeof(x) for x in ap]
4-element Vector{DataType}:
Vector{Int64} (alias for Array{Int64, 1})
Int64
Int64
Tuple{Int64, Int64} The simplest fix would be to declare a union eltype, possibly via |
The reasoning behind this behavior is because there is a common use case with Unitful where a system of second order ODEs is split in two, but some of the values are positions while the others are velocities. This in fact is what started ArrayPartition and still tends to be it's most widely used case in the wild, just without Unitful. Because of this, ArrayPartition operations tend to be specialized to not require the global index. For example, broadcast will not use it, which is why there's not a performance hit for it's uses in SciML. The downside of course is that it cannot guarantee performance on the abstractvector form. That said, the linear index is never particularly fast because it has branching to consider and this removes SIMD optimizations. So you pretty much only want to use it as a fallback anyways. That makes ArrayPartition an odd AbstractVector: it has all of the interface defined, but most functions like iterators have a faster dispatch by avoiding treating it like a Vector. Thus the Abstractvector piece is more for convenience than performance. The Array interface overloads then confirm this by setting traits to say this does not have fast linear indexing, telling downstream codes to use alternative code paths. With all of that in mind, eltype is then somewhat not well-defined in this case, though what would make it interface compliant would be to ensure it is matching the iteration definition correctly. That may need a bit of a bug fix and a bit more testing which shouldn't be too hard. But, I would still stress that even if you get a concrete eltype from this, you still likely want to specialize functions as possible if you need performance, and most common array functions (any, all, etc.) already have these specializations defined |
Everything makes sense, but I don’t think performance is a significant concern here. Linear indexing may not be the fastest, but that’s acceptable. If Also the example provided from @mcabbott is really bad imo which is a gun waiting to blew up someones leg: julia> ap = ArrayPartition([[1,2]], [3,4], [(5,6)])
([[1, 2]], [3, 4], [(5, 6)])
julia> eltype(ap)
Int64
julia> collect(ap)
ERROR: MethodError: Cannot `convert` an object of type Vector{Int64} to an object of type Int64
Closest candidates are:
convert(::Type{T}, ::T) where T<:Number
@ Base number.jl:6
convert(::Type{T}, ::T) where T
@ Base Base.jl:84
convert(::Type{T}, ::Number) where T<:Number
@ Base number.jl:7
...
Stacktrace:
[1] setindex!(A::Vector{Int64}, x::Vector{Int64}, i1::Int64)
@ Base ./array.jl:1021
[2] setindex! Interestingly enough, this works fine (why the previous one fails then?) julia> ap = ArrayPartition([1], ["string"])
([1], ["string"])
julia> collect(ap)
2-element Vector{Any}:
1
"string"
julia> eltype(ap)
Any |
Okay I see where the confusion is coming from. julia> ap = ArrayPartition((ArrayPartition([1,2]), ArrayPartition([3,4]),))
(ArrayPartition{Int64, Tuple{Vector{Int64}}}(([1, 2],)), ArrayPartition{Int64, Tuple{Vector{Int64}}}(([3, 4],)))
julia> eltype(ap)
Int64
julia> ap = ArrayPartition((ArrayPartition([1,2]), ArrayPartition([3.0,4]),))
(ArrayPartition{Int64, Tuple{Vector{Int64}}}(([1, 2],)), ArrayPartition{Float64, Tuple{Vector{Float64}}}(([3.0, 4.0],)))
julia> eltype(ap)
Float64 That is using I think the eltype recursion is mixing up with tuples in there. It's this line of code: https://github.com/SciML/RecursiveArrayTools.jl/blob/master/src/array_partition.jl#L35 I think the issue here is we need to more cleanly define what the bottom is: i.e. is ap[4] == 5 or ap[4] == (5,6)? julia> ap = ArrayPartition([[1,2]], [3,4], [(5,6)])
([[1, 2]], [3, 4], [(5, 6)])
julia> ap[4]
(5, 6) Currently it recurses the eltype definition there but does not recurse the vector definition, so that's the disconnect. I.e. should There's a few solutions I see for this:
It sounds like 2 would be the least breaking here, and what people would expect? |
I believe that support for better recursive arrays/nesting is a separate issue and deserves its own discussion. This particular issue is about the discrepancy between the This violation of the assumption that iterating the collection returns elements of If this behavior is intentional, it should at least be documented. However, it might be a bug, and |
@oscardssmith do you have thoughts on this? |
I think the simplest solution would be to do promotion on access or setindex. not fully sure what the implications would be though |
I think that's reasonable. Can you give it a shot? |
Describe the bug 🐞
In some situations ArrayPartition violates the documentation of
eltype
:But this is not always the case (see MWE below).
As I understand the
ArrayPartition
declareseltype
as a type that all udnerlying elements can be promoted to.This behaviour might be by design, but it has a real practical implication.
Perhaps can be fixed on
ForwardDiff
side by extra promoting, but this can have other weird edge cases.At the very least this behaviour can be documented as desired?
Minimal Reproducible Example 👇
Environment (please complete the following information):
using Pkg; Pkg.status()
versioninfo()
The text was updated successfully, but these errors were encountered: