View Javadoc
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 }