|
| 1 | +# This file is a part of Julia. License is MIT: http://julialang.org/license |
| 2 | + |
| 3 | +module DFT |
| 4 | + |
| 5 | +# DFT plan where the inputs are an array of eltype T |
| 6 | +abstract Plan{T} |
| 7 | + |
| 8 | +import Base: show, summary, size, ndims, length, eltype, |
| 9 | + *, A_mul_B!, inv, \, A_ldiv_B! |
| 10 | + |
| 11 | +eltype{T}(::Plan{T}) = T |
| 12 | +eltype{P<:Plan}(T::Type{P}) = T.parameters[1] |
| 13 | + |
| 14 | +# size(p) should return the size of the input array for p |
| 15 | +size(p::Plan, d) = size(p)[d] |
| 16 | +ndims(p::Plan) = length(size(p)) |
| 17 | +length(p::Plan) = prod(size(p))::Int |
| 18 | + |
| 19 | +############################################################################## |
| 20 | +export fft, ifft, bfft, fft!, ifft!, bfft!, |
| 21 | + plan_fft, plan_ifft, plan_bfft, plan_fft!, plan_ifft!, plan_bfft!, |
| 22 | + rfft, irfft, brfft, plan_rfft, plan_irfft, plan_brfft |
| 23 | + |
| 24 | +complexfloat{T<:FloatingPoint}(x::AbstractArray{Complex{T}}) = x |
| 25 | + |
| 26 | +# return an Array, rather than similar(x), to avoid an extra copy for FFTW |
| 27 | +# (which only works on StridedArray types). |
| 28 | +complexfloat{T<:Complex}(x::AbstractArray{T}) = copy!(Array(typeof(float(one(T))), size(x)), x) |
| 29 | +complexfloat{T<:FloatingPoint}(x::AbstractArray{T}) = copy!(Array(typeof(complex(one(T))), size(x)), x) |
| 30 | +complexfloat{T<:Real}(x::AbstractArray{T}) = copy!(Array(typeof(complex(float(one(T)))), size(x)), x) |
| 31 | + |
| 32 | +# implementations only need to provide plan_X(x, region) |
| 33 | +# for X in (:fft, :bfft, ...): |
| 34 | +for f in (:fft, :bfft, :ifft, :fft!, :bfft!, :ifft!, :rfft) |
| 35 | + pf = symbol(string("plan_", f)) |
| 36 | + @eval begin |
| 37 | + $f(x::AbstractArray) = $pf(x) * x |
| 38 | + $f(x::AbstractArray, region) = $pf(x, region) * x |
| 39 | + $pf(x::AbstractArray; kws...) = $pf(x, 1:ndims(x); kws...) |
| 40 | + end |
| 41 | +end |
| 42 | + |
| 43 | +# promote to a complex floating-point type (out-of-place only), |
| 44 | +# so implementations only need Complex{Float} methods |
| 45 | +for f in (:fft, :bfft, :ifft) |
| 46 | + pf = symbol(string("plan_", f)) |
| 47 | + @eval begin |
| 48 | + $f{T<:Real}(x::AbstractArray{T}, region=1:ndims(x)) = $f(complexfloat(x), region) |
| 49 | + $pf{T<:Real}(x::AbstractArray{T}, region; kws...) = $pf(complexfloat(x), region; kws...) |
| 50 | + $f{T<:Union(Integer,Rational)}(x::AbstractArray{Complex{T}}, region=1:ndims(x)) = $f(complexfloat(x), region) |
| 51 | + $pf{T<:Union(Integer,Rational)}(x::AbstractArray{Complex{T}}, region; kws...) = $pf(complexfloat(x), region; kws...) |
| 52 | + end |
| 53 | +end |
| 54 | +rfft{T<:Union(Integer,Rational)}(x::AbstractArray{T}, region=1:ndims(x)) = rfft(float(x), region) |
| 55 | +plan_rfft{T<:Union(Integer,Rational)}(x::AbstractArray{T}, region; kws...) = plan_rfft(float(x), region; kws...) |
| 56 | + |
| 57 | +# only require implementation to provide *(::Plan{T}, ::Array{T}) |
| 58 | +*{T}(p::Plan{T}, x::AbstractArray) = p * copy!(Array(T, size(x)), x) |
| 59 | + |
| 60 | +# Implementations should also implement A_mul_B!(Y, plan, X) so as to support |
| 61 | +# pre-allocated output arrays. We don't define * in terms of A_mul_B! |
| 62 | +# generically here, however, because of subtleties for in-place and rfft plans. |
| 63 | + |
| 64 | +############################################################################## |
| 65 | +# To support inv, \, and A_ldiv_B!(y, p, x), we require Plan subtypes |
| 66 | +# to have a pinv::Plan field, which caches the inverse plan, and which |
| 67 | +# should be initially undefined. They should also implement |
| 68 | +# plan_inv(p) to construct the inverse of a plan p. |
| 69 | + |
| 70 | +# hack from @simonster (in #6193) to compute the return type of plan_inv |
| 71 | +# without actually calling it or even constructing the empty arrays. |
| 72 | +_pinv_type(p::Plan) = typeof([plan_inv(x) for x in typeof(p)[]]) |
| 73 | +pinv_type(p::Plan) = eltype(_pinv_type(p)) |
| 74 | + |
| 75 | +inv(p::Plan) = |
| 76 | + isdefined(p, :pinv) ? p.pinv::pinv_type(p) : (p.pinv = plan_inv(p)) |
| 77 | +\(p::Plan, x::AbstractArray) = inv(p) * x |
| 78 | +A_ldiv_B!(y::AbstractArray, p::Plan, x::AbstractArray) = A_mul_B!(y, inv(p), x) |
| 79 | + |
| 80 | +############################################################################## |
| 81 | +# implementations only need to provide the unnormalized backwards FFT, |
| 82 | +# similar to FFTW, and we do the scaling generically to get the ifft: |
| 83 | + |
| 84 | +type ScaledPlan{T,P,N} <: Plan{T} |
| 85 | + p::P |
| 86 | + scale::N # not T, to avoid unnecessary promotion to Complex |
| 87 | + pinv::Plan |
| 88 | + ScaledPlan(p, scale) = new(p, scale) |
| 89 | +end |
| 90 | +ScaledPlan{P<:Plan,N<:Number}(p::P, scale::N) = ScaledPlan{eltype(P),P,N}(p, scale) |
| 91 | +ScaledPlan(p::ScaledPlan, α::Number) = ScaledPlan(p.p, p.scale * α) |
| 92 | + |
| 93 | +size(p::ScaledPlan) = size(p.p) |
| 94 | + |
| 95 | +show(io::IO, p::ScaledPlan) = print(io, p.scale, " * ", p.p) |
| 96 | +summary(p::ScaledPlan) = string(p.scale, " * ", summary(p.p)) |
| 97 | + |
| 98 | +*(p::ScaledPlan, x::AbstractArray) = scale!(p.p * x, p.scale) |
| 99 | + |
| 100 | +*(α::Number, p::Plan) = ScaledPlan(p, α) |
| 101 | +*(p::Plan, α::Number) = ScaledPlan(p, α) |
| 102 | +*(I::UniformScaling, p::ScaledPlan) = ScaledPlan(p, I.λ) |
| 103 | +*(p::ScaledPlan, I::UniformScaling) = ScaledPlan(p, I.λ) |
| 104 | +*(I::UniformScaling, p::Plan) = ScaledPlan(p, I.λ) |
| 105 | +*(p::Plan, I::UniformScaling) = ScaledPlan(p, I.λ) |
| 106 | + |
| 107 | +# Normalization for ifft, given unscaled bfft, is 1/prod(dimensions) |
| 108 | +normalization(T, sz, region) = one(T) / prod([sz...][[region...]]) |
| 109 | +normalization(X, region) = normalization(real(eltype(X)), size(X), region) |
| 110 | + |
| 111 | +plan_ifft(x::AbstractArray, region; kws...) = |
| 112 | + ScaledPlan(plan_bfft(x, region; kws...), normalization(x, region)) |
| 113 | +plan_ifft!(x::AbstractArray, region; kws...) = |
| 114 | + ScaledPlan(plan_bfft!(x, region; kws...), normalization(x, region)) |
| 115 | + |
| 116 | +plan_inv(p::ScaledPlan) = ScaledPlan(plan_inv(p.p), inv(p.scale)) |
| 117 | + |
| 118 | +A_mul_B!(y::AbstractArray, p::ScaledPlan, x::AbstractArray) = |
| 119 | + scale!(p.scale, A_mul_B!(y, p.p, x)) |
| 120 | + |
| 121 | +############################################################################## |
| 122 | +# Real-input DFTs are annoying because the output has a different size |
| 123 | +# than the input if we want to gain the full factor-of-two(ish) savings |
| 124 | +# For backward real-data transforms, we must specify the original length |
| 125 | +# of the first dimension, since there is no reliable way to detect this |
| 126 | +# from the data (we can't detect whether the dimension was originally even |
| 127 | +# or odd). |
| 128 | + |
| 129 | +for f in (:brfft, :irfft) |
| 130 | + pf = symbol(string("plan_", f)) |
| 131 | + @eval begin |
| 132 | + $f(x::AbstractArray, d::Integer) = $pf(x, d) * x |
| 133 | + $f(x::AbstractArray, d::Integer, region) = $pf(x, d, region) * x |
| 134 | + $pf(x::AbstractArray, d::Integer;kws...) = $pf(x, d, 1:ndims(x);kws...) |
| 135 | + end |
| 136 | +end |
| 137 | + |
| 138 | +for f in (:brfft, :irfft) |
| 139 | + @eval begin |
| 140 | + $f{T<:Real}(x::AbstractArray{T}, d::Integer, region=1:ndims(x)) = $f(complexfloat(x), d, region) |
| 141 | + $f{T<:Union(Integer,Rational)}(x::AbstractArray{Complex{T}}, d::Integer, region=1:ndims(x)) = $f(complexfloat(x), d, region) |
| 142 | + end |
| 143 | +end |
| 144 | + |
| 145 | +function rfft_output_size(x::AbstractArray, region) |
| 146 | + d1 = first(region) |
| 147 | + osize = [size(x)...] |
| 148 | + osize[d1] = osize[d1]>>1 + 1 |
| 149 | + return osize |
| 150 | +end |
| 151 | + |
| 152 | +function brfft_output_size(x::AbstractArray, d::Integer, region) |
| 153 | + d1 = first(region) |
| 154 | + osize = [size(x)...] |
| 155 | + @assert osize[d1] == d>>1 + 1 |
| 156 | + osize[d1] = d |
| 157 | + return osize |
| 158 | +end |
| 159 | + |
| 160 | +plan_irfft{T}(x::AbstractArray{Complex{T}}, d::Integer, region; kws...) = |
| 161 | + ScaledPlan(plan_brfft(x, d, region; kws...), |
| 162 | + normalization(T, brfft_output_size(x, d, region), region)) |
| 163 | + |
| 164 | +############################################################################## |
| 165 | + |
| 166 | +export fftshift, ifftshift |
| 167 | + |
| 168 | +fftshift(x) = circshift(x, div([size(x)...],2)) |
| 169 | + |
| 170 | +function fftshift(x,dim) |
| 171 | + s = zeros(Int,ndims(x)) |
| 172 | + s[dim] = div(size(x,dim),2) |
| 173 | + circshift(x, s) |
| 174 | +end |
| 175 | + |
| 176 | +ifftshift(x) = circshift(x, div([size(x)...],-2)) |
| 177 | + |
| 178 | +function ifftshift(x,dim) |
| 179 | + s = zeros(Int,ndims(x)) |
| 180 | + s[dim] = -div(size(x,dim),2) |
| 181 | + circshift(x, s) |
| 182 | +end |
| 183 | + |
| 184 | +############################################################################## |
| 185 | + |
| 186 | +# FFTW module (may move to an external package at some point): |
| 187 | +if Base.USE_GPL_LIBS |
| 188 | + include("fft/FFTW.jl") |
| 189 | + importall .FFTW |
| 190 | + export FFTW, dct, idct, dct!, idct!, plan_dct, plan_idct, plan_dct!, plan_idct! |
| 191 | +end |
| 192 | + |
| 193 | +############################################################################## |
| 194 | + |
| 195 | +end |
0 commit comments