From 6222a50ed1ea18e3b7f42efba8abc056aa838c2a Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Sun, 7 Dec 2025 11:12:16 -0500 Subject: [PATCH 1/7] add support for Base.repeat --- src/PeriodicArrays.jl | 43 ++++++++++++++++++++++++ test/test_nontrivial_boundary.jl | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/PeriodicArrays.jl b/src/PeriodicArrays.jl index f976908..73fd23a 100644 --- a/src/PeriodicArrays.jl +++ b/src/PeriodicArrays.jl @@ -229,4 +229,47 @@ function Base.insert!(a::PeriodicVector, i::Integer, item) return a end +function Base.repeat(A::PeriodicArray{T, N}; inner = nothing, outer = nothing) where {T, N} + map = A.map + # If no outer repetition is requested, just repeat the parent array as usual + A_new = repeat(parent(A); inner = inner) + + if !isnothing(outer) + # allow passing a single integer or a tuple/ntuple for per-dimension repeats + if isa(outer, Number) + outer = ntuple(i -> Int(outer), N) + else + outer = ntuple(i -> Int(outer[i]), N) + end + + # if `inner` was provided, A_new already contains the repeated parent + base = A_new + axs = axes(base) + ps = size(base) + newsize = ntuple(i -> ps[i] * outer[i], N) + + # create a tiled parent filled with translated values from `map` + A_tiled = similar(base, newsize) + tile_ranges = ntuple(i -> 0:(outer[i] - 1), N) + for tile in CartesianIndices(tile_ranges) + shifts = Tuple(Int(tile[i]) for i in 1:N) + for pos in CartesianIndices(base) + tgt = ntuple(i -> tile[i] * ps[i] + (pos[i] - firstindex(axs[i]) + 1), N) + @inbounds A_tiled[tgt...] = map(base[pos], shifts...) + end + end + + @inline function map_new(x::T, shift::Vararg{Int, N}) + # shifts passed to this map refer to super-cell shifts; amplify + # by `outer` to convert them to original unit-cell shifts. + amplified = ntuple(i -> shift[i] * outer[i], N) + return map(x, amplified...) + end + + return PeriodicArray(A_tiled, map_new) + end + + return PeriodicArray(A_new, map) +end + end diff --git a/test/test_nontrivial_boundary.jl b/test/test_nontrivial_boundary.jl index a49918e..9e9e682 100644 --- a/test/test_nontrivial_boundary.jl +++ b/test/test_nontrivial_boundary.jl @@ -325,4 +325,60 @@ for f in translation_functions # TODO: Figure out how to fix indexing for non-trivial f #@test a[a .> 4] == 5:9 end + + @testset "repeat 1D" begin + a = PeriodicArray([1, 2, 3], f) + # outer as scalar + ar = repeat(a; outer = 2) + base = parent(a) + # expected tiled parent: tiles over shifts 0,1 + expected = vcat([f(base[i], 0) for i in eachindex(base)]..., [f(base[i], 1) for i in eachindex(base)]...) + @test parent(ar) == expected + @test size(parent(ar)) == (length(base) * 2,) + + val = 5 + @test ar.map(val, 1) == a.map(val, 2) + @test ar.map(val, 3) == a.map(val, 6) + + # inner repetition + ai = repeat(a; inner = 2) + @test parent(ai) == repeat(parent(a); inner = 2) + @test size(parent(ai)) == (length(base) * 2,) + + # combined inner+outer + aio = repeat(a; inner = 2, outer = 3) + @test size(parent(aio)) == (length(base) * 2 * 3,) + + # outer as tuple (1D tuple) + ar2 = repeat(a; outer = (2,)) + @test parent(ar2) == expected + end + + @testset "repeat 2D" begin + b = PeriodicArray(reshape(1:6, 3, 2), f) + o1, o2 = 2, 3 + br = repeat(b; outer = (o1, o2)) + base = parent(b) + expected2 = similar(base, size(base, 1) * o1, size(base, 2) * o2) + for t2 in 0:(o2 - 1), t1 in 0:(o1 - 1) + for pos in CartesianIndices(base) + tgt = ( + t1 * size(base, 1) + (pos[1] - firstindex(axes(base, 1)) + 1), + t2 * size(base, 2) + (pos[2] - firstindex(axes(base, 2)) + 1), + ) + @inbounds expected2[tgt...] = f(base[pos], t1, t2) + end + end + @test parent(br) == expected2 + @test size(parent(br)) == (size(base, 1) * o1, size(base, 2) * o2) + + @test br.map(1, 1, 2) == b.map(1, 2, 6) + @test br.map(7, 0, 1) == b.map(7, 0, 3) + + # inner repetition in 2D + bi = repeat(b; inner = (2, 1)) + @test parent(bi) == repeat(parent(b); inner = (2, 1)) + @test size(parent(bi)) == (size(base, 1) * 2, size(base, 2) * 1) + end + end From 65da22ec1d20f2bdd17148120787d378b8334155 Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Sun, 7 Dec 2025 11:21:59 -0500 Subject: [PATCH 2/7] change version due to new feature --- Project.toml | 2 +- docs/Project.toml | 2 +- docs/files/Project.toml | 2 +- test/Project.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 53ecdb9..abbdebb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PeriodicArrays" uuid = "343d6138-6384-4525-8bee-38906309ab36" authors = ["Andreas Feuerpfeil "] -version = "1.0.0" +version = "1.1.0" [compat] julia = "1.10" diff --git a/docs/Project.toml b/docs/Project.toml index add7dde..3f9e3dd 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,4 +6,4 @@ PeriodicArrays = "343d6138-6384-4525-8bee-38906309ab36" [compat] Documenter = "1" Literate = "2" -PeriodicArrays = "1.0" +PeriodicArrays = "1.1" diff --git a/docs/files/Project.toml b/docs/files/Project.toml index a5a3441..2ebfc8e 100644 --- a/docs/files/Project.toml +++ b/docs/files/Project.toml @@ -2,4 +2,4 @@ PeriodicArrays = "343d6138-6384-4525-8bee-38906309ab36" [compat] -PeriodicArrays = "1.0" +PeriodicArrays = "1.1" diff --git a/test/Project.toml b/test/Project.toml index c19d8b1..9090b89 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,7 +9,7 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [compat] Aqua = "0.8" -PeriodicArrays = "1.0" +PeriodicArrays = "1.1" SafeTestsets = "0.1" Suppressor = "0.2" Test = "1.10" From 43cebfc18cc23150b38502c98c75a3317b16d4fa Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Tue, 9 Dec 2025 16:00:15 -0500 Subject: [PATCH 3/7] update readme after registration --- README.md | 4 ++-- docs/files/README.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e4d2fa0..9fa2e59 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ This package is compatible with [`OffsetArrays.jl`](https://github.com/JuliaArra ## Installation -The package is not yet registered in the Julia general registry. It can be installed trough the package manager with the following command: +The package is registered in the Julia general registry. It can be installed trough the package manager with the following command: ```julia-repl -pkg> add git@github.com:ManyBodyLab/PeriodicArrays.jl.git +pkg> add PeriodicArrays ``` ## Code Samples diff --git a/docs/files/README.jl b/docs/files/README.jl index f197aa0..7ab4808 100644 --- a/docs/files/README.jl +++ b/docs/files/README.jl @@ -19,10 +19,10 @@ # ## Installation -# The package is not yet registered in the Julia general registry. It can be installed trough the package manager with the following command: +# The package is registered in the Julia general registry. It can be installed trough the package manager with the following command: # ```julia-repl -# pkg> add git@github.com:ManyBodyLab/PeriodicArrays.jl.git +# pkg> add PeriodicArrays # ``` # ## Code Samples From 94503c267ca0afa670bc5aa733bcc4550a9df85c Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Wed, 17 Dec 2025 15:08:26 -0500 Subject: [PATCH 4/7] implement Base.reverse --- src/PeriodicArrays.jl | 23 +++++++++++++++++++++++ test/test_nontrivial_boundary.jl | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/PeriodicArrays.jl b/src/PeriodicArrays.jl index 73fd23a..1342e3e 100644 --- a/src/PeriodicArrays.jl +++ b/src/PeriodicArrays.jl @@ -272,4 +272,27 @@ function Base.repeat(A::PeriodicArray{T, N}; inner = nothing, outer = nothing) w return PeriodicArray(A_new, map) end +function Base.reverse(arr::PeriodicArray{T,N,A,F}) where {T,N,A,F} + base = reverse(parent(arr)) + + @inline function map_rev(x::T, shifts::Vararg{Int,N}) + neg = ntuple(i -> -shifts[i], N) + return arr.map(x, neg...) + end + + return PeriodicArray(base, map_rev) +end + +function Base.reverse(arr::PeriodicArray{T,N,A,F}; dims::Integer...) where {T,N,A,F} + base = reverse(parent(arr), dims...) + dimsset = Set(dims) + + @inline function map_rev(x::T, shifts::Vararg{Int,N}) + adj = ntuple(i -> (i in dimsset) ? -shifts[i] : shifts[i], N) + return arr.map(x, adj...) + end + + return PeriodicArray(base, map_rev) +end + end diff --git a/test/test_nontrivial_boundary.jl b/test/test_nontrivial_boundary.jl index 9e9e682..1018844 100644 --- a/test/test_nontrivial_boundary.jl +++ b/test/test_nontrivial_boundary.jl @@ -381,4 +381,22 @@ for f in translation_functions @test size(parent(bi)) == (size(base, 1) * 2, size(base, 2) * 1) end + @testset "reverse" begin + a = PeriodicArray([1,2,3], f) + ra = reverse(a) + @test parent(ra) == reverse(parent(a)) + @test ra[1] == a[3] + @test ra[2] == a[2] + + # 2D reverse across first dimension + b = PeriodicArray(reshape(1:6,3,2), f) + rb1 = reverse(b, 1) + @test parent(rb1) == reverse(parent(b); dims=1) + @test all(rb1[i,j] == b[4 - i, j] for i in -10:10, j in -10:10) + + # full reverse + rb = reverse(b) + @test parent(rb) == reverse(parent(b)) + @test all(rb[i,j] == b[4 - i, 3 - j] for i in -10:10, j in -10:10) + end end From a364a87e4954e7eb610c0cdca23c64d30c93e16c Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Wed, 17 Dec 2025 15:20:15 -0500 Subject: [PATCH 5/7] format --- src/PeriodicArrays.jl | 8 ++++---- test/test_nontrivial_boundary.jl | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PeriodicArrays.jl b/src/PeriodicArrays.jl index 1342e3e..f47369e 100644 --- a/src/PeriodicArrays.jl +++ b/src/PeriodicArrays.jl @@ -272,10 +272,10 @@ function Base.repeat(A::PeriodicArray{T, N}; inner = nothing, outer = nothing) w return PeriodicArray(A_new, map) end -function Base.reverse(arr::PeriodicArray{T,N,A,F}) where {T,N,A,F} +function Base.reverse(arr::PeriodicArray{T, N, A, F}) where {T, N, A, F} base = reverse(parent(arr)) - @inline function map_rev(x::T, shifts::Vararg{Int,N}) + @inline function map_rev(x::T, shifts::Vararg{Int, N}) neg = ntuple(i -> -shifts[i], N) return arr.map(x, neg...) end @@ -283,11 +283,11 @@ function Base.reverse(arr::PeriodicArray{T,N,A,F}) where {T,N,A,F} return PeriodicArray(base, map_rev) end -function Base.reverse(arr::PeriodicArray{T,N,A,F}; dims::Integer...) where {T,N,A,F} +function Base.reverse(arr::PeriodicArray{T, N, A, F}; dims::Integer...) where {T, N, A, F} base = reverse(parent(arr), dims...) dimsset = Set(dims) - @inline function map_rev(x::T, shifts::Vararg{Int,N}) + @inline function map_rev(x::T, shifts::Vararg{Int, N}) adj = ntuple(i -> (i in dimsset) ? -shifts[i] : shifts[i], N) return arr.map(x, adj...) end diff --git a/test/test_nontrivial_boundary.jl b/test/test_nontrivial_boundary.jl index 1018844..96af762 100644 --- a/test/test_nontrivial_boundary.jl +++ b/test/test_nontrivial_boundary.jl @@ -382,21 +382,21 @@ for f in translation_functions end @testset "reverse" begin - a = PeriodicArray([1,2,3], f) + a = PeriodicArray([1, 2, 3], f) ra = reverse(a) @test parent(ra) == reverse(parent(a)) @test ra[1] == a[3] @test ra[2] == a[2] # 2D reverse across first dimension - b = PeriodicArray(reshape(1:6,3,2), f) + b = PeriodicArray(reshape(1:6, 3, 2), f) rb1 = reverse(b, 1) - @test parent(rb1) == reverse(parent(b); dims=1) - @test all(rb1[i,j] == b[4 - i, j] for i in -10:10, j in -10:10) + @test parent(rb1) == reverse(parent(b); dims = 1) + @test all(rb1[i, j] == b[4 - i, j] for i in -10:10, j in -10:10) # full reverse rb = reverse(b) @test parent(rb) == reverse(parent(b)) - @test all(rb[i,j] == b[4 - i, 3 - j] for i in -10:10, j in -10:10) + @test all(rb[i, j] == b[4 - i, 3 - j] for i in -10:10, j in -10:10) end end From ffbc500953df31d96640017c61657e76285cd538 Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Wed, 17 Dec 2025 15:37:55 -0500 Subject: [PATCH 6/7] fix d --- docs/Project.toml | 2 +- docs/files/Project.toml | 2 +- src/PeriodicArrays.jl | 12 ++++++++---- test/Project.toml | 2 +- test/test_nontrivial_boundary.jl | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 3f9e3dd..add7dde 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,4 +6,4 @@ PeriodicArrays = "343d6138-6384-4525-8bee-38906309ab36" [compat] Documenter = "1" Literate = "2" -PeriodicArrays = "1.1" +PeriodicArrays = "1.0" diff --git a/docs/files/Project.toml b/docs/files/Project.toml index 2ebfc8e..a5a3441 100644 --- a/docs/files/Project.toml +++ b/docs/files/Project.toml @@ -2,4 +2,4 @@ PeriodicArrays = "343d6138-6384-4525-8bee-38906309ab36" [compat] -PeriodicArrays = "1.1" +PeriodicArrays = "1.0" diff --git a/src/PeriodicArrays.jl b/src/PeriodicArrays.jl index f47369e..fd1d8af 100644 --- a/src/PeriodicArrays.jl +++ b/src/PeriodicArrays.jl @@ -272,7 +272,12 @@ function Base.repeat(A::PeriodicArray{T, N}; inner = nothing, outer = nothing) w return PeriodicArray(A_new, map) end -function Base.reverse(arr::PeriodicArray{T, N, A, F}) where {T, N, A, F} +function Base.reverse(arr::PeriodicArray{T, N, A, F}; dims=:) where {T, N, A, F} + dims == Colon() && return _reverse(arr) + return _reverse(arr, dims) +end + +function _reverse(arr::PeriodicArray{T, N, A, F}) where {T, N, A, F} base = reverse(parent(arr)) @inline function map_rev(x::T, shifts::Vararg{Int, N}) @@ -282,9 +287,8 @@ function Base.reverse(arr::PeriodicArray{T, N, A, F}) where {T, N, A, F} return PeriodicArray(base, map_rev) end - -function Base.reverse(arr::PeriodicArray{T, N, A, F}; dims::Integer...) where {T, N, A, F} - base = reverse(parent(arr), dims...) +function _reverse(arr::PeriodicArray{T, N, A, F}, dims...) where {T, N, A, F} + base = reverse(parent(arr); dims = dims) dimsset = Set(dims) @inline function map_rev(x::T, shifts::Vararg{Int, N}) diff --git a/test/Project.toml b/test/Project.toml index 9090b89..c19d8b1 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,7 +9,7 @@ TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" [compat] Aqua = "0.8" -PeriodicArrays = "1.1" +PeriodicArrays = "1.0" SafeTestsets = "0.1" Suppressor = "0.2" Test = "1.10" diff --git a/test/test_nontrivial_boundary.jl b/test/test_nontrivial_boundary.jl index 96af762..c0b5bc7 100644 --- a/test/test_nontrivial_boundary.jl +++ b/test/test_nontrivial_boundary.jl @@ -390,7 +390,7 @@ for f in translation_functions # 2D reverse across first dimension b = PeriodicArray(reshape(1:6, 3, 2), f) - rb1 = reverse(b, 1) + rb1 = reverse(b; dims = 1) @test parent(rb1) == reverse(parent(b); dims = 1) @test all(rb1[i, j] == b[4 - i, j] for i in -10:10, j in -10:10) From 71efd7786355feabc5a8f9445e74b54bd2f6bcac Mon Sep 17 00:00:00 2001 From: AFeuerpfeil Date: Wed, 17 Dec 2025 15:42:21 -0500 Subject: [PATCH 7/7] format --- src/PeriodicArrays.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PeriodicArrays.jl b/src/PeriodicArrays.jl index fd1d8af..14891d0 100644 --- a/src/PeriodicArrays.jl +++ b/src/PeriodicArrays.jl @@ -272,7 +272,7 @@ function Base.repeat(A::PeriodicArray{T, N}; inner = nothing, outer = nothing) w return PeriodicArray(A_new, map) end -function Base.reverse(arr::PeriodicArray{T, N, A, F}; dims=:) where {T, N, A, F} +function Base.reverse(arr::PeriodicArray{T, N, A, F}; dims = :) where {T, N, A, F} dims == Colon() && return _reverse(arr) return _reverse(arr, dims) end @@ -287,6 +287,7 @@ function _reverse(arr::PeriodicArray{T, N, A, F}) where {T, N, A, F} return PeriodicArray(base, map_rev) end + function _reverse(arr::PeriodicArray{T, N, A, F}, dims...) where {T, N, A, F} base = reverse(parent(arr); dims = dims) dimsset = Set(dims)