Make no_offset_view inferrable#189
Conversation
This changes the implementation so that the axis *value*s do not affect the type of the output. Technically this is a breaking change, but only at the level of the returned types, not at the level of returned values.
Codecov Report
@@ Coverage Diff @@
## master #189 +/- ##
==========================================
- Coverage 98.92% 98.26% -0.67%
==========================================
Files 5 5
Lines 279 288 +9
==========================================
+ Hits 276 283 +7
- Misses 3 5 +2
Continue to review full report at Codecov.
|
|
I've wondered before if one objective of |
|
I'm sure it is, but anyone who complains can specialize it for their array type. Conversely, we can never work around the instability. |
|
Before merging, I plan to add one fallback: for unknown array types with |
There was a problem hiding this comment.
A good change, but I hope we could pend this PR for a few weeks to see if there are other breaking changes; as far as I know, Augmentor.jl is vulnerable to this change due to its over-verbose dispatches and tests.
Said that, if this PR does fix some known performance issue, please do merge and release it.
On 1.6, it seems to fail tests even on the master branch. Any further thoughts about this? There's no real urgency, but I wondered if the OneTo change here might circumvent Augmentor needing a fix. |
|
Perhaps one choice could be OffsetArrays.no_offset_view(a::AbstractUnitRange) = UnitRange(a) |
|
Better: OffsetArrays.no_offset_view(a::AbstractUnitRange) = first(a):last(a)That's more explicit that it only looks at the values, not the indices. |
|
Wouldn't this be open to someone defining a custom integer type such that |
|
While we are at it, we might unwrap views of OffsetArrays as well. For example, julia> a = reshape(1:25, 5, 5);
julia> sa = @view a[2:3, 2:3];
julia> osa = OffsetArray(sa, OffsetArrays.Origin(2));
julia> sosa = @view osa[:,2];
julia> ososa = OffsetArrays.no_offset_view(sosa)
2-element OffsetArray(view(OffsetArray(view(reshape(::UnitRange{Int64}, 5, 5), 2:3, 2:3), 2:3, 2:3), :, 2), 1:2) with eltype Int64 with indices 1:2:
7
8
julia> av = @view sa[:, 1];
julia> av == ososa
trueIn this case Something like this for example might be added: function no_offset_view(SOA::SubArray{<:Any,<:Any,<:OffsetArray,<:Tuple{Vararg{Union{Real,AbstractUnitRange}}}})
indsSOA = Base.parentindices(SOA)
OA = parent(SOA)
inds = _removeoffset(indsSOA, OA.offsets)
v = view(parent(OA), inds...)
no_offset_view(v)
end
_removeoffset(i::Tuple{AbstractVector, Vararg{Any}}, o::Tuple{Int,Vararg{Int}}) = (first(i) .- first(o), _removeoffset(tail(i), tail(o))...)
_removeoffset(i::Tuple{Real, Vararg{Any}}, o::Tuple{Int, Vararg{Int}}) = (first(i) - first(o), _removeoffset(tail(i), tail(o))...)
_removeoffset(i::Tuple, o::Tuple{}) = iin which case julia> OffsetArrays.no_offset_view(sosa)
2-element view(reshape(::UnitRange{Int64}, 5, 5), 2:3, 2) with eltype Int64:
7
8 |
I guess I was worried about whether |
Closes #198 Co-authored-by: Jishnu Bhattacharya <jishnub@users.noreply.github.com>
055d2f4 to
87f15e9
Compare
Good idea. I was a little bit worried about how far your suggestion delved into the internal representations of all types. What do you think about the more minimal version I just pushed? It doesn't doubly-unwrap like yours, but it does prevent the types from growing even more complex. As a bonus, it handles other types of shifted views, too (that's not piracy because we own julia> using OffsetArrays
julia> A = reshape(1:12, 3, 4)
3×4 reshape(::UnitRange{Int64}, 3, 4) with eltype Int64:
1 4 7 10
2 5 8 11
3 6 9 12
julia> V = view(A, OffsetArrays.IdentityUnitRange(2:3), OffsetArrays.IdentityUnitRange(2:3))
2×2 view(reshape(::UnitRange{Int64}, 3, 4), :, :) with eltype Int64 with indices 2:3×2:3:
5 8
6 9
julia> typeof(V)
SubArray{Int64, 2, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}, Tuple{Base.IdentityUnitRange{UnitRange{Int64}}, Base.IdentityUnitRange{UnitRange{Int64}}}, false}
julia> OffsetArrays.no_offset_view(V)
2×2 view(reshape(::UnitRange{Int64}, 3, 4), 2:3, 2:3) with eltype Int64:
5 8
6 9
julia> typeof(OffsetArrays.no_offset_view(V))
SubArray{Int64, 2, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}, Tuple{UnitRange{Int64}, UnitRange{Int64}}, false}
julia> O = OffsetArray(A, -1:1, 0:3)
3×4 OffsetArray(reshape(::UnitRange{Int64}, 3, 4), -1:1, 0:3) with eltype Int64 with indices -1:1×0:3:
1 4 7 10
2 5 8 11
3 6 9 12
julia> V = view(O, 0:1, 1:2)
2×2 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 4), -1:1, 0:3), 0:1, 1:2) with eltype Int64:
5 8
6 9
julia> typeof(V)
SubArray{Int64, 2, OffsetMatrix{Int64, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}}, Tuple{UnitRange{Int64}, UnitRange{Int64}}, false}
julia> OffsetArrays.no_offset_view(V)
2×2 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 4), -1:1, 0:3), 0:1, 1:2) with eltype Int64:
5 8
6 9
julia> typeof(OffsetArrays.no_offset_view(V))
SubArray{Int64, 2, OffsetMatrix{Int64, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}}, Tuple{UnitRange{Int64}, UnitRange{Int64}}, false}
julia> r1, r2 = OffsetArrays.IdOffsetRange(1:3, -2), OffsetArrays.IdentityUnitRange(2:3)
(OffsetArrays.IdOffsetRange(-1:1), Base.IdentityUnitRange(2:3))
julia> V = view(O, r1, r2)
3×2 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 4), -1:1, 0:3), OffsetArrays.IdOffsetRange(-1:1), :) with eltype Int64 with indices OffsetArrays.IdOffsetRange(-1:1)×2:3:
7 10
8 11
9 12
julia> typeof(V)
SubArray{Int64, 2, OffsetMatrix{Int64, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}}, Tuple{OffsetArrays.IdOffsetRange{Int64, UnitRange{Int64}}, Base.IdentityUnitRange{UnitRange{Int64}}}, false}
julia> OffsetArrays.no_offset_view(V)
3×2 view(OffsetArray(reshape(::UnitRange{Int64}, 3, 4), -1:1, 0:3), -1:1, 2:3) with eltype Int64:
7 10
8 11
9 12
julia> typeof(OffsetArrays.no_offset_view(V))
SubArray{Int64, 2, OffsetMatrix{Int64, Base.ReshapedArray{Int64, 2, UnitRange{Int64}, Tuple{}}}, Tuple{UnitRange{Int64}, UnitRange{Int64}}, false}To me this feels like a good place to be---I don't think we gain a lot by unwrapping the parent OffsetArray, and the risk is less generic handling of the axes. |
|
So weird that codecov marks all of |
This seems like a good (and a pretty cool) solution. If there are specific performance enhancements possible we might look into them later. |
|
We might require some special handling of julia> a = reshape(1:25, 5, 5);
julia> oa = OffsetArray(a, OffsetArrays.Origin(2));
julia> soa = @view oa[:, :];
julia> IndexStyle(soa)
IndexLinear()
julia> OffsetArrays.no_offset_view(soa) |> IndexStyle
IndexCartesian()This might fix it: no_offset_view(a::Base.Slice) = Base.Slice(UnitRange(a)) |
This changes the implementation so that the axis *value*s do not affect the type of the output. Technically this is a breaking change, but only at the level of the returned types, not at the level of returned values.
Closes #198 Co-authored-by: Jishnu Bhattacharya <jishnub@users.noreply.github.com>
Not introduced by this PR, but easily fixed
…setArrays.jl into teh/infer_no_offset_view
In 1.0 Slice doesn't cleanly mean "the whole axis", it's conflated with IdentityUnitRange.
|
OK to merge? |
|
Looks good to be merged |
|
Thanks for all your help, @jishnub! |
|
Should this be 1.6 or 1.5.4? It's not exactly a bugfix, but neither is it a new feature. |
|
Perhaps 1.6, as certain return types are altered |
This changes the implementation so that the axis values
do not affect the type of the output.
Technically this is a breaking change, but only at the level
of the returned types, not at the level of returned values.