-
-
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
A[i, end]
assumes that i
indexes one dimension
#35681
Comments
I'm not really sure how this can be fixed. cc @mbauman |
Another error case, from @simeonschaub: julia> f(n) = CartesianIndex(1, n);
julia> rand(2,3)[f(end)]
ERROR: BoundsError: attempt to access 2×3 Array{Float64,2} at index [1, 6] One idea is to make all such things into runtime errors. Perhaps by lowering to function to_indices(A, ax, I::Tuple{FixDim, Vararg})
I[1].dim == ndims(A) + 1 - length(ax) || error("can't use end here")
I[1].val isa CartesianIndex && !( I[1].val isa CartesianIndex{1}) && error("can't use end here")
to_indices(A, ax, (I[1].val, tail(I)...))
end |
Yeah, I don’t have a good solution here so we explicitly warn about this in the CartesianIndex docs. |
Alright, there is indeed a giant yellow warning box about this! |
Actually I think there's some hope to fix this, at least for non-pathological uses of julia> A = zeros(2,2,1)
2×2×1 Array{Float64,3}:
[:, :, 1] =
0.0 0.0
0.0 0.0
julia> c = CartesianIndex(2,2)
CartesianIndex(2, 2)
julia> A[c, end]
ERROR: BoundsError: attempt to access 2×2×1 Array{Float64,3} at index [2, 2, 2]
Stacktrace:
[1] getindex at ./array.jl:788 [inlined]
[2] getindex(::Array{Float64,3}, ::CartesianIndex{2}, ::Int64) at ./multidimensional.jl:543
[3] top-level scope at REPL[21]:1 Sketch of alternative lowering for julia> count_index_dims(a, inds) = length(Base.to_indices(a, axes(a), inds))
count_index_dims (generic function with 1 method)
julia> getindex(A, c, lastindex(A, count_index_dims(A,(c,))+1))
0.0 For your g(A, i, j) = getindex(A, i, lastindex(A, count_index_dims(A,(i,))), j) The case from @simeonschaub is truly diabolical, I like it! But edge cases like that shouldn't prevent us from fixing the common situations. |
Yes, perhaps I have been letting the lack of the perfect get in the way of the good here; 👍 for re-opening this. I don't particularly like calling |
Right, that does seem kind of icky. I feel like we should have the lastindex_afterdims(A, i, j) Read "lastindex for the dimension which comes after dims For splatting, eg, lastindex_afterdims(A, i, j, ks...) Current lowering rules just implement the following extremely simple rule: lastindex_afterdims(A, inds...) = lastindex(length(inds) + 1) I could have a go at doing this lowering tweak and you can separately work out exactly what the best implementation is for |
How about lowering |
That sounds kind of promising but I don't quite understand your notation. How do we handle |
What I meant was something like this: # A[i, min(j,end)]
getindex(A, i, LazyIndex((_begin, _end) -> min(j, _end)))
# A[i, j, ks..., begin:2:end]
getindex(A, i, j, ks..., LazyIndex((_begin, _end) -> _begin:2:_end)) |
Hmm.... Actually, it might break some code that does something custom and bypasses |
Yes backward compat seems quite hard with that option. It's a really interesting idea though, I quite like the way that it allows I can't really see how we can have a lazy version and also backward compatibility, unless we lower to some new |
OK, that's too bad. I thought we'll have faster closures after this as everybody will start nudging Jeff about indexing slowed down.... |
So I guess we need something like this? indexdim(x) = indexdim(typeof(x))
indexdim(::Type) = 1 # best guess?
indexdim(::Type{T}) where {T <: CartesianIndex} = length(T)
indexdim(::Type{T}) where {T <: AbstractArray} = indexdim(eltype(T))
indexndims(args...) = sum(indexdim, args)
lastindex_afterdims(A, inds...) = lastindex(A, indexndims(inds...) + 1)
firstindex_afterdims(A, inds...) = firstindex(A, indexndims(inds...) + 1) |
This issue makes me wonder if this lowering
can be improved. If |
Can we avoid lifting indexdim(a::AbstractArray) =
mapreduce(indexdim, (d1,d2)->(d1==d2 ? d1 : error("xxx")), a) Which catches terrible oddities such as |
Ah, of course,
Maybe we can make something like |
Is there anything wrong in principle with the |
How about I'm also a bit worried about relying on the optimization in the happy path. I can see that Base.@pure donothing(_) = nothing
@btime foreach(donothing, $(zeros(1000000))) is optimized away but it looks like this happens at LLVM level, not at Julia level. |
Yeah, this is why I ended up with a warning box 😛 I still think a reasonable option is to lower to an error function that simply asserts we're not mixing |
Where do you assert this? My guess is that doing this in |
Currently: julia> Meta.@lower A[end, begin, 1:end]
:($(Expr(:thunk, CodeInfo(
@ none within `top-level scope'
1 ─ %1 = Base.lastindex(A, 1)
│ %2 = Base.axes(A, 2)
│ %3 = Base.first(%2)
│ %4 = Base.lastindex(A, 3)
│ %5 = 1:%4
│ %6 = Base.getindex(A, %1, %3, %5)
└── return %6
)))) This would just add a line before the final |
I see, yes, that's much simpler. |
I like that this is simpler, it's true that introducing a pile of complexity for this relative edge case would be somewhat disappointing. It still needs to handle icky situations like when |
While we wait for the scheme-heroics, EndpointRanges.jl is a simple workaround: julia> a = reshape(1:24, 2, 3, 2, 1, 2)
2×3×2×1×2 reshape(::UnitRange{Int64}, 2, 3, 2, 1, 2) with eltype Int64:
[:, :, 1, 1, 1] =
1 3 5
2 4 6
[:, :, 2, 1, 1] =
7 9 11
8 10 12
[:, :, 1, 1, 2] =
13 15 17
14 16 18
[:, :, 2, 1, 2] =
19 21 23
20 22 24
julia> a[CartesianIndex(2, 2), end, CartesianIndex(1, 1)]
ERROR: BoundsError: attempt to access 2×3×2×1×2 reshape(::UnitRange{Int64}, 2, 3, 2, 1, 2) with eltype Int64 at index [2, 2, 3, 1, 1]
Stacktrace:
[1] throw_boundserror(A::Base.ReshapedArray{Int64, 5, UnitRange{Int64}, Tuple{}}, I::NTuple{5, Int64})
@ Base ./abstractarray.jl:601
[2] checkbounds
@ ./abstractarray.jl:566 [inlined]
[3] _getindex
@ ./abstractarray.jl:1146 [inlined]
[4] getindex(::Base.ReshapedArray{Int64, 5, UnitRange{Int64}, Tuple{}}, ::CartesianIndex{2}, ::Int64, ::CartesianIndex{2})
@ Base ./abstractarray.jl:1120
[5] top-level scope
@ REPL[31]:1
julia> using EndpointRanges
julia> a[CartesianIndex(2, 2), iend, CartesianIndex(1, 1)]
10
julia> a[2, 2, 2, 1, 1]
10 |
Indexing by
end
is lowered here tolastindex(A, 2)
, which isn't correct when the other indices take up more than one dimension (or none):I assumed this worked, but @c42f actually checked!
The text was updated successfully, but these errors were encountered: