1 // ******************************************************************************
2 //
3 // Title: Force Field X.
4 // Description: Force Field X - Software for Molecular Biophysics.
5 // Copyright: Copyright (c) Michael J. Schnieders 2001-2025.
6 //
7 // This file is part of Force Field X.
8 //
9 // Force Field X is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License version 3 as published by
11 // the Free Software Foundation.
12 //
13 // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 // details.
17 //
18 // You should have received a copy of the GNU General Public License along with
19 // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20 // Place, Suite 330, Boston, MA 02111-1307 USA
21 //
22 // Linking this library statically or dynamically with other modules is making a
23 // combined work based on this library. Thus, the terms and conditions of the
24 // GNU General Public License cover the whole combination.
25 //
26 // As a special exception, the copyright holders of this library give you
27 // permission to link this library with independent modules to produce an
28 // executable, regardless of the license terms of these independent modules, and
29 // to copy and distribute the resulting executable under terms of your choice,
30 // provided that you also meet, for each linked independent module, the terms
31 // and conditions of the license of that module. An independent module is a
32 // module which is not derived from or based on this library. If you modify this
33 // library, you may extend this exception to your version of the library, but
34 // you are not obligated to do so. If you do not wish to do so, delete this
35 // exception statement from your version.
36 //
37 // ******************************************************************************
38 package ffx.numerics.fft;
39
40 import java.util.Arrays;
41 import java.util.Random;
42 import java.util.Vector;
43 import java.util.logging.Level;
44 import java.util.logging.Logger;
45
46 import static java.lang.Integer.max;
47 import static java.lang.Math.fma;
48 import static java.lang.System.arraycopy;
49 import static org.apache.commons.math3.util.FastMath.PI;
50 import static org.apache.commons.math3.util.FastMath.cos;
51 import static org.apache.commons.math3.util.FastMath.sin;
52
53 /**
54 * Compute the FFT of complex, double precision data of arbitrary length n. This class uses a mixed
55 * radix method and has special methods to handle factors [2, 3, 4, 5, 6, 7] and a general method for
56 * larger prime factors.
57 *
58 * @author Michal J. Schnieders<br>
59 * Derived from:
60 * <br>
61 * Bruce R. Miller (bruce.miller@nist.gov)
62 * <br>
63 * Contribution of the National Institute of Standards and Technology, not subject to copyright.
64 * <br>
65 * Derived from:
66 * <br>
67 * GSL (Gnu Scientific Library) FFT Code by Brian Gough (bjg@network-theory.co.uk)
68 * @see <ul>
69 * <li><a href="http://dx.doi.org/10.1016/0021-9991(83)90013-X" target="_blank"> Clive
70 * Temperton. Self-sorting mixed-radix fast fourier transforms. Journal of Computational
71 * Physics, 52(1):1-23, 1983. </a>
72 * <li><a href="http://www.jstor.org/stable/2003354" target="_blank"> J. W. Cooley and J. W.
73 * Tukey, Mathematics of Computation 19 (90), 297 (1965) </a>
74 * <li><a href="http://en.wikipedia.org/wiki/Fast_Fourier_transform" target="_blank">FFT at Wikipedia </a>
75 * </ul>
76 * @since 1.0
77 */
78 public class Complex {
79
80 private static final Logger logger = Logger.getLogger(Complex.class.getName());
81 // TINKER v. 5.0 factors to achieve exact numerical agreement.
82 // private static final int[] availableFactors = {5, 4, 3, 2};
83 // private static final int firstUnavailablePrime = 7;
84 private static final int[] availableFactors = {7, 6, 5, 4, 3, 2};
85 private static final int firstUnavailablePrime = 11;
86 /**
87 * Number of complex numbers in the transform.
88 */
89 private final int n;
90 /**
91 * The number of FFTs to process (default = 1).
92 */
93 private final int nFFTs;
94 /**
95 * Offset to the imaginary part of the input
96 * This is 1 for interleaved data.
97 * For blocked data, a typical offset is n in 1-dimension (or nX*nY + n in 2D).
98 */
99 private final int externalIm;
100 /**
101 * Offset to the imaginary part of the input.
102 * Internally this is 1 (for interleaved data) or n (for blocked data).
103 * <p>
104 * The internal imaginary offset cannot be larger than n due to use of a single scratch array of length 2n.
105 */
106 private final int im;
107 /**
108 * Internal increment for packing data (1 for blocked and 2 for interleaved data).
109 */
110 private final int ii;
111 /**
112 * Factorization of n.
113 */
114 private final int[] factors;
115 /**
116 * Twiddle factors.
117 */
118 private final double[][][] twiddle;
119 /**
120 * Packing of non-contiguous data.
121 */
122 private final double[] packedData;
123 /**
124 * Scratch space for the transform.
125 */
126 private final double[] scratch;
127 /**
128 * Constants for each pass.
129 */
130 private final MixedRadixFactor[] mixedRadixFactors;
131 /**
132 * Pass specific data for the transform with
133 * references to the input and output data arrays.
134 */
135 private final PassData[] passData;
136 /**
137 * Use SIMD operators.
138 */
139 private boolean useSIMD;
140 /**
141 * Minimum SIMD loop length set to the preferred SIMD vector length.
142 */
143 private int minSIMDLoopLength;
144
145 /**
146 * Cache the last input dimension.
147 */
148 private static int lastN = -1;
149 /**
150 * Cache the last imaginary offset.
151 */
152 private static int lastIm = -1;
153 /**
154 * Cache the last nFFT size.
155 */
156 private static int lastNFFTs = -1;
157 /**
158 * Cache the last set of radix factors.
159 */
160 private static int[] factorsCache;
161 /**
162 * Cache the last set tiddle factors.
163 */
164 private static double[][][] twiddleCache = null;
165 /**
166 * Cache the last set of radix factors. These classes are static and thread-safe.
167 */
168 private static MixedRadixFactor[] mixedRadixFactorsCache = null;
169
170 /**
171 * Construct a Complex instance for interleaved data of length n. Factorization of n is designed to use special
172 * methods for small factors, and a general routine for large odd prime factors. Scratch memory is
173 * created of length 2*n, which is reused each time a transform is computed.
174 *
175 * @param n Number of complex numbers (n .GT. 1).
176 */
177 public Complex(int n) {
178 this(n, DataLayout1D.INTERLEAVED, 1);
179 }
180
181 /**
182 * Construct a Complex instance for data of length n.
183 * The offset to each imaginary part relative to the real part is given by im.
184 * Factorization of n is designed to use special methods for small factors.
185 * Scratch memory is created of length 2*n, which is reused each time a transform is computed.
186 *
187 * @param n Number of complex numbers (n .GT. 1).
188 * @param dataLayout Data layout (interleaved or blocked).
189 * @param imOffset Offset to the imaginary part of each complex number relative to its real part.
190 */
191 public Complex(int n, DataLayout1D dataLayout, int imOffset) {
192 this(n, dataLayout, imOffset, 1);
193 }
194
195 /**
196 * Construct a Complex instance for data of length n.
197 * The offset to each imaginary part relative to the real part is given by im.
198 * Factorization of n is designed to use special methods for small factors.
199 * Scratch memory is created of length 2*n, which is reused each time a transform is computed.
200 *
201 * @param n Number of complex numbers (n .GT. 1).
202 * @param dataLayout Data layout (interleaved or blocked).
203 * @param imOffset Offset to the imaginary part of each complex number relative to its real part.
204 * @param nFFTs Number of FFTs to process (default = 1).
205 */
206 public Complex(int n, DataLayout1D dataLayout, int imOffset, int nFFTs) {
207 assert (n > 1);
208 this.n = n;
209 this.nFFTs = nFFTs;
210 this.externalIm = imOffset;
211
212 /*
213 * The internal imaginary offset is always 1 or n.
214 *
215 * If the external imaginary offset is greater than n,
216 * the data will be packed into a contiguous array.
217 */
218 if (dataLayout == DataLayout1D.INTERLEAVED) {
219 im = 1;
220 ii = 2;
221 } else {
222 im = n * nFFTs;
223 ii = 1;
224 }
225 packedData = new double[2 * n * nFFTs];
226 scratch = new double[2 * n * nFFTs];
227 passData = new PassData[2];
228 passData[0] = new PassData(1, packedData, 0, scratch, 0);
229 passData[1] = new PassData(1, packedData, 0, scratch, 0);
230
231 // For a 3D transform with 64 threads, there will be 3 * 64 = 192 transforms created.
232 // To conserve memory, the most recent set of twiddles and mixed radix factors are cached.
233 // Successive transforms with the same dimension and imaginary offset will reuse the cached values.
234 // Synchronize the creation of the twiddle factors.
235 synchronized (Complex.class) {
236 // The last set of factors, twiddles and mixed radix factors will be reused.
237 if (this.n == lastN && this.im == lastIm && this.nFFTs == lastNFFTs) {
238 factors = factorsCache;
239 twiddle = twiddleCache;
240 mixedRadixFactors = mixedRadixFactorsCache;
241 } else {
242 // The cache cannot be reused and will be updated.
243 factors = factor(n);
244 twiddle = wavetable(n, factors);
245 mixedRadixFactors = new MixedRadixFactor[factors.length];
246 lastN = this.n;
247 lastIm = this.im;
248 lastNFFTs = this.nFFTs;
249 factorsCache = factors;
250 twiddleCache = twiddle;
251 mixedRadixFactorsCache = mixedRadixFactors;
252 // Allocate space for each pass and radix instances for each factor.
253 int product = 1;
254 for (int i = 0; i < factors.length; i++) {
255 final int factor = factors[i];
256 product *= factor;
257 PassConstants passConstants = new PassConstants(n, im, nFFTs, factor, product, twiddle[i]);
258 switch (factor) {
259 case 2 -> mixedRadixFactors[i] = new MixedRadixFactor2(passConstants);
260 case 3 -> mixedRadixFactors[i] = new MixedRadixFactor3(passConstants);
261 case 4 -> mixedRadixFactors[i] = new MixedRadixFactor4(passConstants);
262 case 5 -> mixedRadixFactors[i] = new MixedRadixFactor5(passConstants);
263 case 6 -> mixedRadixFactors[i] = new MixedRadixFactor6(passConstants);
264 case 7 -> mixedRadixFactors[i] = new MixedRadixFactor7(passConstants);
265 default -> {
266 if (dataLayout == DataLayout1D.BLOCKED) {
267 throw new IllegalArgumentException(
268 " Prime factors greater than 7 are only supported for interleaved data: " + factor);
269 }
270 mixedRadixFactors[i] = new MixedRadixFactorPrime(passConstants);
271 }
272 }
273 }
274 }
275
276 // Do not use SIMD by default for now.
277 useSIMD = false;
278 String simd = System.getProperty("fft.useSIMD", Boolean.toString(useSIMD));
279 try {
280 useSIMD = Boolean.parseBoolean(simd);
281 } catch (Exception e) {
282 logger.info(" Invalid value for fft.useSIMD: " + simd);
283 useSIMD = false;
284 }
285
286 // Minimum SIMD inner loop length.
287 // For interleaved data, the default minimum SIMD loop length is 1 using AVX-128 (1 Real and 1 Imaginary per load).
288 // For blocked data, the default minimum SIMD loop length is 2 using AVX-128 (2 Real or 2 Imaginary per load).
289 // Setting this value higher than one reverts to scalar operations for short inner loop lengths.
290 if (im == 1) {
291 // Interleaved data.
292 minSIMDLoopLength = MixedRadixFactor.LENGTH / 2;
293 } else {
294 // Blocked data.
295 minSIMDLoopLength = MixedRadixFactor.LENGTH;
296 }
297 String loop = System.getProperty("fft.minLoop", Integer.toString(minSIMDLoopLength));
298 try {
299 minSIMDLoopLength = max(minSIMDLoopLength, Integer.parseInt(loop));
300 } catch (Exception e) {
301 logger.info(" Invalid value for fft.minLoop: " + loop);
302 if (im == 1) {
303 // Interleaved data.
304 minSIMDLoopLength = MixedRadixFactor.LENGTH / 2;
305 } else {
306 // Blocked data.
307 minSIMDLoopLength = MixedRadixFactor.LENGTH;
308 }
309 }
310 }
311 }
312
313 /**
314 * String representation of the Complex FFT.
315 *
316 * @return a String.
317 */
318 @Override
319 public String toString() {
320 StringBuilder sb = new StringBuilder(" Complex FFT: n = " + n + ", nFFTs = " + nFFTs + ", im = " + externalIm);
321 sb.append("\n Factors: ").append(Arrays.toString(factors));
322 return sb.toString();
323 }
324
325 /**
326 * Configure use of SIMD operators.
327 *
328 * @param useSIMD True to use SIMD operators.
329 */
330 public void setUseSIMD(boolean useSIMD) {
331 this.useSIMD = useSIMD;
332 }
333
334 /**
335 * Configure the minimum SIMD inner loop length.
336 * For interleaved data, the default minimum SIMD loop length is 1 using AVX-128 (1 Real and 1 Imaginary per load).
337 * For blocked data, the default minimum SIMD loop length is 1 using AVX-128 (2 Real or 2 Imaginary per load).
338 * Setting this value higher than one reverts to scalar operations for short inner loop lengths.
339 *
340 * @param minSIMDLoopLength Minimum SIMD inner loop length.
341 */
342 public void setMinSIMDLoopLength(int minSIMDLoopLength) {
343 if (im == 1 && minSIMDLoopLength < 1) {
344 throw new IllegalArgumentException(" Minimum SIMD loop length for interleaved data is 1 or greater.");
345 }
346 if (im > 2 && minSIMDLoopLength < 2) {
347 throw new IllegalArgumentException(" Minimum SIMD loop length for blocked data is 2 or greater.");
348 }
349 this.minSIMDLoopLength = minSIMDLoopLength;
350 }
351
352 /**
353 * Check if a dimension is a preferred dimension.
354 *
355 * @param dim the dimension to check.
356 * @return true if the dimension is a preferred dimension.
357 */
358 public static boolean preferredDimension(int dim) {
359 if (dim < 2) {
360 return false;
361 }
362
363 // Apply preferred factors.
364 for (int factor : availableFactors) {
365 while ((dim % factor) == 0) {
366 dim /= factor;
367 }
368 }
369 return dim <= 1;
370 }
371
372 /**
373 * Getter for the field <code>factors</code>.
374 *
375 * @return an array of int.
376 */
377 public int[] getFactors() {
378 return factors;
379 }
380
381 /**
382 * Compute the Fast Fourier Transform of data leaving the result in data. The array data must
383 * contain the data points in the following locations:
384 *
385 * <PRE>
386 * Re(d[i]) = data[offset + stride*i]
387 * Im(d[i]) = data[offset + stride*i + im]
388 * </PRE>
389 * <p>
390 * where im is 1 for interleaved data or a constant set when the class was constructed.
391 *
392 * @param data an array of double.
393 * @param offset the offset to the beginning of the data.
394 * @param stride the stride between data points.
395 */
396 public void fft(double[] data, int offset, int stride) {
397 transformInternal(data, offset, stride, -1, 2 * n);
398 }
399
400 /**
401 * Compute the Fast Fourier Transform of data leaving the result in data.
402 * The array data must contain the data points in the following locations:
403 *
404 * <PRE>
405 * Re(d[i]) = data[offset + stride*i] + k * nextFFT
406 * Im(d[i]) = data[offset + stride*i + im] + k * nextFFT
407 * </PRE>
408 * <p>
409 * where im is 1 for interleaved data or a constant set when the class was constructed.
410 * The value of k is the FFT number (0 to nFFTs-1).
411 * The value of nextFFT is the stride between FFT data sets. The nextFFT value is ignored if the number of FFTs is 1.
412 *
413 * @param data an array of double.
414 * @param offset the offset to the beginning of the data.
415 * @param stride the stride between data points.
416 * @param nextFFT the offset to the beginning of the next FFT when nFFTs > 1.
417 */
418 public void fft(double[] data, int offset, int stride, int nextFFT) {
419 transformInternal(data, offset, stride, -1, nextFFT);
420 }
421
422 /**
423 * Compute the (un-normalized) inverse FFT of data, leaving it in place. The frequency domain data
424 * must be in wrap-around order, and be stored in the following locations:
425 *
426 * <PRE>
427 * Re(D[i]) = data[offset + stride*i]
428 * Im(D[i]) = data[offset + stride*i + im]
429 * </PRE>
430 *
431 * @param data an array of double.
432 * @param offset the offset to the beginning of the data.
433 * @param stride the stride between data points.
434 */
435 public void ifft(double[] data, int offset, int stride) {
436 transformInternal(data, offset, stride, +1, 2 * n);
437 }
438
439 /**
440 * Compute the (un-normalized) inverse FFT of data, leaving it in place. The frequency domain data
441 * must be in wrap-around order, and be stored in the following locations:
442 *
443 * <PRE>
444 * Re(d[i]) = data[offset + stride*i] + k * nextFFT
445 * Im(d[i]) = data[offset + stride*i + im] + k * nextFFT
446 * </PRE>
447 * <p>
448 * where im is 1 for interleaved data or a constant set when the class was constructed.
449 * The value of k is the FFT number (0 to nFFTs-1).
450 * The value of nextFFT is the stride between FFT data sets. The nextFFT value is ignored if the number of FFTs is 1.
451 *
452 * @param data an array of double.
453 * @param offset the offset to the beginning of the data.
454 * @param stride the stride between data points.
455 * @param nextFFT the offset to the beginning of the next FFT when nFFTs > 1.
456 */
457 public void ifft(double[] data, int offset, int stride, int nextFFT) {
458 transformInternal(data, offset, stride, +1, nextFFT);
459 }
460
461 /**
462 * Compute the normalized inverse FFT of data, leaving it in place. The frequency domain data must
463 * be stored in the following locations:
464 *
465 * <PRE>
466 * Re(d[i]) = data[offset + stride*i]
467 * Im(d[i]) = data[offset + stride*i + im]
468 * </PRE>
469 * <p>
470 * where im is 1 for interleaved data or a constant set when the class was constructed.
471 *
472 * @param data an array of double.
473 * @param offset the offset to the beginning of the data.
474 * @param stride the stride between data points.
475 */
476 public void inverse(double[] data, int offset, int stride) {
477 inverse(data, offset, stride, 2 * n);
478 }
479
480 /**
481 * Compute the normalized inverse FFT of data, leaving it in place. The frequency domain data must
482 * be stored in the following locations:
483 *
484 * <PRE>
485 * Re(d[i]) = data[offset + stride*i] + k * nextFFT
486 * Im(d[i]) = data[offset + stride*i + im] + k * nextFFT
487 * </PRE>
488 * <p>
489 * where im is 1 for interleaved data or a constant set when the class was constructed.
490 * The value of k is the FFT number (0 to nFFTs-1).
491 * The value of nextFFT is the stride between FFT data sets. The nextFFT value is ignored if the number of FFTs is 1.
492 *
493 * @param data an array of double.
494 * @param offset the offset to the beginning of the data.
495 * @param stride the stride between data points.
496 * @param nextFFT the offset to the beginning of the next FFT when nFFTs > 1.
497 */
498 public void inverse(double[] data, int offset, int stride, int nextFFT) {
499 ifft(data, offset, stride, nextFFT);
500
501 // Normalize inverse FFT with 1/n.
502 double norm = normalization();
503 int index = 0;
504 for (int f = 0; f < nFFTs; f++) {
505 for (int i = 0; i < 2 * n; i++) {
506 data[index++] *= norm;
507 }
508 }
509 }
510
511 /**
512 * Compute the Fast Fourier Transform of data leaving the result in data.
513 *
514 * @param data data an array of double.
515 * @param offset the offset to the beginning of the data.
516 * @param stride the stride between data points.
517 * @param sign the sign to apply (forward -1 and inverse 1).
518 * @param nextFFT the offset to the beginning of the next FFT when nFFTs > 1.
519 */
520 private void transformInternal(
521 final double[] data, final int offset, final int stride, final int sign, final int nextFFT) {
522
523 // Even pass data.
524 passData[0].sign = sign;
525 passData[0].in = data;
526 passData[0].inOffset = offset;
527 passData[0].out = scratch;
528 passData[0].outOffset = 0;
529 // Odd pass data.
530 passData[1].sign = sign;
531 passData[1].in = scratch;
532 passData[1].inOffset = 0;
533 passData[1].out = data;
534 passData[1].outOffset = offset;
535
536 // Configure the pass data for the transform.
537 boolean packed = false;
538 if (stride > 2 || externalIm > n * nFFTs) {
539 // System.out.println(" Packing data: " + n + " offset " + offset + " stride " + stride + " nextFFT " + nextFFT + " n * nFFTs " + (n * nFFTs) + " externalIm " + externalIm);
540 // Pack non-contiguous (stride > 2) data into a contiguous array.
541 packed = true;
542 pack(data, offset, stride, nextFFT);
543 // The even pass input data is in the packedData array with no offset.
544 passData[0].in = packedData;
545 passData[0].inOffset = 0;
546 // The odd pass input data is in the scratch array with no offset.
547 passData[1].out = packedData;
548 passData[1].outOffset = 0;
549 }
550
551 // Perform the FFT by looping over the factors.
552 final int nfactors = factors.length;
553 for (int i = 0; i < nfactors; i++) {
554 final int pass = i % 2;
555 MixedRadixFactor mixedRadixFactor = mixedRadixFactors[i];
556 if (useSIMD && mixedRadixFactor.innerLoopLimit >= minSIMDLoopLength) {
557 mixedRadixFactor.passSIMD(passData[pass]);
558 } else {
559 mixedRadixFactor.passScalar(passData[pass]);
560 }
561
562 }
563
564 // If the number of factors is odd, the final result is in the scratch array.
565 if (nfactors % 2 == 1) {
566 // Copy the scratch array to the data array.
567 if (stride <= 2 && (im == externalIm) && nextFFT == 2 * n) {
568 arraycopy(scratch, 0, data, offset, 2 * n * nFFTs);
569 } else {
570 unpack(scratch, data, offset, stride, nextFFT);
571 }
572 // If the number of factors is even, the data may need to be unpacked.
573 } else if (packed) {
574 unpack(packedData, data, offset, stride, nextFFT);
575 }
576 }
577
578 /**
579 * Pack the data into a contiguous array.
580 *
581 * @param data the data to pack.
582 * @param offset the offset to the first point data.
583 * @param stride the stride between data points.
584 * @param nextFFT the stride between FFTs.
585 */
586 private void pack(double[] data, int offset, int stride, int nextFFT) {
587 int i = 0;
588 for (int f = 0; f < nFFTs; f++) {
589 int inputOffset = offset + f * nextFFT;
590 for (int index = inputOffset, k = 0; k < n; k++, i += ii, index += stride) {
591 packedData[i] = data[index];
592 packedData[i + im] = data[index + externalIm];
593 }
594 }
595 }
596
597 /**
598 * Pack the data into a contiguous array.
599 *
600 * @param data the location to unpack the data to.
601 * @param offset the offset to the first point data.
602 * @param stride the stride between data points.
603 * @param nextFFT the stride between FFTs.
604 */
605 private void unpack(double[] source, double[] data, int offset, int stride, int nextFFT) {
606 int i = 0;
607 for (int f = 0; f < nFFTs; f++) {
608 int outputOffset = offset + f * nextFFT;
609 for (int index = outputOffset, k = 0; k < n; k++, i += ii, index += stride) {
610 data[index] = source[i];
611 data[index + externalIm] = source[i + im];
612 }
613 }
614 }
615
616 /**
617 * Return the normalization factor. Multiply the elements of the back-transformed data to get the
618 * normalized inverse.
619 *
620 * @return a double.
621 */
622 private double normalization() {
623 return 1.0 / n;
624 }
625
626 /**
627 * Factor the data length into preferred factors (those with special methods), falling back to odd
628 * primes that the general routine must handle.
629 *
630 * @param n the length of the data.
631 * @return integer factors
632 */
633 private static int[] factor(int n) {
634 if (n < 2) {
635 return null;
636 }
637 Vector<Integer> v = new Vector<>();
638 int nTest = n;
639
640 // Use the preferred factors first
641 for (int factor : availableFactors) {
642 while ((nTest % factor) == 0) {
643 nTest /= factor;
644 v.add(factor);
645 }
646 }
647
648 // Unavailable odd prime factors.
649 int factor = firstUnavailablePrime;
650 while (nTest > 1) {
651 while ((nTest % factor) != 0) {
652 factor += 2;
653 }
654 nTest /= factor;
655 v.add(factor);
656 }
657 int product = 1;
658 int nf = v.size();
659 int[] ret = new int[nf];
660 for (int i = 0; i < nf; i++) {
661 ret[i] = v.get(i);
662 product *= ret[i];
663 }
664
665 // Report a failed factorization.
666 if (product != n) {
667 StringBuilder sb = new StringBuilder(" FFT factorization failed for " + n + "\n");
668 for (int i = 0; i < nf; i++) {
669 sb.append(" ");
670 sb.append(ret[i]);
671 }
672 sb.append("\n");
673 sb.append(" Factor product = ");
674 sb.append(product);
675 sb.append("\n");
676 logger.severe(sb.toString());
677 System.exit(-1);
678 } else {
679 if (logger.isLoggable(Level.FINEST)) {
680 StringBuilder sb = new StringBuilder(" FFT factorization for " + n + " = ");
681 for (int i = 0; i < nf - 1; i++) {
682 sb.append(ret[i]);
683 sb.append(" * ");
684 }
685 sb.append(ret[nf - 1]);
686 logger.finest(sb.toString());
687 }
688 }
689 return ret;
690 }
691
692 /**
693 * Compute twiddle factors. These are trigonometric constants that depend on the factoring of n.
694 *
695 * @param n the length of the data.
696 * @param factors the factors of n.
697 * @return twiddle factors.
698 */
699 private static double[][][] wavetable(int n, int[] factors) {
700 if (n < 2) {
701 return null;
702 }
703
704 // System.out.println(" Computing twiddle factors for length :" + n);
705 // System.out.println(" Number of factors: " + factors.length);
706
707 final double TwoPI_N = -2.0 * PI / n;
708 final double[][][] ret = new double[factors.length][][];
709 int product = 1;
710 for (int i = 0; i < factors.length; i++) {
711 int factor = factors[i];
712 int product_1 = product;
713 product *= factor;
714 // The number of twiddle factors for the current factor.
715 int outLoopLimit = n / product;
716 // For the general odd pass, we need to add 1.
717 if (factor >= firstUnavailablePrime) {
718 outLoopLimit += 1;
719 }
720 final int nTwiddle = factor - 1;
721 // System.out.println("\n Factor: " + factor);
722 // System.out.println(" Product: " + product);
723 ret[i] = new double[outLoopLimit][2 * nTwiddle];
724 // System.out.printf(" Size: T(%d,%d)\n", outLoopLimit, nTwiddle);
725 final double[][] twid = ret[i];
726 for (int j = 0; j < factor - 1; j++) {
727 twid[0][2 * j] = 1.0;
728 twid[0][2 * j + 1] = 0.0;
729 // System.out.printf(" T(%d,%d) = %10.6f %10.6f\n", 0, j, twid[0][2 * j], twid[0][2 * j + 1]);
730 }
731 for (int k = 1; k < outLoopLimit; k++) {
732 int m = 0;
733 for (int j = 0; j < nTwiddle; j++) {
734 m += k * product_1;
735 m %= n;
736 final double theta = TwoPI_N * m;
737 twid[k][2 * j] = cos(theta);
738 twid[k][2 * j + 1] = sin(theta);
739 // System.out.printf(" T(%d,%d) = %10.6f %10.6f\n", k, j, twid[k][2 * j], twid[k][2 * j + 1]);
740 }
741 }
742 }
743 return ret;
744 }
745
746 /**
747 * Static DFT method used to test the FFT.
748 *
749 * @param in input array.
750 * @param out output array.
751 */
752 public static void dft(double[] in, double[] out) {
753 int n = in.length / 2;
754 for (int k = 0; k < n; k++) { // For each output element
755 double sumReal = 0;
756 double simImag = 0;
757 for (int t = 0; t < n; t++) { // For each input element
758 double angle = (2 * PI * t * k) / n;
759 int re = 2 * t;
760 int im = 2 * t + 1;
761 sumReal = fma(in[re], cos(angle), sumReal);
762 sumReal = fma(in[im], sin(angle), sumReal);
763 simImag = fma(-in[re], sin(angle), simImag);
764 simImag = fma(in[im], cos(angle), simImag);
765 }
766 int re = 2 * k;
767 int im = 2 * k + 1;
768 out[re] = sumReal;
769 out[im] = simImag;
770 }
771 }
772
773 /**
774 * Static DFT method used to test the FFT.
775 *
776 * @param in input array.
777 * @param out output array.
778 */
779 public static void dftBlocked(double[] in, double[] out) {
780 int n = in.length / 2;
781 for (int k = 0; k < n; k++) { // For each output element
782 double sumReal = 0;
783 double simImag = 0;
784 for (int t = 0; t < n; t++) { // For each input element
785 double angle = (2 * PI * t * k) / n;
786 int re = t;
787 int im = t + n;
788 sumReal = fma(in[re], cos(angle), sumReal);
789 sumReal = fma(in[im], sin(angle), sumReal);
790 simImag = fma(-in[re], sin(angle), simImag);
791 simImag = fma(in[im], cos(angle), simImag);
792 }
793 int re = k;
794 int im = k + n;
795 out[re] = sumReal;
796 out[im] = simImag;
797 }
798 }
799
800 /**
801 * Test the Complex FFT.
802 *
803 * @param args an array of {@link java.lang.String} objects.
804 * @throws java.lang.Exception if any.
805 * @since 1.0
806 */
807 public static void main(String[] args) throws Exception {
808 int dimNotFinal = 128;
809 int reps = 5;
810 try {
811 dimNotFinal = Integer.parseInt(args[0]);
812 if (dimNotFinal < 1) {
813 dimNotFinal = 100;
814 }
815 reps = Integer.parseInt(args[1]);
816 if (reps < 1) {
817 reps = 5;
818 }
819 } catch (Exception e) {
820 //
821 }
822 final int dim = dimNotFinal;
823 System.out.printf("Initializing a 1D array of length %d.\n"
824 + "The best timing out of %d repetitions will be used.%n", dim, reps);
825 Complex complex = new Complex(dim);
826 final double[] data = new double[dim * 2];
827 Random random = new Random(1);
828 for (int i = 0; i < dim; i++) {
829 data[2 * i] = random.nextDouble();
830 }
831 double toSeconds = 0.000000001;
832 long seqTime = Long.MAX_VALUE;
833 for (int i = 0; i < reps; i++) {
834 System.out.printf("Iteration %d%n", i + 1);
835 long time = System.nanoTime();
836 complex.fft(data, 0, 2);
837 complex.ifft(data, 0, 2);
838 time = (System.nanoTime() - time);
839 System.out.printf("Sequential: %12.9f%n", toSeconds * time);
840 if (time < seqTime) {
841 seqTime = time;
842 }
843 }
844 System.out.printf("Best Sequential Time: %12.9f%n", toSeconds * seqTime);
845 }
846
847 }