IPPL (Independent Parallel Particle Layer)
IPPL
Loading...
Searching...
No Matches
FFTTruncatedGreenPeriodicPoissonSolver.hpp
Go to the documentation of this file.
1//
2// Class FFTTruncatedGreenPeriodicPoissonSolver
3// Poisson solver for periodic boundaries, based on FFTs.
4// Solves laplace(phi) = -rho, and E = -grad(phi).
5//
6// Uses a convolution with a Green's function given by:
7// G(r) = forceConstant * erf(alpha * r) / r,
8// alpha = controls long-range interaction.
9//
10//
11
12namespace ippl {
13
15 // constructor and destructor
16
17 template <typename FieldLHS, typename FieldRHS>
26
27 template <typename FieldLHS, typename FieldRHS>
40
41 template <typename FieldLHS, typename FieldRHS>
54
55 template <typename FieldLHS, typename FieldRHS>
60
62 // initializeFields method, called in constructor
63
64 template <typename FieldLHS, typename FieldRHS>
66 static_assert(Dim == 3, "Dimension other than 3 not supported in FFTTruncatedGreenPeriodicPoissonSolver!");
67
68 // get layout and mesh
69 layout_mp = &(this->rhs_mp->getLayout());
70 mesh_mp = &(this->rhs_mp->get_mesh());
71 mpi::Communicator comm = layout_mp->comm;
72
73 // get mesh spacing
74 hr_m = mesh_mp->getMeshSpacing();
75
76 // get origin
77 Vector_t origin = mesh_mp->getOrigin();
78
79 // create domain for the real fields
80 domain_m = layout_mp->getDomain();
81
82 // get the mesh spacings and sizes for each dimension
83 for (unsigned int i = 0; i < Dim; ++i) {
84 nr_m[i] = domain_m[i].length();
85 }
86
87 // define decomposition (parallel / serial)
88 std::array<bool, Dim> isParallel = layout_mp->isParallel();
89
90 // create the domain for the transformed (complex) fields
91 // since we use HeFFTe for the transforms it doesn't require permuting to the right
92 // one of the dimensions has only (n/2 +1) as our original fields are fully real
93 // the dimension is given by the user via r2c_direction
94 unsigned int RCDirection = this->params_m.template get<int>("r2c_direction");
95 for (unsigned int i = 0; i < Dim; ++i) {
96 if (i == RCDirection)
97 domainComplex_m[RCDirection] = Index(nr_m[RCDirection] / 2 + 1);
98 else
99 domainComplex_m[i] = Index(nr_m[i]);
100 }
101
102 // create mesh and layout for the real to complex FFT transformed fields
103 using mesh_type = typename lhs_type::Mesh_t;
104 meshComplex_m = std::unique_ptr<mesh_type>(new mesh_type(domainComplex_m, hr_m, origin));
106 std::unique_ptr<FieldLayout_t>(new FieldLayout_t(comm, domainComplex_m, isParallel));
107
108 // initialize fields
109 grn_m.initialize(*mesh_mp, *layout_mp);
113
114 // create the FFT object
115 fft_m = std::make_unique<FFT_t>(*layout_mp, *layoutComplex_m, this->params_m);
116 fft_m->warmup(grn_m, grntr_m); // warmup the FFT object
117
118 // these are fields that are used for calculating the Green's function
119 for (unsigned int d = 0; d < Dim; ++d) {
120 grnIField_m[d].initialize(*mesh_mp, *layout_mp);
121
122 // get number of ghost points and the Kokkos view to iterate over field
123 auto view = grnIField_m[d].getView();
124 const int nghost = grnIField_m[d].getNghost();
125 const auto& ldom = layout_mp->getLocalNDIndex();
126
127 // the length of the physical domain
128 const int size = nr_m[d];
129
130 // Kokkos parallel for loop to initialize grnIField[d]
131 switch (d) {
132 case 0:
133 Kokkos::parallel_for(
134 "Helper index Green field initialization",
135 ippl::getRangePolicy(view, nghost),
136 KOKKOS_LAMBDA(const int i, const int j, const int k) {
137 // go from local indices to global
138 const int ig = i + ldom[0].first() - nghost;
139 const int jg = j + ldom[1].first() - nghost;
140 const int kg = k + ldom[2].first() - nghost;
141
142 // assign (index)^2 if 0 <= index < N, and (2N-index)^2 elsewhere
143 const bool outsideN = (ig >= size / 2);
144 view(i, j, k) = (size * outsideN - ig) * (size * outsideN - ig);
145
146 // add 1.0 if at (0,0,0) to avoid singularity
147 const bool isOrig = ((ig == 0) && (jg == 0) && (kg == 0));
148 view(i, j, k) += isOrig * 1.0;
149 });
150 break;
151 case 1:
152 Kokkos::parallel_for(
153 "Helper index Green field initialization",
154 ippl::getRangePolicy(view, nghost),
155 KOKKOS_LAMBDA(const int i, const int j, const int k) {
156 // go from local indices to global
157 const int jg = j + ldom[1].first() - nghost;
158
159 // assign (index)^2 if 0 <= index < N, and (2N-index)^2 elsewhere
160 const bool outsideN = (jg >= size / 2);
161 view(i, j, k) = (size * outsideN - jg) * (size * outsideN - jg);
162 });
163 break;
164 case 2:
165 Kokkos::parallel_for(
166 "Helper index Green field initialization",
167 ippl::getRangePolicy(view, nghost),
168 KOKKOS_LAMBDA(const int i, const int j, const int k) {
169 // go from local indices to global
170 const int kg = k + ldom[2].first() - nghost;
171
172 // assign (index)^2 if 0 <= index < N, and (2N-index)^2 elsewhere
173 const bool outsideN = (kg >= size / 2);
174 view(i, j, k) = (size * outsideN - kg) * (size * outsideN - kg);
175 });
176 break;
177 }
178 }
179
180 // call greensFunction and we will get the transformed G in the class attribute
181 // this is done in initialization so that we already have the precomputed fct
182 // for all timesteps (green's fct will only change if mesh size changes)
183
185 };
186
188 // compute electric potential by solving Poisson's eq given a field rho and mesh spacings hr
189 template <typename FieldLHS, typename FieldRHS>
191 // get the output type (sol, grad, or sol & grad)
192 const int out = this->params_m.template get<int>("output_type");
193
194 // set the mesh & spacing, which may change each timestep
195 mesh_mp = &(this->rhs_mp->get_mesh());
196
197 // check whether the mesh spacing has changed with respect to the old one
198 // if yes, update and set green flag to true
199 bool green = false;
200 for (unsigned int i = 0; i < Dim; ++i) {
201 if (hr_m[i] != mesh_mp->getMeshSpacing(i)) {
202 hr_m[i] = mesh_mp->getMeshSpacing(i);
203 green = true;
204 }
205 }
206
207 // set mesh spacing on the other grids again
208 meshComplex_m->setMeshSpacing(hr_m);
209
210 // forward FFT of the charge density field on doubled grid
211 rhotr_m = 0.0;
212 fft_m->transform(FORWARD, *(this->rhs_mp), rhotr_m);
213
214 // call greensFunction to recompute if the mesh spacing has changed
215 if (green) {
217 }
218
219 // multiply FFT(rho2)*FFT(green)
220 // convolution becomes multiplication in FFT
222
223 using index_array_type = typename RangePolicy<Dim>::index_array_type;
224 if ((out == Base::GRAD) || (out == Base::SOL_AND_GRAD)) {
225 // Compute gradient in Fourier space and then
226 // take inverse FFT.
227
228 const Trhs pi = Kokkos::numbers::pi_v<Trhs>;
229 Kokkos::complex<Trhs> imag = {0.0, 1.0};
230
231 auto view = rhotr_m.getView();
232 const int nghost = rhotr_m.getNghost();
233 auto tempview = tempFieldComplex_m.getView();
234 auto viewRhs = this->rhs_mp->getView();
235 auto viewLhs = this->lhs_mp->getView();
236 const int nghostL = this->lhs_mp->getNghost();
237 const auto& lDomComplex = layoutComplex_m->getLocalNDIndex();
238
239 // define some member variables in local scope for the parallel_for
240 Vector_t hsize = hr_m;
242 Vector_t origin = mesh_mp->getOrigin();
243
244 for (size_t gd = 0; gd < Dim; ++gd) {
246 "Gradient FFTPeriodicPoissonSolver", getRangePolicy(view, nghost),
247 KOKKOS_LAMBDA(const index_array_type& args) {
248 Vector<int, Dim> iVec = args - nghost;
249
250 for (unsigned d = 0; d < Dim; ++d) {
251 iVec[d] += lDomComplex[d].first();
252 }
253
254 Vector_t kVec;
255
256 for (size_t d = 0; d < Dim; ++d) {
257 const Trhs Len = N[d] * hsize[d];
258 bool shift = (iVec[d] > (N[d] / 2));
259 bool notMid = (iVec[d] != (N[d] / 2));
260 // For the noMid part see
261 // https://math.mit.edu/~stevenj/fft-deriv.pdf Algorithm 1
262 kVec[d] = notMid * 2 * pi / Len * (iVec[d] - shift * N[d]);
263 }
264
265 Trhs Dr = 0;
266 for (unsigned d = 0; d < Dim; ++d) {
267 Dr += kVec[d] * kVec[d];
268 }
269
270 apply(tempview, args) = apply(view, args);
271
272 bool isNotZero = (Dr != 0.0);
273
274 apply(tempview, args) *= -(isNotZero * imag * kVec[gd]);
275 });
276
277 fft_m->transform(BACKWARD, *this->rhs_mp, tempFieldComplex_m);
278
280 "Assign Gradient FFTPeriodicPoissonSolver", getRangePolicy(viewLhs, nghostL),
281 KOKKOS_LAMBDA(const index_array_type& args) {
282 apply(viewLhs, args)[gd] = apply(viewRhs, args);
283 });
284 }
285
286 // normalization is double counted due to 2 transforms
287 *(this->lhs_mp) = *(this->lhs_mp) * nr_m[0] * nr_m[1] * nr_m[2];
288 // discretization of integral requires h^3 factor
289 *(this->lhs_mp) = *(this->lhs_mp) * hr_m[0] * hr_m[1] * hr_m[2];
290 }
291
292 if ((out == Base::SOL) || (out == Base::SOL_AND_GRAD)) {
293 // inverse FFT of the product and store the electrostatic potential in rho2_mr
294 fft_m->transform(BACKWARD, *(this->rhs_mp), rhotr_m);
295
296 // normalization is double counted due to 2 transforms
297 *(this->rhs_mp) = *(this->rhs_mp) * nr_m[0] * nr_m[1] * nr_m[2];
298 // discretization of integral requires h^3 factor
299 *(this->rhs_mp) = *(this->rhs_mp) * hr_m[0] * hr_m[1] * hr_m[2];
300 }
301 };
302
304 // calculate FFT of the Green's function
305
306 template <typename FieldLHS, typename FieldRHS>
308 grn_m = 0.0;
309
310 // This alpha parameter is a choice for the Green's function
311 // it controls the "range" of the Green's function (e.g.
312 // for the collision modelling method, it indicates
313 // the splitting between Particle-Particle interactions
314 // and the Particle-Mesh computations).
315 const Trhs alpha = this->params_m. template get<Trhs>("alpha");
316 const Trhs forceConstant = this->params_m. template get<Trhs>("force_constant");
317
318 // calculate square of the mesh spacing for each dimension
319 Vector_t hrsq(hr_m * hr_m);
320
321 // use the grnIField_m helper field to compute Green's function
322 for (unsigned int i = 0; i < Dim; ++i) {
323 grn_m = grn_m + grnIField_m[i] * hrsq[i];
324 }
325
326 typename Field_t::view_type view = grn_m.getView();
327 const int nghost = grn_m.getNghost();
328 const auto& ldom = layout_mp->getLocalNDIndex();
329
330 // Kokkos parallel for loop to find (0,0,0) point and regularize
331 Kokkos::parallel_for(
332 "Assign Green's function ", ippl::getRangePolicy(view, nghost),
333 KOKKOS_LAMBDA(const int i, const int j, const int k) {
334 // go from local indices to global
335 const int ig = i + ldom[0].first() - nghost;
336 const int jg = j + ldom[1].first() - nghost;
337 const int kg = k + ldom[2].first() - nghost;
338
339 const bool isOrig = (ig == 0 && jg == 0 && kg == 0);
340
341 Trhs r = Kokkos::real(Kokkos::sqrt(view(i, j, k)));
342 view(i, j, k) = (!isOrig) * forceConstant * (Kokkos::erf(alpha * r) / r);
343 });
344
345 // perform the FFT of the Green's function for the convolution
346 fft_m->transform(FORWARD, grn_m, grntr_m);
347 };
348
349} // namespace ippl
const double pi
ippl::FieldLayout< Dim > FieldLayout_t
Definition datatypes.h:21
Definition Archive.h:20
@ FORWARD
Definition FFT.h:63
@ BACKWARD
Definition FFT.h:64
KOKKOS_INLINE_FUNCTION constexpr decltype(auto) apply(const View &view, const Coords &coords)
RangePolicy< View::rank, typenameView::execution_space, PolicyArgs... >::policy_type getRangePolicy(const View &view, int shift=0)
void parallel_for(const std::string &name, const ExecPolicy &policy, const FunctorType &functor)
KOKKOS_INLINE_FUNCTION auto & get(Tuple< Ts... > &t)
Accessor function to get an element mutable reference at a specific index from a Tuple.
Definition Tuple.h:314
lhs_type * lhs_mp
Definition Poisson.h:123
ParameterList params_m
Definition Poisson.h:120
void setLhs(lhs_type &lhs)
Definition Poisson.h:90
virtual void setRhs(rhs_type &rhs)
Definition Poisson.h:96
rhs_type * rhs_mp
Definition Poisson.h:122
::ippl::Vector< index_type, Dim > index_array_type