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
1210module OffsetArrays
1311
14- using Base: Indices, tail, @propagate_inbounds
15- using Base : IdentityUnitRange
12+ using Base: tail, @propagate_inbounds
13+ using IdentityUnitRange
1614
1715export 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)
9492@propagate_inbounds function Base. getindex (r:: IdOffsetRange , s:: AbstractUnitRange{<:Integer} )
9593 return r. parent[s .- r. offset] .+ r. offset
9694end
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)
9997end
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
102103Broadcast. 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
106107Broadcast. 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
115167struct 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
118174end
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
129190end
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))
158240end
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))
165247end
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))
170260parenttype (:: Type{OffsetArray{T,N,AA}} ) where {T,N,AA} = AA
171261parenttype (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-
187275Base. similar (A:: OffsetArray , :: Type{T} , dims:: Dims ) where T =
188276 similar (parent (A), T, dims)
189277function 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))
192280end
193281
194282# reshape accepts a single colon
195- const OffsetAxis = Union{OffsetAxisKnownLength, Colon}
196283Base. reshape (A:: AbstractArray , inds:: OffsetAxis... ) = reshape (A, inds)
197284function 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))
200287end
201288
202289# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return
203290# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...)))
204291Base. 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
207294Base. reshape (A:: OffsetArray , inds:: Tuple{Union{Integer,Base.OneTo},Vararg{Union{Integer,Base.OneTo}}} ) = reshape (parent (A), inds)
208295Base. reshape (A:: OffsetArray , inds:: Dims ) = reshape (parent (A), inds)
@@ -211,9 +298,9 @@ Base.reshape(A::OffsetVector, ::Colon) = A
211298Base. reshape (A:: OffsetArray , inds:: Union{Int,Colon} ...) = reshape (parent (A), inds)
212299Base. 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))
217304end
218305
219306Base. 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
277364Base. 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
297391function Base. show (io:: IO , r:: OffsetRange )
@@ -306,18 +400,11 @@ Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r)
306400Base. resize! (A:: OffsetVector , nl:: Integer ) = (resize! (A. parent, nl); A)
307401Base. push! (A:: OffsetVector , x... ) = (push! (A. parent, x... ); A)
308402Base. pop! (A:: OffsetVector ) = pop! (A. parent)
403+ Base. append! (A:: OffsetVector , items) = (append! (A. parent, items); A)
309404Base. 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))
322409end
323410Base. showindices (io:: IO , ind1:: IdOffsetRange , inds:: IdOffsetRange... ) = Base. showindices (io, map (UnitRange, (ind1, inds... ))... )
0 commit comments