rgsl/fft.rs
1//
2// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
3//
4
5/*!
6# Fast Fourier Transforms (FFTs)
7
8This chapter describes functions for performing Fast Fourier Transforms (FFTs). The library includes radix-2 routines (for lengths which are
9a power of two) and mixed-radix routines (which work for any length). For efficiency there are separate versions of the routines for real data
10and for complex data. The mixed-radix routines are a reimplementation of the FFTPACK library of Paul Swarztrauber. Fortran code for FFTPACK
11is available on Netlib (FFTPACK also includes some routines for sine and cosine transforms but these are currently not available in GSL). For
12details and derivations of the underlying algorithms consult the document GSL FFT Algorithms (see FFT References and Further Reading)
13
14## Mathematical Definitions
15
16Fast Fourier Transforms are efficient algorithms for calculating the discrete Fourier transform (DFT),
17
18x_j = \sum_{k=0}^{n-1} z_k \exp(-2\pi i j k / n)
19
20The DFT usually arises as an approximation to the continuous Fourier transform when functions are sampled at discrete intervals in space or time.
21The naive evaluation of the discrete Fourier transform is a matrix-vector multiplication W\vec{z}. A general matrix-vector multiplication takes
22O(n^2) operations for n data-points. Fast Fourier transform algorithms use a divide-and-conquer strategy to factorize the matrix W into smaller
23sub-matrices, corresponding to the integer factors of the length n. If n can be factorized into a product of integers f_1 f_2 ... f_m then the
24DFT can be computed in O(n \sum f_i) operations. For a radix-2 FFT this gives an operation count of O(n \log_2 n).
25
26All the FFT functions offer three types of transform: forwards, inverse and backwards, based on the same mathematical definitions. The definition
27of the forward Fourier transform, x = FFT(z), is,
28
29x_j = \sum_{k=0}^{n-1} z_k \exp(-2\pi i j k / n)
30
31and the definition of the inverse Fourier transform, x = IFFT(z), is,
32
33z_j = {1 \over n} \sum_{k=0}^{n-1} x_k \exp(2\pi i j k / n).
34The factor of 1/n makes this a true inverse. For example, a call to gsl_fft_complex_forward followed by a call to gsl_fft_complex_inverse should
35return the original data (within numerical errors).
36
37In general there are two possible choices for the sign of the exponential in the transform/ inverse-transform pair. GSL follows the same convention
38as FFTPACK, using a negative exponential for the forward transform. The advantage of this convention is that the inverse transform recreates the
39original function with simple Fourier synthesis. Numerical Recipes uses the opposite convention, a positive exponential in the forward transform.
40
41The backwards FFT is simply our terminology for an unscaled version of the inverse FFT,
42
43z^{backwards}_j = \sum_{k=0}^{n-1} x_k \exp(2\pi i j k / n).
44
45When the overall scale of the result is unimportant it is often convenient to use the backwards FFT instead of the inverse to save unnecessary
46divisions.
47
48## Overview of complex data FFTs
49
50The inputs and outputs for the complex FFT routines are packed arrays of floating point numbers. In a packed array the real and imaginary parts
51of each complex number are placed in alternate neighboring elements. For example, the following definition of a packed array of length 6,
52
53```C
54double x[3*2];
55gsl_complex_packed_array data = x;
56// can be used to hold an array of three complex numbers, z[3], in the following way,
57
58data[0] = Re(z[0])
59data[1] = Im(z[0])
60data[2] = Re(z[1])
61data[3] = Im(z[1])
62data[4] = Re(z[2])
63data[5] = Im(z[2])
64```
65
66The array indices for the data have the same ordering as those in the definition of the DFT—i.e. there are no index transformations or
67permutations of the data.
68
69A stride parameter allows the user to perform transforms on the elements `z[stride*i]` instead of `z[i]`. A stride greater than 1 can be used
70to take an in-place FFT of the column of a matrix. A stride of 1 accesses the array without any additional spacing between elements.
71
72To perform an FFT on a vector argument, such as gsl_vector_complex * v, use the following definitions (or their equivalents) when calling
73the functions described in this chapter:
74
75gsl_complex_packed_array data = v->data;
76size_t stride = v->stride;
77size_t n = v->size;
78For physical applications it is important to remember that the index appearing in the DFT does not correspond directly to a physical frequency.
79If the time-step of the DFT is \Delta then the frequency-domain includes both positive and negative frequencies, ranging from -1/(2\Delta)
80through 0 to +1/(2\Delta). The positive frequencies are stored from the beginning of the array up to the middle, and the negative frequencies
81are stored backwards from the end of the array.
82
83Here is a table which shows the layout of the array data, and the correspondence between the time-domain data z, and the frequency-domain
84data x.
85
86index z x = FFT(z)
87
880 z(t = 0) x(f = 0)
891 z(t = 1) x(f = 1/(n Delta))
902 z(t = 2) x(f = 2/(n Delta))
91. ........ ..................
92n/2 z(t = n/2) x(f = +1/(2 Delta),
93 -1/(2 Delta))
94. ........ ..................
95n-3 z(t = n-3) x(f = -3/(n Delta))
96n-2 z(t = n-2) x(f = -2/(n Delta))
97n-1 z(t = n-1) x(f = -1/(n Delta))
98
99When n is even the location n/2 contains the most positive and negative frequencies (+1/(2 \Delta), -1/(2 \Delta)) which are equivalent. If
100n is odd then general structure of the table above still applies, but n/2 does not appear.
101
102# Radix-2 FFT routines for complex data
103
104The radix-2 algorithms described in this section are simple and compact, although not necessarily the most efficient. They use the Cooley-Tukey
105algorithm to compute in-place complex FFTs for lengths which are a power of 2—no additional storage is required. The corresponding self-sorting
106mixed-radix routines offer better performance at the expense of requiring additional working space.
107
108## Mixed-radix FFT routines for complex data
109
110This section describes mixed-radix FFT algorithms for complex data. The mixed-radix functions work for FFTs of any length. They are a
111reimplementation of Paul Swarztrauber’s Fortran FFTPACK library. The theory is explained in the review article Self-sorting Mixed-radix FFTs
112by Clive Temperton. The routines here use the same indexing scheme and basic algorithms as FFTPACK.
113
114The mixed-radix algorithm is based on sub-transform modules—highly optimized small length FFTs which are combined to create larger FFTs. There
115are efficient modules for factors of 2, 3, 4, 5, 6 and 7. The modules for the composite factors of 4 and 6 are faster than combining the modules
116for 2*2 and 2*3.
117
118For factors which are not implemented as modules there is a fall-back to a general length-n module which uses Singleton’s method for efficiently
119computing a DFT. This module is O(n^2), and slower than a dedicated module would be but works for any length n. Of course, lengths which use the
120general length-n module will still be factorized as much as possible. For example, a length of 143 will be factorized into 11*13. Large prime
121factors are the worst case scenario, e.g. as found in n=2*3*99991, and should be avoided because their O(n^2) scaling will dominate the run-time
122(consult the document GSL FFT Algorithms included in the GSL distribution if you encounter this problem).
123
124The mixed-radix initialization function gsl_fft_complex_wavetable_alloc returns the list of factors chosen by the library for a given length n.
125It can be used to check how well the length has been factorized, and estimate the run-time. To a first approximation the run-time scales as
126n \sum f_i, where the f_i are the factors of n. For programs under user control you may wish to issue a warning that the transform will be slow
127when the length is poorly factorized. If you frequently encounter data lengths which cannot be factorized using the existing small-prime modules
128consult GSL FFT Algorithms for details on adding support for other factors.
129
130## Overview of real data FFTs
131
132The functions for real data are similar to those for complex data. However, there is an important difference between forward and inverse transforms.
133The Fourier transform of a real sequence is not real. It is a complex sequence with a special symmetry:
134
135z_k = z_{n-k}^*
136
137A sequence with this symmetry is called conjugate-complex or half-complex. This different structure requires different storage layouts for the
138forward transform (from real to half-complex) and inverse transform (from half-complex back to real). As a consequence the routines are divided
139into two sets: functions in gsl_fft_real which operate on real sequences and functions in gsl_fft_halfcomplex which operate on half-complex sequences.
140
141Functions in gsl_fft_real compute the frequency coefficients of a real sequence. The half-complex coefficients c of a real sequence x are given
142by Fourier analysis,
143
144c_k = \sum_{j=0}^{n-1} x_j \exp(-2 \pi i j k /n)
145
146Functions in gsl_fft_halfcomplex compute inverse or backwards transforms. They reconstruct real sequences by Fourier synthesis from their half-complex
147frequency coefficients, c,
148
149x_j = {1 \over n} \sum_{k=0}^{n-1} c_k \exp(2 \pi i j k /n)
150
151The symmetry of the half-complex sequence implies that only half of the complex numbers in the output need to be stored. The remaining half can be
152reconstructed using the half-complex symmetry condition. This works for all lengths, even and odd—when the length is even the middle value where k=n/2
153is also real. Thus only n real numbers are required to store the half-complex sequence, and the transform of a real sequence can be stored in the
154same size array as the original data.
155
156The precise storage arrangements depend on the algorithm, and are different for radix-2 and mixed-radix routines. The radix-2 function operates
157in-place, which constrains the locations where each element can be stored. The restriction forces real and imaginary parts to be stored far apart.
158The mixed-radix algorithm does not have this restriction, and it stores the real and imaginary parts of a given term in neighboring locations (which
159is desirable for better locality of memory accesses).
160!*/
161
162/// These functions compute forward, backward and inverse FFTs of length n with stride stride, on the packed complex array data using an in-place radix-2
163/// decimation-in-time algorithm. The length of the transform is restricted to powers of two. For the transform version of the function
164/// the sign argument can be either forward (-1) or backward (+1).
165///
166/// The functions return a value of crate::Value::Success if no errors were detected, or Value::Dom if the length n is not a power of two.
167pub mod radix2 {
168 use crate::Value;
169
170 #[doc(alias = "gsl_fft_complex_radix2_forward")]
171 pub fn forward(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
172 let ret = unsafe { sys::gsl_fft_complex_radix2_forward(data.as_mut_ptr(), stride, n) };
173 result_handler!(ret, ())
174 }
175
176 #[doc(alias = "gsl_fft_complex_radix2_transform")]
177 pub fn transform(
178 data: &mut [f64],
179 stride: usize,
180 n: usize,
181 sign: ::FftDirection,
182 ) -> Result<(), Value> {
183 let ret = unsafe {
184 sys::gsl_fft_complex_radix2_transform(data.as_mut_ptr(), stride, n, sign.into())
185 };
186 result_handler!(ret, ())
187 }
188
189 #[doc(alias = "gsl_fft_complex_radix2_backward")]
190 pub fn backward(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
191 let ret = unsafe { sys::gsl_fft_complex_radix2_backward(data.as_mut_ptr(), stride, n) };
192 result_handler!(ret, ())
193 }
194
195 #[doc(alias = "gsl_fft_complex_radix2_inverse")]
196 pub fn inverse(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
197 let ret = unsafe { sys::gsl_fft_complex_radix2_inverse(data.as_mut_ptr(), stride, n) };
198 result_handler!(ret, ())
199 }
200
201 /// This is decimation-in-frequency version of the radix-2 FFT function.
202 #[doc(alias = "gsl_fft_complex_radix2_dif_forward")]
203 pub fn dif_forward(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
204 let ret = unsafe { sys::gsl_fft_complex_radix2_dif_forward(data.as_mut_ptr(), stride, n) };
205 result_handler!(ret, ())
206 }
207
208 /// This is decimation-in-frequency version of the radix-2 FFT function.
209 #[doc(alias = "gsl_fft_complex_radix2_dif_transform")]
210 pub fn dif_transform(
211 data: &mut [f64],
212 stride: usize,
213 n: usize,
214 sign: ::FftDirection,
215 ) -> Result<(), Value> {
216 let ret = unsafe {
217 sys::gsl_fft_complex_radix2_dif_transform(data.as_mut_ptr(), stride, n, sign.into())
218 };
219 result_handler!(ret, ())
220 }
221
222 /// This is decimation-in-frequency version of the radix-2 FFT function.
223 #[doc(alias = "gsl_fft_complex_radix2_dif_backward")]
224 pub fn dif_backward(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
225 let ret = unsafe { sys::gsl_fft_complex_radix2_dif_backward(data.as_mut_ptr(), stride, n) };
226 result_handler!(ret, ())
227 }
228
229 /// This is decimation-in-frequency version of the radix-2 FFT function.
230 #[doc(alias = "gsl_fft_complex_radix2_dif_inverse")]
231 pub fn dif_inverse(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
232 let ret = unsafe { sys::gsl_fft_complex_radix2_dif_inverse(data.as_mut_ptr(), stride, n) };
233 result_handler!(ret, ())
234 }
235}
236
237/// This section describes radix-2 FFT algorithms for real data. They use the Cooley-Tukey algorithm to compute in-place FFTs for lengths which
238/// are a power of 2.
239pub mod real_radix2 {
240 use crate::Value;
241
242 /// This function computes an in-place radix-2 FFT of length n and stride stride on the real array data. The output is a half-complex sequence,
243 /// which is stored in-place. The arrangement of the half-complex terms uses the following scheme: for k < n/2 the real part of the k-th term
244 /// is stored in location k, and the corresponding imaginary part is stored in location n-k. Terms with k > n/2 can be reconstructed using the
245 /// symmetry z_k = z^*_{n-k}. The terms for k=0 and k=n/2 are both purely real, and count as a special case. Their real parts are stored in
246 /// locations 0 and n/2 respectively, while their imaginary parts which are zero are not stored.
247 ///
248 /// The following table shows the correspondence between the output data and the equivalent results obtained by considering the input data as
249 /// a complex sequence with zero imaginary part (assuming stride=1),
250 ///
251 /// ```text
252 /// complex[0].real = data[0]
253 /// complex[0].imag = 0
254 /// complex[1].real = data[1]
255 /// complex[1].imag = data[n-1]
256 /// ............... ................
257 /// complex[k].real = data[k]
258 /// complex[k].imag = data[n-k]
259 /// ............... ................
260 /// complex[n/2].real = data[n/2]
261 /// complex[n/2].imag = 0
262 /// ............... ................
263 /// complex[k'].real = data[k] k' = n - k
264 /// complex[k'].imag = -data[n-k]
265 /// ............... ................
266 /// complex[n-1].real = data[1]
267 /// complex[n-1].imag = -data[n-1]
268 /// ```
269 ///
270 /// Note that the output data can be converted into the full complex sequence using the function gsl_fft_halfcomplex_radix2_unpack described
271 /// below.
272 #[doc(alias = "gsl_fft_real_radix2_transform")]
273 pub fn transform(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
274 let ret = unsafe { sys::gsl_fft_real_radix2_transform(data.as_mut_ptr(), stride, n) };
275 result_handler!(ret, ())
276 }
277
278 /// This function computes the inverse or backwards in-place radix-2 FFT of length n and stride stride on the half-complex sequence data
279 /// stored according the output scheme used by gsl_fft_real_radix2. The result is a real array stored in natural order.
280 #[doc(alias = "gsl_fft_halfcomplex_radix2_inverse")]
281 pub fn inverse(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
282 let ret = unsafe { sys::gsl_fft_halfcomplex_radix2_inverse(data.as_mut_ptr(), stride, n) };
283 result_handler!(ret, ())
284 }
285
286 /// This function computes the inverse or backwards in-place radix-2 FFT of length n and stride stride on the half-complex sequence data
287 /// stored according the output scheme used by gsl_fft_real_radix2. The result is a real array stored in natural order.
288 #[doc(alias = "gsl_fft_halfcomplex_radix2_backward")]
289 pub fn backward(data: &mut [f64], stride: usize, n: usize) -> Result<(), Value> {
290 let ret = unsafe { sys::gsl_fft_halfcomplex_radix2_backward(data.as_mut_ptr(), stride, n) };
291 result_handler!(ret, ())
292 }
293
294 /// This function converts halfcomplex_coefficient, an array of half-complex coefficients as returned by gsl_fft_real_radix2_transform,
295 /// into an ordinary complex array, complex_coefficient. It fills in the complex array using the symmetry z_k = z_{n-k}^* to reconstruct
296 /// the redundant elements. The algorithm for the conversion is,
297 ///
298 /// ```C
299 /// complex_coefficient[0].real
300 /// = halfcomplex_coefficient[0];
301 /// complex_coefficient[0].imag
302 /// = 0.0;
303 ///
304 /// for (i = 1; i < n - i; i++)
305 /// {
306 /// double hc_real
307 /// = halfcomplex_coefficient[i*stride];
308 /// double hc_imag
309 /// = halfcomplex_coefficient[(n-i)*stride];
310 /// complex_coefficient[i*stride].real = hc_real;
311 /// complex_coefficient[i*stride].imag = hc_imag;
312 /// complex_coefficient[(n - i)*stride].real = hc_real;
313 /// complex_coefficient[(n - i)*stride].imag = -hc_imag;
314 /// }
315 ///
316 /// if (i == n - i)
317 /// {
318 /// complex_coefficient[i*stride].real
319 /// = halfcomplex_coefficient[(n - 1)*stride];
320 /// complex_coefficient[i*stride].imag
321 /// = 0.0;
322 /// }
323 /// ```
324 #[doc(alias = "gsl_fft_halfcomplex_radix2_unpack")]
325 pub fn unpack(
326 halfcomplex_coefficient: &mut [f64],
327 complex_coefficient: &mut [f64],
328 stride: usize,
329 n: usize,
330 ) -> Result<(), Value> {
331 let ret = unsafe {
332 sys::gsl_fft_halfcomplex_radix2_unpack(
333 halfcomplex_coefficient.as_mut_ptr(),
334 complex_coefficient.as_mut_ptr(),
335 stride,
336 n,
337 )
338 };
339 result_handler!(ret, ())
340 }
341}