Skip to content

Commit 64d37f5

Browse files
authored
Update the OffsetArrays test helper (#56892)
This updates the test helper to match v1.15.0 of `OffsetArrays.jl`. The current version was copied over from v1.11.2, which was released on May 20, 2022, so this bump fetches the intermediate updates to the package. The main changes are updates to `unsafe_wrap` and `reshape`. In the latter, several redundant methods are now removed, whereas, in the former, methods are added to wrap a `Ptr` in an `OffsetArray` (JuliaArrays/OffsetArrays.jl#275 (comment)). A third major change is that an `OffsetArray` now shares its parent's `eltype` and `ndims` in the struct definition, whereas previously this was ensured through the constructor. Other miscellaneous changes are included, such as performance-related ones.
1 parent 8fac39b commit 64d37f5

File tree

1 file changed

+72
-43
lines changed

1 file changed

+72
-43
lines changed

test/testhelpers/OffsetArrays.jl

+72-43
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# This test file is designed to exercise support for generic indexing,
66
# even though offset arrays aren't implemented in Base.
77

8-
# OffsetArrays v1.11.2
8+
# OffsetArrays v1.15.0
99
# No compat patch and docstrings
1010
module OffsetArrays
1111

@@ -73,10 +73,15 @@ end
7373
IdOffsetRange(r::IdOffsetRange) = r
7474

7575
# Constructor to make `show` round-trippable
76+
# try to preserve typeof(values) if the indices are known to be 1-based
77+
_subtractindexoffset(values, indices::Union{Base.OneTo, IdentityUnitRange{<:Base.OneTo}}, offset) = values
78+
_subtractindexoffset(values, indices, offset) = _subtractoffset(values, offset)
7679
function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer})
7780
length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length"))
81+
values_nooffset = no_offset_view(values)
7882
offset = first(indices) - 1
79-
return IdOffsetRange(values .- offset, offset)
83+
values_minus_offset = _subtractindexoffset(values_nooffset, indices, offset)
84+
return IdOffsetRange(values_minus_offset, offset)
8085
end
8186

8287
# Conversions to an AbstractUnitRange{Int} (and to an OrdinalRange{Int,Int} on Julia v"1.6") are necessary
@@ -110,12 +115,19 @@ offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange =
110115
@inline Base.unsafe_indices(r::IdOffsetRange) = (Base.axes1(r),)
111116
@inline Base.length(r::IdOffsetRange) = length(r.parent)
112117
@inline Base.isempty(r::IdOffsetRange) = isempty(r.parent)
118+
#= We specialize on reduced_indices to work around cases where the parent axis type doesn't
119+
support reduced_index, but the axes do support reduced_indices
120+
The difference is that reduced_index expects the axis type to remain unchanged,
121+
which may not always be possible, eg. for statically sized axes
122+
See https://github.com/JuliaArrays/OffsetArrays.jl/issues/204
123+
=#
124+
function Base.reduced_indices(inds::Tuple{IdOffsetRange, Vararg{IdOffsetRange}}, d::Int)
125+
parents_reduced = Base.reduced_indices(map(parent, inds), d)
126+
ntuple(i -> IdOffsetRange(parents_reduced[i], inds[i].offset), Val(length(inds)))
127+
end
113128
Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i))
114129
# Workaround for #92 on Julia < 1.4
115130
Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i))
116-
for f in [:firstindex, :lastindex]
117-
@eval @inline Base.$f(r::IdOffsetRange) = $f(r.parent) + r.offset
118-
end
119131
for f in [:first, :last]
120132
# coerce the type to deal with values that get promoted on addition (eg. Bool)
121133
@eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset)
@@ -142,7 +154,7 @@ end
142154
@inline function Base.getindex(r::IdOffsetRange, i::Integer)
143155
i isa Bool && throw(ArgumentError("invalid index: $i of type Bool"))
144156
@boundscheck checkbounds(r, i)
145-
@inbounds eltype(r)(r.parent[oftype(r.offset, i) - r.offset] + r.offset)
157+
@inbounds eltype(r)(r.parent[i - r.offset] + r.offset)
146158
end
147159

148160
# Logical indexing following https://github.com/JuliaLang/julia/pull/31829
@@ -186,18 +198,20 @@ for R in [:IIUR, :IdOffsetRange]
186198
end
187199

188200
# offset-preserve broadcasting
189-
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange{T}, x::Integer) where T =
190-
IdOffsetRange{T}(r.parent .- x, r.offset)
191-
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdOffsetRange{T}, x::Integer) where T =
192-
IdOffsetRange{T}(r.parent .+ x, r.offset)
193-
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange{T}) where T =
194-
IdOffsetRange{T}(x .+ r.parent, r.offset)
201+
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange, x::Integer) =
202+
IdOffsetRange(r.parent .- x, r.offset)
203+
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdOffsetRange, x::Integer) =
204+
IdOffsetRange(r.parent .+ x, r.offset)
205+
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange) =
206+
IdOffsetRange(x .+ r.parent, r.offset)
207+
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(big), r::IdOffsetRange) =
208+
IdOffsetRange(big.(r.parent), r.offset)
195209

196210
Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")")
197211

198212
# Optimizations
199213
@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset)
200-
Base._firstslice(r::IdOffsetRange) = IdOffsetRange(Base._firstslice(r.parent), r.offset)
214+
Base._firstslice(i::IdOffsetRange) = IdOffsetRange(Base._firstslice(i.parent), i.offset)
201215

202216
########################################################################################################
203217
# origin.jl
@@ -309,12 +323,12 @@ _popreshape(A::AbstractArray, ax, inds) = A
309323

310324
# Technically we know the length of CartesianIndices but we need to convert it first, so here we
311325
# don't put it in OffsetAxisKnownLength.
312-
const OffsetAxisKnownLength = Union{Integer,AbstractUnitRange}
313-
const OffsetAxis = Union{OffsetAxisKnownLength,Colon}
314-
const ArrayInitializer = Union{UndefInitializer,Missing,Nothing}
326+
const OffsetAxisKnownLength = Union{Integer, AbstractUnitRange}
327+
const OffsetAxis = Union{OffsetAxisKnownLength, Colon}
328+
const ArrayInitializer = Union{UndefInitializer, Missing, Nothing}
315329

316330
## OffsetArray
317-
struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N}
331+
struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N}
318332
parent::AA
319333
offsets::NTuple{N,Int}
320334
@inline function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Int}; checkoverflow = true) where {T, N, AA<:AbstractArray{T,N}}
@@ -482,6 +496,10 @@ Base.parent(A::OffsetArray) = A.parent
482496
# Base.Broadcast.BroadcastStyle(::Type{<:OffsetArray{<:Any, <:Any, AA}}) where AA = Base.Broadcast.BroadcastStyle(AA)
483497

484498
@inline Base.size(A::OffsetArray) = size(parent(A))
499+
# specializing length isn't necessary, as length(A) = prod(size(A)),
500+
# but specializing length enables constant-propagation for statically sized arrays
501+
# see https://github.com/JuliaArrays/OffsetArrays.jl/pull/304
502+
@inline Base.length(A::OffsetArray) = length(parent(A))
485503

486504
@inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets)
487505
@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d))
@@ -528,7 +546,9 @@ _similar_axes_or_length(A, T, ax::I, ::I) where {I} = similar(A, T, map(_indexle
528546
_similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength, ax))
529547

530548
# reshape accepts a single colon
531-
Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds)
549+
# this method is limited to AbstractUnitRange{<:Integer} to avoid method overwritten errors if Base defines the same,
550+
# see https://github.com/JuliaLang/julia/pull/56850
551+
Base.reshape(A::AbstractArray, inds::Union{Integer, Colon, AbstractUnitRange{<:Integer}}...) = reshape(A, inds)
532552
function Base.reshape(A::AbstractArray, inds::Tuple{Vararg{OffsetAxis}})
533553
AR = reshape(no_offset_view(A), map(_indexlength, inds))
534554
O = OffsetArray(AR, map(_offset, axes(AR), inds))
@@ -553,21 +573,9 @@ _reshape2(A, inds) = reshape(A, inds)
553573
_reshape2(A::OffsetArray, inds) = reshape(parent(A), inds)
554574
_reshape_nov(A, inds) = _reshape(no_offset_view(A), inds)
555575

556-
Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
557-
OffsetArray(_reshape(parent(A), inds), map(_toaxis, inds))
558576
# And for non-offset axes, we can just return a reshape of the parent directly
559-
Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = _reshape_nov(A, inds)
560577
Base.reshape(A::OffsetArray, inds::Tuple{Integer,Vararg{Integer}}) = _reshape_nov(A, inds)
561-
Base.reshape(A::OffsetArray, inds::Tuple{Union{Colon, Integer}, Vararg{Union{Colon, Integer}}}) = _reshape_nov(A, inds)
562578
Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds)
563-
Base.reshape(A::OffsetVector, ::Colon) = A
564-
Base.reshape(A::OffsetVector, ::Tuple{Colon}) = A
565-
Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(A, inds)
566-
Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = _reshape_nov(A, inds)
567-
# The following two additional methods for Colon are added to resolve method ambiguities to
568-
# Base: https://github.com/JuliaLang/julia/pull/45387#issuecomment-1132859663
569-
Base.reshape(A::OffsetArray, inds::Colon) = _reshape_nov(A, inds)
570-
Base.reshape(A::OffsetArray, inds::Tuple{Colon}) = _reshape_nov(A, inds)
571579

572580
# permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way
573581
# This is a stopgap solution
@@ -583,7 +591,7 @@ Base.fill!(A::OffsetArray, x) = parent_call(Ap -> fill!(Ap, x), A)
583591
# Δi = i - first(r)
584592
# i′ = first(r.parent) + Δi
585593
# and one obtains the result below.
586-
parentindex(r::IdOffsetRange, i) = oftype(r.offset, i) - r.offset
594+
parentindex(r::IdOffsetRange, i) = i - r.offset
587595

588596
@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,0}) = A.parent[]
589597

@@ -632,7 +640,7 @@ Base.copy(A::OffsetArray) = parent_call(copy, A)
632640

633641
Base.strides(A::OffsetArray) = strides(parent(A))
634642
Base.elsize(::Type{OffsetArray{T,N,A}}) where {T,N,A} = Base.elsize(A)
635-
Base.cconvert(::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(Ptr{T}, parent(A))
643+
Base.cconvert(P::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(P, parent(A))
636644

637645
# For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194
638646
Base.dataids(A::OffsetArray) = Base.dataids(parent(A))
@@ -732,15 +740,6 @@ if eltype(IIUR) === Int
732740
Base.map(::Type{T}, r::IdentityUnitRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r))
733741
end
734742

735-
# mapreduce is faster with an IdOffsetRange than with an OffsetUnitRange
736-
# We therefore convert OffsetUnitRanges to IdOffsetRanges with the same values and axes
737-
function Base.mapreduce(f, op, A1::OffsetUnitRange{<:Integer}, As::OffsetUnitRange{<:Integer}...; kw...)
738-
As = (A1, As...)
739-
ofs = map(A -> first(axes(A,1)) - 1, As)
740-
AIds = map((A, of) -> IdOffsetRange(_subtractoffset(parent(A), of), of), As, ofs)
741-
mapreduce(f, op, AIds...; kw...)
742-
end
743-
744743
# Optimize certain reductions that treat an OffsetVector as a list
745744
for f in [:minimum, :maximum, :extrema, :sum]
746745
@eval Base.$f(r::OffsetRange) = $f(parent(r))
@@ -762,7 +761,8 @@ Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A)
762761
Base.empty!(A::OffsetVector) = (empty!(A.parent); A)
763762

764763
# These functions keep the summary compact
765-
function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}}})
764+
const OffsetIndices = Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}
765+
function Base.inds2string(inds::Tuple{OffsetIndices, Vararg{OffsetIndices}})
766766
Base.inds2string(map(UnitRange, inds))
767767
end
768768
Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...)
@@ -786,7 +786,33 @@ function Base.replace_in_print_matrix(A::OffsetArray{<:Any,1}, i::Integer, j::In
786786
Base.replace_in_print_matrix(parent(A), ip, j, s)
787787
end
788788

789+
# Actual unsafe_wrap implementation
790+
@inline function _unsafe_wrap(pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; own = false, kw...) where {T,N}
791+
_checkindices(N, inds, "indices")
792+
AA = Base.unsafe_wrap(Array, pointer, map(_indexlength, inds); own=own)
793+
OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...)
794+
end
795+
const OffsetArrayUnion{T,N} = Union{Type{OffsetArray}, Type{OffsetArray{T}}, Type{OffsetArray{T,N}}, Type{OffsetArray{T1, N} where T1}} where {T,N}
796+
797+
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N}
798+
_unsafe_wrap(pointer, inds; kw...)
799+
end
800+
# Avoid ambiguity
801+
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, <:Integer}; kw...) where {T,N}
802+
_unsafe_wrap(pointer, inds; kw...)
803+
end
804+
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{OffsetAxisKnownLength,N}; kw...) where {T,N}
805+
_unsafe_wrap(pointer, inds; kw...)
806+
end
807+
# Avoid ambiguity
808+
@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{Integer,N}; kw...) where {T,N}
809+
_unsafe_wrap(pointer, inds; kw...)
810+
end
811+
789812
no_offset_view(A::OffsetArray) = no_offset_view(parent(A))
813+
no_offset_view(a::Base.Slice{<:Base.OneTo}) = a
814+
no_offset_view(a::Base.Slice) = Base.Slice(UnitRange(a))
815+
no_offset_view(S::SubArray) = view(parent(S), map(no_offset_view, parentindices(S))...)
790816
no_offset_view(a::Array) = a
791817
no_offset_view(i::Number) = i
792818
no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A)
@@ -802,9 +828,12 @@ _no_offset_view(::Any, A::AbstractUnitRange) = UnitRange(A)
802828
# These two helpers are deliberately not exported; their meaning can be very different in
803829
# other scenarios and will be very likely to cause name conflicts if exported.
804830
#####
831+
832+
_halfroundInt(v, r::RoundingMode) = div(v, 2, r)
833+
805834
function center(A::AbstractArray, r::RoundingMode=RoundDown)
806835
map(axes(A)) do inds
807-
round(Int, (length(inds)-1)/2, r) + first(inds)
836+
_halfroundInt(length(inds)-1, r) + first(inds)
808837
end
809838
end
810839

0 commit comments

Comments
 (0)