Skip to content

Commit 18323b1

Browse files
committed
upgrade to OffsetArrays v1.3.0
1 parent 6caba8a commit 18323b1

File tree

1 file changed

+162
-75
lines changed

1 file changed

+162
-75
lines changed

test/testhelpers/OffsetArrays.jl

Lines changed: 162 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@
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.1.3
8+
# OffsetArrays v1.3.0
99
# No compat patch and docstrings
10-
# cherry-pick 1d294f8ea0f6ccd3a2b413535a6e79fac61af728
11-
# cherry-pick reshape bug fix: https://github.com/JuliaArrays/OffsetArrays.jl/pull/151
1210
module OffsetArrays
1311

14-
using Base: Indices, tail, @propagate_inbounds
15-
using Base: IdentityUnitRange
12+
using Base: tail, @propagate_inbounds
13+
using IdentityUnitRange
1614

1715
export OffsetArray, OffsetMatrix, OffsetVector
1816

@@ -72,9 +70,9 @@ offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange{T} whe
7270
@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset)
7371
@inline Base.unsafe_indices(r::IdOffsetRange) = (r,)
7472
@inline Base.length(r::IdOffsetRange) = length(r.parent)
75-
Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i))
76-
# Workaround for #92 on Julia < 1.4
77-
Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i))
73+
for f in [:firstindex, :lastindex]
74+
@eval Base.$f(r::IdOffsetRange) = $f(r.parent) .+ r.offset
75+
end
7876

7977
@inline function Base.iterate(r::IdOffsetRange)
8078
ret = iterate(r.parent)
@@ -94,9 +92,12 @@ end
9492
@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::AbstractUnitRange{<:Integer})
9593
return r.parent[s .- r.offset] .+ r.offset
9694
end
97-
@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::IdOffsetRange)
95+
@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::IdentityUnitRange)
9896
return IdOffsetRange(r.parent[s .- r.offset], r.offset)
9997
end
98+
@propagate_inbounds function Base.getindex(r::IdOffsetRange, s::IdOffsetRange)
99+
return IdOffsetRange(r.parent[s.parent .+ (s.offset - r.offset)] .+ (r.offset - s.offset), s.offset)
100+
end
100101

101102
# offset-preserve broadcasting
102103
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange{T}, x::Integer) where T =
@@ -106,67 +107,156 @@ Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdO
106107
Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange{T}) where T =
107108
IdOffsetRange{T}(x .+ r.parent, r.offset)
108109

109-
Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(",first(r), ':', last(r),")")
110+
Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(", first(r), ':', last(r), ")")
110111

111112
# Optimizations
112113
@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset)
113114

115+
struct Origin{T <: Union{Tuple,Int}}
116+
index::T
117+
end
118+
Origin(I::NTuple{N,Int}) where N = Origin{typeof(I)}(I)
119+
Origin(I::CartesianIndex) = Origin(I.I)
120+
Origin(I1::Int, In::Int...) = Origin((I1, In...))
121+
# Origin(0) != Origin((0, )) but they work the same with broadcasting
122+
Origin(n::Int) = Origin{Int}(n)
123+
124+
(o::Origin)(A::AbstractArray) = o.index .- first.(axes(A))
125+
126+
### Low-level utilities ###
127+
128+
_indexoffset(r::AbstractRange) = first(r) - 1
129+
_indexoffset(i::Integer) = 0
130+
_indexoffset(i::Colon) = 0
131+
_indexlength(r::AbstractRange) = length(r)
132+
_indexlength(i::Integer) = i
133+
_indexlength(i::Colon) = Colon()
134+
135+
_offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
136+
_offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent)
137+
138+
abstract type AxisConversionStyle end
139+
struct SingleRange <: AxisConversionStyle end
140+
struct TupleOfRanges <: AxisConversionStyle end
141+
142+
AxisConversionStyle(::Type) = SingleRange()
143+
AxisConversionStyle(::Type{<:CartesianIndices}) = TupleOfRanges()
144+
145+
_convertTupleAbstractUnitRange(x) = _convertTupleAbstractUnitRange(AxisConversionStyle(typeof(x)), x)
146+
_convertTupleAbstractUnitRange(::SingleRange, x) = (convert(AbstractUnitRange{Int}, x),)
147+
_convertTupleAbstractUnitRange(::TupleOfRanges, x) = convert(Tuple{Vararg{AbstractUnitRange{Int}}}, x)
148+
149+
_toAbstractUnitRanges(t::Tuple) = (_convertTupleAbstractUnitRange(first(t))..., _toAbstractUnitRanges(tail(t))...)
150+
_toAbstractUnitRanges(::Tuple{}) = ()
151+
152+
# ensure that the indices are consistent in the constructor
153+
_checkindices(A::AbstractArray, indices, label) = _checkindices(ndims(A), indices, label)
154+
function _checkindices(N::Integer, indices, label)
155+
throw_argumenterror(N, indices, label) = throw(ArgumentError(label * " $indices are not compatible with a $(N)D array"))
156+
N == length(indices) || throw_argumenterror(N, indices, label)
157+
end
158+
159+
160+
# Technically we know the length of CartesianIndices but we need to convert it first, so here we
161+
# don't put it in OffsetAxisKnownLength.
162+
const OffsetAxisKnownLength = Union{Integer,AbstractUnitRange}
163+
const OffsetAxis = Union{OffsetAxisKnownLength,Colon}
164+
const ArrayInitializer = Union{UndefInitializer,Missing,Nothing}
165+
114166
## OffsetArray
115167
struct OffsetArray{T,N,AA<:AbstractArray} <: AbstractArray{T,N}
116168
parent::AA
117169
offsets::NTuple{N,Int}
170+
function OffsetArray{T,N,AA}(parent::AA, offsets::NTuple{N,Int}) where {T,N,AA <: AbstractArray}
171+
@boundscheck overflow_check.(axes(parent), offsets)
172+
new{T,N,AA}(parent, offsets)
173+
end
118174
end
119-
OffsetVector{T,AA<:AbstractArray} = OffsetArray{T,1,AA}
120-
OffsetMatrix{T,AA<:AbstractArray} = OffsetArray{T,2,AA}
121175

122-
## OffsetArray constructors
176+
const OffsetVector{T,AA <: AbstractArray} = OffsetArray{T,1,AA}
177+
178+
const OffsetMatrix{T,AA <: AbstractArray} = OffsetArray{T,2,AA}
123179

124-
offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
125-
offset(axparent::AbstractUnitRange, ax::Integer) = 1 - first(axparent)
180+
function overflow_check(r, offset::T) where T
181+
# This gives some performance boost https://github.com/JuliaLang/julia/issues/33273
182+
throw_upper_overflow_error() = throw(ArgumentError("Boundary overflow detected: offset $offset should be equal or less than $(typemax(T) - last(r))"))
183+
throw_lower_overflow_error() = throw(ArgumentError("Boundary overflow detected: offset $offset should be equal or greater than $(typemin(T) - first(r))"))
126184

127-
function OffsetArray(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) where {T,N}
128-
OffsetArray{T,N,typeof(A)}(A, offsets)
185+
if offset > 0 && last(r) > typemax(T) - offset
186+
throw_upper_overflow_error()
187+
elseif offset < 0 && first(r) < typemin(T) - offset
188+
throw_lower_overflow_error()
189+
end
129190
end
130-
OffsetArray(A::AbstractArray{T,0}, offsets::Tuple{}) where T =
131-
OffsetArray{T,0,typeof(A)}(A, ())
132191

133-
OffsetArray(A::AbstractArray{T,N}, offsets::Vararg{Int,N}) where {T,N} =
134-
OffsetArray(A, offsets)
135-
OffsetArray(A::AbstractArray{T,0}) where {T} = OffsetArray(A, ())
192+
# Tuples of integers are treated as offsets
193+
# Empty Tuples are handled here
194+
function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}})
195+
_checkindices(A, offsets, "offsets")
196+
OffsetArray{eltype(A),ndims(A),typeof(A)}(A, offsets)
197+
end
136198

137-
const ArrayInitializer = Union{UndefInitializer, Missing, Nothing}
138-
OffsetArray{T,N}(init::ArrayInitializer, inds::Indices{N}) where {T,N} =
139-
OffsetArray(Array{T,N}(init, map(indexlength, inds)), map(indexoffset, inds))
140-
OffsetArray{T}(init::ArrayInitializer, inds::Indices{N}) where {T,N} = OffsetArray{T,N}(init, inds)
141-
OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds)
142-
OffsetArray{T}(init::ArrayInitializer, inds::Vararg{AbstractUnitRange,N}) where {T,N} = OffsetArray{T,N}(init, inds)
199+
# These methods are necessary to disallow incompatible dimensions for
200+
# the OffsetVector and the OffsetMatrix constructors
201+
for (FT, ND) in ((:OffsetVector, :1), (:OffsetMatrix, :2))
202+
@eval function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}})
203+
_checkindices(A, offsets, "offsets")
204+
OffsetArray{eltype(A),$ND,typeof(A)}(A, offsets)
205+
end
206+
FTstr = string(FT)
207+
@eval function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}})
208+
throw(ArgumentError($FTstr * " requires a " * string($ND) * "D array"))
209+
end
210+
end
143211

144-
# OffsetVector constructors
145-
OffsetVector(A::AbstractVector, offset) = OffsetArray(A, offset)
146-
OffsetVector{T}(init::ArrayInitializer, inds::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds)
212+
## OffsetArray constructors
213+
for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
214+
# Nested OffsetArrays may strip off the wrapper and collate the offsets
215+
@eval function $FT(A::OffsetArray, offsets::Tuple{Vararg{Integer}})
216+
_checkindices(A, offsets, "offsets")
217+
$FT(parent(A), map(+, A.offsets, offsets))
218+
end
219+
220+
# In general, indices get converted to AbstractUnitRanges.
221+
# CartesianIndices{N} get converted to N ranges
222+
@eval function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}})
223+
$FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds)))
224+
end
225+
226+
# convert ranges to offsets
227+
@eval function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}})
228+
_checkindices(A, inds, "indices")
229+
# Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558
230+
throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices"))
231+
lA = size(A)
232+
lI = map(length, inds)
233+
lA == lI || throw_dimerr(lA, lI)
234+
$FT(A, map(_offset, axes(A), inds))
235+
end
147236

148-
# OffsetMatrix constructors
149-
OffsetMatrix(A::AbstractMatrix, offset1, offset2) = OffsetArray(A, offset1, offset2)
150-
OffsetMatrix{T}(init::ArrayInitializer, inds1::AbstractUnitRange, inds2::AbstractUnitRange) where {T} = OffsetArray{T}(init, inds1, inds2)
237+
@eval $FT(A::AbstractArray, inds::Vararg) = $FT(A, inds)
151238

152-
function OffsetArray(A::AbstractArray{T,N}, inds::NTuple{N,AbstractUnitRange}) where {T,N}
153-
axparent = axes(A)
154-
lA = map(length, axparent)
155-
lI = map(length, inds)
156-
lA == lI || throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices"))
157-
OffsetArray(A, map(offset, axparent, inds))
239+
@eval $FT(A::AbstractArray, origin::Origin) = $FT(A, origin(A))
158240
end
159-
OffsetArray(A::AbstractArray{T,N}, inds::Vararg{AbstractUnitRange,N}) where {T,N} =
160-
OffsetArray(A, inds)
161241

162-
# avoid a level of indirection when nesting OffsetArrays
163-
function OffsetArray(A::OffsetArray, offsets::NTuple{N,Int}) where {N}
164-
OffsetArray(parent(A), offsets .+ A.offsets)
242+
# array initialization
243+
function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}) where {T,N}
244+
_checkindices(N, inds, "indices")
245+
AA = Array{T,N}(init, map(_indexlength, inds))
246+
OffsetArray{T,N,typeof(AA)}(AA, map(_indexoffset, inds))
165247
end
166-
OffsetArray(A::OffsetArray{T,0}, inds::Tuple{}) where {T} = OffsetArray(parent(A), ())
167-
# OffsetArray(A::OffsetArray{T,N}, inds::Tuple{}) where {T,N} = error("this should never be called")
248+
function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple) where {T,N}
249+
OffsetArray{T,N}(init, _toAbstractUnitRanges(inds))
250+
end
251+
OffsetArray{T,N}(init::ArrayInitializer, inds::Vararg) where {T,N} = OffsetArray{T,N}(init, inds)
252+
253+
OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N,OffsetAxisKnownLength}) where {T,N} = OffsetArray{T,N}(init, inds)
254+
function OffsetArray{T}(init::ArrayInitializer, inds::Tuple) where {T}
255+
OffsetArray{T}(init, _toAbstractUnitRanges(inds))
256+
end
257+
OffsetArray{T}(init::ArrayInitializer, inds::Vararg) where {T} = OffsetArray{T}(init, inds)
168258

169-
Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA))
259+
Base.IndexStyle(::Type{OA}) where {OA <: OffsetArray} = IndexStyle(parenttype(OA))
170260
parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA
171261
parenttype(A::OffsetArray) = parenttype(typeof(A))
172262

@@ -182,27 +272,24 @@ Base.eachindex(::IndexLinear, A::OffsetVector) = axes(A, 1)
182272
@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d))
183273
@inline Base.axes1(A::OffsetArray{T,0}) where {T} = IdOffsetRange(axes(parent(A), 1)) # we only need to specialize this one
184274

185-
const OffsetAxisKnownLength = Union{Integer, UnitRange, Base.OneTo, IdentityUnitRange, IdOffsetRange}
186-
187275
Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T =
188276
similar(parent(A), T, dims)
189277
function Base.similar(A::AbstractArray, ::Type{T}, inds::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T
190-
B = similar(A, T, map(indexlength, inds))
191-
return OffsetArray(B, map(offset, axes(B), inds))
278+
B = similar(A, T, map(_indexlength, inds))
279+
return OffsetArray(B, map(_offset, axes(B), inds))
192280
end
193281

194282
# reshape accepts a single colon
195-
const OffsetAxis = Union{OffsetAxisKnownLength, Colon}
196283
Base.reshape(A::AbstractArray, inds::OffsetAxis...) = reshape(A, inds)
197284
function Base.reshape(A::AbstractArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}})
198-
AR = reshape(A, map(indexlength, inds))
199-
return OffsetArray(AR, map(offset, axes(AR), inds))
285+
AR = reshape(A, map(_indexlength, inds))
286+
return OffsetArray(AR, map(_offset, axes(AR), inds))
200287
end
201288

202289
# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
203290
# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
204291
Base.reshape(A::OffsetArray, inds::Tuple{OffsetAxis,Vararg{OffsetAxis}}) =
205-
OffsetArray(reshape(parent(A), map(indexlength, inds)), map(indexoffset, inds))
292+
OffsetArray(reshape(parent(A), map(_indexlength, inds)), map(_indexoffset, inds))
206293
# And for non-offset axes, we can just return a reshape of the parent directly
207294
Base.reshape(A::OffsetArray, inds::Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}}) = reshape(parent(A), inds)
208295
Base.reshape(A::OffsetArray, inds::Dims) = reshape(parent(A), inds)
@@ -211,9 +298,9 @@ Base.reshape(A::OffsetVector, ::Colon) = A
211298
Base.reshape(A::OffsetArray, inds::Union{Int,Colon}...) = reshape(parent(A), inds)
212299
Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = reshape(parent(A), inds)
213300

214-
function Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T<:AbstractArray}
215-
P = T(undef, map(indexlength, shape))
216-
OffsetArray(P, map(offset, axes(P), shape))
301+
function Base.similar(::Type{T}, shape::Tuple{OffsetAxis,Vararg{OffsetAxis}}) where {T <: AbstractArray}
302+
P = T(undef, map(_indexlength, shape))
303+
OffsetArray(P, map(_offset, axes(P), shape))
217304
end
218305

219306
Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} =
@@ -276,22 +363,29 @@ const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer
276363

277364
Base.step(a::OffsetRange) = step(parent(a))
278365

279-
Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets)
280-
Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]]
281-
Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets)
366+
@propagate_inbounds Base.getindex(a::OffsetRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets)
367+
@propagate_inbounds function Base.getindex(a::OffsetRange, r::IdOffsetRange)
368+
OffsetArray(a.parent[r.parent .+ (r.offset - a.offsets[1])], r.offset)
369+
end
370+
@propagate_inbounds Base.getindex(r::OffsetRange, s::IIUR) =
371+
OffsetArray(r[s.indices], s)
372+
@propagate_inbounds Base.getindex(a::OffsetRange, r::AbstractRange) = a.parent[r .- a.offsets[1]]
373+
@propagate_inbounds Base.getindex(a::AbstractRange, r::OffsetRange) = OffsetArray(a[parent(r)], r.offsets)
282374

283375
@propagate_inbounds Base.getindex(r::UnitRange, s::IIUR) =
284376
OffsetArray(r[s.indices], s)
285377

286378
@propagate_inbounds Base.getindex(r::StepRange, s::IIUR) =
287379
OffsetArray(r[s.indices], s)
288380

289-
@inline @propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::IIUR) where T =
381+
# this method is needed for ambiguity resolution
382+
@propagate_inbounds Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::IIUR) where T =
290383
OffsetArray(r[s.indices], s)
291-
@inline @propagate_inbounds Base.getindex(r::StepRangeLen{T}, s::IIUR) where {T} =
384+
385+
@propagate_inbounds Base.getindex(r::StepRangeLen{T}, s::IIUR) where {T} =
292386
OffsetArray(r[s.indices], s)
293387

294-
@inline @propagate_inbounds Base.getindex(r::LinRange, s::IIUR) =
388+
@propagate_inbounds Base.getindex(r::LinRange, s::IIUR) =
295389
OffsetArray(r[s.indices], s)
296390

297391
function Base.show(io::IO, r::OffsetRange)
@@ -306,18 +400,11 @@ Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r)
306400
Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A)
307401
Base.push!(A::OffsetVector, x...) = (push!(A.parent, x...); A)
308402
Base.pop!(A::OffsetVector) = pop!(A.parent)
403+
Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A)
309404
Base.empty!(A::OffsetVector) = (empty!(A.parent); A)
310405

311-
### Low-level utilities ###
312-
313-
indexoffset(r::AbstractRange) = first(r) - 1
314-
indexoffset(i::Integer) = 0
315-
indexoffset(i::Colon) = 0
316-
indexlength(r::AbstractRange) = length(r)
317-
indexlength(i::Integer) = i
318-
indexlength(i::Colon) = Colon()
319-
320-
function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}}}})
406+
# These functions keep the summary compact
407+
function Base.inds2string(inds::Tuple{Vararg{Union{IdOffsetRange,IdentityUnitRange{<:IdOffsetRange}}}})
321408
Base.inds2string(map(UnitRange, inds))
322409
end
323410
Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...)

0 commit comments

Comments
 (0)