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 }