Skip to content

Commit d10d558

Browse files
committed
new DFT api
1 parent 33ff40f commit d10d558

16 files changed

+1416
-1127
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ Library improvements
187187

188188
* New `vecdot` function, analogous to `vecnorm`, for Euclidean inner products over any iterable container ([#11067]).
189189

190+
* `p = plan_fft(x)` and similar functions now return a `Base.DFT.Plan` object, rather
191+
than an anonymous function. Calling it via `p(x)` is deprecated in favor of
192+
`p * x` or `p \ x` (for the inverse), and it can also be used with `A_mul_B!`
193+
to employ pre-allocated output arrays ([#12087]).
194+
190195
* Strings
191196

192197
* NUL-terminated strings should now be passed to C via the new `Cstring` type, not `Ptr{UInt8}` or `Ptr{Cchar}`,
@@ -1511,3 +1516,4 @@ Too numerous to mention.
15111516
[#11922]: https://github.com/JuliaLang/julia/issues/11922
15121517
[#11985]: https://github.com/JuliaLang/julia/issues/11985
15131518
[#12031]: https://github.com/JuliaLang/julia/issues/12031
1519+
[#12087]: https://github.com/JuliaLang/julia/issues/12087

base/complex.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ real(x::Real) = x
3434
imag(x::Real) = zero(x)
3535
reim(z) = (real(z), imag(z))
3636

37+
real{T<:Real}(::Type{T}) = T
38+
real{T<:Real}(::Type{Complex{T}}) = T
39+
3740
isreal(x::Real) = true
3841
isreal(z::Complex) = imag(z) == 0
3942
isimag(z::Number) = real(z) == 0

base/deprecated.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,23 +474,31 @@ export float32_isvalid, float64_isvalid
474474
@deprecate ($)(x::Char, y::Char) Char(UInt32(x) $ UInt32(y))
475475

476476
# 11241
477-
478477
@deprecate is_valid_char(ch::Char) isvalid(ch)
479478
@deprecate is_valid_ascii(str::ASCIIString) isvalid(str)
480479
@deprecate is_valid_utf8(str::UTF8String) isvalid(str)
481480
@deprecate is_valid_utf16(str::UTF16String) isvalid(str)
482481
@deprecate is_valid_utf32(str::UTF32String) isvalid(str)
483-
484482
@deprecate is_valid_char(ch) isvalid(Char, ch)
485483
@deprecate is_valid_ascii(str) isvalid(ASCIIString, str)
486484
@deprecate is_valid_utf8(str) isvalid(UTF8String, str)
487485
@deprecate is_valid_utf16(str) isvalid(UTF16String, str)
488486
@deprecate is_valid_utf32(str) isvalid(UTF32String, str)
489487

490488
# 11379
491-
492489
@deprecate utf32(c::Integer...) UTF32String(UInt32[c...,0])
493490

491+
# 12087
492+
@deprecate call(P::Base.DFT.Plan, A) P * A
493+
for f in (:plan_fft, :plan_ifft, :plan_bfft, :plan_fft!, :plan_ifft!, :plan_bfft!, :plan_rfft)
494+
@eval @deprecate $f(A, dims, flags) $f(A, dims; flags=flags)
495+
@eval @deprecate $f(A, dims, flags, tlim) $f(A, dims; flags=flags, timelimit=tlim)
496+
end
497+
for f in (:plan_brfft, :plan_irfft)
498+
@eval @deprecate $f(A, d, dims, flags) $f(A, d, dims; flags=flags)
499+
@eval @deprecate $f(A, d, dims, flags, tlim) $f(A, d, dims; flags=flags, timelimit=tlim)
500+
end
501+
494502
# 10862
495503

496504
function chol(A::AbstractMatrix, uplo::Symbol)

base/dft.jl

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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

Comments
 (0)