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 }