Skip to content

Commit bff8e7b

Browse files
Convert to OffsetArrays (#230)
* Convert to OffsetArrays * Add no-op conversion tests * new constructors * convert instead of constructing the parent type * Avoid stack overflow in Vararg * rename _maybemap to _of_eltype Co-authored-by: Johnny Chen <johnnychen94@hotmail.com> * fix indexing on nightly * use UnitRange{Int} instead of UnitRange for indexing * strip offset before converting * fix conversion to nested OffsetArrays * add test for custom indices * add tests * remove redundant methods * further reduce methods Co-authored-by: Johnny Chen <johnnychen94@hotmail.com>
1 parent cca160c commit bff8e7b

File tree

3 files changed

+202
-10
lines changed

3 files changed

+202
-10
lines changed

src/OffsetArrays.jl

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N}
117117
end
118118
end
119119

120-
function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Integer}) where {T, N, AA<:AbstractArray{T,N}}
121-
OffsetArray{T, N, AA}(parent, map(x -> convert(Int, x)::Int, offsets))
122-
end
123-
124120
"""
125121
OffsetVector(v, index)
126122
@@ -194,11 +190,11 @@ for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
194190
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}})
195191
_checkindices(A, offsets, "offsets")
196192
# ensure that the offsets may be added together without an overflow
197-
foreach(overflow_check, A.offsets, offsets)
193+
map(overflow_check, A.offsets, offsets)
198194
$FT(parent(A), map(+, A.offsets, offsets))
199195
end
200196
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}})
201-
$FT(A, map(x -> convert(Int, x)::Int, offsets))
197+
$FT(A, map(Int, offsets))
202198
end
203199

204200
# In general, indices get converted to AbstractUnitRanges.
@@ -219,10 +215,51 @@ for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
219215
end
220216

221217
@eval @inline $FT(A::AbstractArray, inds::Vararg) = $FT(A, inds)
218+
@eval @inline $FT(A::AbstractArray) = $FT(A, ntuple(zero, Val(ndims(A))))
222219

223220
@eval @inline $FT(A::AbstractArray, origin::Origin) = $FT(A, origin(A))
224221
end
225222

223+
# conversion-related methods
224+
@inline OffsetArray{T}(M::AbstractArray, I...) where {T} = OffsetArray{T,ndims(M)}(M, I...)
225+
226+
@inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...) where {T,N}
227+
M2 = _of_eltype(T, M)
228+
OffsetArray{T,N,typeof(M2)}(M2, I...)
229+
end
230+
231+
@inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Vararg) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I)
232+
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}) where {T,N,A<:AbstractArray{T,N}}
233+
map(overflow_check, axes(M), I)
234+
Mv = no_offset_view(M)
235+
MvA = convert(A, Mv)::A
236+
Iof = map(+, _offsets(M), I)
237+
OffsetArray{T,N,A}(MvA, Iof)
238+
end
239+
@inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}) where {T, N, AA<:AbstractArray{T,N}}
240+
OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int})
241+
end
242+
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}) where {T,N,A<:AbstractArray{T,N}}
243+
_checkindices(M, I, "indices")
244+
# Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558
245+
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"))
246+
lM = size(M)
247+
lI = map(length, I)
248+
lM == lI || throw_dimerr(lM, lI)
249+
OffsetArray{T,N,A}(M, map(_offset, axes(M), I))
250+
end
251+
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple) where {T,N,A<:AbstractArray{T,N}}
252+
OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)))
253+
end
254+
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}) where {T,N,A<:AbstractArray{T,N}}
255+
Mv = no_offset_view(M)
256+
MvA = convert(A, Mv)::A
257+
OffsetArray{T,N,A}(MvA, _offsets(M))
258+
end
259+
@inline OffsetArray{T,N,A}(M::A) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)))
260+
261+
Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M)
262+
226263
# array initialization
227264
@inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}) where {T,N}
228265
_checkindices(N, inds, "indices")
@@ -421,9 +458,11 @@ end
421458

422459
# avoid hitting the slow method getindex(::Array, ::AbstractRange{Int})
423460
# instead use the faster getindex(::Array, ::UnitRange{Int})
424-
@propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR})
425-
B = A[_contiguousindexingtype(r)]
426-
_maybewrapoffset(B, axes(r))
461+
if VERSION <= v"1.7.0-DEV.1039"
462+
@propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR})
463+
B = A[_contiguousindexingtype(r)]
464+
_maybewrapoffset(B, axes(r))
465+
end
427466
end
428467

429468
# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods

src/utils.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ _strip_IdOffsetRange(r) = r
1313
_offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
1414
_offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent)
1515

16+
_offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A))
17+
1618
"""
1719
OffsetArrays.AxisConversionStyle(typeof(indices))
1820
@@ -95,3 +97,6 @@ if VERSION <= v"1.7.0-DEV.1039"
9597
else
9698
_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r
9799
end
100+
101+
_of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M
102+
_of_eltype(T, M::AbstractArray) = map(T, M)

test/runtests.jl

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ struct WeirdInteger{T} <: Integer
280280
x :: T
281281
end
282282
# assume that it doesn't behave as expected
283-
Base.convert(::Type{Int}, a::WeirdInteger) = a
283+
Base.Int(a::WeirdInteger) = a
284284

285285
@testset "Constructors" begin
286286
@testset "Single-entry arrays in dims 0:5" begin
@@ -2104,4 +2104,152 @@ end
21042104
end
21052105
end
21062106

2107+
# issue 171
2108+
struct Foo2
2109+
o::OffsetArray{Float64,1,Array{Float64,1}}
2110+
end
2111+
2112+
@testset "convert" begin
2113+
d = Diagonal([1,1,1])
2114+
M = convert(Matrix{Float64}, d)
2115+
od = OffsetArray(d, 1, 1)
2116+
oM = convert(OffsetMatrix{Float64, Matrix{Float64}}, od)
2117+
@test eltype(oM) == Float64
2118+
@test typeof(parent(oM)) == Matrix{Float64}
2119+
@test oM == od
2120+
oM2 = convert(OffsetMatrix{Float64, Matrix{Float64}}, d)
2121+
@test eltype(oM2) == Float64
2122+
@test typeof(parent(oM2)) == Matrix{Float64}
2123+
@test oM2 == d
2124+
2125+
# issue 171
2126+
O = zeros(Int, 0:2)
2127+
F = Foo2(O)
2128+
@test F.o == O
2129+
2130+
a = [MMatrix{2,2}(1:4) for i = 1:2]
2131+
oa = [OffsetArray(ai, 0, 0) for ai in a]
2132+
b = ones(2,2)
2133+
@test b * a == b * oa
2134+
2135+
for a = [1:4, ones(1:5)]
2136+
for T in [OffsetArray, OffsetVector,
2137+
OffsetArray{eltype(a)}, OffsetArray{Float32},
2138+
OffsetVector{eltype(a)}, OffsetVector{Float32},
2139+
OffsetVector{Float32, Vector{Float32}},
2140+
OffsetVector{Float32, OffsetVector{Float32, Vector{Float32}}},
2141+
OffsetVector{eltype(a), typeof(a)},
2142+
]
2143+
2144+
@test convert(T, a) isa T
2145+
@test convert(T, a) == a
2146+
2147+
b = T(a)
2148+
@test b isa T
2149+
@test b == a
2150+
2151+
b = T(a, 0)
2152+
@test b isa T
2153+
@test b == a
2154+
2155+
b = T(a, axes(a))
2156+
@test b isa T
2157+
@test b == a
2158+
end
2159+
2160+
a2 = reshape(a, :, 1)
2161+
for T in [OffsetArray{Float32}, OffsetMatrix{Float32}, OffsetArray{Float32, 2, Matrix{Float32}}]
2162+
b = T(a2, 0, 0)
2163+
@test b isa T
2164+
@test b == a2
2165+
2166+
b = T(a2, axes(a2))
2167+
@test b isa T
2168+
@test b == a2
2169+
2170+
b = T(a2, 1, 1)
2171+
@test axes(b) == map((x,y) -> x .+ y, axes(a2), (1,1))
2172+
2173+
b = T(a2)
2174+
@test b isa T
2175+
@test b == a2
2176+
end
2177+
a3 = reshape(a, :, 1, 1)
2178+
for T in [OffsetArray{Float32}, OffsetArray{Float32, 3}, OffsetArray{Float32, 3, Array{Float32,3}}]
2179+
b = T(a3, 0, 0, 0)
2180+
@test b isa T
2181+
@test b == a3
2182+
2183+
b = T(a3, axes(a3))
2184+
@test b isa T
2185+
@test b == a3
2186+
2187+
b = T(a3, 1, 1, 1)
2188+
@test axes(b) == map((x,y) -> x .+ y, axes(a3), (1,1,1))
2189+
2190+
b = T(a3)
2191+
@test b isa T
2192+
@test b == a3
2193+
end
2194+
end
2195+
2196+
a = ones(2:3)
2197+
b = convert(OffsetArray, a)
2198+
@test a === b
2199+
b = convert(OffsetVector, a)
2200+
@test a === b
2201+
2202+
# test that non-Int offsets work correctly
2203+
a = 1:4
2204+
b1 = OffsetVector{Float64,Vector{Float64}}(a, 2)
2205+
b2 = OffsetVector{Float64,Vector{Float64}}(a, big(2))
2206+
@test b1 == b2
2207+
2208+
a = ones(2:3)
2209+
b1 = OffsetArray{Float64, 1, typeof(a)}(a, (-1,))
2210+
b2 = OffsetArray{Float64, 1, typeof(a)}(a, (-big(1),))
2211+
@test b1 == b2
2212+
2213+
# test for custom offset arrays
2214+
a = ZeroBasedRange(1:3)
2215+
for T in [OffsetVector{Float64, UnitRange{Float64}}, OffsetVector{Int, Vector{Int}},
2216+
OffsetVector{Float64,OffsetVector{Float64,UnitRange{Float64}}},
2217+
OffsetArray{Int,1,OffsetArray{Int,1,UnitRange{Int}}},
2218+
]
2219+
2220+
b = T(a)
2221+
@test b isa T
2222+
@test b == a
2223+
2224+
b = T(a, 2:4)
2225+
@test b isa T
2226+
@test axes(b, 1) == 2:4
2227+
@test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a)
2228+
2229+
b = T(a, 1)
2230+
@test b isa T
2231+
@test axes(b, 1) == 1:3
2232+
@test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a)
2233+
2234+
c = convert(T, a)
2235+
@test c isa T
2236+
@test c == a
2237+
end
2238+
2239+
# test using custom indices
2240+
a = ones(2,2)
2241+
for T in [OffsetMatrix{Int}, OffsetMatrix{Float64}, OffsetMatrix{Float64, Matrix{Float64}},
2242+
OffsetMatrix{Int, Matrix{Int}}]
2243+
2244+
b = T(a, ZeroBasedIndexing())
2245+
@test b isa T
2246+
@test axes(b) == (0:1, 0:1)
2247+
end
2248+
2249+
# changing the number of dimensions is not permitted
2250+
A = rand(2,2)
2251+
@test_throws MethodError convert(OffsetArray{Float64, 3}, A)
2252+
@test_throws MethodError convert(OffsetArray{Float64, 3, Array{Float64,3}}, A)
2253+
end
2254+
21072255
include("origin.jl")

0 commit comments

Comments
 (0)