Skip to content

Commit b3d386c

Browse files
authored
Merge pull request #98 from JuliaMath/an/rfftas2cfft
Compute real 1D FFT for 1D arrays with two complex FFTs when possible
2 parents 651535c + c266455 commit b3d386c

2 files changed

Lines changed: 139 additions & 38 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "FFTA"
22
uuid = "b86e33f2-c0db-4aa1-a6e0-ab43e668529e"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
authors = ["Danny Sharp <dannys4@mit.edu> and contributors"]
55

66
[deps]

src/plan.jl

Lines changed: 138 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,25 @@ struct FFTAPlan_re{T,N} <: FFTAPlan{T,N}
1919
flen::Int
2020
end
2121

22-
Base.size(p::FFTAPlan, i::Int) = i <= length(p.callgraph) ? first(p.callgraph[i].nodes).sz : 1
22+
Base.size(p::FFTAPlan_cx, i::Int) = i <= length(p.callgraph) ? first(p.callgraph[i].nodes).sz : 1
23+
function Base.size(p::FFTAPlan_re{<:Any,1}, i::Int)
24+
if i == p.region[]
25+
p.flen
26+
elseif i <= length(p.callgraph)
27+
first(p.callgraph[i].nodes).sz
28+
else
29+
1
30+
end
31+
end
32+
function Base.size(p::FFTAPlan_re{<:Any,2}, i::Int)
33+
if i == 1
34+
return p.flen
35+
elseif i == 2
36+
first(p.callgraph[2].nodes).sz
37+
else
38+
1
39+
end
40+
end
2341
Base.size(p::FFTAPlan{<:Any,N}) where N = ntuple(Base.Fix1(size, p), Val{N}())
2442

2543
Base.complex(p::FFTAPlan_re{T,N}) where {T,N} = FFTAPlan_cx{T,N}(p.callgraph, p.region, p.dir, p.pinv)
@@ -61,9 +79,14 @@ end
6179
function AbstractFFTs.plan_rfft(x::AbstractArray{T,N}, region; kwargs...)::FFTAPlan_re{Complex{T}} where {T <: Real,N}
6280
FFTN = length(region)
6381
if FFTN == 1
64-
g = CallGraph{Complex{T}}(size(x,region[]))
82+
n = size(x, region[])
83+
# For even length problems, we solve the real problem with
84+
# two n/2 complex FFTs followed by a butterfly. For odd size
85+
# problems, we just solve the problem as a single complex
86+
nn = iseven(n) ? n >> 1 : n
87+
g = CallGraph{Complex{T}}(nn)
6588
pinv = FFTAInvPlan{Complex{T},FFTN}()
66-
return FFTAPlan_re{Complex{T},FFTN}(tuple(g), region, FFT_FORWARD, pinv, size(x,region[]))
89+
return FFTAPlan_re{Complex{T},FFTN}(tuple(g), region, FFT_FORWARD, pinv, n)
6790
elseif FFTN == 2
6891
sort!(region)
6992
g1 = CallGraph{Complex{T}}(size(x,region[1]))
@@ -78,7 +101,11 @@ end
78101
function AbstractFFTs.plan_brfft(x::AbstractArray{T,N}, len, region; kwargs...)::FFTAPlan_re{T} where {T,N}
79102
FFTN = length(region)
80103
if FFTN == 1
81-
g = CallGraph{T}(len)
104+
# For even length problems, we solve the real problem with
105+
# two n/2 complex FFTs followed by a butterfly. For odd size
106+
# problems, we just solve the problem as a single complex
107+
nn = iseven(len) ? len >> 1 : len
108+
g = CallGraph{T}(nn)
82109
pinv = FFTAInvPlan{T,FFTN}()
83110
return FFTAPlan_re{T,FFTN}((g,), region, FFT_BACKWARD, pinv, len)
84111
elseif FFTN == 2
@@ -96,6 +123,7 @@ end
96123
# Multiplication
97124
## mul!
98125
### Complex
126+
#### 1D plan 1D array
99127
function LinearAlgebra.mul!(y::AbstractVector{U}, p::FFTAPlan_cx{T,1}, x::AbstractVector{T}) where {T,U}
100128
if axes(x) != axes(y)
101129
throw(DimensionMismatch("input array has axes $(axes(x)), but output array has axes $(axes(y))"))
@@ -106,6 +134,7 @@ function LinearAlgebra.mul!(y::AbstractVector{U}, p::FFTAPlan_cx{T,1}, x::Abstra
106134
fft!(y, x, 1, 1, p.dir, p.callgraph[1][1].type, p.callgraph[1], 1)
107135
end
108136

137+
#### 1D plan ND array
109138
function LinearAlgebra.mul!(y::AbstractArray{U,N}, p::FFTAPlan_cx{T,1}, x::AbstractArray{T,N}) where {T,U,N}
110139
Base.require_one_based_indexing(x)
111140
if axes(x) != axes(y)
@@ -123,6 +152,7 @@ function LinearAlgebra.mul!(y::AbstractArray{U,N}, p::FFTAPlan_cx{T,1}, x::Abstr
123152
end
124153
end
125154

155+
#### 2D plan ND array
126156
function LinearAlgebra.mul!(y::AbstractArray{U,N}, p::FFTAPlan_cx{T,2}, x::AbstractArray{T,N}) where {T,U,N}
127157
Base.require_one_based_indexing(x)
128158
if axes(x) != axes(y)
@@ -192,11 +222,70 @@ end
192222
function Base.:*(p::FFTAPlan_re{Complex{T},1}, x::AbstractVector{T}) where {T<:Real}
193223
Base.require_one_based_indexing(x)
194224
if p.dir === FFT_FORWARD
195-
x_c = similar(x, Complex{T})
196-
copy!(x_c, x)
197-
y = similar(x_c)
198-
LinearAlgebra.mul!(y, complex(p), x_c)
199-
return y[1:end÷2 + 1]
225+
n = p.flen
226+
if iseven(n)
227+
# For problems of even size, we solve the rfft problem by splitting the
228+
# problem into the even and odd part and solving the simultanously as
229+
# a single (complex) fft of half the size, see equations (6)-(8) of
230+
# Sorensen, H. V., D. Jones, Michael Heideman, and C. Burrus.
231+
# "Real-valued fast Fourier transform algorithms."
232+
# IEEE Transactions on acoustics, speech, and signal processing 35, no. 6 (2003): 849-863.
233+
if x isa Vector && isbitstype(T)
234+
# For a vector of bits, we can just reintepret the bits to get the
235+
# approciate representation of even (zero based) elements as the real
236+
# part and the odd as the complex part
237+
x_c = reinterpret(Complex{T}, x)
238+
else
239+
# for non-bits, we'd have to copy to a new array
240+
x_c = complex.(view(x, 1:2:n), view(x, 2:2:n))
241+
end
242+
243+
m = n >> 1
244+
# Allocate complex result vector of half the input size plus one
245+
y = similar(x_c, m + 1)
246+
# Solve the complex fft of half the size
247+
LinearAlgebra.mul!(view(y, 1:m), complex(p), x_c)
248+
249+
# The w stored in the plan is for m, not n, so probably cheapest to
250+
# just recompute it instead of taking a square root
251+
wj = w = cispi(-T(2) / n)
252+
253+
# Construct the result by first constructing the elements of the
254+
# real and imaginary part, followed by the usual radix-2 assembly,
255+
# see eq (9)
256+
@inbounds begin
257+
y1 = y[1]
258+
y[1] = real(y1) + imag(y1)
259+
y[end] = real(y1) - imag(y1)
260+
for j in 2:((m >> 1) + 1)
261+
yj = y[j]
262+
yjr, yji = real(yj), imag(yj)
263+
ymj = y[m - j + 2]
264+
ymjr, ymji = real(ymj), imag(ymj)
265+
XX = complex(
266+
(yjr + ymjr) * T(0.5),
267+
(yji - ymji) * T(0.5),
268+
)
269+
XY = complex(
270+
(ymji + yji) * T(0.5),
271+
(ymjr - yjr) * T(0.5),
272+
)
273+
y[j] = XX + wj*XY
274+
y[m - j + 2] = conj(XX - wj*XY)
275+
wj *= w
276+
end
277+
end
278+
return y
279+
else
280+
# when the problem cannot be split in two equal size chunks we
281+
# convert the problem to a complex fft and truncate the redundant
282+
# part of the result vector
283+
x_c = similar(x, Complex{T})
284+
copy!(x_c, x)
285+
y = similar(x_c)
286+
LinearAlgebra.mul!(y, complex(p), x_c)
287+
return y[1:end÷2 + 1]
288+
end
200289
end
201290
throw(ArgumentError("only FFT_FORWARD supported for real vectors"))
202291
end
@@ -205,12 +294,43 @@ end
205294
function Base.:*(p::FFTAPlan_re{T,1}, x::AbstractVector{T}) where {T<:Complex}
206295
Base.require_one_based_indexing(x)
207296
if p.dir === FFT_BACKWARD
208-
x_tmp = similar(x, p.flen)
209-
x_tmp[1:end÷2 + 1] .= x
210-
x_tmp[end÷2 + 2:end] .= iseven(p.flen) ? conj.(x[end-1:-1:2]) : conj.(x[end:-1:2])
211-
y = similar(x_tmp)
212-
LinearAlgebra.mul!(y, complex(p), x_tmp)
213-
return real(y)
297+
n = p.flen
298+
# See explantion of this approach in the method for the FORWARD transform
299+
if iseven(n)
300+
m = n >> 1
301+
wj = w = cispi(T(2) / n)
302+
x_tmp = similar(x, length(x) - 1)
303+
x_tmp[1] = complex(
304+
(real(x[1]) + real(x[end])),
305+
(real(x[1]) - real(x[end]))
306+
)
307+
for j in 2:((m >> 1) + 1)
308+
XX = x[j] + conj(x[m - j + 2])
309+
XY = wj*(x[j] - conj(x[m - j + 2]))
310+
x_tmp[j] = complex(
311+
real(XX) - imag(XY),
312+
real(XY) + imag(XX)
313+
)
314+
x_tmp[m - j + 2] = complex(
315+
real(XX) + imag(XY),
316+
real(XY) - imag(XX)
317+
)
318+
wj *= w
319+
end
320+
y_c = complex(p) * x_tmp
321+
if isbitstype(T)
322+
return copy(reinterpret(real(T), y_c))
323+
else
324+
return mapreduce(t -> [real(t); imag(t)], vcat, y_c)
325+
end
326+
else
327+
x_tmp = similar(x, n)
328+
x_tmp[1:end÷2 + 1] .= x
329+
x_tmp[end÷2 + 2:end] .= iseven(n) ? conj.(x[end-1:-1:2]) : conj.(x[end:-1:2])
330+
y = similar(x_tmp)
331+
LinearAlgebra.mul!(y, complex(p), x_tmp)
332+
return real(y)
333+
end
214334
end
215335
throw(ArgumentError("only FFT_BACKWARD supported for complex vectors"))
216336
end
@@ -220,12 +340,7 @@ end
220340
function Base.:*(p::FFTAPlan_re{Complex{T},1}, x::AbstractArray{T,N}) where {T<:Real, N}
221341
Base.require_one_based_indexing(x)
222342
if p.dir === FFT_FORWARD
223-
half_1 = 1:(p.flen ÷ 2 + 1)
224-
x_c = similar(x, Complex{T})
225-
copy!(x_c, x)
226-
y = similar(x_c)
227-
LinearAlgebra.mul!(y, complex(p), x_c)
228-
return copy(selectdim(y, p.region[1], half_1))
343+
return mapslices(Base.Fix1(*, p), x; dims = p.region[1])
229344
end
230345
throw(ArgumentError("only FFT_FORWARD supported for real arrays"))
231346
end
@@ -237,21 +352,7 @@ function Base.:*(p::FFTAPlan_re{T,1}, x::AbstractArray{T,N}) where {T<:Complex,
237352
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[]))"))
238353
end
239354
if p.dir === FFT_BACKWARD
240-
# # for the inverse transformation we have to reconstruct the full array
241-
half_1 = 1:(p.flen ÷ 2 + 1)
242-
half_2 = half_1[end]+1:p.flen
243-
res_size = ntuple(i->ifelse(i==p.region[1], p.flen, size(x,i)), ndims(x))
244-
# for the inverse transformation we have to reconstruct the full array
245-
x_full = similar(x, res_size)
246-
# use first half as is
247-
copy!(selectdim(x_full, p.region[1], half_1), x)
248-
start_reverse = size(x, p.region[1]) - iseven(p.flen)
249-
half_reverse = (start_reverse:-1:2)
250-
# the second half is reversed and conjugated
251-
map!(conj, selectdim(x_full, p.region[1], half_2), selectdim(x, p.region[1], half_reverse))
252-
y = similar(x_full)
253-
LinearAlgebra.mul!(y, complex(p), x_full)
254-
return real(y)
355+
return mapslices(Base.Fix1(*, p), x; dims = p.region[1])
255356
end
256357
throw(ArgumentError("only FFT_BACKWARD supported for complex arrays"))
257358
end
@@ -289,7 +390,7 @@ function Base.:*(p::FFTAPlan_re{T,2}, x::AbstractArray{T,N}) where {T<:Complex,
289390
# the second half in the first transform dimension is reversed and conjugated
290391
x_half_2 = selectdim(x_full, p.region[1], half_2) # view to the second half of x
291392
start_reverse = size(x, p.region[1]) - iseven(p.flen)
292-
393+
293394
map!(conj, x_half_2, selectdim(x, p.region[1], start_reverse:-1:2))
294395
# for the 2D transform we have to reverse index 2:end of the same block in the second transform dimension as well
295396
reverse!(selectdim(x_half_2, p.region[2], 2:size(x, p.region[2])), dims=p.region[2])

0 commit comments

Comments
 (0)