Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Origin as a callable to construct OffsetArrays #276

Merged
merged 5 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
6 changes: 6 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
5 changes: 4 additions & 1 deletion src/OffsetArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)

Expand Down
4 changes: 2 additions & 2 deletions src/axes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
67 changes: 60 additions & 7 deletions src/origin.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,76 @@
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(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int 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)
jishnub marked this conversation as resolved.
Show resolved Hide resolved
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
```

!!! 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 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)); # another way to do the same

julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3)
true
```
"""
struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}}
index::T
Expand All @@ -33,6 +84,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))
21 changes: 20 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -2594,6 +2594,25 @@ end

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
@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
end

@testset "misc" begin
@test OffsetArrays._subtractoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int}
@test OffsetArrays._subtractoffset(Base.OneTo(2), 1) == 0:1
Expand Down