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  
42  /**
43   * This algorithm factors a size n FFT into nX * nY,
44   * computes nY inner FFTs of size nX and nX inner FFTs of size nY,
45   * then combines the results to get the final answer.
46   *
47   * This is incomplete.
48   */
49  public class Complex1D {
50  
51    /**
52     * Overall FFT length.
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   /**
115    * Construct a Complex instance for interleaved data of length n. Factorization of n is designed to use special
116    * methods for small factors, and a general routine for large odd prime factors. Scratch memory is
117    * created of length 2*n, which is reused each time a transform is computed.
118    *
119    * @param n Number of complex numbers (n .GT. 1).
120    */
121   public Complex1D(int n) {
122     this(n, DataLayout1D.INTERLEAVED, 1);
123   }
124 
125   /**
126    * Construct a Complex instance for data of length n.
127    * The offset to each imaginary part relative to the real part is given by im.
128    * Factorization of n is designed to use special methods for small factors.
129    * Scratch memory is 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    * @param dataLayout Data layout (interleaved or blocked).
133    * @param imOffset   Offset to the imaginary part of each complex number relative to its real part.
134    */
135   public Complex1D(int n, DataLayout1D dataLayout, int imOffset) {
136     this.n = n;
137     this.dataLayout = dataLayout;
138     this.externalIm = imOffset;
139 
140     // Determine nX * nY = n.
141     int[] factors = Complex.factor(n);
142 
143     // Only the width transform will be used.
144     if (factors.length == 1) {
145       this.nX = n;
146       this.nY = 1;
147       fftX = new Complex(nX, dataLayout, imOffset);
148       // The variables below are not used in this case.
149       fftY = null;
150       internalDataLayout = null;
151       buffer = null;
152       internalIm = -1;
153       ii = -1;
154       nextX = -1;
155       nextY = -1;
156       trNextX = -1;
157       trNextY = 1;
158     } else {
159       int n1 = 1;
160       int n2 = 1;
161       for (int i = 0; i < factors.length; i++) {
162         // Even factors contribute to n1.
163         // Odd factors contribute to n2.
164         if (i % 2 == 0) {
165           n1 *= factors[i];
166         } else {
167           n2 *= factors[i];
168         }
169       }
170       nX = n1;
171       nY = n2;
172       buffer = new double[n * 2];
173       internalDataLayout = dataLayout;
174       if (dataLayout == DataLayout1D.INTERLEAVED) {
175         nextX = 2;
176         nextY = 2 * nX;
177         trNextY = 2;
178         trNextX = 2 * nY;
179         ii = 2;
180         internalIm = 1;
181       } else {
182         nextX = 1;
183         nextY = nX;
184         trNextY = 1;
185         trNextX = nY;
186         ii = 1;
187         internalIm = nX * nY;
188       }
189       fftY = new Complex(nY, dataLayout, externalIm, nX);
190       fftX = new Complex(nX, internalDataLayout, internalIm, nY);
191     }
192   }
193 
194   /**
195    * Compute the Fast Fourier Transform of data leaving the result in data. The array data must
196    * contain the data points in the following locations:
197    *
198    * <PRE>
199    * Re(d[i]) = data[offset + stride*i]
200    * Im(d[i]) = data[offset + stride*i + im]
201    * </PRE>
202    * <p>
203    * where im is 1 for interleaved data or a constant set when the class was constructed.
204    *
205    * @param data   an array of double.
206    * @param offset the offset to the beginning of the data.
207    * @param stride the stride between data points.
208    */
209   public void fft(double[] data, int offset, int stride) {
210     // STEP 1: We need to pack the data if the stride is greater than 2. Skip for now.
211     // To Do.
212 
213     // STEP 2: Perform nX FFTs of size nY.
214     fftY.fft(data, offset, stride);
215 
216     // STEP 3: Apply twiddle factors
217     // To Do.
218 
219     // STEP 4: Transpose
220     // transpose(data, offset);
221 
222     // STEP 5: Perform nY FFTs of size nX.
223     fftX.fft(buffer, 0, 2);
224 
225     // STEP 6: Un-Transpose
226     // unTranspose(data, offset);
227   }
228 
229   /**
230    * Compute the (un-normalized) inverse FFT of data, leaving it in place. The frequency domain data
231    * must be in wrap-around order, and be stored in the following locations:
232    *
233    * <PRE>
234    * Re(D[i]) = data[offset + stride*i]
235    * Im(D[i]) = data[offset + stride*i + im]
236    * </PRE>
237    *
238    * @param data   an array of double.
239    * @param offset the offset to the beginning of the data.
240    * @param stride the stride between data points.
241    */
242   public void ifft(double[] data, int offset, int stride) {
243     // TO DO
244   }
245 
246   /**
247    * Transpose the input array for Fourier transforms of length width.
248    * <p>
249    * Input order:
250    * real(x,y) = input[offset + x*nextX + y*nextY]
251    * imag(x,y) = input[offset + x*nextX + y*nextY + im]
252    * Output order:
253    * real(x,y) = packedData[y*trNextY + x*trNextX]
254    * imag(x,y) = packedData[y*trNextY + x*trXextX + im]
255    *
256    * @param input  The input data.
257    * @param offset The offset into the input data.
258    */
259   private void transpose(final double[] input, int offset) {
260     int index = 0;
261     // Outer loop over the X dimension.
262     for (int x = 0; x < nX; x++) {
263       int dx = offset + x * nextX;
264       // Inner loop over the Y dimension (the number of FFTs).
265       for (int y = 0; y < nY; y++) {
266         double real = input[dx + y * nextY];
267         double imag = input[dx + y * nextY + externalIm];
268         // Contiguous storage into the packed array.
269         buffer[index] = real;
270         buffer[index + internalIm] = imag;
271         index += ii;
272       }
273     }
274   }
275 
276   /**
277    * Unpack the output array after Fourier transforms.
278    * <p>
279    * Input order:
280    * real_xy = packedData[y*trNextY + x*trNextX]
281    * imag_xy = packedData[y*trNextY + x*trXextX + im]
282    * Output order:
283    * real_xy = output[offset + x*nextX + y*nextY]
284    * imag_xy = output[offset + x*nextX + y*nextY + im]
285    *
286    * @param output The output data.
287    * @param offset The offset into the output data.
288    */
289   private void unTranspose(final double[] output, int offset) {
290     int index = offset;
291     // Outer loop over the Y dimension.
292     for (int y = 0; y < nY; y++) {
293       int dy = y * trNextY;
294       // Inner loop over the X dimension.
295       for (int x = 0; x < nX; x++) {
296         double real = buffer[dy + x * trNextX];
297         double imag = buffer[dy + x * trNextX + internalIm];
298         // Contiguous storage into the output array.
299         output[index] = real;
300         output[index + externalIm] = imag;
301         index += ii;
302       }
303     }
304   }
305 
306   /**
307    * Test the Complex FFT.
308    *
309    * @param args an array of {@link java.lang.String} objects.
310    * @throws java.lang.Exception if any.
311    * @since 1.0
312    */
313   public static void main(String[] args) throws Exception {
314     int dimNotFinal = 128;
315     int reps = 5;
316     try {
317       dimNotFinal = Integer.parseInt(args[0]);
318       if (dimNotFinal < 1) {
319         dimNotFinal = 128;
320       }
321       reps = Integer.parseInt(args[1]);
322       if (reps < 1) {
323         reps = 5;
324       }
325     } catch (Exception e) {
326       //
327     }
328     final int dim = dimNotFinal;
329     System.out.printf("Initializing a 1D array of length %d.\n"
330         + "The best timing out of %d repetitions will be used.%n", dim, reps);
331     Complex1D complex = new Complex1D(dim);
332     final double[] data = new double[dim * 2];
333     Random random = new Random(1);
334     for (int i = 0; i < dim; i++) {
335       data[2 * i] = random.nextDouble();
336     }
337     double toSeconds = 0.000000001;
338     long seqTime = Long.MAX_VALUE;
339     for (int i = 0; i < reps; i++) {
340       System.out.printf("Iteration %d%n", i + 1);
341       long time = System.nanoTime();
342       complex.fft(data, 0, 2);
343       complex.ifft(data, 0, 2);
344       time = (System.nanoTime() - time);
345       System.out.printf("Sequential: %12.9f%n", toSeconds * time);
346       if (time < seqTime) {
347         seqTime = time;
348       }
349     }
350     System.out.printf("Best Sequential Time:  %12.9f%n", toSeconds * seqTime);
351   }
352 
353 }