| Documentation | Downloads |
|---|---|
| Build Status | Coverage | Style Guide | Quality assurance |
|---|---|---|---|
PeriodicArrays.jl adds the PeriodicArray type which can be backed by any AbstractArray. The idea of this package is based on CircularArrays.jl and extends its functionality to support user-defined translation rules for periodic indexing.
A PeriodicArray{T,N,A,F,G} is an AbstractArray{T,N} backed by a data array of type A<:AbstractArray{T,N}, a forward map fmap of type F, and an inverse map imap of type G.
The maps define how data at out-of-bounds indices is translated to and from valid indices in the data array.
fmap and imap can be any callable objects (e.g. functions or structs) that define
fmap(x, shift::Vararg{Int,N})where x is an element of the array and shift encodes the unit cell in which we index.
PeriodicArray accepts the maps as PeriodicArray(data, fmap) or
PeriodicArray(data, fmap, imap).
If neither map is provided, both default to the identity and the array behaves like a CircularArray.
Constraints on imap (the inverse map used by setindex!):
imapmust satisfyimap(fmap(x, shift...), shift...) == xfor all validxandshift, so that round-tripping a value throughgetindex/setindex!is lossless.- When
imapis omitted, it defaults to(x, shifts...) -> fmap(x, -shifts...). This default is correct wheneverfmapis self-inverse under shift negation, i.e.fmap(fmap(x, s...), -s...) == x. - If
fmapdoes not satisfy the self-inverse property, supply a customimap. If mutation through out-of-bounds indices should be explicitly forbidden, pass animapthat e.g. throws:imap_error(x, shift...) = error("mutation through out-of-bounds indices is not supported") a = PeriodicArray(data, fmap, imap_error)
This package is compatible with OffsetArrays.jl.
The package is registered in the Julia general registry. It can be installed trough the package manager with the following command:
pkg> add PeriodicArraysjulia> using PeriodicArrays
julia> a = PeriodicVector([1,2,3])
julia> a[0:4]
5-element PeriodicVector(::Vector{Int64}):
3
1
2
3
1
julia> f(x, shift...) = x + 10 * sum(shift)
julia> a2 = PeriodicArray([1,2,3], f);
julia> a2[0:4]
5-element PeriodicVector(::Vector{Int64}):
-7
1
2
3
11
julia> struct MyTranslator end;
julia> (f::MyTranslator)(x, shift) = x - shift;
julia> a3 = PeriodicArray([1,2,3], MyTranslator());
julia> a3[0:4]
5-element PeriodicVector(::Vector{Int64}):
4
1
2
3
0
julia> using OffsetArrays
julia> data = reshape(1:9, 3, 3);
julia> i = OffsetArray(1:5, -2:2);
julia> a4 = PeriodicMatrix(data, f);
julia> a4[i,i]
5×5 PeriodicArray(OffsetArray(::Matrix{Int64}, -2:2, -2:2)) with indices -2:2×-2:2:
1 4 7 11 14
2 5 8 12 15
3 6 9 13 16
11 14 17 21 24
12 15 18 22 25Iterated indexing for mutation does not work when the map is non-trivial.
For a PeriodicArray whose elements are themselves mutable (e.g. an array of matrices), writing
x[out_of_bounds_index][i, j] = valuesilently does nothing to x. The reason is that x[out_of_bounds_index] applies the map and returns a new, transformed copy of the element; the subsequent assignment mutates only that temporary object, not the underlying data.
For in-bounds indices the element is returned by reference and mutation works as expected.
Workaround — mapped_ref:
ref = mapped_ref(x, out_of_bounds_index)
ref[i, j] = value # applies imap and writes back into parent(x)mapped_ref returns a MappedRef: a lazy wrapper that applies the forward map on reads
and the inverse map on writes, so no temporary copy is created and the mutation propagates
correctly into the underlying data.
PeriodicArrays.jl is licensed under the MIT License. By using or interacting with this software in any way, you agree to the license of this software.