-
Notifications
You must be signed in to change notification settings - Fork 55
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
Added rfft! and irfft! functionality through PaddedRFFTArray type. #54
Open
favba
wants to merge
14
commits into
JuliaMath:master
Choose a base branch
from
favba:pull-request/2993370b
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+292
−0
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
2993370
Added rfft! and irfft! functionality through PaddedRFFTArray type.
favba 76e5bce
Added rfft! and irfft! functionality through PaddedRFFTArray type.
favba 9c87761
Removed unsafe complex view. Using custom getindex and setindex!
favba ad9a293
Merge branch 'pull-request/2993370b' of https://github.com/favba/FFTW…
favba 6c48ff5
merge master.
favba 87327dd
Remove compatibility with v0.6.
favba e7e994a
Using let block for tests.
favba 6fe7cd7
Fix rand deprecation warning on tests.
favba 833d973
fix plan_brfft! flags.
favba b7ec5bf
Use IOBuffer instead of tem_file for tests.
favba 044fc26
Let array be overridden by plan.
favba de2657e
Use Colon instead of UnitRange for real view.
favba a88455b
Val(Nm1) instead of Nm1 for tuple creation.
favba 102af34
micro-optimization: OneTo for first dimension of real view.
favba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,5 +44,6 @@ end | |
|
||
include("fft.jl") | ||
include("dct.jl") | ||
include("rfft!.jl") | ||
|
||
end # module |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
import Base: IndexStyle, getindex, setindex!, eltype, \, similar, copy, real, read! | ||
|
||
export PaddedRFFTArray, plan_rfft!, rfft!, plan_irfft!, plan_brfft!, brfft!, irfft! | ||
|
||
|
||
|
||
# As the time this code was written the new `ReinterpretArray` introduced in | ||
# Julia v0.7 had major performace issues. Those issues were bypassed with the usage of the | ||
# custom getindex and setindex! below. Hopefully, once the performance issues with ReinterpretArray | ||
# are solved we can just index the reinterpret array directly. | ||
|
||
struct PaddedRFFTArray{T<:fftwReal,N,Nm1,L} <: DenseArray{Complex{T},N} | ||
data::Array{T,N} | ||
r::SubArray{T,N,Array{T,N},Tuple{Base.OneTo{Int},Vararg{Base.Slice{Base.OneTo{Int}},Nm1}},L} # Real view skipping padding | ||
c::Base.ReinterpretArray{Complex{T},N,T,Array{T,N}} | ||
|
||
function PaddedRFFTArray{T,N,Nm1,L}(rr::Array{T,N},nx::Int) where {T<:fftwReal,N,Nm1,L} | ||
fsize = size(rr)[1] | ||
iseven(fsize) || throw( | ||
ArgumentError("First dimension of allocated array must have even number of elements")) | ||
(nx == fsize-2 || nx == fsize-1) || throw( | ||
ArgumentError("Number of elements on the first dimension of array must be either 1 or 2 less than the number of elements on the first dimension of the allocated array")) | ||
c = reinterpret(Complex{T}, rr) | ||
r = view(rr, Base.OneTo(nx), ntuple(i->Colon(),Val(Nm1))...) | ||
return new{T, N, Nm1, L}(rr,r,c) | ||
end # function | ||
end # struct | ||
|
||
@generated function PaddedRFFTArray{T,N}(rr::Array{T,N},nx::Int) where {T<:fftwReal,N} | ||
:(PaddedRFFTArray{T,N,$(N-1),$(N === 1 ? true : false)}(rr,nx)) | ||
end | ||
|
||
@inline real(S::PaddedRFFTArray) = S.r | ||
|
||
@inline complex_view(S::PaddedRFFTArray) = S.c | ||
|
||
@inline data(S::PaddedRFFTArray) = S.data | ||
|
||
copy(S::PaddedRFFTArray) = PaddedRFFTArray(copy(data(S)),size(real(S),1)) | ||
|
||
similar(f::PaddedRFFTArray,::Type{T},dims::Tuple{Vararg{Int,N}}) where {T, N} = | ||
PaddedRFFTArray{T}(dims) | ||
similar(f::PaddedRFFTArray{T,N,L},dims::NTuple{N2,Int}) where {T,N,L,N2} = | ||
PaddedRFFTArray{T}(dims) | ||
similar(f::PaddedRFFTArray,::Type{T}) where {T} = | ||
PaddedRFFTArray{T}(size(real(f))) | ||
similar(f::PaddedRFFTArray{T,N}) where {T,N} = | ||
PaddedRFFTArray{T,N}(similar(data(f)), size(real(f),1)) | ||
|
||
size(S::PaddedRFFTArray) = | ||
size(complex_view(S)) | ||
|
||
IndexStyle(::Type{T}) where {T<:PaddedRFFTArray} = | ||
IndexLinear() | ||
|
||
@inline function getindex(A::PaddedRFFTArray{T,N}, i2::Integer) where {T,N} | ||
d = data(A) | ||
i = 2i2 | ||
@boundscheck checkbounds(d,i) | ||
@inbounds begin | ||
return Complex{T}(d[i-1],d[i]) | ||
end | ||
end | ||
|
||
@inline @generated function getindex(A::PaddedRFFTArray{T,N}, I2::Vararg{Integer,N}) where {T,N} | ||
ip = :(2*I2[1]) | ||
t = Expr(:tuple) | ||
for i=2:N | ||
push!(t.args,:(I2[$i])) | ||
end | ||
quote | ||
d = data(A) | ||
i = $ip | ||
I = $t | ||
@boundscheck checkbounds(d,i,I...) | ||
@inbounds begin | ||
return Complex{T}(d[i-1,I...],d[i,I...]) | ||
end | ||
end | ||
end | ||
|
||
@inline function setindex!(A::PaddedRFFTArray{T,N},x, i2::Integer) where {T,N} | ||
d = data(A) | ||
i = 2i2 | ||
@boundscheck checkbounds(d,i) | ||
@inbounds begin | ||
d[i-1] = real(x) | ||
d[i] = imag(x) | ||
end | ||
A | ||
end | ||
|
||
@inline @generated function setindex!(A::PaddedRFFTArray{T,N}, x, I2::Vararg{Integer,N}) where {T,N} | ||
ip = :(2*I2[1]) | ||
t = Expr(:tuple) | ||
for i=2:N | ||
push!(t.args,:(I2[$i])) | ||
end | ||
quote | ||
d = data(A) | ||
i = $ip | ||
I = $t | ||
@boundscheck checkbounds(d,i,I...) | ||
@inbounds begin | ||
d[i-1,I...] = real(x) | ||
d[i,I...] = imag(x) | ||
end | ||
A | ||
end | ||
end | ||
|
||
PaddedRFFTArray(rr::Array{T,N},nx::Int) where {T<:fftwReal,N} = PaddedRFFTArray{T,N}(rr,nx) | ||
|
||
function PaddedRFFTArray{T}(ndims::Vararg{Integer,N}) where {T,N} | ||
fsize = (ndims[1]÷2 + 1)*2 | ||
a = zeros(T,(fsize, ndims[2:end]...)) | ||
PaddedRFFTArray{T,N}(a, ndims[1]) | ||
end | ||
|
||
PaddedRFFTArray{T}(ndims::NTuple{N,Integer}) where {T,N} = | ||
PaddedRFFTArray{T}(ndims...) | ||
|
||
PaddedRFFTArray(ndims::Vararg{Integer,N}) where N = | ||
PaddedRFFTArray{Float64}(ndims...) | ||
|
||
PaddedRFFTArray(ndims::NTuple{N,Integer}) where N = | ||
PaddedRFFTArray{Float64}(ndims...) | ||
|
||
function PaddedRFFTArray{T}(a::AbstractArray{<:Real,N}) where {T<:fftwReal,N} | ||
t = PaddedRFFTArray{T}(size(a)) | ||
@inbounds copyto!(t.r, a) | ||
return t | ||
end | ||
|
||
PaddedRFFTArray(a::AbstractArray{<:Real}) = PaddedRFFTArray{Float64}(a) | ||
|
||
function PaddedRFFTArray(stream, dims) | ||
field = PaddedRFFTArray(dims) | ||
return read!(stream,field) | ||
end | ||
|
||
function PaddedRFFTArray{T}(stream, dims) where T | ||
field = PaddedRFFTArray{T}(dims) | ||
return read!(stream,field) | ||
end | ||
|
||
function read!(file::AbstractString, field::PaddedRFFTArray) | ||
open(file) do io | ||
return read!(io,field) | ||
end | ||
end | ||
|
||
# Read a binary file of an unpaded array directly to a PaddedRFFT array, without the need | ||
# of the creation of a intermediary Array. If the data is already padded then the user | ||
# should just use PaddedRFFTArray{T}(read("file",unpaddeddim),nx) | ||
function read!(stream::IO, field::PaddedRFFTArray{T,N,L}) where {T,N,L} | ||
rr = data(field) | ||
dims = size(real(field)) | ||
nx = dims[1] | ||
nb = sizeof(T)*nx | ||
npencils = prod(dims)÷nx | ||
npad = iseven(nx) ? 2 : 1 | ||
for i=0:(npencils-1) | ||
unsafe_read(stream,Ref(rr,Int((nx+npad)*i+1)),nb) | ||
end | ||
return field | ||
end | ||
|
||
|
||
########################################################################################### | ||
# Foward plans | ||
|
||
function plan_rfft!(X::PaddedRFFTArray{T,N}, region; | ||
flags::Integer=ESTIMATE, | ||
timelimit::Real=NO_TIMELIMIT) where {T<:fftwReal,N} | ||
|
||
(1 in region) || throw(ArgumentError("The first dimension must always be transformed")) | ||
return rFFTWPlan{T,FORWARD,true,N}(real(X), complex_view(X), region, flags, timelimit) | ||
end | ||
|
||
plan_rfft!(f::PaddedRFFTArray;kws...) = plan_rfft!(f, 1:ndims(f); kws...) | ||
|
||
*(p::rFFTWPlan{T,FORWARD,true,N},f::PaddedRFFTArray{T,N}) where {T<:fftwReal,N} = | ||
(mul!(complex_view(f), p, real(f)); f) | ||
|
||
rfft!(f::PaddedRFFTArray, region=1:ndims(f)) = plan_rfft!(f, region) * f | ||
|
||
function \(p::rFFTWPlan{T,FORWARD,true,N},f::PaddedRFFTArray{T,N}) where {T<:fftwReal,N} | ||
isdefined(p,:pinv) || (p.pinv = plan_irfft!(f,p.region)) | ||
return p.pinv * f | ||
end | ||
|
||
|
||
########################################################################################## | ||
# Inverse plans | ||
|
||
function plan_brfft!(X::PaddedRFFTArray{T,N}, region; | ||
flags::Integer=ESTIMATE, | ||
timelimit::Real=NO_TIMELIMIT) where {T<:fftwReal,N} | ||
(1 in region) || throw(ArgumentError("The first dimension must always be transformed")) | ||
return rFFTWPlan{Complex{T},BACKWARD,true,N}(complex_view(X), real(X), region, flags,timelimit) | ||
end | ||
|
||
plan_brfft!(f::PaddedRFFTArray;kws...) = plan_brfft!(f,1:ndims(f);kws...) | ||
|
||
*(p::rFFTWPlan{Complex{T},BACKWARD,true,N},f::PaddedRFFTArray{T,N}) where {T<:fftwReal,N} = | ||
(mul!(real(f), p, complex_view(f)); real(f)) | ||
|
||
brfft!(f::PaddedRFFTArray, region=1:ndims(f)) = plan_brfft!(f, region) * f | ||
|
||
function plan_irfft!(x::PaddedRFFTArray{T,N}, region; kws...) where {T,N} | ||
ScaledPlan(plan_brfft!(x, region; kws...),normalization(T, size(real(x)), region)) | ||
end | ||
|
||
plan_irfft!(f::PaddedRFFTArray;kws...) = plan_irfft!(f,1:ndims(f);kws...) | ||
|
||
*(p::ScaledPlan,f::PaddedRFFTArray) = begin | ||
p.p * f | ||
rmul!(data(f), p.scale) | ||
real(f) | ||
end | ||
|
||
irfft!(f::PaddedRFFTArray, region=1:ndims(f)) = plan_irfft!(f,region) * f |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
let a = rand(Float64,(8,4,4)), b = PaddedRFFTArray(a), c = copy(b) | ||
|
||
@testset "PaddedRFFTArray creation" begin | ||
@test a == real(b) | ||
@test c == b | ||
@test c.r == b.r | ||
@test typeof(similar(b)) === typeof(b) | ||
@test size(similar(b,Float32)) === size(b) | ||
@test size(similar(b,Float32).r) === size(b.r) | ||
@test size(similar(b,(4,4,4)).r) === (4,4,4) | ||
@test size(similar(b,Float32,(4,4,4)).r) === (4,4,4) | ||
end | ||
|
||
@testset "rfft! and irfft!" begin | ||
@test rfft(a) ≈ rfft!(b) | ||
@test a ≈ irfft!(b) | ||
@test rfft(a,1:2) ≈ rfft!(b,1:2) | ||
@test a ≈ irfft!(b,1:2) | ||
@test rfft(a,(1,3)) ≈ rfft!(b,(1,3)) | ||
@test a ≈ irfft!(b,(1,3)) | ||
|
||
p = plan_rfft!(c) | ||
@test p*c ≈ rfft!(b) | ||
@test p\c ≈ irfft!(b) | ||
|
||
a = rand(Float64,(9,4,4)) | ||
b = PaddedRFFTArray(a) | ||
@test a == real(b) | ||
@test rfft(a) ≈ rfft!(b) | ||
@test a ≈ irfft!(b) | ||
@test rfft(a,1:2) ≈ rfft!(b,1:2) | ||
@test a ≈ irfft!(b,1:2) | ||
@test rfft(a,(1,3)) ≈ rfft!(b,(1,3)) | ||
@test a ≈ irfft!(b,(1,3)) | ||
end | ||
|
||
@testset "Read binary file to PaddedRFFTArray" begin | ||
for s in ((8,4,4),(9,4,4),(8,),(9,)) | ||
aa = rand(Float64,s) | ||
f = IOBuffer() | ||
write(f,aa) | ||
@test aa == real(PaddedRFFTArray(seekstart(f),s)) | ||
aa = rand(Float32,s) | ||
f = IOBuffer() | ||
write(f,aa) | ||
@test aa == real(PaddedRFFTArray{Float32}(seekstart(f),s)) | ||
end | ||
end | ||
|
||
@testset "brfft!" begin | ||
a = rand(Float64,(4,4)) | ||
b = PaddedRFFTArray(a) | ||
rfft!(b) | ||
@test (brfft!(b) ./ 16) ≈ a | ||
end | ||
|
||
@testset "FFTW MEASURE flag" begin | ||
c = similar(b) | ||
p = plan_rfft!(c,flags=FFTW.MEASURE) | ||
p.pinv = plan_irfft!(c,flags=FFTW.MEASURE) | ||
c .= b | ||
@test c == b | ||
@test p*c ≈ rfft!(b) | ||
@test p\c ≈ irfft!(b) | ||
end | ||
end #let block |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it also check bounds with
i-1
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I guess since
ip=2*I2[1]
, if it is in-bounds theni-1
must also be in-bounds.