From b2d040f81cd0f94ff42511b36bd99e25b31fdf9e Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 8 May 2022 19:56:03 +0400 Subject: [PATCH 1/5] Origin as a callable to construct OffsetArrays --- docs/src/index.md | 6 ++++++ src/OffsetArrays.jl | 5 ++++- src/axes.jl | 4 ++-- src/origin.jl | 40 +++++++++++++++++++++++++++++++++------- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 41d07637..00fe4e1f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -63,6 +63,12 @@ OffsetArray(A, OffsetArrays.Origin(-1, -1)) OffsetArray(OA, OffsetArrays.Origin(-1, -1)) ``` +An equivalent — but possibly more convenient — way to specify the origin of an array is + +```@repl index +OffsetArrays.Origin(-1, -1)(A) +``` + Sometimes, it will be convenient to shift the center coordinate of the given array to `(0, 0, ...)`, `OffsetArrays.centered` is a helper for this very purpose: diff --git a/src/OffsetArrays.jl b/src/OffsetArrays.jl index a00aedff..ca3cb474 100644 --- a/src/OffsetArrays.jl +++ b/src/OffsetArrays.jl @@ -207,9 +207,12 @@ for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix) @eval @inline $FT(A::AbstractArray, inds...; kw...) = $FT(A, inds; kw...) @eval @inline $FT(A::AbstractArray; checkoverflow = false) = $FT(A, ntuple(zero, Val(ndims(A))), checkoverflow = checkoverflow) - @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin(A); checkoverflow = checkoverflow) + @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin.index .- first.(axes(A)); checkoverflow = checkoverflow) end +(o::Origin)(A::AbstractArray) = OffsetArray(A, o) +Origin(A::OffsetArray) = Origin(first.(axes(A))) + # conversion-related methods @inline OffsetArray{T}(M::AbstractArray, I...; kw...) where {T} = OffsetArray{T,ndims(M)}(M, I...; kw...) diff --git a/src/axes.jl b/src/axes.jl index d67df67e..0422141f 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -22,7 +22,7 @@ julia> ro[-1] -1 julia> ro[3] -ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int,UnitRange{Int}}) with indices -1:1 at index [3] +ERROR: BoundsError: attempt to access 3-element IdOffsetRange{$Int, UnitRange{$Int}} with indices -1:1 at index [3] ``` If the range doesn't start at 1, the values may be different from the indices: @@ -37,7 +37,7 @@ julia> ro[-1] 9 julia> ro[3] -ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int,UnitRange{Int}}) with indices -1:1 at index [3] +ERROR: BoundsError: attempt to access 3-element IdOffsetRange{$Int, UnitRange{$Int}} with indices -1:1 at index [3] ``` # Extended help diff --git a/src/origin.jl b/src/origin.jl index fcd1c4f6..cf359572 100644 --- a/src/origin.jl +++ b/src/origin.jl @@ -3,25 +3,49 @@ Origin(origin::Tuple) Origin(origin::CartesianIndex) -A helper type to construct OffsetArray with given origin. +A helper type to construct OffsetArray with a given origin. This is not exported. -The `origin` of an array is defined as the index of its first element, i.e., `first.(axes(A))`. +The `origin` of an array is defined as the tuple of the first index along each axis, i.e., `first.(axes(A))`. # Example -```jldoctest; setup=:(using OffsetArrays) +```jldoctest origin; setup=:(using OffsetArrays) julia> a = [1 2; 3 4]; -julia> OffsetArray(a, OffsetArrays.Origin(0, 1)) +julia> using OffsetArrays: Origin + +julia> OffsetArray(a, Origin(0, 1)) 2×2 OffsetArray(::$(Array{Int,2}), 0:1, 1:2) with eltype $Int with indices 0:1×1:2: 1 2 3 4 -julia> OffsetArray(a, OffsetArrays.Origin(0)) # short notation for `Origin(0, 0)` +julia> OffsetArray(a, Origin(0)) # short notation for `Origin(0, 0)` 2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1: 1 2 3 4 ``` + +An `Origin` object is callable, and it may shift the origin of an array to the specified point. + +```jldoctest origin +julia> b = Origin(0)(a) # shift the origin of the array to (0,0) +2×2 OffsetArray(::Matrix{Int64}, 0:1, 0:1) with eltype Int64 with indices 0:1×0:1: + 1 2 + 3 4 +``` + +The type `Origin`, when called with an `AbstractArray` as the argument, will return an instance +corresponding ot the origin of the array. + +```jldoctest origin +julia> origin_b = Origin(b) # retrieve the origin of the array as an Origin instance +Origin(0, 0) + +julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in this case to (0,0) +2×2 OffsetArray(::Matrix{Float64}, 0:1, 0:1) with eltype Float64 with indices 0:1×0:1: + 1.0 1.0 + 1.0 1.0 +``` """ struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} index::T @@ -33,6 +57,8 @@ Origin(I::Number...) = Origin(I) # Origin(0) != Origin((0, )) but they work the same with broadcasting Origin(n::Number) = Origin{Int}(Int(n)) -(o::Origin)(A::AbstractArray) = o.index .- first.(axes(A)) - Base.Broadcast.broadcastable(o::Origin) = Ref(o) + +_showidx(index::Integer) = "(" * string(index) * ")" +_showidx(index::Tuple) = string(index) +Base.show(io::IO, o::Origin) = print(io, "Origin", _showidx(o.index)) From ea33992eaee2faa8fbb35b2328fb6ed473f52c23 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 8 May 2022 22:51:58 +0400 Subject: [PATCH 2/5] add tests and broadcasting tip --- Project.toml | 2 +- src/origin.jl | 28 ++++++++++++++++++++++++++++ test/runtests.jl | 16 +++++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 490aaad5..8dc0d863 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "OffsetArrays" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.10.8" +version = "1.11.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" diff --git a/src/origin.jl b/src/origin.jl index cf359572..69885c99 100644 --- a/src/origin.jl +++ b/src/origin.jl @@ -46,6 +46,34 @@ julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in 1.0 1.0 1.0 1.0 ``` + +!!! tip + One may broadcast an `Origin` instance over multiple arrays to shift them all to the same origin. + ```jldoctest + julia> using OffsetArrays: Origin + + julia> a = [1 2; 3 4]; # origin at (1,1) + + julia> b = Origin(2,3)(a); # origin at (2,3) + + julia> c = Origin(4)(a); # origin at (4,4) + + julia> ao, bo, co = Origin(0).((a, b, c)); # shift all origins to (0,0) + + julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (0,0) + true + + julia> ao, bo, co = Origin(b).((a, b, c)); # shift all orgigins to that of b + + julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) + true + + julia> ao, bo, co = OffsetArray.((a, b, c), Origin(b)); # same as above + + julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) + true + ``` + """ struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} index::T diff --git a/test/runtests.jl b/test/runtests.jl index 0ffaea07..3e0a07c2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ using OffsetArrays -using OffsetArrays: IdentityUnitRange, no_offset_view, IIUR +using OffsetArrays: IdentityUnitRange, no_offset_view, IIUR, Origin using Base: Slice using OffsetArrays: IdOffsetRange using Test, Aqua, Documenter @@ -2594,6 +2594,20 @@ end include("origin.jl") +@testset "Origin" begin + a = [1 2; 3 4]; + for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))] + b = Origin(index)(a) + @test first.(axes(b)) == firstinds + @test Origin(b) === Origin(firstinds) + end + io = IOBuffer() + show(io, Origin(1)) + @test String(take!(io)) == "Origin(1)" + show(io, Origin(1, 1)) + @test String(take!(io)) == "Origin(1, 1)" +end + @testset "misc" begin @test OffsetArrays._subtractoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int} @test OffsetArrays._subtractoffset(Base.OneTo(2), 1) == 0:1 From 674d3407b23a42affad1dacb003d11c9f0d6b3a1 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 8 May 2022 22:53:12 +0400 Subject: [PATCH 3/5] fix typo --- src/origin.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/origin.jl b/src/origin.jl index 69885c99..c66e2cc9 100644 --- a/src/origin.jl +++ b/src/origin.jl @@ -63,12 +63,12 @@ julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (0,0) true - julia> ao, bo, co = Origin(b).((a, b, c)); # shift all orgigins to that of b + julia> ao, bo, co = Origin(b).((a, b, c)); # shift all origins to that of b julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) true - julia> ao, bo, co = OffsetArray.((a, b, c), Origin(b)); # same as above + julia> ao, bo, co = OffsetArray.((a, b, c), Origin(b)); # another way to do the same julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) true From 2eeb7371b97e224c117f959e0a8b8d00130c9180 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 8 May 2022 23:54:50 +0400 Subject: [PATCH 4/5] interpolate Int --- src/origin.jl | 5 ++--- test/runtests.jl | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/origin.jl b/src/origin.jl index c66e2cc9..a22fb5a7 100644 --- a/src/origin.jl +++ b/src/origin.jl @@ -29,7 +29,7 @@ An `Origin` object is callable, and it may shift the origin of an array to the s ```jldoctest origin julia> b = Origin(0)(a) # shift the origin of the array to (0,0) -2×2 OffsetArray(::Matrix{Int64}, 0:1, 0:1) with eltype Int64 with indices 0:1×0:1: +2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1: 1 2 3 4 ``` @@ -42,7 +42,7 @@ julia> origin_b = Origin(b) # retrieve the origin of the array as an Origin inst Origin(0, 0) julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in this case to (0,0) -2×2 OffsetArray(::Matrix{Float64}, 0:1, 0:1) with eltype Float64 with indices 0:1×0:1: +2×2 OffsetArray(::$(Array{Float64, 2}), 0:1, 0:1) with eltype Float64 with indices 0:1×0:1: 1.0 1.0 1.0 1.0 ``` @@ -73,7 +73,6 @@ julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) true ``` - """ struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} index::T diff --git a/test/runtests.jl b/test/runtests.jl index 3e0a07c2..dce2c7b8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2595,17 +2595,21 @@ end include("origin.jl") @testset "Origin" begin - a = [1 2; 3 4]; - for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))] - b = Origin(index)(a) - @test first.(axes(b)) == firstinds - @test Origin(b) === Origin(firstinds) + @testset "as a callable" begin + a = [1 2; 3 4]; + for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))] + b = Origin(index)(a) + @test first.(axes(b)) == firstinds + @test Origin(b) === Origin(firstinds) + end + end + @testset "display" begin + io = IOBuffer() + show(io, Origin(1)) + @test String(take!(io)) == "Origin(1)" + show(io, Origin(1, 1)) + @test String(take!(io)) == "Origin(1, 1)" end - io = IOBuffer() - show(io, Origin(1)) - @test String(take!(io)) == "Origin(1)" - show(io, Origin(1, 1)) - @test String(take!(io)) == "Origin(1, 1)" end @testset "misc" begin From d2905cbe828dbcf10d369689964a487ef2517d48 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 9 May 2022 00:01:42 +0400 Subject: [PATCH 5/5] Constructor test --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index dce2c7b8..780a2997 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2597,6 +2597,7 @@ include("origin.jl") @testset "Origin" begin @testset "as a callable" begin a = [1 2; 3 4]; + @test OffsetArray(a, Origin(2)) === Origin(2)(a) for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))] b = Origin(index)(a) @test first.(axes(b)) == firstinds