diff --git a/src/main.jl b/src/main.jl index 7e9d5bd..bee210a 100644 --- a/src/main.jl +++ b/src/main.jl @@ -1,15 +1,15 @@ -fft(X::AbstractArray{<:Complex}, region::Union{Int,AbstractVector} = 1:ndims(X)) = plan_fft(X, region) * X -fft(X::AbstractArray, region::Union{Int,AbstractVector} = 1:ndims(X)) = fft(complex(X), region) +fft(X::AbstractArray{<:Complex,N}, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = plan_fft(X, region) * X +fft(X::AbstractArray{<:Any,N}, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = fft(complex(X), region) -bfft(X::AbstractArray{<:Complex}, region::Union{Int,AbstractVector} = 1:ndims(X)) = plan_bfft(X, region) * X +bfft(X::AbstractArray{<:Complex,N}, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = plan_bfft(X, region) * X -ifft(X::AbstractArray{<:Complex}, region::Union{Int,AbstractVector} = 1:ndims(X)) = bfft(X, region) / mapreduce(Base.Fix1(size, X), *, region; init=1) +ifft(X::AbstractArray{<:Complex,N}, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = bfft(X, region) / mapreduce(Base.Fix1(size, X), *, region; init=1) -rfft(X::AbstractArray{<:Real}, region::Union{Int,AbstractVector} = 1:ndims(X)) = plan_rfft(X, region) * X +rfft(X::AbstractArray{<:Real,N}, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = plan_rfft(X, region) * X -brfft(X::AbstractArray{<:Complex}, len::Int, region::Union{Int,AbstractVector} = 1:ndims(X)) = plan_brfft(X, len, region) * X +brfft(X::AbstractArray{<:Complex,N}, len::Int, region::NTuple{D,Int} where D = ntuple(identity, N)) where N = plan_brfft(X, len, region) * X -function irfft(X::AbstractArray{<:Complex}, len::Int, region::Union{Int,AbstractVector} = 1:ndims(X)) +function irfft(X::AbstractArray{<:Complex,N}, len::Int, region::NTuple{D,Int} where D = ntuple(identity, N)) where N Y = brfft(X, len, region) Y ./= mapreduce(Base.Fix1(size, Y), *, region; init=1) return Y diff --git a/src/plan.jl b/src/plan.jl index db22a6e..4fe516d 100644 --- a/src/plan.jl +++ b/src/plan.jl @@ -1,18 +1,18 @@ # Plans -abstract type FFTAPlan{T,N} end +abstract type FFTAPlan{T,D} end -struct FFTAInvPlan{T,N} <: FFTAPlan{T,N} end +struct FFTAInvPlan{T,D} <: FFTAPlan{T,D} end -struct FFTAPlan_cx{T,N} <: FFTAPlan{T,N} - callgraph::NTuple{N, CallGraph{T}} - region::Union{Int,AbstractVector{<:Int}} +struct FFTAPlan_cx{T,D} <: FFTAPlan{T,D} + callgraph::NTuple{D, CallGraph{T}} + region::NTuple{D,Int} dir::Direction pinv::FFTAInvPlan{T} end -struct FFTAPlan_re{T,N} <: FFTAPlan{T,N} - callgraph::NTuple{N, CallGraph{T}} - region::Union{Int,AbstractVector{<:Int}} +struct FFTAPlan_re{T,D} <: FFTAPlan{T,D} + callgraph::NTuple{D, CallGraph{T}} + region::NTuple{D, Int} dir::Direction pinv::FFTAInvPlan{T} flen::Int @@ -23,69 +23,65 @@ Base.size(p::FFTAPlan{<:Any,N}) where N = ntuple(Base.Fix1(size, p), Val{N}()) Base.complex(p::FFTAPlan_re{T,N}) where {T,N} = FFTAPlan_cx{T,N}(p.callgraph, p.region, p.dir, p.pinv) -function plan_fft(x::AbstractArray{T,N}, region::Union{Int,AbstractVector} = 1:N)::FFTAPlan_cx{T} where {T <: Complex, N} - FFTN = length(region) - if FFTN == 1 - g = CallGraph{T}(size(x,region[])) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_cx{T,FFTN}((g,), region, FFT_FORWARD, pinv) - elseif FFTN == 2 - sort!(region) - g1 = CallGraph{T}(size(x,region[1])) - g2 = CallGraph{T}(size(x,region[2])) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_cx{T,FFTN}((g1,g2), region, FFT_FORWARD, pinv) +function plan_fft(x::AbstractArray{T,N}, region::NTuple{D,Int} = ntuple(identity, N))::FFTAPlan_cx{T} where {T <: Complex, N, D} + if D == 1 + g = CallGraph{T}(size(x, region[1])) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_cx{T,D}((g,), region, FFT_FORWARD, pinv) + elseif D == 2 + region = sort(region) + g1 = CallGraph{T}(size(x, region[1])) + g2 = CallGraph{T}(size(x, region[2])) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_cx{T,D}((g1, g2), region, FFT_FORWARD, pinv) else throw(ArgumentError("only supports 1D and 2D FFTs")) end end -function plan_bfft(x::AbstractArray{T,N}, region::Union{Int,AbstractVector} = 1:N)::FFTAPlan_cx{T} where {T <: Complex,N} - FFTN = length(region) - if FFTN == 1 - g = CallGraph{T}(size(x,region[])) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_cx{T,FFTN}((g,), region, FFT_BACKWARD, pinv) - elseif FFTN == 2 - sort!(region) +function plan_bfft(x::AbstractArray{T,N}, region::NTuple{D,Int} = ntuple(identity, N))::FFTAPlan_cx{T} where {T <: Complex, N, D} + if D == 1 + g = CallGraph{T}(size(x, region[1])) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_cx{T,D}((g,), region, FFT_BACKWARD, pinv) + elseif D == 2 + region = sort(region) g1 = CallGraph{T}(size(x,region[1])) g2 = CallGraph{T}(size(x,region[2])) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_cx{T,FFTN}((g1,g2), region, FFT_BACKWARD, pinv) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_cx{T,D}((g1,g2), region, FFT_BACKWARD, pinv) else throw(ArgumentError("only supports 1D and 2D FFTs")) end end -function plan_rfft(x::AbstractArray{T,N}, region::Union{Int,AbstractVector} = 1:N)::FFTAPlan_re{Complex{T}} where {T <: Real,N} - FFTN = length(region) - if FFTN == 1 - g = CallGraph{Complex{T}}(size(x,region[])) - pinv = FFTAInvPlan{Complex{T},FFTN}() - return FFTAPlan_re{Complex{T},FFTN}(tuple(g), region, FFT_FORWARD, pinv, size(x,region[])) - elseif FFTN == 2 - sort!(region) - g1 = CallGraph{Complex{T}}(size(x,region[1])) - g2 = CallGraph{Complex{T}}(size(x,region[2])) - pinv = FFTAInvPlan{Complex{T},FFTN}() - return FFTAPlan_re{Complex{T},FFTN}(tuple(g1,g2), region, FFT_FORWARD, pinv, size(x,region[1])) +function plan_rfft(x::AbstractArray{T,N}, region::NTuple{D,Int} = ntuple(identity, N))::FFTAPlan_re{Complex{T}} where {T <: Real, N, D} + if D == 1 + g = CallGraph{Complex{T}}(size(x, region[1])) + pinv = FFTAInvPlan{Complex{T},D}() + return FFTAPlan_re{Complex{T},D}(tuple(g), region, FFT_FORWARD, pinv, size(x, region[1])) + elseif D == 2 + region = sort(region) + g1 = CallGraph{Complex{T}}(size(x, region[1])) + g2 = CallGraph{Complex{T}}(size(x, region[2])) + pinv = FFTAInvPlan{Complex{T},D}() + return FFTAPlan_re{Complex{T},D}(tuple(g1,g2), region, FFT_FORWARD, pinv, size(x,region[1])) else throw(ArgumentError("only supports 1D and 2D FFTs")) end end -function plan_brfft(x::AbstractArray{T,N}, len::Int, region::Union{Int,AbstractVector} = 1:N)::FFTAPlan_re{T} where {T,N} - FFTN = length(region) - if FFTN == 1 +function plan_brfft(x::AbstractArray{T,N}, len::Int, region::NTuple{D,Int} = ntuple(identity, N))::FFTAPlan_re{T} where {T, N, D} + if D == 1 g = CallGraph{T}(len) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_re{T,FFTN}((g,), region, FFT_BACKWARD, pinv, len) - elseif FFTN == 2 - sort!(region) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_re{T,D}((g,), region, FFT_BACKWARD, pinv, len) + elseif D == 2 + region = sort(region) g1 = CallGraph{T}(len) - g2 = CallGraph{T}(size(x,region[2])) - pinv = FFTAInvPlan{T,FFTN}() - return FFTAPlan_re{T,FFTN}((g1,g2), region, FFT_BACKWARD, pinv, len) + g2 = CallGraph{T}(size(x, region[2])) + pinv = FFTAInvPlan{T,D}() + return FFTAPlan_re{T,D}((g1,g2), region, FFT_BACKWARD, pinv, len) else throw(ArgumentError("only supports 1D and 2D FFTs")) end @@ -110,11 +106,11 @@ function LinearAlgebra.mul!(y::AbstractArray{U,N}, p::FFTAPlan_cx{T,1}, x::Abstr if axes(x) != axes(y) throw(DimensionMismatch("input array has axes $(axes(x)), but output array has axes $(axes(y))")) end - if size(p, 1) != size(x, p.region[]) - throw(DimensionMismatch("plan has size $(size(p, 1)), but input array has size $(size(x, p.region[])) along region $(p.region[])")) + if size(p, 1) != size(x, p.region[1]) + throw(DimensionMismatch("plan has size $(size(p, 1)), but input array has size $(size(x, p.region[1])) along region $(p.region[1])")) end - Rpre = CartesianIndices(size(x)[1:p.region[]-1]) - Rpost = CartesianIndices(size(x)[p.region[]+1:end]) + Rpre = CartesianIndices(size(x)[1:p.region[1]-1]) + Rpost = CartesianIndices(size(x)[p.region[1]+1:end]) for Ipre in Rpre for Ipost in Rpost @views fft!(y[Ipre,:,Ipost], x[Ipre,:,Ipost], 1, 1, p.dir, p.callgraph[1][1].type, p.callgraph[1], 1) @@ -136,8 +132,8 @@ function LinearAlgebra.mul!(y::AbstractArray{U,N}, p::FFTAPlan_cx{T,2}, x::Abstr R1 = CartesianIndices(size(x)[1:p.region[1]-1]) R2 = CartesianIndices(size(x)[p.region[1]+1:p.region[2]-1]) R3 = CartesianIndices(size(x)[p.region[2]+1:end]) - y_tmp = similar(y, axes(y)[p.region]) - rows,cols = size(x)[p.region] + y_tmp = similar(y, (axes(y, p.region[1]), axes(y, p.region[2]))) + rows, cols = size(x, p.region[1]), size(x, p.region[2]) # Introduce function barrier here since the variables used in the loop ranges aren't inferred. This # is partly because the region field of the plan is abstractly typed but even if that wasn't the case, # it might be a bit tricky to construct the Rxs in an inferred way. @@ -232,8 +228,8 @@ end ##### Backward function Base.:*(p::FFTAPlan_re{T,1}, x::AbstractArray{T,N}) where {T<:Complex, N} Base.require_one_based_indexing(x) - if p.flen ÷ 2 + 1 != size(x, p.region[]) - throw(DimensionMismatch("real 1D plan has size $(p.flen). Dimension of input array along region $(p.region[]) should have size $(size(p, p.region[]) ÷ 2 + 1), but has size $(size(x, p.region[]))")) + if p.flen ÷ 2 + 1 != size(x, p.region[1]) + throw(DimensionMismatch("real 1D plan has size $(p.flen). Dimension of input array along region $(p.region[1]) should have size $(size(p, p.region[1]) ÷ 2 + 1), but has size $(size(x, p.region[1]))")) end if p.dir === FFT_BACKWARD # # for the inverse transformation we have to reconstruct the full array @@ -288,7 +284,7 @@ function Base.:*(p::FFTAPlan_re{T,2}, x::AbstractArray{T,N}) where {T<:Complex, # the second half in the first transform dimension is reversed and conjugated x_half_2 = selectdim(x_full, p.region[1], half_2) # view to the second half of x start_reverse = size(x, p.region[1]) - iseven(p.flen) - + map!(conj, x_half_2, selectdim(x, p.region[1], start_reverse:-1:2)) # for the 2D transform we have to reverse index 2:end of the same block in the second transform dimension as well reverse!(selectdim(x_half_2, p.region[2], 2:size(x, p.region[2])), dims=p.region[2]) diff --git a/test/argument_checking.jl b/test/argument_checking.jl index a6271f3..09b89f1 100644 --- a/test/argument_checking.jl +++ b/test/argument_checking.jl @@ -4,10 +4,10 @@ using LinearAlgebra: LinearAlgebra @testset "Only 1D and 2D FFTs" begin xr = zeros(2, 2) xc = complex(xr) - @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_fft(xc, 1:3) - @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_bfft(xc, 1:3) - @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_rfft(xr, 1:3) - @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_brfft(xc, 2, 1:3) + @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_fft(xc, (1, 2, 3)) + @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_bfft(xc, (1, 2, 3)) + @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_rfft(xr, (1, 2, 3)) + @test_throws ArgumentError("only supports 1D and 2D FFTs") plan_brfft(xc, 2, (1, 2, 3)) end @testset "mismatch between plan and array" begin @@ -34,7 +34,7 @@ end xc2p = [[xc2; ones(1, size(xr2, 2))] ones(size(xc2, 1) + 1, 1)] @testset "1D plan, region=$(region)" for region in 1:2 - yr2 = rfft(xr2, region) + yr2 = rfft(xr2, (region,)) yr2p = if region == 1 [yr2; ones(1, size(yr2, 2))] @@ -42,10 +42,10 @@ end [yr2 ones(size(yr2, 1), 1)] end - @test_throws DimensionMismatch plan_fft(xc2, region) * xc2p - @test_throws DimensionMismatch plan_bfft(xc2, region) * xc2p - @test_throws DimensionMismatch plan_rfft(xr2, region) * xr2p - @test_throws DimensionMismatch plan_brfft(yr2, size(xr2, region), region) * yr2p + @test_throws DimensionMismatch plan_fft(xc2, (region,)) * xc2p + @test_throws DimensionMismatch plan_bfft(xc2, (region,)) * xc2p + @test_throws DimensionMismatch plan_rfft(xr2, (region,)) * xr2p + @test_throws DimensionMismatch plan_brfft(yr2, size(xr2, region), (region,)) * yr2p end @testset "2D plan" begin @@ -74,7 +74,7 @@ end y2 = similar(x2, size(x2, 1) + 1, size(x2, 2) + 1) @testset "1D plan, region=$(region)" for region in [1, 2] - @test_throws DimensionMismatch LinearAlgebra.mul!(y2, plan_fft(x2, region), x2) + @test_throws DimensionMismatch LinearAlgebra.mul!(y2, plan_fft(x2, (region,)), x2) end @testset "2D plan" begin diff --git a/test/onedim/complex_backward.jl b/test/onedim/complex_backward.jl index 6ff4e1c..899ba11 100644 --- a/test/onedim/complex_backward.jl +++ b/test/onedim/complex_backward.jl @@ -27,7 +27,7 @@ end x = complex.(randn(n, n + 1, n + 2), randn(n, n + 1, n + 2)) @testset "against 1D array with mapslices, r=$r" for r in 1:3 - @test bfft(x, r) == mapslices(bfft, x; dims = r) + @test bfft(x, (r,)) == mapslices(bfft, x; dims = r) end end diff --git a/test/onedim/complex_forward.jl b/test/onedim/complex_forward.jl index 3441338..fae5324 100644 --- a/test/onedim/complex_forward.jl +++ b/test/onedim/complex_forward.jl @@ -6,8 +6,8 @@ using FFTA, Test y_ref = 0*y y_ref[1] = N @test y ≈ y_ref atol=1e-12 - @test y == fft(reshape(x,1,1,N),3)[1,1,:] - @test y == fft(reshape(x,N,1), 1)[:,1] + @test y == fft(reshape(x, 1, 1, N), (3,))[1,1,:] + @test y == fft(reshape(x, N, 1), (1,))[:,1] end @testset "1D plan, 1D array. Size: $n" for n in 1:64 @@ -26,7 +26,7 @@ end x = complex.(randn(n, n + 1, n + 2), randn(n, n + 1, n + 2)) @testset "against 1D array with mapslices, r=$r" for r in 1:3 - @test fft(x, r) == mapslices(fft, x; dims = r) + @test fft(x, (r,)) == mapslices(fft, x; dims = r) end end diff --git a/test/onedim/real_backward.jl b/test/onedim/real_backward.jl index 6277f66..ec92471 100644 --- a/test/onedim/real_backward.jl +++ b/test/onedim/real_backward.jl @@ -31,12 +31,12 @@ end x = randn(n, n + 1, n + 2) @testset "round tripping with irfft, r=$r" for r in 1:3 - @test irfft(rfft(x, r), size(x,r), r) ≈ x + @test irfft(rfft(x, (r,)), size(x, r), (r,)) ≈ x end @testset "against 1D array with mapslices, r=$r" for r in 1:3 - y = rfft(x, r) - @test brfft(y, size(x, r), r) == mapslices(t -> brfft(t, size(x, r)), y; dims = r) + y = rfft(x, (r,)) + @test brfft(y, size(x, r), (r,)) == mapslices(t -> brfft(t, size(x, r)), y; dims = r) end end diff --git a/test/onedim/real_forward.jl b/test/onedim/real_forward.jl index 0b8f127..26b8415 100644 --- a/test/onedim/real_forward.jl +++ b/test/onedim/real_forward.jl @@ -6,8 +6,8 @@ using FFTA, Test y_ref = 0*y y_ref[1] = N @test y ≈ y_ref atol=1e-12 - @test y == rfft(reshape(x,1,1,N),3)[1,1,:] - @test y == rfft(reshape(x,N,1),1)[:,1] + @test y == rfft(reshape(x, 1, 1, N), (3,))[1,1,:] + @test y == rfft(reshape(x, N, 1), (1,))[:,1] end @testset "1D plan, 1D array. Size: $n" for n in 1:64 @@ -33,7 +33,7 @@ end x = randn(n, n + 1, n + 2) @testset "against 1D array with mapslices, r=$r" for r in 1:3 - @test rfft(x, r) == mapslices(rfft, x; dims = r) + @test rfft(x, (r,)) == mapslices(rfft, x; dims = r) end end diff --git a/test/twodim/complex_backward.jl b/test/twodim/complex_backward.jl index 7eee15c..91ee61c 100644 --- a/test/twodim/complex_backward.jl +++ b/test/twodim/complex_backward.jl @@ -27,8 +27,8 @@ end @testset "2D plan, ND array. Size: $n" for n in 1:64 x = complex.(randn(n, n + 1, n + 2), randn(n, n + 1, n + 2)) - @testset "against 1D array with mapslices, r=$r" for r in [[1,2], [1,3], [2,3]] - @test bfft(x, r) == mapslices(bfft, x; dims = r) + @testset "against 1D array with mapslices, r=$r" for r in [(1,2), (1,3), (2,3)] + @test bfft(x, r) == mapslices(bfft, x; dims = [r...]) end end diff --git a/test/twodim/complex_forward.jl b/test/twodim/complex_forward.jl index efe1f51..0ee42f2 100644 --- a/test/twodim/complex_forward.jl +++ b/test/twodim/complex_forward.jl @@ -7,9 +7,9 @@ using FFTA, Test y_ref[1] = length(x) @test y ≈ y_ref x = randn(N,N) - @test fft(x) ≈ fft(reshape(x,1,N,N), [2,3])[1,:,:] - @test fft(x) ≈ fft(reshape(x,1,N,N,1), [2,3])[1,:,:,1] - @test fft(x) ≈ fft(reshape(x,1,1,N,N,1), [3,4])[1,1,:,:,1] + @test fft(x) ≈ fft(reshape(x, 1, N, N), (2, 3))[1,:,:] + @test fft(x) ≈ fft(reshape(x, 1, N, N, 1), (2, 3))[1,:,:,1] + @test fft(x) ≈ fft(reshape(x, 1, 1, N, N, 1), (3, 4))[1,1,:,:,1] end @testset "2D plan, 2D array. Size: $n" for n in 1:64 @@ -29,8 +29,8 @@ end @testset "2D plan, ND array. Size: $n" for n in 1:64 x = complex.(randn(n, n + 1, n + 2), randn(n, n + 1, n + 2)) - @testset "against 1D array with mapslices, r=$r" for r in [[1,2], [1,3], [2,3]] - @test fft(x, r) == mapslices(fft, x; dims = r) + @testset "against 1D array with mapslices, r=$r" for r in [(1,2), (1,3), (2,3)] + @test fft(x, r) == mapslices(fft, x; dims = [r...]) end end diff --git a/test/twodim/real_backward.jl b/test/twodim/real_backward.jl index 246ebb6..76a8edc 100644 --- a/test/twodim/real_backward.jl +++ b/test/twodim/real_backward.jl @@ -27,13 +27,13 @@ end @testset "2D plan, ND array. Size: $n" for n in 1:64 x = randn(n, n + 1, n + 2) - @testset "round trip with irfft, r=$r" for r in [[1,2], [1,3], [2,3]] - @test x ≈ irfft(rfft(x,r), size(x,r[1]), r) + @testset "round trip with irfft, r=$r" for r in [(1,2), (1,3), (2,3)] + @test x ≈ irfft(rfft(x, r), size(x, r[1]), r) end - @testset "against 2D array with mapslices, r=$r" for r in [[1,2], [1,3], [2,3]] + @testset "against 2D array with mapslices, r=$r" for r in [(1,2), (1,3), (2,3)] y = rfft(x, r) - @test brfft(y, size(x, r[1]), r) == mapslices(t -> brfft(t, size(x, r[1])), y; dims = r) + @test brfft(y, size(x, r[1]), r) == mapslices(t -> brfft(t, size(x, r[1])), y; dims = [r...]) end end diff --git a/test/twodim/real_forward.jl b/test/twodim/real_forward.jl index 1e91131..af809f8 100644 --- a/test/twodim/real_forward.jl +++ b/test/twodim/real_forward.jl @@ -7,9 +7,9 @@ using FFTA, Test y_ref[1] = length(x) @test y ≈ y_ref x = randn(N,N) - @test rfft(x) ≈ rfft(reshape(x,1,N,N), [2,3])[1,:,:] - @test rfft(x) ≈ rfft(reshape(x,1,N,N,1), [2,3])[1,:,:,1] - @test rfft(x) ≈ rfft(reshape(x,1,1,N,N,1), [3,4])[1,1,:,:,1] + @test rfft(x) ≈ rfft(reshape(x ,1, N, N), (2, 3))[1,:,:] + @test rfft(x) ≈ rfft(reshape(x, 1, N, N, 1), (2, 3))[1,:,:,1] + @test rfft(x) ≈ rfft(reshape(x, 1, 1, N, N, 1), (3, 4))[1,1,:,:,1] @test size(rfft(x)) == (N÷2+1, N) end @@ -30,8 +30,8 @@ end @testset "2D plan, ND array. Size: $n" for n in 1:64 x = randn(n, n + 1, n + 2) - @testset "against 1D array with mapslices, r=$r" for r in [[1,2], [1,3], [2,3]] - @test rfft(x, r) == mapslices(rfft, x; dims = r) + @testset "against 1D array with mapslices, r=$r" for r in [(1,2), (1,3), (2,3)] + @test rfft(x, r) == mapslices(rfft, x; dims = [r...]) end end