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 }