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-2026.
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.Random;
41  import java.util.logging.Logger;
42  
43  /**
44   * This algorithm factors a size n FFT into nX * nY,
45   * computes nY inner FFTs of size nX and nX inner FFTs of size nY,
46   * then combines the results to get the final answer.
47   *
48   * This is incomplete.
49   */
50  public class Complex1D {
51  
52    private static final Logger logger = Logger.getLogger(Complex1D.class.getName());
53  
54    private final int n;
55    /**
56     * The overall FFT length n = nX * nY.
57     */
58    private final int nX;
59    /**
60     * Compute FFTs of length nX.
61     */
62    private final Complex fftX;
63    /**
64     * The next real value along the X dimension.
65     */
66    private final int nextX;
67    /**
68     * The overall FFT length n = nX * nY.
69     */
70    private final int nY;
71    /**
72     * Compute FFTs of length height.
73     */
74    private final Complex fftY;
75    /**
76     * The next real value along the Y dimension.
77     */
78    private final int nextY;
79    /**
80     * The input data layout as interleaved or blocked.
81     */
82    private final DataLayout1D dataLayout;
83    /**
84     * Offset to the imaginary part of the input
85     * This is 1 for interleaved data.
86     * For blocked data, a typical offset is n in 1-dimension (or nX*nY + n in 2D).
87     */
88    private final int externalIm;
89    /**
90     * Internal data format.
91     */
92    private final DataLayout1D internalDataLayout;
93    /**
94     * Internal offset to the next real value (2 for interleaved, 1 for blocked).
95     */
96    private final int ii;
97    /**
98     * Internally use an interleaved data format.
99     */
100   private final int internalIm;
101   /**
102    * Internal buffer to store rearranged data.
103    */
104   private final double[] buffer;
105   /**
106    * The offset between real values along the X-dimension in the transposed packed data.
107    */
108   private final int trNextX;
109   /**
110    * The offset between real values along the Y-dimension in the transposed packed data.
111    */
112   private final int trNextY;
113   /**
114    * Cached real twiddle factors for indices x*nY + y.
115    */
116   private final double[] twiddleRe;
117   /**
118    * Cached imaginary twiddle factors for indices x*nY + y.
119    */
120   private final double[] twiddleIm;
121   /**
122    * Use SIMD operators.
123    */
124   private boolean useSIMD;
125 
126   /**
127    * Construct a Complex instance for interleaved data of length n. Factorization of n is designed to use special
128    * methods for small factors, and a general routine for large odd prime factors. Scratch memory is
129    * created of length 2*n, which is reused each time a transform is computed.
130    *
131    * @param n Number of complex numbers (n .GT. 1).
132    */
133   public Complex1D(int n) {
134     this(n, DataLayout1D.INTERLEAVED, 1);
135   }
136 
137   /**
138    * Construct a Complex instance for data of length n.
139    * The offset to each imaginary part relative to the real part is given by im.
140    * Factorization of n is designed to use special methods for small factors.
141    * Scratch memory is created of length 2*n, which is reused each time a transform is computed.
142    *
143    * @param n          Number of complex numbers (n .GT. 1).
144    * @param dataLayout Data layout (interleaved or blocked).
145    * @param imOffset   Offset to the imaginary part of each complex number relative to its real part.
146    */
147   public Complex1D(int n, DataLayout1D dataLayout, int imOffset) {
148     this.n = n;
149     this.dataLayout = dataLayout;
150     this.externalIm = imOffset;
151 
152     // Determine nX * nY = n.
153     int[] factors = Complex.factor(n);
154 
155     // Only the width transform will be used.
156     if (factors.length == 1) {
157       this.nX = n;
158       this.nY = 1;
159       fftX = new Complex(nX, dataLayout, imOffset);
160       // The variables below are not used in this case.
161       fftY = null;
162       internalDataLayout = null;
163       buffer = null;
164       internalIm = -1;
165       ii = -1;
166       nextX = -1;
167       nextY = -1;
168       trNextX = -1;
169       trNextY = 1;
170       twiddleRe = null;
171       twiddleIm = null;
172     } else {
173       int n1 = 1;
174       int n2 = 1;
175       for (int i = 0; i < factors.length; i++) {
176         // Even factors contribute to n1.
177         // Odd factors contribute to n2.
178         if (i % 2 == 0) {
179           n1 *= factors[i];
180         } else {
181           n2 *= factors[i];
182         }
183       }
184       nX = n1;
185       nY = n2;
186       buffer = new double[n * 2];
187       internalDataLayout = dataLayout;
188       if (dataLayout == DataLayout1D.INTERLEAVED) {
189         nextX = 2;
190         nextY = 2 * nX;
191         trNextY = 2;
192         trNextX = 2 * nY;
193         ii = 2;
194         internalIm = 1;
195       } else {
196         nextX = 1;
197         nextY = nX;
198         trNextY = 1;
199         trNextX = nY;
200         ii = 1;
201         internalIm = nX * nY;
202       }
203       fftY = new Complex(nY, dataLayout, externalIm, nX);
204       fftX = new Complex(nX, internalDataLayout, internalIm, nY);
205       twiddleRe = new double[n];
206       twiddleIm = new double[n];
207       precomputeTwiddleFactors();
208     }
209 
210     // Use SIMD by default.
211     useSIMD = true;
212     String simd = System.getProperty("fft.simd", Boolean.toString(useSIMD));
213     try {
214       useSIMD = Boolean.parseBoolean(simd);
215     } catch (Exception e) {
216       logger.info(" Invalid value for fft.simd: " + simd);
217       useSIMD = false;
218     }
219     fftX.setUseSIMD(useSIMD);
220     if (fftY != null) {
221       fftY.setUseSIMD(useSIMD);
222     }
223   }
224 
225   /**
226    * Configure use of SIMD operators.
227    *
228    * @param useSIMD True to use SIMD operators.
229    */
230   public void setUseSIMD(boolean useSIMD) {
231     fftX.setUseSIMD(useSIMD);
232     if (fftY != null) {
233       fftY.setUseSIMD(useSIMD);
234     }
235   }
236 
237   /**
238    * Compute the Fast Fourier Transform of data leaving the result in data. The array data must
239    * contain the data points in the following locations:
240    *
241    * <PRE>
242    * Re(d[i]) = data[offset + stride*i]
243    * Im(d[i]) = data[offset + stride*i + im]
244    * </PRE>
245    * <p>
246    * where im is 1 for interleaved data or a constant set when the class was constructed.
247    *
248    * @param data   an array of double.
249    * @param offset the offset to the beginning of the data.
250    * @param stride the stride between data points.
251    */
252   public void fft(double[] data, int offset, int stride) {
253     if (nY == 1) {
254       fftX.fft(data, offset, stride);
255       return;
256     }
257 
258     // STEP 1: We need to pack the data if the stride is greater than the native layout. Skip for now.
259     if (stride != ii) {
260       throw new UnsupportedOperationException(
261           "Complex1D.fft currently requires stride == " + ii + " for this data layout.");
262     }
263 
264     // STEP 2: Perform nX FFTs of size nY.
265     fftY.fft(data, offset, stride);
266 
267     // STEP 3: Apply twiddle factors while transposing.
268     transpose(data, offset);
269 
270     // STEP 4: Perform nY FFTs of size nX.
271     fftX.fft(buffer, 0, ii);
272 
273     // STEP 5: Un-Transpose
274     unTranspose(data, offset);
275   }
276 
277   /**
278    * Compute the (un-normalized) inverse FFT of data, leaving it in place. The frequency domain data
279    * must be in wrap-around order, and be stored in the following locations:
280    *
281    * <PRE>
282    * Re(D[i]) = data[offset + stride*i]
283    * Im(D[i]) = data[offset + stride*i + im]
284    * </PRE>
285    *
286    * @param data   an array of double.
287    * @param offset the offset to the beginning of the data.
288    * @param stride the stride between data points.
289    */
290   public void ifft(double[] data, int offset, int stride) {
291     if (nY == 1) {
292       fftX.ifft(data, offset, stride);
293       return;
294     }
295 
296     // STEP 1: We need to pack the data if the stride is greater than the native layout. Skip for now.
297     if (stride != ii) {
298       throw new UnsupportedOperationException(
299           "Complex1D.ifft currently requires stride == " + ii + " for this data layout.");
300     }
301 
302     // STEP 2: Pack the frequency-domain data for nY inverse FFTs of size nX.
303     transposeFrequencyDomain(data, offset);
304 
305     // STEP 3: Perform nY inverse FFTs of size nX.
306     fftX.ifft(buffer, 0, ii);
307 
308     // STEP 4: Scatter back to the intermediate layout while applying inverse twiddle factors.
309     unTransposeIntermediate(data, offset);
310 
311     // STEP 5: Perform nX inverse FFTs of size nY.
312     fftY.ifft(data, offset, stride);
313   }
314 
315   /**
316    * Precompute the Cooley-Tukey twiddle factors between the inner FFT stages.
317    */
318   private void precomputeTwiddleFactors() {
319     final double twoPiOverN = -2.0 * Math.PI / n;
320     for (int x = 0; x < nX; x++) {
321       int offset = x * nY;
322       twiddleRe[offset] = 1.0;
323       twiddleIm[offset] = 0.0;
324       for (int y = 1; y < nY; y++) {
325         double theta = twoPiOverN * x * y;
326         twiddleRe[offset + y] = Math.cos(theta);
327         twiddleIm[offset + y] = Math.sin(theta);
328       }
329     }
330   }
331 
332   /**
333    * Pack the twiddled, transposed data into the point-major layout used by batched Complex FFTs.
334    * <p>
335    * Input order:
336    * real(x,y) = input[offset + x*nextX + y*nextY]
337    * imag(x,y) = input[offset + x*nextX + y*nextY + im]
338    * Output order:
339    * real(x,y) = buffer[x*trNextX + y*trNextY]
340    * imag(x,y) = buffer[x*trNextX + y*trNextY + internalIm]
341    *
342    * @param input  The input data.
343    * @param offset The offset into the input data.
344    */
345   private void transpose(final double[] input, int offset) {
346     int index = 0;
347     // Outer loop over the X dimension.
348     for (int x = 0; x < nX; x++) {
349       int dx = offset + x * nextX;
350       int twiddleOffset = x * nY;
351       // Inner loop over the Y dimension (the number of FFTs).
352       for (int y = 0; y < nY; y++) {
353         double real = input[dx + y * nextY];
354         double imag = input[dx + y * nextY + externalIm];
355         if (y == 0) {
356           buffer[index] = real;
357           buffer[index + internalIm] = imag;
358         } else {
359           double cosine = twiddleRe[twiddleOffset + y];
360           double sine = twiddleIm[twiddleOffset + y];
361           buffer[index] = real * cosine - imag * sine;
362           buffer[index + internalIm] = real * sine + imag * cosine;
363         }
364         index += ii;
365       }
366     }
367   }
368 
369   /**
370    * Pack contiguous frequency-domain input into the point-major layout used by batched inverse FFTs
371    * of length nX.
372    * <p>
373    * Input order:
374    * real(x,y) = input[offset + x*trNextX + y*trNextY]
375    * imag(x,y) = input[offset + x*trNextX + y*trNextY + im]
376    * Output order:
377    * real(x,y) = buffer[x*trNextX + y*trNextY]
378    * imag(x,y) = buffer[x*trNextX + y*trNextY + internalIm]
379    *
380    * @param input  The input data.
381    * @param offset The offset into the input data.
382    */
383   private void transposeFrequencyDomain(final double[] input, int offset) {
384     int index = 0;
385     // Outer loop over the X dimension.
386     for (int x = 0; x < nX; x++) {
387       int dx = offset + x * trNextX;
388       // Inner loop over the Y dimension.
389       for (int y = 0; y < nY; y++) {
390         int indexXY = dx + y * trNextY;
391         buffer[index] = input[indexXY];
392         buffer[index + internalIm] = input[indexXY + externalIm];
393         index += ii;
394       }
395     }
396   }
397 
398   /**
399    * Unpack the point-major FFT results back into contiguous output order.
400    * <p>
401    * Input order:
402    * real_xy = buffer[x*trNextX + y*trNextY]
403    * imag_xy = buffer[x*trNextX + y*trNextY + internalIm]
404    * Output order:
405    * real_xy = output[offset + (x*nY + y)*ii]
406    * imag_xy = output[offset + (x*nY + y)*ii + im]
407    *
408    * @param output The output data.
409    * @param offset The offset into the output data.
410    */
411   private void unTranspose(final double[] output, int offset) {
412     int outputIndex = offset;
413     // Outer loop over the X dimension.
414     for (int x = 0; x < nX; x++) {
415       int dx = x * trNextX;
416       // Inner loop over the Y dimension.
417       for (int y = 0; y < nY; y++) {
418         int indexXY = dx + y * trNextY;
419         output[outputIndex] = buffer[indexXY];
420         output[outputIndex + externalIm] = buffer[indexXY + internalIm];
421         outputIndex += ii;
422       }
423     }
424   }
425 
426   /**
427    * Unpack point-major inverse FFT results back into the intermediate x + nX*y layout used by the
428    * final inverse FFTs of length nY, while applying the inverse twiddle factors.
429    * <p>
430    * Input order:
431    * real_xy = buffer[x*trNextX + y*trNextY]
432    * imag_xy = buffer[x*trNextX + y*trNextY + internalIm]
433    * Output order:
434    * real_xy = output[offset + x*nextX + y*nextY]
435    * imag_xy = output[offset + x*nextX + y*nextY + im]
436    *
437    * @param output The output data.
438    * @param offset The offset into the output data.
439    */
440   private void unTransposeIntermediate(final double[] output, int offset) {
441     int index = 0;
442     // Outer loop over the X dimension.
443     for (int x = 0; x < nX; x++) {
444       int dx = offset + x * nextX;
445       int twiddleOffset = x * nY;
446       // Inner loop over the Y dimension.
447       for (int y = 0; y < nY; y++) {
448         int indexXY = dx + y * nextY;
449         double real = buffer[index];
450         double imag = buffer[index + internalIm];
451         if (y == 0) {
452           output[indexXY] = real;
453           output[indexXY + externalIm] = imag;
454         } else {
455           double cosine = twiddleRe[twiddleOffset + y];
456           double sine = twiddleIm[twiddleOffset + y];
457           output[indexXY] = real * cosine + imag * sine;
458           output[indexXY + externalIm] = imag * cosine - real * sine;
459         }
460         index += ii;
461       }
462     }
463   }
464 
465   /**
466    * Test the Complex FFT.
467    *
468    * @param args an array of {@link java.lang.String} objects.
469    * @throws java.lang.Exception if any.
470    * @since 1.0
471    */
472   public static void main(String[] args) throws Exception {
473     int dimNotFinal = 128;
474     int reps = 5;
475     try {
476       dimNotFinal = Integer.parseInt(args[0]);
477       if (dimNotFinal < 1) {
478         dimNotFinal = 128;
479       }
480       reps = Integer.parseInt(args[1]);
481       if (reps < 1) {
482         reps = 5;
483       }
484     } catch (Exception e) {
485       //
486     }
487     final int dim = dimNotFinal;
488     System.out.printf("Initializing a 1D array of length %d.\n"
489         + "The best timing out of %d repetitions will be used.%n", dim, reps);
490     Complex1D complex = new Complex1D(dim);
491     final double[] data = new double[dim * 2];
492     Random random = new Random(1);
493     for (int i = 0; i < dim; i++) {
494       data[2 * i] = random.nextDouble();
495     }
496     double toSeconds = 0.000000001;
497     long seqTime = Long.MAX_VALUE;
498     for (int i = 0; i < reps; i++) {
499       System.out.printf("Iteration %d%n", i + 1);
500       long time = System.nanoTime();
501       complex.fft(data, 0, 2);
502       complex.ifft(data, 0, 2);
503       time = (System.nanoTime() - time);
504       System.out.printf("Sequential: %12.9f%n", toSeconds * time);
505       if (time < seqTime) {
506         seqTime = time;
507       }
508     }
509     System.out.printf("Best Sequential Time:  %12.9f%n", toSeconds * seqTime);
510   }
511 
512 }