PSCF v1.4.0
cpu/FFT.tpp
1#ifndef PRDC_CPU_FFT_TPP
2#define PRDC_CPU_FFT_TPP
3
4/*
5* PSCF - Polymer Self-Consistent Field
6*
7* Copyright 2015 - 2025, The Regents of the University of Minnesota
8* Distributed under the terms of the GNU General Public License.
9*/
10
11#include "FFT.h"
12#include "RField.h"
13#include "CField.h"
14
15/*
16* A note about const_casts:
17*
18* The FFTW library is used in this file to perform discrete Fourier
19* transforms. FFTW's complex-to-real inverse transform overwrites its
20* input array, but all other out-of-place transforms leave the input
21* array unaltered. However, all transforms in the FFTW library require
22* non-const pointers to the input array, even though they do not alter
23* the array.
24*
25* In order to maintain const-correctness in PSCF, this FFT class accepts
26* const input arrays for its methods that perform a Fourier transform,
27* unless the transform is expected to modify / overwrite its input (as
28* is the case for complex-to-real inverse transforms). This denotes to
29* the caller of the method that the input array will not be altered,
30* which is an accurate representation of the expected behavior.
31*
32* However, the const-correctness of this FFT class creates a conflict
33* with the FFTW library's requirement of non-const inputs. This conflict
34* is resolved using a const_cast, in which the const pointer to the input
35* array is made non-const when passed into FFTW functions. The use of
36* const_cast is reserved only for these few select cases in which we are
37* confident that the input array will not be modified.
38*
39* For more information about the relevant FFTW methods, see the FFTW
40* documentation at https://www.fftw.org/fftw3_doc/index.html. In Section
41* 4.3.2, it is stated that, by default, "an out-of-place transform must
42* not change its input array," except for complex-to-real transforms, in
43* which case "no input-preserving algorithms are implemented." Finally,
44* we note that the unit tests for this FFT class check that the input
45* array is unaltered, allowing developers to continually ensure that
46* the FFTW functions do not modify their input unexpectedly.
47*/
48
49namespace Pscf {
50namespace Prdc {
51namespace Cpu {
52
53 using namespace Util;
54
55 /*
56 * Default constructor.
57 */
58 template <int D>
60 : meshDimensions_(0),
61 rSize_(0),
62 kSize_(0),
63 rcfPlan_(0),
64 criPlan_(0),
65 ccfPlan_(0),
66 cciPlan_(0),
67 isSetup_(false)
68 {}
69
70 /*
71 * Destructor.
72 */
73 template <int D>
75 {
76 if (rcfPlan_) {
77 fftw_destroy_plan(rcfPlan_);
78 }
79 if (criPlan_) {
80 fftw_destroy_plan(criPlan_);
81 }
82 if (ccfPlan_) {
83 fftw_destroy_plan(ccfPlan_);
84 }
85 if (cciPlan_) {
86 fftw_destroy_plan(cciPlan_);
87 }
88 }
89
90 /*
91 * Setup mesh dimensions, work memory and FFT plans.
92 */
93 template <int D>
95 {
96 // Precondition
97 UTIL_CHECK(!isSetup_);
98
99 // Set and check mesh dimensions
100 rSize_ = 1;
101 kSize_ = 1;
102 for (int i = 0; i < D; ++i) {
104 meshDimensions_[i] = meshDimensions[i];
105 rSize_ *= meshDimensions[i];
106 if (i < D - 1) {
107 kSize_ *= meshDimensions[i];
108 } else {
109 kSize_ *= (meshDimensions[i]/2 + 1);
110 }
111 }
112
113 // Allocate kFieldCopy_ array if necessary
114 if (!kFieldCopy_.isAllocated()) {
115 kFieldCopy_.allocate(meshDimensions);
116 } else {
117 if (kFieldCopy_.capacity() != kSize_) {
118 kFieldCopy_.deallocate();
119 kFieldCopy_.allocate(meshDimensions);
120 }
121 }
122 UTIL_CHECK(meshDimensions == kFieldCopy_.meshDimensions());
123 UTIL_CHECK(kFieldCopy_.capacity() == kSize_);
124
125 // Create temporary RField and CField objects used for plans
126 RField<D> rField;
127 rField.allocate(meshDimensions);
129 UTIL_CHECK(rField.capacity() == rSize_);
130
131 CField<D> cFieldIn;
132 cFieldIn.allocate(meshDimensions);
134 UTIL_CHECK(cFieldIn.capacity() == rSize_);
135
136 CField<D> cFieldOut;
137 cFieldOut.allocate(meshDimensions);
139 UTIL_CHECK(cFieldOut.capacity() == rSize_);
140
141 // Make FFTW plans (see explicit specializations in FFT.cpp)
142 makePlans(rField, kFieldCopy_, cFieldIn, cFieldOut);
143
144 isSetup_ = true;
145 }
146
147 // Real <-> Complex Transforms
148
149 /*
150 * Execute real-to-complex forward transform.
151 */
152 template <int D>
154 RFieldDft<D>& kField)
155 const
156 {
157 UTIL_CHECK(isSetup_)
158 UTIL_CHECK(rField.capacity() == rSize_);
159 UTIL_CHECK(kField.capacity() == kSize_);
160 UTIL_CHECK(rField.meshDimensions() == meshDimensions_);
161 UTIL_CHECK(kField.meshDimensions() == meshDimensions_);
162
163 // Execute preplanned forward transform
164 // (See note at top of file explaining this use of const_cast)
165 fftw_execute_dft_r2c(rcfPlan_, const_cast<double*>(&rField[0]),
166 &kField[0]);
167
168 // Rescale the resulting array
169 double scale = 1.0/double(rSize_);
170 for (int i = 0; i < kSize_; ++i) {
171 kField[i][0] *= scale;
172 kField[i][1] *= scale;
173 }
174 }
175
176 /*
177 * Execute inverse (complex-to-real) transform.
178 */
179 template <int D>
180 void
182 const
183 {
184 UTIL_CHECK(isSetup_)
185 UTIL_CHECK(rField.capacity() == rSize_);
186 UTIL_CHECK(kField.capacity() == kSize_);
187 UTIL_CHECK(rField.meshDimensions() == meshDimensions_);
188 UTIL_CHECK(kField.meshDimensions() == meshDimensions_);
189
190 // Execute preplanned inverse transform
191 fftw_execute_dft_c2r(criPlan_, &kField[0], &rField[0]);
192 }
193
194 /*
195 * Execute inverse (complex-to-real) transform without destroying input.
196 */
197 template <int D>
198 void
200 RField<D>& rField)
201 const
202 {
203 UTIL_CHECK(kFieldCopy_.capacity() == kField.capacity());
204
205 kFieldCopy_ = kField;
206 inverseTransformUnsafe(kFieldCopy_, rField);
207 }
208
209 // Complex <-> Complex Transforms
210
211 /*
212 * Execute complex-to-complex forward transform.
213 */
214 template <int D>
215 void
217 const
218 {
219 UTIL_CHECK(isSetup_)
220 UTIL_CHECK(rField.capacity() == rSize_);
221 UTIL_CHECK(rField.meshDimensions() == meshDimensions_);
222 UTIL_CHECK(kField.capacity() == rSize_);
223 UTIL_CHECK(kField.meshDimensions() == meshDimensions_);
224
225 // Execute preplanned forward transform
226 // (See note at top of file explaining this use of const_cast)
227 fftw_execute_dft(ccfPlan_, const_cast<fftw_complex*>(&rField[0]),
228 &kField[0]);
229
230 // Rescale the resulting array
231 double scale = 1.0/double(rSize_);
232 for (int i = 0; i < rSize_; ++i) {
233 kField[i][0] *= scale;
234 kField[i][1] *= scale;
235 }
236 }
237
238 /*
239 * Execute inverse (complex-to-complex) transform.
240 */
241 template <int D>
242 void
244 const
245 {
246 UTIL_CHECK(isSetup_)
247 UTIL_CHECK(rField.capacity() == rSize_);
248 UTIL_CHECK(kField.capacity() == rSize_);
249 UTIL_CHECK(rField.meshDimensions() == meshDimensions_);
250 UTIL_CHECK(kField.meshDimensions() == meshDimensions_);
251
252 // Execute preplanned inverse transform
253 // (See note at top of file explaining this use of const_cast)
254 fftw_execute_dft(cciPlan_, const_cast<fftw_complex*>(&kField[0]),
255 &rField[0]);
256 }
257
258 // Static function
259
260 /*
261 * Compute dimensions and size of the k-size mesh (static)
262 */
263 template <int D>
264 void FFT<D>::computeKMesh(IntVec<D> const & rMeshDimensions,
265 IntVec<D> & kMeshDimensions,
266 int & kSize )
267 {
268 kSize = 1;
269 for (int i = 0; i < D; ++i) {
270 if (i < D - 1) {
271 kMeshDimensions[i] = rMeshDimensions[i];
272 } else {
273 kMeshDimensions[i] = rMeshDimensions[i]/2 + 1;
274 }
275 kSize *= kMeshDimensions[i];
276 }
277 }
278
279}
280}
281}
282#endif
An IntVec<D, T> is a D-component vector of elements of integer type T.
Definition IntVec.h:27
Field of complex double precision values on an FFT mesh.
Definition cpu/CField.h:29
const IntVec< D > & meshDimensions() const
Return mesh dimensions by constant reference.
Definition cpu/CField.h:119
void allocate(const IntVec< D > &meshDimensions)
Allocate the underlying C array for an FFT grid.
FFT()
Default constructor.
Definition cpu/FFT.tpp:59
IntVec< D > const & meshDimensions() const
Return the dimensions of the grid for which this was setup.
Definition cpu/FFT.h:264
static void computeKMesh(IntVec< D > const &rMeshDimensions, IntVec< D > &kMeshDimensions, int &kSize)
Compute dimensions and size of k-space mesh for DFT of real data.
Definition cpu/FFT.tpp:264
void forwardTransform(RField< D > const &in, RFieldDft< D > &out) const
Compute forward (real-to-complex) discrete Fourier transform (DFT).
Definition cpu/FFT.tpp:153
void setup(IntVec< D > const &meshDimensions)
Setup grid dimensions, FFT plans and work space.
Definition cpu/FFT.tpp:94
void inverseTransformSafe(RFieldDft< D > const &in, RField< D > &out) const
Compute inverse (complex-to-real) DFT without overwriting input.
Definition cpu/FFT.tpp:199
void inverseTransform(CField< D > const &in, CField< D > &out) const
Compute complex-to-complex inverse Fourier transform.
Definition cpu/FFT.tpp:243
virtual ~FFT()
Destructor.
Definition cpu/FFT.tpp:74
void inverseTransformUnsafe(RFieldDft< D > &in, RField< D > &out) const
Compute inverse (complex-to-real) DFT, overwriting the input.
Definition cpu/FFT.tpp:181
Fourier transform of a real field on an FFT mesh.
IntVec< D > const & meshDimensions() const
Return vector of spatial mesh dimensions by constant reference.
Field of real double precision values on an FFT mesh.
Definition cpu/RField.h:27
void allocate(IntVec< D > const &meshDimensions)
Allocate the underlying C array for an FFT grid.
const IntVec< D > & meshDimensions() const
Return mesh dimensions by constant reference.
Definition cpu/RField.h:111
int capacity() const
Return allocated size.
Definition Array.h:144
#define UTIL_CHECK(condition)
Assertion macro suitable for serial or parallel production code.
Definition global.h:68
Fields and FFTs for periodic boundary conditions (CPU)
Definition complex.cpp:12
Periodic fields and crystallography.
Definition complex.cpp:11
PSCF package top-level namespace.