Skip to content
57 changes: 48 additions & 9 deletions src/OffsetArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N}
end
end

function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Integer}) where {T, N, AA<:AbstractArray{T,N}}
OffsetArray{T, N, AA}(parent, map(x -> convert(Int, x)::Int, offsets))
end

"""
OffsetVector(v, index)

Expand Down Expand Up @@ -194,11 +190,11 @@ for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix)
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}})
_checkindices(A, offsets, "offsets")
# ensure that the offsets may be added together without an overflow
foreach(overflow_check, A.offsets, offsets)
map(overflow_check, A.offsets, offsets)
$FT(parent(A), map(+, A.offsets, offsets))
end
@eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}})
$FT(A, map(x -> convert(Int, x)::Int, offsets))
$FT(A, map(Int, offsets))
end

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

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

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

# conversion-related methods
@inline OffsetArray{T}(M::AbstractArray, I...) where {T} = OffsetArray{T,ndims(M)}(M, I...)

@inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...) where {T,N}
M2 = _of_eltype(T, M)
OffsetArray{T,N,typeof(M2)}(M2, I...)
end

@inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Vararg) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I)
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}) where {T,N,A<:AbstractArray{T,N}}
map(overflow_check, axes(M), I)
Mv = no_offset_view(M)
MvA = convert(A, Mv)::A
Iof = map(+, _offsets(M), I)
OffsetArray{T,N,A}(MvA, Iof)
end
@inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}) where {T, N, AA<:AbstractArray{T,N}}
OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int})
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}) where {T,N,A<:AbstractArray{T,N}}
_checkindices(M, I, "indices")
# Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558
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"))
lM = size(M)
lI = map(length, I)
lM == lI || throw_dimerr(lM, lI)
OffsetArray{T,N,A}(M, map(_offset, axes(M), I))
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple) where {T,N,A<:AbstractArray{T,N}}
OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)))
end
@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}) where {T,N,A<:AbstractArray{T,N}}
Mv = no_offset_view(M)
MvA = convert(A, Mv)::A
OffsetArray{T,N,A}(MvA, _offsets(M))
end
@inline OffsetArray{T,N,A}(M::A) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)))

Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M)

# array initialization
@inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}) where {T,N}
_checkindices(N, inds, "indices")
Expand Down Expand Up @@ -421,9 +458,11 @@ end

# avoid hitting the slow method getindex(::Array, ::AbstractRange{Int})
# instead use the faster getindex(::Array, ::UnitRange{Int})
@propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR})
B = A[_contiguousindexingtype(r)]
_maybewrapoffset(B, axes(r))
if VERSION <= v"1.7.0-DEV.1039"
@propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR})
B = A[_contiguousindexingtype(r)]
_maybewrapoffset(B, axes(r))
end
end

# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods
Expand Down
5 changes: 5 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ _strip_IdOffsetRange(r) = r
_offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent)
_offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent)

_offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A))

"""
OffsetArrays.AxisConversionStyle(typeof(indices))

Expand Down Expand Up @@ -95,3 +97,6 @@ if VERSION <= v"1.7.0-DEV.1039"
else
_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r
end

_of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M
_of_eltype(T, M::AbstractArray) = map(T, M)
150 changes: 149 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ struct WeirdInteger{T} <: Integer
x :: T
end
# assume that it doesn't behave as expected
Base.convert(::Type{Int}, a::WeirdInteger) = a
Base.Int(a::WeirdInteger) = a

@testset "Constructors" begin
@testset "Single-entry arrays in dims 0:5" begin
Expand Down Expand Up @@ -2104,4 +2104,152 @@ end
end
end

# issue 171
struct Foo2
o::OffsetArray{Float64,1,Array{Float64,1}}
end

@testset "convert" begin
d = Diagonal([1,1,1])
M = convert(Matrix{Float64}, d)
od = OffsetArray(d, 1, 1)
oM = convert(OffsetMatrix{Float64, Matrix{Float64}}, od)
@test eltype(oM) == Float64
@test typeof(parent(oM)) == Matrix{Float64}
@test oM == od
oM2 = convert(OffsetMatrix{Float64, Matrix{Float64}}, d)
@test eltype(oM2) == Float64
@test typeof(parent(oM2)) == Matrix{Float64}
@test oM2 == d

# issue 171
O = zeros(Int, 0:2)
F = Foo2(O)
@test F.o == O

a = [MMatrix{2,2}(1:4) for i = 1:2]
oa = [OffsetArray(ai, 0, 0) for ai in a]
b = ones(2,2)
@test b * a == b * oa

for a = [1:4, ones(1:5)]
for T in [OffsetArray, OffsetVector,
OffsetArray{eltype(a)}, OffsetArray{Float32},
OffsetVector{eltype(a)}, OffsetVector{Float32},
OffsetVector{Float32, Vector{Float32}},
OffsetVector{Float32, OffsetVector{Float32, Vector{Float32}}},
OffsetVector{eltype(a), typeof(a)},
]

@test convert(T, a) isa T
@test convert(T, a) == a

b = T(a)
@test b isa T
@test b == a

b = T(a, 0)
@test b isa T
@test b == a

b = T(a, axes(a))
@test b isa T
@test b == a
end

a2 = reshape(a, :, 1)
for T in [OffsetArray{Float32}, OffsetMatrix{Float32}, OffsetArray{Float32, 2, Matrix{Float32}}]
b = T(a2, 0, 0)
@test b isa T
@test b == a2

b = T(a2, axes(a2))
@test b isa T
@test b == a2

b = T(a2, 1, 1)
@test axes(b) == map((x,y) -> x .+ y, axes(a2), (1,1))

b = T(a2)
@test b isa T
@test b == a2
end
a3 = reshape(a, :, 1, 1)
for T in [OffsetArray{Float32}, OffsetArray{Float32, 3}, OffsetArray{Float32, 3, Array{Float32,3}}]
b = T(a3, 0, 0, 0)
@test b isa T
@test b == a3

b = T(a3, axes(a3))
@test b isa T
@test b == a3

b = T(a3, 1, 1, 1)
@test axes(b) == map((x,y) -> x .+ y, axes(a3), (1,1,1))

b = T(a3)
@test b isa T
@test b == a3
end
end

a = ones(2:3)
b = convert(OffsetArray, a)
@test a === b
b = convert(OffsetVector, a)
@test a === b

# test that non-Int offsets work correctly
a = 1:4
b1 = OffsetVector{Float64,Vector{Float64}}(a, 2)
b2 = OffsetVector{Float64,Vector{Float64}}(a, big(2))
@test b1 == b2

a = ones(2:3)
b1 = OffsetArray{Float64, 1, typeof(a)}(a, (-1,))
b2 = OffsetArray{Float64, 1, typeof(a)}(a, (-big(1),))
@test b1 == b2

# test for custom offset arrays
a = ZeroBasedRange(1:3)
for T in [OffsetVector{Float64, UnitRange{Float64}}, OffsetVector{Int, Vector{Int}},
OffsetVector{Float64,OffsetVector{Float64,UnitRange{Float64}}},
OffsetArray{Int,1,OffsetArray{Int,1,UnitRange{Int}}},
]

b = T(a)
@test b isa T
@test b == a

b = T(a, 2:4)
@test b isa T
@test axes(b, 1) == 2:4
@test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a)

b = T(a, 1)
@test b isa T
@test axes(b, 1) == 1:3
@test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a)

c = convert(T, a)
@test c isa T
@test c == a
end

# test using custom indices
a = ones(2,2)
for T in [OffsetMatrix{Int}, OffsetMatrix{Float64}, OffsetMatrix{Float64, Matrix{Float64}},
OffsetMatrix{Int, Matrix{Int}}]

b = T(a, ZeroBasedIndexing())
@test b isa T
@test axes(b) == (0:1, 0:1)
end

# changing the number of dimensions is not permitted
A = rand(2,2)
@test_throws MethodError convert(OffsetArray{Float64, 3}, A)
@test_throws MethodError convert(OffsetArray{Float64, 3, Array{Float64,3}}, A)
end

include("origin.jl")