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  /**
41   * Compute the 3D FFT of complex, double precision input of arbitrary dimensions via 1D Mixed Radix
42   * FFTs.
43   *
44   * <p>
45   * For interleaved data, the location of the input point [x, y, z] within the input array must be:
46   * <br>
47   * double real = input[x*nextX + y*nextY + z*nextZ] <br>
48   * double imag = input[x*nextX + y*nextY + z*nextZ + 1] <br>
49   * where <br>
50   * int nextX = 2 <br>
51   * int nextY = 2*nX <br>
52   * int nextZ = 2*nX*nY <br>
53   *
54   * <p>
55   * For blocked data along x, the location of the input point [x, y, z] within the input array must be:
56   * <br>
57   * double real = input[x*nextX + y*nextY + z*nextZ] <br>
58   * double imag = input[x*nextX + y*nextY + z*nextZ + im] <br>
59   * where <br>
60   * int nextX = 1 <br>
61   * int nextY = 2*nX <br>
62   * int nextZ = 2*nX*nY <br>
63   * int im = nX for BLOCKED_X<br>
64   * int im = nX*nY for BLOCKED_XY<br>
65   * int im = nX*nY*nZ for BLOCKED_XYZ<br>
66   *
67   * @author Michal J. Schnieders
68   * @see Complex
69   * @since 1.0
70   */
71  public class Complex3D {
72  
73    /**
74     * The number of points along the X-axis.
75     */
76    private final int nX;
77    /**
78     * The number of points along the Y-axis.
79     */
80    private final int nY;
81    /**
82     * The number of points along the Z-axis.
83     */
84    private final int nZ;
85    /**
86     * The offset from each real point to the corresponding imaginary point.
87     */
88    private final int im;
89    /**
90     * The offset from each real point to the next real point (either 1 or 2).
91     */
92    private final int ii;
93    /**
94     * The offset from each real point to the next real point along the X-dimension (either 1 or 2).
95     */
96    private final int nextX;
97    /**
98     * The offset from each real point to the next real point along the Y-dimension (either nX or 2*nX).
99     */
100   private final int nextY;
101   /**
102    * The offset from each real point to the next real point along the Z-dimension (either nX*nY or 2*nX*nY).
103    */
104   private final int nextZ;
105   /**
106    * The 2D FFT for the XY-plane.
107    */
108   private final Complex2D fftXY;
109   /**
110    * The 1D FFT for the Z-axis.
111    */
112   private final Complex fftZN;
113   /**
114    * The work array holding a 2D YZ-plane for transforms along the Z-axis.
115    */
116   private final double[] work;
117   /**
118    * The offset from each real point to its corresponding imaginary point for the 2D YZ-plane.
119    */
120   private final int internalImZ;
121   /**
122    * The offset from each real point to the next real point for the 2D YZ-plane (1 or 2).
123    */
124   private final int internalNextZ;
125   /**
126    * The reciprocal space data.
127    */
128   private final double[] recip;
129 
130   private boolean useSIMD;
131   private boolean packFFTs;
132 
133   /**
134    * Initialize the 3D FFT for complex 3D matrix using interleaved data layout.
135    *
136    * @param nX X-dimension.
137    * @param nY Y-dimension.
138    * @param nZ Z-dimension.
139    */
140   public Complex3D(int nX, int nY, int nZ) {
141     this(nX, nY, nZ, DataLayout3D.INTERLEAVED);
142   }
143 
144   /**
145    * Initialize the 3D FFT for complex 3D matrix.
146    *
147    * @param nX     X-dimension.
148    * @param nY     Y-dimension.
149    * @param nZ     Z-dimension.
150    * @param layout Data layout.
151    */
152   public Complex3D(int nX, int nY, int nZ, DataLayout3D layout) {
153     this.nX = nX;
154     this.nY = nY;
155     this.nZ = nZ;
156 
157     DataLayout2D dataLayoutXY;
158     DataLayout1D dataLayoutZ;
159     switch (layout) {
160       default:
161       case INTERLEAVED:
162         // Interleaved data layout.
163         im = 1;
164         ii = 2;
165         nextX = 2;
166         nextY = 2 * nX;
167         nextZ = 2 * nX * nY;
168         dataLayoutXY = DataLayout2D.INTERLEAVED;
169         dataLayoutZ = DataLayout1D.INTERLEAVED;
170         // Transforms along the Z-axis will be repacked into 1D interleaved format.
171         internalNextZ = 2;
172         internalImZ = 1;
173         break;
174       case BLOCKED_X:
175         // Blocking is along the X-axis.
176         im = nX;
177         ii = 1;
178         nextX = 1;
179         nextY = 2 * nX;
180         nextZ = 2 * nX * nY;
181         dataLayoutXY = DataLayout2D.BLOCKED_X;
182         dataLayoutZ = DataLayout1D.BLOCKED;
183         // Transforms along the Z-axis will be repacked into 1D blocked format.
184         internalNextZ = 1;
185         internalImZ = nY * nZ;
186         break;
187       case BLOCKED_XY:
188         // Blocking is based on 2D XY-planes.
189         im = nX * nY;
190         ii = 1;
191         nextX = 1;
192         nextY = nX;
193         nextZ = 2 * nY * nX;
194         dataLayoutXY = DataLayout2D.BLOCKED_XY;
195         dataLayoutZ = DataLayout1D.BLOCKED;
196         // Transforms along the Z-axis will be repacked into 1D blocked format.
197         internalNextZ = 1;
198         internalImZ = nY * nZ;
199         break;
200       case BLOCKED_XYZ:
201         // Blocking is based on 3D XYZ-volume with all real values followed by all imaginary.
202         im = nX * nY * nZ;
203         ii = 1;
204         nextX = 1;
205         nextY = nX;
206         nextZ = nY * nX;
207         dataLayoutXY = DataLayout2D.BLOCKED_XY;
208         dataLayoutZ = DataLayout1D.BLOCKED;
209         // Transforms along the Z-axis will be repacked into 1D blocked format.
210         internalNextZ = 1;
211         internalImZ = nY * nZ;
212         break;
213     }
214 
215     // Do not use SIMD by default for now.
216     useSIMD = false;
217     String simd = System.getProperty("fft.useSIMD", Boolean.toString(useSIMD));
218     try {
219       useSIMD = Boolean.parseBoolean(simd);
220     } catch (Exception e) {
221       useSIMD = false;
222     }
223 
224     packFFTs = false;
225     String pack = System.getProperty("fft.packFFTs", Boolean.toString(packFFTs));
226     try {
227       packFFTs = Boolean.parseBoolean(pack);
228     } catch (Exception e) {
229       packFFTs = false;
230     }
231 
232     // Allocate memory for the reciprocal space data to be repacked into the order needed by the convolution routine.
233     recip = new double[nX * nY * nZ];
234     fftXY = new Complex2D(nX, nY, dataLayoutXY, im);
235     fftXY.setPackFFTs(packFFTs);
236     fftXY.setUseSIMD(useSIMD);
237     fftZN = new Complex(nZ, dataLayoutZ, internalImZ, nY);
238     fftZN.setUseSIMD(useSIMD);
239 
240     // Transforms along Z-data are repacked into a local work array.
241     work = new double[2 * nZ * nY];
242   }
243 
244   /**
245    * Set the 2D transform to use SIMD instructions.
246    *
247    * @param useSIMD True to use SIMD instructions.
248    */
249   public void setUseSIMD(boolean useSIMD) {
250     this.useSIMD = useSIMD;
251     fftXY.setUseSIMD(useSIMD);
252     fftZN.setUseSIMD(useSIMD);
253   }
254 
255   /**
256    * Set the 2D transform to pack FFTs.
257    *
258    * @param packFFTs True to pack FFTs.
259    */
260   public void setPackFFTs(boolean packFFTs) {
261     this.packFFTs = packFFTs;
262     fftXY.setPackFFTs(packFFTs);
263   }
264 
265   /**
266    * Determine the index of the complex number in the 1D array from the X, Y and Z indices.
267    *
268    * @param i  the index along the X-axis.
269    * @param j  the index along the Y-axis.
270    * @param k  the index along the Z-axis.
271    * @param nX the number of points along the X-axis.
272    * @param nY the number of points along the Y-axis.
273    * @return the index of the complex number in the 1D array.
274    */
275   public static int interleavedIndex(int i, int j, int k, int nX, int nY) {
276     return 2 * (i + nX * (j + nY * k));
277   }
278 
279   /**
280    * Determine the index of the complex number in the blocked 1D array from the X, Y and Z indices.
281    *
282    * @param i      the index along the X-axis.
283    * @param j      the index along the Y-axis.
284    * @param k      the index along the Z-axis.
285    * @param nX     the number of points along the X-axis.
286    * @param nY     the number of points along the Y-axis.
287    * @param layout the data layout.
288    * @return the index of the complex number in the 1D array using blocked data layout.
289    */
290   public static int index3D(int i, int j, int k, int nX, int nY, DataLayout3D layout) {
291     return switch (layout) {
292       case INTERLEAVED -> interleavedIndex(i, j, k, nX, nY);
293       case BLOCKED_X -> i + 2 * (nX * (j + nY * k));
294       case BLOCKED_XY -> i + nX * (j + 2 * nY * k);
295       case BLOCKED_XYZ -> i + nX * (j + nY * k);
296     };
297   }
298 
299   /**
300    * Compute the 3D FFT.
301    *
302    * @param input The input array must be of size 2 * nX * nY * nZ.
303    */
304   public void fft(final double[] input) {
305     for (int z = 0; z < nZ; z++) {
306       fftXY.fft(input, z * nextZ);
307     }
308     for (int x = 0, offset = 0; x < nX; x++) {
309       selectYZPlane(offset, input);
310       fftZN.fft(work, 0, ii);
311       replaceYZPlane(offset, input);
312       offset += nextX;
313     }
314   }
315 
316   /**
317    * Compute the inverse 3D FFT.
318    *
319    * @param input The input array must be of size 2 * nX * nY * nZ.
320    */
321   public void ifft(final double[] input) {
322     for (int offset = 0, x = 0; x < nX; x++) {
323       selectYZPlane(offset, input);
324       fftZN.ifft(work, 0, ii);
325       replaceYZPlane(offset, input);
326       offset += nextX;
327     }
328     for (int z = 0; z < nZ; z++) {
329       fftXY.ifft(input, z * nextZ);
330     }
331   }
332 
333   /**
334    * Perform a convolution.
335    *
336    * @param input The input array.
337    */
338   public void convolution(final double[] input) {
339     for (int z = 0; z < nZ; z++) {
340       fftXY.fft(input, z * nextZ);
341     }
342     for (int x = 0, offset = 0; x < nX; x++) {
343       selectYZPlane(offset, input);
344       fftZN.fft(work, 0, internalNextZ);
345       recipConv(x, work);
346       fftZN.ifft(work, 0, internalNextZ);
347       replaceYZPlane(offset, input);
348       offset += nextX;
349     }
350     for (int z = 0; z < nZ; z++) {
351       fftXY.ifft(input, z * nextZ);
352     }
353   }
354 
355   /**
356    * Setter for the field <code>recip</code>.
357    *
358    * @param recip an array of double.
359    */
360   public void setRecip(double[] recip) {
361     // Reorder the reciprocal space data for convolution.
362     // Input
363     // trNextY = ii
364     // trNextZ = nY*ii
365     // real[y, z] = work[y*trNextY + z*trNextZ]
366     // imag[y, z] = work[y*trNextY + z*trNextZ + internalImZ]
367     int recipNextY = nX;
368     int recipNextZ = nY * nX;
369     int index = 0;
370     for (int x = 0; x < nX; x++) {
371       int dx = x;
372       for (int z = 0; z < nZ; z++) {
373         int dz = dx + z * recipNextZ;
374         for (int y = 0; y < nY; y++) {
375           int conv = y * recipNextY + dz;
376           this.recip[index] = recip[conv];
377           index++;
378         }
379       }
380     }
381   }
382 
383   /**
384    * Select a YZ-plane at fixed X into a contiguous block of memory.
385    *
386    * @param offset The offset into the input array.
387    * @param input  The input array.
388    */
389   private void selectYZPlane(int offset, double[] input) {
390     // Input
391     // real[x, y, z] = input[offset + y*nextY + z*nextZ]
392     // imag[x, y, z] = input[offset + y*nextY + z*nextZ + im]
393     // Collect a Y-Z plane at fixed X.
394     // Output
395     // trNextY = ii
396     // trNextZ = nY*ii
397     // real[y, z] = work[y*trNextY + z*trNextZ]
398     // imag[y, z] = work[y*trNextY + z*trNextZ + internalImZ]
399     int index = 0;
400     for (int z = 0; z < nZ; z++) {
401       int dz = offset + z * nextZ;
402       for (int y = 0; y < nY; y++) {
403         double real = input[y * nextY + dz];
404         double imag = input[y * nextY + dz + im];
405         work[index] = real;
406         work[index + internalImZ] = imag;
407         index += ii;
408       }
409     }
410   }
411 
412   /**
413    * Replace the Y-Z plane at fixed X back into the input array.
414    *
415    * @param offset The offset into the output array.
416    * @param output The input array.
417    */
418   private void replaceYZPlane(int offset, double[] output) {
419     // Input
420     // trNextY = ii
421     // trNextZ = nY*ii
422     // real[y, z] = work[y*trNextY + z*trNextZ]
423     // imag[y, z] = work[y*trNextY + z*trNextZ + internalImZ]
424     // Output
425     // real[x, y, z] = input[offset + y*nextY + z*nextZ]
426     // imag[x, y, z] = input[offset + y*nextY + z*nextZ + im]
427     int index = 0;
428     for (int z = 0; z < nZ; z++) {
429       int dzOut = offset + z * nextZ;
430       for (int y = 0; y < nY; y++) {
431         int dyOut = y * nextY;
432         double real = work[index];
433         double imag = work[index + internalImZ];
434         index += ii;
435         output[dyOut + dzOut] = real;
436         output[dyOut + dzOut + im] = imag;
437       }
438     }
439   }
440 
441   /**
442    * Perform a multiplication by the reciprocal space data.
443    *
444    * @param x    The X-value for this Y-Z plane.
445    * @param work The input array.
446    */
447   private void recipConv(int x, double[] work) {
448     int index = 0;
449     int rindex = x * (nY * nZ);
450     for (int z = 0; z < nZ; z++) {
451       for (int y = 0; y < nY; y++) {
452         double r = recip[rindex++];
453         work[index] *= r;
454         work[index + internalImZ] *= r;
455         index += ii;
456       }
457     }
458   }
459 
460 }