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 }