IPPL API Reference
Independent Parallel Particle Layer C++ API
Loading...
Searching...
No Matches
FFTOpenPoissonSolver.hpp
1//
2// Class FFTOpenPoissonSolver
3// FFT-based Poisson Solver for open boundaries.
4// Solves laplace(phi) = -rho, and E = -grad(phi).
5//
6//
7// Communication helpers (pack / unpack / solver_send / solver_recv) now live in
8// Field/FieldBufferOps.hpp and are included via FFTOpenPoissonSolver.h. All call
9// sites below use the ippl::detail:: qualified names.
10
11namespace ippl {
12
13 using detail::pack;
14 using detail::solver_recv;
15 using detail::solver_send;
16 using detail::unpack;
17
19 // constructor and destructor
20 template <typename FieldLHS, typename FieldRHS>
21 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::FFTOpenPoissonSolver()
22 : Base()
23 , mesh_mp(nullptr)
24 , layout_mp(nullptr)
25 , mesh2_m(nullptr)
26 , layout2_m(nullptr)
27 , meshComplex_m(nullptr)
28 , layoutComplex_m(nullptr)
29 , mesh4_m(nullptr)
30 , layout4_m(nullptr)
31 , mesh2n1_m(nullptr)
32 , layout2n1_m(nullptr)
33 , isGradFD_m(false) {
34 setDefaultParameters();
35 }
36
37 template <typename FieldLHS, typename FieldRHS>
38 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::FFTOpenPoissonSolver(rhs_type& rhs,
39 ParameterList& params)
40 : mesh_mp(nullptr)
41 , layout_mp(nullptr)
42 , mesh2_m(nullptr)
43 , layout2_m(nullptr)
44 , meshComplex_m(nullptr)
45 , layoutComplex_m(nullptr)
46 , mesh4_m(nullptr)
47 , layout4_m(nullptr)
48 , mesh2n1_m(nullptr)
49 , layout2n1_m(nullptr)
50 , isGradFD_m(false) {
51 using T = typename FieldLHS::value_type::value_type;
52 static_assert(std::is_floating_point<T>::value, "Not a floating point type");
53
54 setDefaultParameters();
55 this->params_m.merge(params);
56 this->params_m.update("output_type", Base::SOL);
57
58 this->setRhs(rhs);
59 }
60
61 template <typename FieldLHS, typename FieldRHS>
62 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::FFTOpenPoissonSolver(lhs_type& lhs, rhs_type& rhs,
63 ParameterList& params)
64 : mesh_mp(nullptr)
65 , layout_mp(nullptr)
66 , mesh2_m(nullptr)
67 , layout2_m(nullptr)
68 , meshComplex_m(nullptr)
69 , layoutComplex_m(nullptr)
70 , mesh4_m(nullptr)
71 , layout4_m(nullptr)
72 , mesh2n1_m(nullptr)
73 , layout2n1_m(nullptr)
74 , isGradFD_m(false) {
75 using T = typename FieldLHS::value_type::value_type;
76 static_assert(std::is_floating_point<T>::value, "Not a floating point type");
77
78 setDefaultParameters();
79 this->params_m.merge(params);
80
81 this->setLhs(lhs);
82 this->setRhs(rhs);
83 }
84
86 // override setRhs to call class-specific initialization
87 template <typename FieldLHS, typename FieldRHS>
89 Base::setRhs(rhs);
90
91 // start a timer
92 static IpplTimings::TimerRef initialize = IpplTimings::getTimer("Initialize");
93 IpplTimings::startTimer(initialize);
94
95 initializeFields();
96
97 IpplTimings::stopTimer(initialize);
98 }
99
101 // allows user to set gradient of phi = Efield instead of spectral
102 // calculation of Efield (which uses FFTs)
103
104 template <typename FieldLHS, typename FieldRHS>
106 // get the output type (sol, grad, or sol & grad)
107 const int out = this->params_m.template get<int>("output_type");
108
109 if (out != Base::SOL_AND_GRAD) {
110 throw IpplException(
111 "FFTOpenPoissonSolver::setGradFD()",
112 "Cannot use gradient for Efield computation unless output type is SOL_AND_GRAD");
113 } else {
114 isGradFD_m = true;
115 }
116 }
117
119 // initializeFields method, called in constructor
120
121 template <typename FieldLHS, typename FieldRHS>
122 void FFTOpenPoissonSolver<FieldLHS, FieldRHS>::initializeFields() {
123 // get algorithm and hessian flag from parameter list
124 const int alg = this->params_m.template get<int>("algorithm");
125 const int greensFunctionType = this->params_m.template get<int>("greens_function");
126 const bool hessian = this->params_m.template get<bool>("hessian");
127
128 // first check if valid algorithm choice
129 if ((alg != Algorithm::VICO) && (alg != Algorithm::HOCKNEY)
130 && (alg != Algorithm::BIHARMONIC) && (alg != Algorithm::DCT_VICO)) {
131 throw IpplException("FFTOpenPoissonSolver::initializeFields()",
132 "Currently only HOCKNEY, VICO, DCT_VICO, and BIHARMONIC are "
133 "supported for open BCs");
134 }
135
136 if ((greensFunctionType != GreenFunction::STANDARD)
137 && (greensFunctionType != GreenFunction::INTEGRATED)) {
138 throw IpplException("FFTOpenPoissonSolver::initializeFields()",
139 "Currently only STANDARD and INTEGRATED Green's functions are "
140 "supported for open BCs");
141 }
142
143 if ((greensFunctionType == GreenFunction::INTEGRATED)
144 && ((alg != Algorithm::HOCKNEY) || (Dim != 3))) {
145 throw IpplException("FFTOpenPoissonSolver::initializeFields()",
146 "The integrated Green's function is currently only implemented "
147 "for 3D HOCKNEY open BCs");
148 }
149
150 // check dimension
151 if ((Dim == 2) && (alg != Algorithm::HOCKNEY)) {
152 throw IpplException("FFTOpenPoissonSolver::initializeFields()",
153 "Only HOCKNEY supports 2D!");
154 } else if (Dim == 1) {
155 throw IpplException("FFTOpenPoissonSolver::initializeFields()",
156 "Only 2D and 3D solves supported!");
157 }
158
159 // get layout and mesh
160 layout_mp = &(this->rhs_mp->getLayout());
161 mesh_mp = &(this->rhs_mp->get_mesh());
162 mpi::Communicator comm = layout_mp->comm;
163
164 // get mesh spacing and origin
165 hr_m = mesh_mp->getMeshSpacing();
166 vector_type origin = mesh_mp->getOrigin();
167
168 // create domain for the real fields
169 domain_m = layout_mp->getDomain();
170
171 // get the mesh spacings and sizes for each dimension
172 for (unsigned int i = 0; i < Dim; ++i) {
173 nr_m[i] = domain_m[i].length();
174
175 // create the doubled domain for the FFT procedure
176 domain2_m[i] = Index(2 * nr_m[i]);
177 }
178
179 // define decomposition (parallel / serial)
180 std::array<bool, Dim> isParallel = layout_mp->isParallel();
181
182 // create double sized mesh and layout objects using the previously defined domain2_m
183 using mesh_type = typename lhs_type::Mesh_t;
184 mesh2_m = std::unique_ptr<mesh_type>(new mesh_type(domain2_m, hr_m, origin));
185 layout2_m = std::unique_ptr<FieldLayout_t>(new FieldLayout_t(comm, domain2_m, isParallel));
186
187 // create the domain for the transformed (complex) fields
188 // since we use HeFFTe for the transforms it doesn't require permuting to the right
189 // one of the dimensions has only (n/2 +1) as our original fields are fully real
190 // the dimension is given by the user via r2c_direction
191 unsigned int RCDirection = this->params_m.template get<int>("r2c_direction");
192 for (unsigned int i = 0; i < Dim; ++i) {
193 if (i == RCDirection) {
194 domainComplex_m[RCDirection] = Index(nr_m[RCDirection] + 1);
195 } else {
196 domainComplex_m[i] = Index(2 * nr_m[i]);
197 }
198 }
199
200 // create mesh and layout for the real to complex FFT transformed fields
201 meshComplex_m = std::unique_ptr<mesh_type>(new mesh_type(domainComplex_m, hr_m, origin));
202 layoutComplex_m =
203 std::unique_ptr<FieldLayout_t>(new FieldLayout_t(comm, domainComplex_m, isParallel));
204
205 // initialize fields
206 storage_field.initialize(*mesh2_m, *layout2_m);
207 rho2tr_m.initialize(*meshComplex_m, *layoutComplex_m);
208 grntr_m.initialize(*meshComplex_m, *layoutComplex_m);
209
210 int out = this->params_m.template get<int>("output_type");
211 if (((out == Base::GRAD || out == Base::SOL_AND_GRAD) && !isGradFD_m) || hessian) {
212 temp_m.initialize(*meshComplex_m, *layoutComplex_m);
213 }
214
215 if (hessian) {
216 hess_m.initialize(*mesh_mp, *layout_mp);
217 }
218
219 // create the FFT object
220 fft_m = std::make_unique<FFT_t>(*layout2_m, *layoutComplex_m, this->params_m);
221
222 // if Vico, also need to create mesh and layout for 4N Fourier domain
223 // on this domain, the truncated Green's function is defined
224 // also need to create the 4N complex grid, on which precomputation step done
225 if (alg == Algorithm::VICO || alg == Algorithm::BIHARMONIC) {
226 // start a timer
227 static IpplTimings::TimerRef initialize_vico =
228 IpplTimings::getTimer("Initialize: extra Vico");
229 IpplTimings::startTimer(initialize_vico);
230
231 for (unsigned int i = 0; i < Dim; ++i) {
232 domain4_m[i] = Index(4 * nr_m[i]);
233 }
234
235 // 4N grid
236 using mesh_type = typename lhs_type::Mesh_t;
237 mesh4_m = std::unique_ptr<mesh_type>(new mesh_type(domain4_m, hr_m, origin));
238 layout4_m =
239 std::unique_ptr<FieldLayout_t>(new FieldLayout_t(comm, domain4_m, isParallel));
240
241 // initialize fields
242 grnL_m.initialize(*mesh4_m, *layout4_m);
243
244 // create a Complex-to-Complex FFT object to transform for layout4
245 fft4n_m = std::make_unique<FFT<CCTransform, CxField_gt>>(*layout4_m, this->params_m);
246
247 IpplTimings::stopTimer(initialize_vico);
248 }
249
250 // if DCT_VICO, need 2N+1 mesh, layout, domain, and green's function field for
251 // precomputation
252 if (alg == Algorithm::DCT_VICO) {
253 // start a timer
254 static IpplTimings::TimerRef initialize_vico =
255 IpplTimings::getTimer("Initialize: extra Vico");
256 IpplTimings::startTimer(initialize_vico);
257
258 // 2N+1 domain for DCT
259 for (unsigned int i = 0; i < Dim; ++i) {
260 domain2n1_m[i] = Index(2 * nr_m[i] + 1);
261 }
262
263 // 2N+1 grid
264 mesh2n1_m = std::unique_ptr<mesh_type>(new mesh_type(domain2n1_m, hr_m, origin));
265 layout2n1_m =
266 std::unique_ptr<FieldLayout_t>(new FieldLayout_t(comm, domain2n1_m, isParallel));
267
268 // initialize fields
269 grn2n1_m.initialize(*mesh2n1_m, *layout2n1_m); // 2N+1 grnL
270
271 // create real to real FFT object for 2N+1 grid
272 fft2n1_m = std::make_unique<FFT<Cos1Transform, Field_t>>(*layout2n1_m, this->params_m);
273
274 IpplTimings::stopTimer(initialize_vico);
275 }
276
277 // these are fields that are used for calculating the standard Green's function for Hockney
278 if ((alg == Algorithm::HOCKNEY) && (greensFunctionType == GreenFunction::STANDARD)) {
279 // start a timer
280 static IpplTimings::TimerRef initialize_hockney =
281 IpplTimings::getTimer("Initialize: extra Hockney");
282 IpplTimings::startTimer(initialize_hockney);
283
284 for (unsigned int gd = 0; gd < Dim; ++gd) {
285 grnIField_m[gd].initialize(*mesh2_m, *layout2_m);
286
287 // get number of ghost points and the Kokkos view to iterate over field
288 auto view = grnIField_m[gd].getView();
289 const int nghost = grnIField_m[gd].getNghost();
290 const auto& ldom = layout2_m->getLocalNDIndex();
291
292 // the length of the physical domain
293 const int size = nr_m[gd];
294
295 // Kokkos parallel for loop to initialize grnIField[d]
296 using index_array_type = typename RangePolicy<Dim>::index_array_type;
297 ippl::parallel_for(
298 "Helper index Green field initialization",
299 grnIField_m[gd].getFieldRangePolicy(),
300 KOKKOS_LAMBDA(const index_array_type& args) {
301 scalar_type checkVal = 0.0;
302
303 // go from local indices to global
304 Vector<int, Dim> igVec = args - nghost;
305 for (unsigned d = 0; d < Dim; ++d) {
306 igVec[d] += ldom[d].first();
307 checkVal += igVec[d];
308 }
309
310 // assign (index)^2 if 0 <= index < N, and (2N-index)^2 elsewhere
311 const bool outsideN = (igVec[gd] >= size);
312 apply(view, args) =
313 (2 * size * outsideN - igVec[gd]) * (2 * size * outsideN - igVec[gd]);
314
315 // add 1.0 if at (0,0,0) to avoid singularity
316 const bool isOrig = (checkVal == 0);
317 apply(view, args) += isOrig * 1.0;
318 });
319 }
320 IpplTimings::stopTimer(initialize_hockney);
321 }
322
323 static IpplTimings::TimerRef warmup = IpplTimings::getTimer("Warmup");
324 IpplTimings::startTimer(warmup);
325
326 // "empty" transforms to warmup all the FFTs
327 fft_m->warmup(rho2_mr, rho2tr_m);
328 if (alg == Algorithm::VICO || alg == Algorithm::BIHARMONIC) {
329 fft4n_m->warmup(grnL_m);
330 }
331 if (alg == Algorithm::DCT_VICO) {
332 fft2n1_m->warmup(grn2n1_m);
333 }
334
335 IpplTimings::stopTimer(warmup);
336
337 rho2_mr = 0.0;
338 rho2tr_m = 0.0;
339 if (alg == Algorithm::VICO || alg == Algorithm::BIHARMONIC) {
340 grnL_m = 0.0;
341 }
342 if (alg == Algorithm::DCT_VICO) {
343 grn2n1_m = 0.0;
344 }
345
346 // call greensFunction and we will get the transformed G in the class attribute
347 // this is done in initialization so that we already have the precomputed fct
348 // for all timesteps (green's fct will only change if mesh size changes)
349 static IpplTimings::TimerRef ginit = IpplTimings::getTimer("Green Init");
350 IpplTimings::startTimer(ginit);
351 greensFunction();
352 IpplTimings::stopTimer(ginit);
353 };
354
356 // compute electric potential by solving Poisson's eq given a field rho and mesh spacings hr
357 template <typename FieldLHS, typename FieldRHS>
359 // start a timer
360 static IpplTimings::TimerRef solve = IpplTimings::getTimer("Solve");
361 IpplTimings::startTimer(solve);
362
363 // get the output type (sol, grad, or sol & grad)
364 const int out = this->params_m.template get<int>("output_type");
365
366 // get the algorithm (hockney, vico, or biharmonic)
367 const int alg = this->params_m.template get<int>("algorithm");
368
369 // get hessian flag (if true, we compute the Hessian)
370 const bool hessian = this->params_m.template get<bool>("hessian");
371
372 // set the mesh & spacing, which may change each timestep
373 mesh_mp = &(this->rhs_mp->get_mesh());
374
375 // check whether the mesh spacing has changed with respect to the old one
376 // if yes, update and set green flag to true
377 bool green = false;
378 for (unsigned int i = 0; i < Dim; ++i) {
379 if (hr_m[i] != mesh_mp->getMeshSpacing(i)) {
380 hr_m[i] = mesh_mp->getMeshSpacing(i);
381 green = true;
382 }
383 }
384
385 // set mesh spacing on the other grids again
386 mesh2_m->setMeshSpacing(hr_m);
387 meshComplex_m->setMeshSpacing(hr_m);
388
389 // field object on the doubled grid; zero-padded
390 rho2_mr = 0.0;
391
392 // start a timer
393 static IpplTimings::TimerRef stod = IpplTimings::getTimer("Solve: Physical to double");
394 IpplTimings::startTimer(stod);
395
396 // store rho (RHS) in the lower left quadrant of the doubled grid
397 // with or without communication (if only 1 rank)
398
399 const int ranks = Comm->size();
400
401 auto view2 = rho2_mr.getView();
402 auto view1 = this->rhs_mp->getView();
403
404 const int nghost2 = rho2_mr.getNghost();
405 const int nghost1 = this->rhs_mp->getNghost();
406
407 const auto& ldom2 = layout2_m->getLocalNDIndex();
408 const auto& ldom1 = layout_mp->getLocalNDIndex();
409
410 using index_array_type = typename RangePolicy<Dim>::index_array_type;
411
412 if (ranks > 1) {
413 // COMMUNICATION
414 const auto& lDomains2 = layout2_m->getHostLocalDomains();
415
416 // send
417 std::vector<MPI_Request> requests(0);
418
419 for (int i = 0; i < ranks; ++i) {
420 if (lDomains2[i].touches(ldom1)) {
421 auto intersection = lDomains2[i].intersect(ldom1);
422
423 solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom1, nghost1, view1,
424 fd_m, requests);
425 }
426 }
427
428 // receive
429 const auto& lDomains1 = layout_mp->getHostLocalDomains();
430
431 for (int i = 0; i < ranks; ++i) {
432 if (lDomains1[i].touches(ldom2)) {
433 auto intersection = lDomains1[i].intersect(ldom2);
434
435 mpi::Communicator::size_type nrecvs;
436 nrecvs = intersection.size();
437
438 buffer_type buf = Comm->getBuffer<memory_space, Trhs>(nrecvs);
439
440 Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs), nrecvs);
441 buf->resetReadPos();
442
443 unpack(intersection, view2, fd_m, nghost2, ldom2);
444 }
445 }
446
447 // wait for all messages to be received
448 if (requests.size() > 0) {
449 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
450 }
451 ippl::Comm->freeAllBuffers();
452 } else {
453 ippl::parallel_for(
454 "Write rho on the doubled grid", this->rhs_mp->getFieldRangePolicy(),
455 KOKKOS_LAMBDA(const index_array_type& args) {
456 scalar_type checkVal = 0;
457
458 Vector<int, Dim> igVec1 = args - nghost1;
459 Vector<int, Dim> igVec2 = args - nghost2;
460
461 for (unsigned d = 0; d < Dim; ++d) {
462 igVec1[d] += ldom1[d].first();
463 igVec2[d] += ldom2[d].first();
464
465 checkVal += Kokkos::abs(igVec1[d] - igVec2[d]);
466 }
467
468 // Write physical rho on [0,N-1] of doubled field.
469 // Check whether we are in the 1st quadrant by checking whether
470 // the global indices for view1 and view2 are equal.
471 // This is done using checkVal, which should be 0 if ig1 = ig2.
472 const bool isQuadrant1 = (checkVal == 0);
473 apply(view2, args) = apply(view1, args) * isQuadrant1;
474 });
475 }
476
477 IpplTimings::stopTimer(stod);
478
479 // start a timer
480 static IpplTimings::TimerRef fftrho = IpplTimings::getTimer("FFT: Rho");
481 IpplTimings::startTimer(fftrho);
482
483 // forward FFT of the charge density field on doubled grid
484 fft_m->transform(FORWARD, rho2_mr, rho2tr_m);
485
486 IpplTimings::stopTimer(fftrho);
487
488 // call greensFunction to recompute if the mesh spacing has changed
489 if (green) {
490 greensFunction();
491 }
492
493 // multiply FFT(rho2)*FFT(green)
494 // convolution becomes multiplication in FFT
495 // minus sign since we are solving laplace(phi) = -rho
496 rho2tr_m = -rho2tr_m * grntr_m;
497
498 // if output_type is SOL or SOL_AND_GRAD, we caculate solution
499 if ((out == Base::SOL) || (out == Base::SOL_AND_GRAD)) {
500 // start a timer
501 static IpplTimings::TimerRef fftc = IpplTimings::getTimer("FFT: Convolution");
502 IpplTimings::startTimer(fftc);
503
504 // inverse FFT of the product and store the electrostatic potential in rho2_mr
505 fft_m->transform(BACKWARD, rho2_mr, rho2tr_m);
506
507 IpplTimings::stopTimer(fftc);
508 // Hockney: multiply the rho2_mr field by the total number of points to account for
509 // double counting (rho and green) of normalization factor in forward transform
510 // also multiply by the mesh spacing^3 (to account for discretization)
511 // Vico: need to multiply by normalization factor of 1/4N^3,
512 // since only backward transform was performed on the 4N grid
513 // DCT_VICO: need to multiply by a factor of (2N)^3 to match the normalization factor in
514 // the transform.
515 for (unsigned int i = 0; i < Dim; ++i) {
516 switch (alg) {
517 case Algorithm::HOCKNEY:
518 rho2_mr = rho2_mr * 2.0 * nr_m[i] * hr_m[i];
519 break;
520 case Algorithm::VICO:
521 case Algorithm::BIHARMONIC:
522 rho2_mr = rho2_mr * 2.0 * (1.0 / 4.0);
523 break;
524 case Algorithm::DCT_VICO:
525 rho2_mr = rho2_mr * (1.0 / 4.0);
526 break;
527 default:
528 throw IpplException(
529 "FFTOpenPoissonSolver::initializeFields()",
530 "Currently only HOCKNEY, VICO, DCT_VICO, and BIHARMONIC are "
531 "supported for open BCs");
532 }
533 }
534
535 // start a timer
536 static IpplTimings::TimerRef dtos = IpplTimings::getTimer("Solve: Double to physical");
537 IpplTimings::startTimer(dtos);
538
539 // get the physical part only --> physical electrostatic potential is now given in RHS
540 // need communication if more than one rank
541
542 if (ranks > 1) {
543 // COMMUNICATION
544
545 // send
546 const auto& lDomains1 = layout_mp->getHostLocalDomains();
547
548 std::vector<MPI_Request> requests(0);
549
550 for (int i = 0; i < ranks; ++i) {
551 if (lDomains1[i].touches(ldom2)) {
552 auto intersection = lDomains1[i].intersect(ldom2);
553
554 solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2, nghost2,
555 view2, fd_m, requests);
556 }
557 }
558
559 // receive
560 const auto& lDomains2 = layout2_m->getHostLocalDomains();
561
562 for (int i = 0; i < ranks; ++i) {
563 if (ldom1.touches(lDomains2[i])) {
564 auto intersection = ldom1.intersect(lDomains2[i]);
565
566 mpi::Communicator::size_type nrecvs;
567 nrecvs = intersection.size();
568
569 buffer_type buf = Comm->getBuffer<memory_space, Trhs>(nrecvs);
570
571 Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs),
572 nrecvs);
573 buf->resetReadPos();
574
575 unpack(intersection, view1, fd_m, nghost1, ldom1);
576 }
577 }
578
579 // wait for all messages to be received
580 if (requests.size() > 0) {
581 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
582 }
583 ippl::Comm->freeAllBuffers();
584
585 } else {
586 ippl::parallel_for(
587 "Write the solution into the LHS on physical grid",
588 this->rhs_mp->getFieldRangePolicy(),
589 KOKKOS_LAMBDA(const index_array_type& args) {
590 scalar_type checkVal = 0;
591
592 Vector<int, Dim> igVec1 = args - nghost1;
593 Vector<int, Dim> igVec2 = args - nghost2;
594
595 for (unsigned d = 0; d < Dim; ++d) {
596 igVec1[d] += ldom1[d].first();
597 igVec2[d] += ldom2[d].first();
598
599 checkVal += Kokkos::abs(igVec1[d] - igVec2[d]);
600 }
601
602 // Take [0,N-1] quadrant as physical solution.
603 // Check whether we are in the 1st quadrant by checking whether
604 // the global indices for view1 and view2 are equal.
605 // This is done using checkVal, which should be 0 if ig1 = ig2.
606 const bool isQuadrant1 = (checkVal == 0);
607 apply(view1, args) = apply(view2, args) * isQuadrant1;
608 });
609 }
610 IpplTimings::stopTimer(dtos);
611 }
612
613 // if we want finite differences Efield = -grad(phi)
614 // instead of computing it in Fourier domain
615 // this is only possible if SOL_AND_GRAD is the output type
616 if (isGradFD_m && (out == Base::SOL_AND_GRAD)) {
617 *(this->lhs_mp) = -grad(*this->rhs_mp);
618 }
619
620 // if output_type is GRAD or SOL_AND_GRAD, we calculate E-field (gradient in Fourier domain)
621 if (((out == Base::GRAD) || (out == Base::SOL_AND_GRAD)) && (!isGradFD_m)) {
622 // start a timer
623 static IpplTimings::TimerRef efield = IpplTimings::getTimer("Solve: Electric field");
624 IpplTimings::startTimer(efield);
625
626 // get E field view (LHS)
627 auto viewL = this->lhs_mp->getView();
628 const int nghostL = this->lhs_mp->getNghost();
629
630 // get rho2tr_m view (as we want to multiply by ik then transform)
631 auto viewR = rho2tr_m.getView();
632 const int nghostR = rho2tr_m.getNghost();
633 const auto& ldomR = layoutComplex_m->getLocalNDIndex();
634
635 // use temp_m as a temporary complex field
636 auto view_g = temp_m.getView();
637
638 // define some constants
639 const scalar_type pi = Kokkos::numbers::pi_v<scalar_type>;
640 const Kokkos::complex<Trhs> I = {0.0, 1.0};
641
642 // define some member variables in local scope for the parallel_for
643 vector_type hsize = hr_m;
644 Vector<int, Dim> N = nr_m;
645
646 // loop over each component (E = vector field)
647 for (size_t gd = 0; gd < Dim; ++gd) {
648 // loop over rho2tr_m to multiply by -ik (gradient in Fourier space)
649 ippl::parallel_for(
650 "Gradient - E field", rho2tr_m.getFieldRangePolicy(),
651 KOKKOS_LAMBDA(const index_array_type& args) {
652 // global indices for 2N rhotr_m
653 Vector<int, Dim> igVec = args - nghostR;
654 for (unsigned d = 0; d < Dim; ++d) {
655 igVec[d] += ldomR[d].first();
656 }
657
658 scalar_type k_gd;
659 const scalar_type Len = N[gd] * hsize[gd];
660 const bool shift = (igVec[gd] > N[gd]);
661 const bool notMid = (igVec[gd] != N[gd]);
662
663 k_gd = notMid * (pi / Len) * (igVec[gd] - shift * 2 * N[gd]);
664
665 apply(view_g, args) = -(I * k_gd) * apply(viewR, args);
666 });
667
668 // start a timer
669 static IpplTimings::TimerRef ffte = IpplTimings::getTimer("FFT: Efield");
670 IpplTimings::startTimer(ffte);
671
672 // transform to get E-field
673 fft_m->transform(BACKWARD, rho2_mr, temp_m);
674
675 IpplTimings::stopTimer(ffte);
676
677 // apply proper normalization
678 for (unsigned int i = 0; i < Dim; ++i) {
679 switch (alg) {
680 case Algorithm::HOCKNEY:
681 rho2_mr = rho2_mr * 2.0 * nr_m[i] * hr_m[i];
682 break;
683 case Algorithm::VICO:
684 case Algorithm::BIHARMONIC:
685 rho2_mr = rho2_mr * 2.0 * (1.0 / 4.0);
686 break;
687 case Algorithm::DCT_VICO:
688 rho2_mr = rho2_mr * (1.0 / 4.0);
689 break;
690 default:
691 throw IpplException(
692 "FFTOpenPoissonSolver::initializeFields()",
693 "Currently only HOCKNEY, VICO, DCT_VICO, and BIHARMONIC are "
694 "supported for open BCs");
695 }
696 }
697
698 // start a timer
699 static IpplTimings::TimerRef edtos =
700 IpplTimings::getTimer("Efield: double to phys.");
701 IpplTimings::startTimer(edtos);
702
703 // restrict to physical grid (N^3) and assign to LHS (E-field)
704 // communication needed if more than one rank
705 if (ranks > 1) {
706 // COMMUNICATION
707
708 // send
709 const auto& lDomains1 = layout_mp->getHostLocalDomains();
710 std::vector<MPI_Request> requests(0);
711
712 for (int i = 0; i < ranks; ++i) {
713 if (lDomains1[i].touches(ldom2)) {
714 auto intersection = lDomains1[i].intersect(ldom2);
715
716 solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2, nghost2,
717 view2, fd_m, requests);
718 }
719 }
720
721 // receive
722 const auto& lDomains2 = layout2_m->getHostLocalDomains();
723
724 for (int i = 0; i < ranks; ++i) {
725 if (ldom1.touches(lDomains2[i])) {
726 auto intersection = ldom1.intersect(lDomains2[i]);
727
728 mpi::Communicator::size_type nrecvs;
729 nrecvs = intersection.size();
730
731 buffer_type buf = Comm->getBuffer<memory_space, Trhs>(nrecvs);
732
733 Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf, nrecvs * sizeof(Trhs),
734 nrecvs);
735 buf->resetReadPos();
736
737 unpack(intersection, viewL, gd, fd_m, nghostL, ldom1);
738 }
739 }
740
741 // wait for all messages to be received
742 if (requests.size() > 0) {
743 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
744 }
745 ippl::Comm->freeAllBuffers();
746
747 } else {
748 ippl::parallel_for(
749 "Write the E-field on physical grid", this->lhs_mp->getFieldRangePolicy(),
750 KOKKOS_LAMBDA(const index_array_type& args) {
751 scalar_type checkVal = 0;
752
753 Vector<int, Dim> igVec1 = args - nghostL;
754 Vector<int, Dim> igVec2 = args - nghost2;
755
756 for (unsigned d = 0; d < Dim; ++d) {
757 igVec1[d] += ldom1[d].first();
758 igVec2[d] += ldom2[d].first();
759
760 checkVal += Kokkos::abs(igVec1[d] - igVec2[d]);
761 }
762
763 // Take [0,N-1] quadrant as physical solution.
764 // Check whether we are in the 1st quadrant by checking whether
765 // the global indices for view1 and view2 are equal.
766 // This is done using checkVal, which should be 0 if ig1 = ig2.
767 const bool isQuadrant1 = (checkVal == 0);
768 apply(viewL, args)[gd] = apply(view2, args) * isQuadrant1;
769 });
770 }
771 IpplTimings::stopTimer(edtos);
772 }
773 IpplTimings::stopTimer(efield);
774 }
775
776 // if user asked for Hessian, compute in Fourier domain (-k^2 multiplication)
777 if (hessian) {
778 // start a timer
779 static IpplTimings::TimerRef hess = IpplTimings::getTimer("Solve: Hessian");
780 IpplTimings::startTimer(hess);
781
782 // get Hessian matrix view (LHS)
783 auto viewH = hess_m.getView();
784 const int nghostH = hess_m.getNghost();
785
786 // get rho2tr_m view (as we want to multiply by -k^2 then transform)
787 auto viewR = rho2tr_m.getView();
788 const int nghostR = rho2tr_m.getNghost();
789 const auto& ldomR = layoutComplex_m->getLocalNDIndex();
790
791 // use temp_m as a temporary complex field
792 auto view_g = temp_m.getView();
793
794 // define some constants
795 const scalar_type pi = Kokkos::numbers::pi_v<scalar_type>;
796
797 // define some member variables in local scope for the parallel_for
798 vector_type hsize = hr_m;
799 Vector<int, Dim> N = nr_m;
800
801 // loop over each component (Hessian = Matrix field)
802 for (size_t row = 0; row < Dim; ++row) {
803 for (size_t col = 0; col < Dim; ++col) {
804 // loop over rho2tr_m to multiply by -k^2 (second derivative in Fourier space)
805 // if diagonal element (row = col), do not need N/2 term = 0
806 // else, if mixed derivative, need kVec = 0 at N/2
807
808 ippl::parallel_for(
809 "Hessian", rho2tr_m.getFieldRangePolicy(),
810 KOKKOS_LAMBDA(const index_array_type& args) {
811 // global indices for 2N rhotr_m
812 Vector<int, Dim> igVec = args - nghostR;
813 for (unsigned d = 0; d < Dim; ++d) {
814 igVec[d] += ldomR[d].first();
815 }
816
817 // compute wave-vector
818 Vector_t kVec;
819 for (size_t d = 0; d < Dim; ++d) {
820 const scalar_type Len = N[d] * hsize[d];
821 const bool shift = (igVec[d] > N[d]);
822 const bool isMid = (igVec[d] == N[d]);
823 const bool notDiag = (row != col);
824
825 kVec[d] = (1 - (notDiag * isMid)) * (pi / Len)
826 * (igVec[d] - shift * 2 * N[d]);
827 }
828
829 apply(view_g, args) = -(kVec[col] * kVec[row]) * apply(viewR, args);
830 });
831
832 // start a timer
833 static IpplTimings::TimerRef ffth = IpplTimings::getTimer("FFT: Hessian");
834 IpplTimings::startTimer(ffth);
835
836 // transform to get Hessian
837 fft_m->transform(BACKWARD, rho2_mr, temp_m);
838
839 IpplTimings::stopTimer(ffth);
840
841 // apply proper normalization
842 for (unsigned int i = 0; i < Dim; ++i) {
843 switch (alg) {
844 case Algorithm::HOCKNEY:
845 rho2_mr = rho2_mr * 2.0 * nr_m[i] * hr_m[i];
846 break;
847 case Algorithm::VICO:
848 case Algorithm::BIHARMONIC:
849 rho2_mr = rho2_mr * 2.0 * (1.0 / 4.0);
850 break;
851 case Algorithm::DCT_VICO:
852 rho2_mr = rho2_mr * (1.0 / 4.0);
853 break;
854 default:
855 throw IpplException(
856 "FFTOpenPoissonSolver::initializeFields()",
857 "Currently only HOCKNEY, VICO, DCT_VICO, and BIHARMONIC are "
858 "supported for open BCs");
859 }
860 }
861
862 // start a timer
863 static IpplTimings::TimerRef hdtos =
864 IpplTimings::getTimer("Hessian: double to phys.");
865 IpplTimings::startTimer(hdtos);
866
867 // restrict to physical grid (N^3) and assign to Matrix field (Hessian)
868 // communication needed if more than one rank
869 if (ranks > 1) {
870 // COMMUNICATION
871
872 // send
873 const auto& lDomains1 = layout_mp->getHostLocalDomains();
874 std::vector<MPI_Request> requests(0);
875
876 for (int i = 0; i < ranks; ++i) {
877 if (lDomains1[i].touches(ldom2)) {
878 auto intersection = lDomains1[i].intersect(ldom2);
879
880 solver_send(mpi::tag::OPEN_SOLVER, 0, i, intersection, ldom2,
881 nghost2, view2, fd_m, requests);
882 }
883 }
884
885 // receive
886 const auto& lDomains2 = layout2_m->getHostLocalDomains();
887
888 for (int i = 0; i < ranks; ++i) {
889 if (ldom1.touches(lDomains2[i])) {
890 auto intersection = ldom1.intersect(lDomains2[i]);
891
892 mpi::Communicator::size_type nrecvs;
893 nrecvs = intersection.size();
894
895 buffer_type buf = Comm->getBuffer<memory_space, Trhs>(nrecvs);
896
897 Comm->recv(i, mpi::tag::OPEN_SOLVER, fd_m, *buf,
898 nrecvs * sizeof(Trhs), nrecvs);
899 buf->resetReadPos();
900
901 unpack(intersection, viewH, fd_m, nghostH, ldom1, row, col);
902 }
903 }
904
905 // wait for all messages to be received
906 if (requests.size() > 0) {
907 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
908 }
909 ippl::Comm->freeAllBuffers();
910
911 } else {
912 ippl::parallel_for(
913 "Write Hessian on physical grid", hess_m.getFieldRangePolicy(),
914 KOKKOS_LAMBDA(const index_array_type& args) {
915 scalar_type checkVal = 0;
916
917 Vector<int, Dim> igVec1 = args - nghostH;
918 Vector<int, Dim> igVec2 = args - nghost2;
919
920 for (unsigned d = 0; d < Dim; ++d) {
921 igVec1[d] += ldom1[d].first();
922 igVec2[d] += ldom2[d].first();
923
924 checkVal += Kokkos::abs(igVec1[d] - igVec2[d]);
925 }
926
927 // Take [0,N-1] quadrant as physical solution.
928 // Check whether we are in the 1st quadrant by checking whether
929 // the global indices for view1 and view2 are equal.
930 // This is done using checkVal, which should be 0 if ig1 = ig2.
931 const bool isQuadrant1 = (checkVal == 0);
932 apply(viewH, args)[row][col] = apply(view2, args) * isQuadrant1;
933 });
934 }
935 IpplTimings::stopTimer(hdtos);
936 }
937 }
938 IpplTimings::stopTimer(hess);
939 }
940 IpplTimings::stopTimer(solve);
941 };
942
944 // calculate FFT of the Green's function
945
946 template <typename FieldLHS, typename FieldRHS>
947 KOKKOS_INLINE_FUNCTION typename FFTOpenPoissonSolver<FieldLHS, FieldRHS>::scalar_type
949 const scalar_type& numerator, const scalar_type& denominator) {
950 const scalar_type pi = Kokkos::numbers::pi_v<scalar_type>;
951 if (denominator == scalar_type(0)) {
952 if (numerator > scalar_type(0)) {
953 return scalar_type(0.5) * pi;
954 }
955 if (numerator < scalar_type(0)) {
956 return -scalar_type(0.5) * pi;
957 }
958 return scalar_type(0);
959 }
960 return Kokkos::atan(numerator / denominator);
961 }
962
963 template <typename FieldLHS, typename FieldRHS>
964 KOKKOS_INLINE_FUNCTION typename FFTOpenPoissonSolver<FieldLHS, FieldRHS>::scalar_type
965 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::integratedGreenLogTerm(const scalar_type& a,
966 const scalar_type& b,
967 const scalar_type& c,
968 const scalar_type& r) {
969 const scalar_type coeff = b * c;
970 return (coeff == scalar_type(0)) ? scalar_type(0) : coeff * Kokkos::log(a + r);
971 }
972
973 template <typename FieldLHS, typename FieldRHS>
974 KOKKOS_INLINE_FUNCTION typename FFTOpenPoissonSolver<FieldLHS, FieldRHS>::scalar_type
975 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::integratedGreenAntiderivative(const scalar_type& x,
976 const scalar_type& y,
977 const scalar_type& z) {
978 const scalar_type r2 = x * x + y * y + z * z;
979 if (r2 == scalar_type(0)) {
980 return scalar_type(0);
981 }
982
983 const scalar_type r = Kokkos::sqrt(r2);
984 scalar_type value = scalar_type(0);
985 value -= scalar_type(0.5) * z * z * integratedGreenAtanTerm(x * y, z * r);
986 value -= scalar_type(0.5) * y * y * integratedGreenAtanTerm(x * z, y * r);
987 value -= scalar_type(0.5) * x * x * integratedGreenAtanTerm(y * z, x * r);
988 value += integratedGreenLogTerm(x, y, z, r);
989 value += integratedGreenLogTerm(y, x, z, r);
990 value += integratedGreenLogTerm(z, x, y, r);
991 return value;
992 }
993
994 template <typename FieldLHS, typename FieldRHS>
995 KOKKOS_INLINE_FUNCTION typename FFTOpenPoissonSolver<FieldLHS, FieldRHS>::scalar_type
996 FFTOpenPoissonSolver<FieldLHS, FieldRHS>::integratedGreenAverage(const scalar_type& x,
997 const scalar_type& y,
998 const scalar_type& z,
999 const vector_type& h) {
1000 const scalar_type half = scalar_type(0.5);
1001 const scalar_type x0 = x - half * h[0];
1002 const scalar_type x1 = x + half * h[0];
1003 const scalar_type y0 = y - half * h[1];
1004 const scalar_type y1 = y + half * h[1];
1005 const scalar_type z0 = z - half * h[2];
1006 const scalar_type z1 = z + half * h[2];
1007
1008 scalar_type integral = integratedGreenAntiderivative(x1, y1, z1);
1009 integral -= integratedGreenAntiderivative(x0, y1, z1);
1010 integral -= integratedGreenAntiderivative(x1, y0, z1);
1011 integral += integratedGreenAntiderivative(x0, y0, z1);
1012 integral -= integratedGreenAntiderivative(x1, y1, z0);
1013 integral += integratedGreenAntiderivative(x0, y1, z0);
1014 integral += integratedGreenAntiderivative(x1, y0, z0);
1015 integral -= integratedGreenAntiderivative(x0, y0, z0);
1016
1017 return integral / (h[0] * h[1] * h[2]);
1018 }
1019
1020 template <typename FieldLHS, typename FieldRHS>
1022 const scalar_type pi = Kokkos::numbers::pi_v<scalar_type>;
1023 grn_mr = 0.0;
1024
1025 const int alg = this->params_m.template get<int>("algorithm");
1026 const int greensFunctionType = this->params_m.template get<int>("greens_function");
1027
1028 using index_array_type = typename RangePolicy<Dim>::index_array_type;
1029
1030 if (alg == Algorithm::VICO || alg == Algorithm::BIHARMONIC) {
1031 if constexpr (Dim == 3) {
1032 Vector_t l(hr_m * nr_m);
1033 Vector_t hs_m;
1034 double L_sum(0.0);
1035
1036 // compute length of the physical domain
1037 // compute Fourier domain spacing
1038 for (unsigned int i = 0; i < Dim; ++i) {
1039 hs_m[i] = pi * 0.5 / l[i];
1040 L_sum = L_sum + l[i] * l[i];
1041 }
1042
1043 // define the origin of the 4N grid
1044 vector_type origin;
1045
1046 for (unsigned int i = 0; i < Dim; ++i) {
1047 origin[i] = -2 * nr_m[i] * pi / l[i];
1048 }
1049
1050 // set mesh for the 4N mesh
1051 mesh4_m->setMeshSpacing(hs_m);
1052
1053 // size of truncation window
1054 L_sum = Kokkos::sqrt(L_sum);
1055 // we choose a window 10% larger than domain (arbitrary choice)
1056 L_sum = 1.1 * L_sum;
1057
1058 // initialize grnL_m
1059 typename CxField_gt::view_type view_g = grnL_m.getView();
1060 const int nghost_g = grnL_m.getNghost();
1061 const auto& ldom_g = layout4_m->getLocalNDIndex();
1062
1063 Vector<int, Dim> size = nr_m;
1064
1065 // Kokkos parallel for loop to assign analytic grnL_m
1066 if (alg == Algorithm::VICO) {
1067 ippl::parallel_for(
1068 "Initialize Green's function ", grnL_m.getFieldRangePolicy(),
1069 KOKKOS_LAMBDA(const index_array_type& args) {
1070 scalar_type checkVal = 0;
1071
1072 // go from local indices to global
1073 Vector<int, Dim> igVec = args - nghost_g;
1074 for (unsigned d = 0; d < Dim; ++d) {
1075 igVec[d] += ldom_g[d].first();
1076 checkVal += igVec[d];
1077 }
1078
1079 // compute s
1080 Tg s = 0;
1081 for (unsigned d = 0; d < Dim; ++d) {
1082 bool isOutside = (igVec[d] > 2 * size[d] - 1);
1083 const Tg t = igVec[d] * hs_m[d] + isOutside * origin[d];
1084 s += (t * t);
1085 }
1086 s = Kokkos::sqrt(s);
1087
1088 // assign the green's function value
1089 // if (0,0,0), assign L^2/2 (analytical limit of sinc)
1090 const bool isOrig = (checkVal == 0);
1091 const Tg analyticLim = -L_sum * L_sum * 0.5;
1092 const Tg value = -2.0
1093 * (Kokkos::sin(0.5 * L_sum * s) / (s + isOrig * 1.0))
1094 * (Kokkos::sin(0.5 * L_sum * s) / (s + isOrig * 1.0));
1095
1096 apply(view_g, args) = (!isOrig) * value + isOrig * analyticLim;
1097 });
1098 } else if (alg == Algorithm::BIHARMONIC) {
1099 ippl::parallel_for(
1100 "Initialize Green's function ", grnL_m.getFieldRangePolicy(),
1101 KOKKOS_LAMBDA(const index_array_type& args) {
1102 scalar_type checkVal = 0;
1103
1104 // go from local indices to global
1105 Vector<int, Dim> igVec = args - nghost_g;
1106 for (unsigned d = 0; d < Dim; ++d) {
1107 igVec[d] += ldom_g[d].first();
1108 checkVal += igVec[d];
1109 }
1110
1111 // compute s
1112 Tg s = 0;
1113 for (unsigned d = 0; d < Dim; ++d) {
1114 bool isOutside = (igVec[d] > 2 * size[d] - 1);
1115 const Tg t = igVec[d] * hs_m[d] + isOutside * origin[d];
1116 s += (t * t);
1117 }
1118 s = Kokkos::sqrt(s);
1119
1120 // assign the green's function value
1121 // if (0,0,0), assign L^2/2 (analytical limit of sinc)
1122 const bool isOrig = (checkVal == 0);
1123 const Tg analyticLim = -L_sum * L_sum * L_sum * L_sum / 8.0;
1124 const Tg value =
1125 -((2 - (L_sum * L_sum * s * s)) * Kokkos::cos(L_sum * s)
1126 + 2 * L_sum * s * Kokkos::sin(L_sum * s) - 2)
1127 / (2 * s * s * s * s + isOrig * 1.0);
1128
1129 apply(view_g, args) = (!isOrig) * value + isOrig * analyticLim;
1130 });
1131 }
1132
1133 // start a timer
1134 static IpplTimings::TimerRef fft4 = IpplTimings::getTimer("FFT: Precomputation");
1135 IpplTimings::startTimer(fft4);
1136
1137 // inverse Fourier transform of the green's function for precomputation
1138 fft4n_m->transform(BACKWARD, grnL_m);
1139
1140 IpplTimings::stopTimer(fft4);
1141
1142 // Restrict transformed grnL_m to 2N domain after precomputation step
1143
1144 // get the field data first
1145 typename Field_t::view_type view = grn_mr.getView();
1146 const int nghost = grn_mr.getNghost();
1147 const auto& ldom = layout2_m->getLocalNDIndex();
1148
1149 // start a timer
1150 static IpplTimings::TimerRef ifftshift = IpplTimings::getTimer("Vico shift loop");
1151 IpplTimings::startTimer(ifftshift);
1152
1153 // get number of ranks to see if need communication
1154 const int ranks = Comm->size();
1155
1156 if (ranks > 1) {
1157 communicateVico(size, view_g, ldom_g, nghost_g, view, ldom, nghost);
1158 } else {
1159 // restrict the green's function to a (2N)^3 grid from the (4N)^3 grid
1160 using mdrange_type = Kokkos::MDRangePolicy<Kokkos::Rank<3>>;
1161 Kokkos::parallel_for(
1162 "Restrict domain of Green's function from 4N to 2N",
1163 mdrange_type({nghost, nghost, nghost}, {view.extent(0) - nghost - size[0],
1164 view.extent(1) - nghost - size[1],
1165 view.extent(2) - nghost - size[2]}),
1166 KOKKOS_LAMBDA(const int i, const int j, const int k) {
1167 // go from local indices to global
1168 const int ig = i + ldom[0].first() - nghost;
1169 const int jg = j + ldom[1].first() - nghost;
1170 const int kg = k + ldom[2].first() - nghost;
1171
1172 const int ig2 = i + ldom_g[0].first() - nghost_g;
1173 const int jg2 = j + ldom_g[1].first() - nghost_g;
1174 const int kg2 = k + ldom_g[2].first() - nghost_g;
1175
1176 const bool isOrig = (ig == ig2) && (jg == jg2) && (kg == kg2);
1177 view(i, j, k) = isOrig * real(view_g(i, j, k));
1178
1179 // Now fill the rest of the field
1180 const int s = 2 * size[0] - ig - 1 - ldom_g[0].first() + nghost_g;
1181 const int p = 2 * size[1] - jg - 1 - ldom_g[1].first() + nghost_g;
1182 const int q = 2 * size[2] - kg - 1 - ldom_g[2].first() + nghost_g;
1183
1184 view(s, j, k) = real(view_g(i + 1, j, k));
1185 view(i, p, k) = real(view_g(i, j + 1, k));
1186 view(i, j, q) = real(view_g(i, j, k + 1));
1187 view(s, j, q) = real(view_g(i + 1, j, k + 1));
1188 view(s, p, k) = real(view_g(i + 1, j + 1, k));
1189 view(i, p, q) = real(view_g(i, j + 1, k + 1));
1190 view(s, p, q) = real(view_g(i + 1, j + 1, k + 1));
1191 });
1192 }
1193 IpplTimings::stopTimer(ifftshift);
1194 }
1195 } else if (alg == Algorithm::DCT_VICO) {
1196 if constexpr (Dim == 3) {
1197 Vector_t l(hr_m * nr_m);
1198 Vector_t hs_m;
1199 double L_sum(0.0);
1200
1201 // compute length of the physical domain
1202 // compute Fourier domain spacing
1203 for (unsigned int i = 0; i < Dim; ++i) {
1204 hs_m[i] = Kokkos::numbers::pi_v<Trhs> * 0.5 / l[i];
1205 L_sum = L_sum + l[i] * l[i];
1206 }
1207
1208 // define the origin of the 2N+1 grid
1209 Vector_t origin = 0.0;
1210
1211 // set mesh for the 2N+1 mesh
1212 mesh2n1_m->setMeshSpacing(hs_m);
1213
1214 // size of truncation window
1215 L_sum = Kokkos::sqrt(L_sum);
1216 L_sum = 1.1 * L_sum;
1217
1218 // initialize grn2n1_m
1219 Vector<int, Dim> size = nr_m;
1220
1221 // initialize grn2n1_m
1222 typename Field_t::view_type view_g2n1 = grn2n1_m.getView();
1223 const int nghost_g2n1 = grn2n1_m.getNghost();
1224 const auto& ldom_g2n1 = layout2n1_m->getLocalNDIndex();
1225
1226 ippl::parallel_for(
1227 "Initialize 2N+1 Green's function ", grn2n1_m.getFieldRangePolicy(),
1228 KOKKOS_LAMBDA(const index_array_type& args) {
1229 scalar_type checkVal = 0;
1230
1231 // go from local indices to global
1232 Vector<int, Dim> igVec = args - nghost_g2n1;
1233 for (unsigned d = 0; d < Dim; ++d) {
1234 igVec[d] += ldom_g2n1[d].first();
1235 checkVal += igVec[d];
1236 }
1237
1238 // compute s
1239 Tg s = 0;
1240 for (unsigned d = 0; d < Dim; ++d) {
1241 double t = igVec[d] * hs_m[d];
1242 s += (t * t);
1243 }
1244 s = Kokkos::sqrt(s);
1245
1246 const bool isOrig = (checkVal == 0);
1247 const double val = -2.0
1248 * (Kokkos::sin(0.5 * L_sum * s) / (s + isOrig * 1.0))
1249 * (Kokkos::sin(0.5 * L_sum * s) / (s + isOrig * 1.0));
1250 const double analyticLim = -L_sum * L_sum * 0.5;
1251
1252 apply(view_g2n1, args) = ((!isOrig) * val) + (isOrig * analyticLim);
1253 });
1254
1255 // start a timer
1256 static IpplTimings::TimerRef fft4 = IpplTimings::getTimer("FFT: Precomputation");
1257 IpplTimings::startTimer(fft4);
1258
1259 // inverse DCT transform of 2N+1 green's function for the precomputation
1260 fft2n1_m->transform(BACKWARD, grn2n1_m);
1261
1262 IpplTimings::stopTimer(fft4);
1263
1264 // Restrict transformed grn2n1_m to 2N domain after precomputation step
1265
1266 // get the field data first
1267 typename Field_t::view_type view = grn_mr.getView();
1268 const int nghost = grn_mr.getNghost();
1269 const auto& ldom = layout2_m->getLocalNDIndex();
1270
1271 // start a timer
1272 static IpplTimings::TimerRef ifftshift = IpplTimings::getTimer("Vico shift loop");
1273 IpplTimings::startTimer(ifftshift);
1274
1275 // get number of ranks to see if need communication
1276 int ranks = ippl::Comm->size();
1277
1278 // range type for Kokkos loop
1279 using mdrange_type = Kokkos::MDRangePolicy<Kokkos::Rank<3>>;
1280
1281 if (ranks > 1) {
1282 communicateVico(size, view_g2n1, ldom_g2n1, nghost_g2n1, view, ldom, nghost);
1283 } else {
1284 // restrict the green's function to a (2N)^3 grid from the (2N+1)^3 grid
1285 Kokkos::parallel_for(
1286 "Restrict domain of Green's function from 2N+1 to 2N",
1287 mdrange_type({nghost, nghost, nghost}, {view.extent(0) - nghost - size[0],
1288 view.extent(1) - nghost - size[1],
1289 view.extent(2) - nghost - size[2]}),
1290 KOKKOS_LAMBDA(const int i, const int j, const int k) {
1291 // go from local indices to global
1292 const int ig = i + ldom[0].first() - nghost;
1293 const int jg = j + ldom[1].first() - nghost;
1294 const int kg = k + ldom[2].first() - nghost;
1295
1296 const int ig2 = i + ldom_g2n1[0].first() - nghost_g2n1;
1297 const int jg2 = j + ldom_g2n1[1].first() - nghost_g2n1;
1298 const int kg2 = k + ldom_g2n1[2].first() - nghost_g2n1;
1299
1300 const bool isOrig = ((ig == ig2) && (jg == jg2) && (kg == kg2));
1301 view(i, j, k) = isOrig * view_g2n1(i, j, k);
1302
1303 // Now fill the rest of the field
1304 const int s = 2 * size[0] - ig - 1 - ldom_g2n1[0].first() + nghost_g2n1;
1305 const int p = 2 * size[1] - jg - 1 - ldom_g2n1[1].first() + nghost_g2n1;
1306 const int q = 2 * size[2] - kg - 1 - ldom_g2n1[2].first() + nghost_g2n1;
1307
1308 view(s, j, k) = view_g2n1(i + 1, j, k);
1309 view(i, p, k) = view_g2n1(i, j + 1, k);
1310 view(i, j, q) = view_g2n1(i, j, k + 1);
1311 view(s, j, q) = view_g2n1(i + 1, j, k + 1);
1312 view(s, p, k) = view_g2n1(i + 1, j + 1, k);
1313 view(i, p, q) = view_g2n1(i, j + 1, k + 1);
1314 view(s, p, q) = view_g2n1(i + 1, j + 1, k + 1);
1315 });
1316 }
1317 }
1318 } else {
1319 // Hockney case
1320
1321 typename Field_t::view_type view = grn_mr.getView();
1322 const int nghost = grn_mr.getNghost();
1323 const auto& ldom = layout2_m->getLocalNDIndex();
1324
1325 if (greensFunctionType == GreenFunction::INTEGRATED) {
1326 Vector<int, Dim> nr = nr_m;
1327 vector_type hs = hr_m;
1328
1329 ippl::parallel_for(
1330 "Integrated Green's function ", grn_mr.getFieldRangePolicy(),
1331 KOKKOS_LAMBDA(const index_array_type& args) {
1332 Vector<int, Dim> igVec = args - nghost;
1333 for (unsigned d = 0; d < Dim; ++d) {
1334 igVec[d] += ldom[d].first();
1335 }
1336
1337 scalar_type offset[Dim];
1338 for (unsigned int d = 0; d < Dim; ++d) {
1339 const int igSigned =
1340 (igVec[d] < nr[d]) ? igVec[d] : igVec[d] - 2 * nr[d];
1341 offset[d] = static_cast<scalar_type>(igSigned) * hs[d];
1342 }
1343
1344 const scalar_type avg =
1345 integratedGreenAverage(offset[0], offset[1], offset[2], hs);
1346 apply(view, args) = -avg / (scalar_type(4) * pi);
1347 });
1348 } else {
1349 // calculate square of the mesh spacing for each dimension
1350 Vector_t hrsq(hr_m * hr_m);
1351
1352 // use the grnIField_m helper field to compute Green's function
1353 for (unsigned int i = 0; i < Dim; ++i) {
1354 grn_mr = grn_mr + grnIField_m[i] * hrsq[i];
1355 }
1356
1357 // Formula of Green's function (2D and 3D supported for Hockney)
1358 if (Dim == 2) {
1359 grn_mr = log(sqrt(grn_mr)) / (2 * pi);
1360 } else if (Dim == 3) {
1361 grn_mr = -1.0 / (4.0 * pi * sqrt(grn_mr));
1362 }
1363
1364 // Kokkos parallel for loop to find (0,0,0) point and regularize
1365 ippl::parallel_for(
1366 "Regularize Green's function ", grn_mr.getFieldRangePolicy(),
1367 KOKKOS_LAMBDA(const index_array_type& args) {
1368 scalar_type checkVal = 0;
1369
1370 // go from local indices to global
1371 Vector<int, Dim> igVec = args - nghost;
1372 for (unsigned d = 0; d < Dim; ++d) {
1373 igVec[d] += ldom[d].first();
1374 checkVal += igVec[d];
1375 }
1376
1377 // if (0,0,0), assign to it 1/(4*pi)
1378 const bool isOrig = (checkVal == 0);
1379 apply(view, args) =
1380 isOrig * (-1.0 / (4.0 * pi)) + (!isOrig) * apply(view, args);
1381 });
1382 }
1383 }
1384
1385 // start a timer
1386 static IpplTimings::TimerRef fftg = IpplTimings::getTimer("FFT: Green");
1387 IpplTimings::startTimer(fftg);
1388
1389 // perform the FFT of the Green's function for the convolution
1390 fft_m->transform(FORWARD, grn_mr, grntr_m);
1391
1392 IpplTimings::stopTimer(fftg);
1393 };
1394
1395 template <typename FieldLHS, typename FieldRHS>
1397 Vector<int, Dim> size, typename CxField_gt::view_type view_g,
1398 const ippl::NDIndex<Dim> ldom_g, const int nghost_g, typename Field_t::view_type view,
1399 const ippl::NDIndex<Dim> ldom, const int nghost) {
1400 const auto& lDomains2 = layout2_m->getHostLocalDomains();
1401 const auto& lDomains4 = layout4_m->getHostLocalDomains();
1402
1403 std::vector<MPI_Request> requests(0);
1404 const int ranks = Comm->size();
1405
1406 // 1st step: Define 8 domains corresponding to the different quadrants
1407 ippl::NDIndex<Dim> none;
1408 for (unsigned i = 0; i < Dim; i++) {
1409 none[i] = ippl::Index(size[i]);
1410 }
1411
1413 x[0] = ippl::Index(size[0], 2 * size[0] - 1);
1414 x[1] = ippl::Index(size[1]);
1415 x[2] = ippl::Index(size[2]);
1416
1418 y[0] = ippl::Index(size[0]);
1419 y[1] = ippl::Index(size[1], 2 * size[1] - 1);
1420 y[2] = ippl::Index(size[2]);
1421
1423 z[0] = ippl::Index(size[0]);
1424 z[1] = ippl::Index(size[1]);
1425 z[2] = ippl::Index(size[2], 2 * size[2] - 1);
1426
1428 xy[0] = ippl::Index(size[0], 2 * size[0] - 1);
1429 xy[1] = ippl::Index(size[1], 2 * size[1] - 1);
1430 xy[2] = ippl::Index(size[2]);
1431
1433 xz[0] = ippl::Index(size[0], 2 * size[0] - 1);
1434 xz[1] = ippl::Index(size[1]);
1435 xz[2] = ippl::Index(size[2], 2 * size[2] - 1);
1436
1438 yz[0] = ippl::Index(size[0]);
1439 yz[1] = ippl::Index(size[1], 2 * size[1] - 1);
1440 yz[2] = ippl::Index(size[2], 2 * size[2] - 1);
1441
1443 for (unsigned i = 0; i < Dim; i++) {
1444 xyz[i] = ippl::Index(size[i], 2 * size[i] - 1);
1445 }
1446
1447 // 2nd step: send
1448 for (int i = 0; i < ranks; ++i) {
1449 auto domain2 = lDomains2[i];
1450
1451 if (domain2.touches(none)) {
1452 auto intersection = domain2.intersect(none);
1453
1454 if (ldom_g.touches(intersection)) {
1455 intersection = intersection.intersect(ldom_g);
1456
1457 solver_send(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom_g, nghost_g, view_g,
1458 fd_m, requests);
1459 }
1460 }
1461
1462 if (domain2.touches(x)) {
1463 auto intersection = domain2.intersect(x);
1464 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1465 (2 * size[0] - intersection[0].last()), -1);
1466
1467 ippl::NDIndex<Dim> domain4;
1468 domain4[0] = xdom;
1469 domain4[1] = intersection[1];
1470 domain4[2] = intersection[2];
1471
1472 if (ldom_g.touches(domain4)) {
1473 intersection = ldom_g.intersect(domain4);
1474
1475 solver_send(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom_g, nghost_g, view_g,
1476 fd_m, requests);
1477 }
1478 }
1479
1480 if (domain2.touches(y)) {
1481 auto intersection = domain2.intersect(y);
1482 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1483 (2 * size[1] - intersection[1].last()), -1);
1484
1485 ippl::NDIndex<Dim> domain4;
1486 domain4[0] = intersection[0];
1487 domain4[1] = ydom;
1488 domain4[2] = intersection[2];
1489
1490 if (ldom_g.touches(domain4)) {
1491 intersection = ldom_g.intersect(domain4);
1492
1493 solver_send(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom_g, nghost_g, view_g,
1494 fd_m, requests);
1495 }
1496 }
1497
1498 if (domain2.touches(z)) {
1499 auto intersection = domain2.intersect(z);
1500 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1501 (2 * size[2] - intersection[2].last()), -1);
1502
1503 ippl::NDIndex<Dim> domain4;
1504 domain4[0] = intersection[0];
1505 domain4[1] = intersection[1];
1506 domain4[2] = zdom;
1507
1508 if (ldom_g.touches(domain4)) {
1509 intersection = ldom_g.intersect(domain4);
1510
1511 solver_send(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom_g, nghost_g, view_g,
1512 fd_m, requests);
1513 }
1514 }
1515
1516 if (domain2.touches(xy)) {
1517 auto intersection = domain2.intersect(xy);
1518 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1519 (2 * size[0] - intersection[0].last()), -1);
1520 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1521 (2 * size[1] - intersection[1].last()), -1);
1522
1523 ippl::NDIndex<Dim> domain4;
1524 domain4[0] = xdom;
1525 domain4[1] = ydom;
1526 domain4[2] = intersection[2];
1527
1528 if (ldom_g.touches(domain4)) {
1529 intersection = ldom_g.intersect(domain4);
1530
1531 solver_send(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom_g, nghost_g, view_g,
1532 fd_m, requests);
1533 }
1534 }
1535
1536 if (domain2.touches(yz)) {
1537 auto intersection = domain2.intersect(yz);
1538 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1539 (2 * size[1] - intersection[1].last()), -1);
1540 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1541 (2 * size[2] - intersection[2].last()), -1);
1542
1543 ippl::NDIndex<Dim> domain4;
1544 domain4[0] = intersection[0];
1545 domain4[1] = ydom;
1546 domain4[2] = zdom;
1547
1548 if (ldom_g.touches(domain4)) {
1549 intersection = ldom_g.intersect(domain4);
1550 solver_send(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom_g, nghost_g, view_g,
1551 fd_m, requests);
1552 }
1553 }
1554
1555 if (domain2.touches(xz)) {
1556 auto intersection = domain2.intersect(xz);
1557 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1558 (2 * size[0] - intersection[0].last()), -1);
1559 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1560 (2 * size[2] - intersection[2].last()), -1);
1561
1562 ippl::NDIndex<Dim> domain4;
1563 domain4[0] = xdom;
1564 domain4[1] = intersection[1];
1565 domain4[2] = zdom;
1566
1567 if (ldom_g.touches(domain4)) {
1568 intersection = ldom_g.intersect(domain4);
1569 solver_send(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom_g, nghost_g, view_g,
1570 fd_m, requests);
1571 }
1572 }
1573
1574 if (domain2.touches(xyz)) {
1575 auto intersection = domain2.intersect(xyz);
1576 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1577 (2 * size[0] - intersection[0].last()), -1);
1578 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1579 (2 * size[1] - intersection[1].last()), -1);
1580 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1581 (2 * size[2] - intersection[2].last()), -1);
1582
1583 ippl::NDIndex<Dim> domain4;
1584 domain4[0] = xdom;
1585 domain4[1] = ydom;
1586 domain4[2] = zdom;
1587
1588 if (ldom_g.touches(domain4)) {
1589 intersection = ldom_g.intersect(domain4);
1590 solver_send(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom_g, nghost_g, view_g,
1591 fd_m, requests);
1592 }
1593 }
1594 }
1595
1596 // 3rd step: receive
1597 for (int i = 0; i < ranks; ++i) {
1598 if (ldom.touches(none)) {
1599 auto intersection = ldom.intersect(none);
1600
1601 if (lDomains4[i].touches(intersection)) {
1602 intersection = intersection.intersect(lDomains4[i]);
1603
1604 solver_recv(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom, nghost, view,
1605 fd_m);
1606 }
1607 }
1608
1609 if (ldom.touches(x)) {
1610 auto intersection = ldom.intersect(x);
1611
1612 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1613 (2 * size[0] - intersection[0].last()), -1);
1614
1615 ippl::NDIndex<Dim> domain4;
1616 domain4[0] = xdom;
1617 domain4[1] = intersection[1];
1618 domain4[2] = intersection[2];
1619
1620 if (lDomains4[i].touches(domain4)) {
1621 domain4 = lDomains4[i].intersect(domain4);
1622 domain4[0] = ippl::Index(2 * size[0] - domain4[0].first(),
1623 2 * size[0] - domain4[0].last(), -1);
1624
1625 intersection = intersection.intersect(domain4);
1626
1627 solver_recv(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom, nghost, view, fd_m,
1628 {true, false, false});
1629 }
1630 }
1631
1632 if (ldom.touches(y)) {
1633 auto intersection = ldom.intersect(y);
1634
1635 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1636 (2 * size[1] - intersection[1].last()), -1);
1637
1638 ippl::NDIndex<Dim> domain4;
1639 domain4[0] = intersection[0];
1640 domain4[1] = ydom;
1641 domain4[2] = intersection[2];
1642
1643 if (lDomains4[i].touches(domain4)) {
1644 domain4 = lDomains4[i].intersect(domain4);
1645 domain4[1] = ippl::Index(2 * size[1] - domain4[1].first(),
1646 2 * size[1] - domain4[1].last(), -1);
1647
1648 intersection = intersection.intersect(domain4);
1649
1650 solver_recv(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom, nghost, view, fd_m,
1651 {false, true, false});
1652 }
1653 }
1654
1655 if (ldom.touches(z)) {
1656 auto intersection = ldom.intersect(z);
1657
1658 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1659 (2 * size[2] - intersection[2].last()), -1);
1660
1661 ippl::NDIndex<Dim> domain4;
1662 domain4[0] = intersection[0];
1663 domain4[1] = intersection[1];
1664 domain4[2] = zdom;
1665
1666 if (lDomains4[i].touches(domain4)) {
1667 domain4 = lDomains4[i].intersect(domain4);
1668 domain4[2] = ippl::Index(2 * size[2] - domain4[2].first(),
1669 2 * size[2] - domain4[2].last(), -1);
1670
1671 intersection = intersection.intersect(domain4);
1672
1673 solver_recv(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom, nghost, view, fd_m,
1674 {false, false, true});
1675 }
1676 }
1677
1678 if (ldom.touches(xy)) {
1679 auto intersection = ldom.intersect(xy);
1680
1681 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1682 (2 * size[0] - intersection[0].last()), -1);
1683 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1684 (2 * size[1] - intersection[1].last()), -1);
1685
1686 ippl::NDIndex<Dim> domain4;
1687 domain4[0] = xdom;
1688 domain4[1] = ydom;
1689 domain4[2] = intersection[2];
1690
1691 if (lDomains4[i].touches(domain4)) {
1692 domain4 = lDomains4[i].intersect(domain4);
1693 domain4[0] = ippl::Index(2 * size[0] - domain4[0].first(),
1694 2 * size[0] - domain4[0].last(), -1);
1695 domain4[1] = ippl::Index(2 * size[1] - domain4[1].first(),
1696 2 * size[1] - domain4[1].last(), -1);
1697
1698 intersection = intersection.intersect(domain4);
1699
1700 solver_recv(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom, nghost, view, fd_m,
1701 {true, true, false});
1702 }
1703 }
1704
1705 if (ldom.touches(yz)) {
1706 auto intersection = ldom.intersect(yz);
1707
1708 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1709 (2 * size[1] - intersection[1].last()), -1);
1710 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1711 (2 * size[2] - intersection[2].last()), -1);
1712
1713 ippl::NDIndex<Dim> domain4;
1714 domain4[0] = intersection[0];
1715 domain4[1] = ydom;
1716 domain4[2] = zdom;
1717
1718 if (lDomains4[i].touches(domain4)) {
1719 domain4 = lDomains4[i].intersect(domain4);
1720 domain4[1] = ippl::Index(2 * size[1] - domain4[1].first(),
1721 2 * size[1] - domain4[1].last(), -1);
1722 domain4[2] = ippl::Index(2 * size[2] - domain4[2].first(),
1723 2 * size[2] - domain4[2].last(), -1);
1724
1725 intersection = intersection.intersect(domain4);
1726
1727 solver_recv(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom, nghost, view, fd_m,
1728 {false, true, true});
1729 }
1730 }
1731
1732 if (ldom.touches(xz)) {
1733 auto intersection = ldom.intersect(xz);
1734
1735 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1736 (2 * size[0] - intersection[0].last()), -1);
1737 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1738 (2 * size[2] - intersection[2].last()), -1);
1739
1740 ippl::NDIndex<Dim> domain4;
1741 domain4[0] = xdom;
1742 domain4[1] = intersection[1];
1743 domain4[2] = zdom;
1744
1745 if (lDomains4[i].touches(domain4)) {
1746 domain4 = lDomains4[i].intersect(domain4);
1747 domain4[0] = ippl::Index(2 * size[0] - domain4[0].first(),
1748 2 * size[0] - domain4[0].last(), -1);
1749 domain4[2] = ippl::Index(2 * size[2] - domain4[2].first(),
1750 2 * size[2] - domain4[2].last(), -1);
1751
1752 intersection = intersection.intersect(domain4);
1753
1754 solver_recv(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom, nghost, view, fd_m,
1755 {true, false, true});
1756 }
1757 }
1758
1759 if (ldom.touches(xyz)) {
1760 auto intersection = ldom.intersect(xyz);
1761
1762 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1763 (2 * size[0] - intersection[0].last()), -1);
1764 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1765 (2 * size[1] - intersection[1].last()), -1);
1766 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1767 (2 * size[2] - intersection[2].last()), -1);
1768
1769 ippl::NDIndex<Dim> domain4;
1770 domain4[0] = xdom;
1771 domain4[1] = ydom;
1772 domain4[2] = zdom;
1773
1774 if (lDomains4[i].touches(domain4)) {
1775 domain4 = lDomains4[i].intersect(domain4);
1776 domain4[0] = ippl::Index(2 * size[0] - domain4[0].first(),
1777 2 * size[0] - domain4[0].last(), -1);
1778 domain4[1] = ippl::Index(2 * size[1] - domain4[1].first(),
1779 2 * size[1] - domain4[1].last(), -1);
1780 domain4[2] = ippl::Index(2 * size[2] - domain4[2].first(),
1781 2 * size[2] - domain4[2].last(), -1);
1782
1783 intersection = intersection.intersect(domain4);
1784
1785 solver_recv(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom, nghost, view, fd_m,
1786 {true, true, true});
1787 }
1788 }
1789 }
1790
1791 if (requests.size() > 0) {
1792 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
1793 }
1794 ippl::Comm->freeAllBuffers();
1795 };
1796
1797 // CommunicateVico for DCT_VICO (2N+1 to 2N)
1798 template <typename FieldLHS, typename FieldRHS>
1799 void FFTOpenPoissonSolver<FieldLHS, FieldRHS>::communicateVico(
1800 Vector<int, Dim> size, typename Field_t::view_type view_g, const ippl::NDIndex<Dim> ldom_g,
1801 const int nghost_g, typename Field_t::view_type view, const ippl::NDIndex<Dim> ldom,
1802 const int nghost) {
1803 const auto& lDomains2 = layout2_m->getHostLocalDomains();
1804 const auto& lDomains2n1 = layout2n1_m->getHostLocalDomains();
1805
1806 std::vector<MPI_Request> requests(0);
1807 const int ranks = ippl::Comm->size();
1808
1809 // 1st step: Define 8 domains corresponding to the different quadrants
1810 ippl::NDIndex<Dim> none;
1811 for (unsigned i = 0; i < Dim; i++) {
1812 none[i] = ippl::Index(size[i]);
1813 }
1814
1816 x[0] = ippl::Index(size[0], 2 * size[0] - 1);
1817 x[1] = ippl::Index(size[1]);
1818 x[2] = ippl::Index(size[2]);
1819
1821 y[0] = ippl::Index(size[0]);
1822 y[1] = ippl::Index(size[1], 2 * size[1] - 1);
1823 y[2] = ippl::Index(size[2]);
1824
1826 z[0] = ippl::Index(size[0]);
1827 z[1] = ippl::Index(size[1]);
1828 z[2] = ippl::Index(size[2], 2 * size[2] - 1);
1829
1831 xy[0] = ippl::Index(size[0], 2 * size[0] - 1);
1832 xy[1] = ippl::Index(size[1], 2 * size[1] - 1);
1833 xy[2] = ippl::Index(size[2]);
1834
1836 xz[0] = ippl::Index(size[0], 2 * size[0] - 1);
1837 xz[1] = ippl::Index(size[1]);
1838 xz[2] = ippl::Index(size[2], 2 * size[2] - 1);
1839
1841 yz[0] = ippl::Index(size[0]);
1842 yz[1] = ippl::Index(size[1], 2 * size[1] - 1);
1843 yz[2] = ippl::Index(size[2], 2 * size[2] - 1);
1844
1846 for (unsigned i = 0; i < Dim; i++) {
1847 xyz[i] = ippl::Index(size[i], 2 * size[i] - 1);
1848 }
1849
1850 // 2nd step: send
1851 for (int i = 0; i < ranks; ++i) {
1852 auto domain2 = lDomains2[i];
1853
1854 if (domain2.touches(none)) {
1855 auto intersection = domain2.intersect(none);
1856
1857 if (ldom_g.touches(intersection)) {
1858 intersection = intersection.intersect(ldom_g);
1859
1860 solver_send(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom_g, nghost_g, view_g,
1861 fd_m, requests);
1862 }
1863 }
1864
1865 if (domain2.touches(x)) {
1866 auto intersection = domain2.intersect(x);
1867 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1868 (2 * size[0] - intersection[0].last()), -1);
1869
1870 ippl::NDIndex<Dim> domain2n1;
1871 domain2n1[0] = xdom;
1872 domain2n1[1] = intersection[1];
1873 domain2n1[2] = intersection[2];
1874
1875 if (ldom_g.touches(domain2n1)) {
1876 intersection = ldom_g.intersect(domain2n1);
1877
1878 solver_send(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom_g, nghost_g, view_g,
1879 fd_m, requests);
1880 }
1881 }
1882
1883 if (domain2.touches(y)) {
1884 auto intersection = domain2.intersect(y);
1885 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1886 (2 * size[1] - intersection[1].last()), -1);
1887
1888 ippl::NDIndex<Dim> domain2n1;
1889 domain2n1[0] = intersection[0];
1890 domain2n1[1] = ydom;
1891 domain2n1[2] = intersection[2];
1892
1893 if (ldom_g.touches(domain2n1)) {
1894 intersection = ldom_g.intersect(domain2n1);
1895
1896 solver_send(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom_g, nghost_g, view_g,
1897 fd_m, requests);
1898 }
1899 }
1900
1901 if (domain2.touches(z)) {
1902 auto intersection = domain2.intersect(z);
1903 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1904 (2 * size[2] - intersection[2].last()), -1);
1905
1906 ippl::NDIndex<Dim> domain2n1;
1907 domain2n1[0] = intersection[0];
1908 domain2n1[1] = intersection[1];
1909 domain2n1[2] = zdom;
1910
1911 if (ldom_g.touches(domain2n1)) {
1912 intersection = ldom_g.intersect(domain2n1);
1913
1914 solver_send(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom_g, nghost_g, view_g,
1915 fd_m, requests);
1916 }
1917 }
1918
1919 if (domain2.touches(xy)) {
1920 auto intersection = domain2.intersect(xy);
1921 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1922 (2 * size[0] - intersection[0].last()), -1);
1923 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1924 (2 * size[1] - intersection[1].last()), -1);
1925
1926 ippl::NDIndex<Dim> domain2n1;
1927 domain2n1[0] = xdom;
1928 domain2n1[1] = ydom;
1929 domain2n1[2] = intersection[2];
1930
1931 if (ldom_g.touches(domain2n1)) {
1932 intersection = ldom_g.intersect(domain2n1);
1933
1934 solver_send(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom_g, nghost_g, view_g,
1935 fd_m, requests);
1936 }
1937 }
1938
1939 if (domain2.touches(yz)) {
1940 auto intersection = domain2.intersect(yz);
1941 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1942 (2 * size[1] - intersection[1].last()), -1);
1943 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1944 (2 * size[2] - intersection[2].last()), -1);
1945
1946 ippl::NDIndex<Dim> domain2n1;
1947 domain2n1[0] = intersection[0];
1948 domain2n1[1] = ydom;
1949 domain2n1[2] = zdom;
1950
1951 if (ldom_g.touches(domain2n1)) {
1952 intersection = ldom_g.intersect(domain2n1);
1953
1954 solver_send(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom_g, nghost_g, view_g,
1955 fd_m, requests);
1956 }
1957 }
1958
1959 if (domain2.touches(xz)) {
1960 auto intersection = domain2.intersect(xz);
1961 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1962 (2 * size[0] - intersection[0].last()), -1);
1963 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1964 (2 * size[2] - intersection[2].last()), -1);
1965
1966 ippl::NDIndex<Dim> domain2n1;
1967 domain2n1[0] = xdom;
1968 domain2n1[1] = intersection[1];
1969 domain2n1[2] = zdom;
1970
1971 if (ldom_g.touches(domain2n1)) {
1972 intersection = ldom_g.intersect(domain2n1);
1973
1974 solver_send(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom_g, nghost_g, view_g,
1975 fd_m, requests);
1976 }
1977 }
1978
1979 if (domain2.touches(xyz)) {
1980 auto intersection = domain2.intersect(xyz);
1981 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
1982 (2 * size[0] - intersection[0].last()), -1);
1983 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
1984 (2 * size[1] - intersection[1].last()), -1);
1985 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
1986 (2 * size[2] - intersection[2].last()), -1);
1987
1988 ippl::NDIndex<Dim> domain2n1;
1989 domain2n1[0] = xdom;
1990 domain2n1[1] = ydom;
1991 domain2n1[2] = zdom;
1992
1993 if (ldom_g.touches(domain2n1)) {
1994 intersection = ldom_g.intersect(domain2n1);
1995
1996 solver_send(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom_g, nghost_g, view_g,
1997 fd_m, requests);
1998 }
1999 }
2000 }
2001
2002 // 3rd step: receive
2003 for (int i = 0; i < ranks; ++i) {
2004 if (ldom.touches(none)) {
2005 auto intersection = ldom.intersect(none);
2006
2007 if (lDomains2n1[i].touches(intersection)) {
2008 intersection = intersection.intersect(lDomains2n1[i]);
2009
2010 solver_recv(mpi::tag::VICO_SOLVER, 0, i, intersection, ldom, nghost, view,
2011 fd_m);
2012 }
2013 }
2014
2015 if (ldom.touches(x)) {
2016 auto intersection = ldom.intersect(x);
2017
2018 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
2019 (2 * size[0] - intersection[0].last()), -1);
2020
2021 ippl::NDIndex<Dim> domain2n1;
2022 domain2n1[0] = xdom;
2023 domain2n1[1] = intersection[1];
2024 domain2n1[2] = intersection[2];
2025
2026 if (lDomains2n1[i].touches(domain2n1)) {
2027 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2028 domain2n1[0] = ippl::Index(2 * size[0] - domain2n1[0].first(),
2029 2 * size[0] - domain2n1[0].last(), -1);
2030
2031 intersection = intersection.intersect(domain2n1);
2032
2033 solver_recv(mpi::tag::VICO_SOLVER, 1, i, intersection, ldom, nghost, view, fd_m,
2034 {true, false, false});
2035 }
2036 }
2037
2038 if (ldom.touches(y)) {
2039 auto intersection = ldom.intersect(y);
2040
2041 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
2042 (2 * size[1] - intersection[1].last()), -1);
2043
2044 ippl::NDIndex<Dim> domain2n1;
2045 domain2n1[0] = intersection[0];
2046 domain2n1[1] = ydom;
2047 domain2n1[2] = intersection[2];
2048
2049 if (lDomains2n1[i].touches(domain2n1)) {
2050 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2051 domain2n1[1] = ippl::Index(2 * size[1] - domain2n1[1].first(),
2052 2 * size[1] - domain2n1[1].last(), -1);
2053
2054 intersection = intersection.intersect(domain2n1);
2055
2056 solver_recv(mpi::tag::VICO_SOLVER, 2, i, intersection, ldom, nghost, view, fd_m,
2057 {false, true, false});
2058 }
2059 }
2060
2061 if (ldom.touches(z)) {
2062 auto intersection = ldom.intersect(z);
2063
2064 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
2065 (2 * size[2] - intersection[2].last()), -1);
2066
2067 ippl::NDIndex<Dim> domain2n1;
2068 domain2n1[0] = intersection[0];
2069 domain2n1[1] = intersection[1];
2070 domain2n1[2] = zdom;
2071
2072 if (lDomains2n1[i].touches(domain2n1)) {
2073 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2074 domain2n1[2] = ippl::Index(2 * size[2] - domain2n1[2].first(),
2075 2 * size[2] - domain2n1[2].last(), -1);
2076
2077 intersection = intersection.intersect(domain2n1);
2078
2079 solver_recv(mpi::tag::VICO_SOLVER, 3, i, intersection, ldom, nghost, view, fd_m,
2080 {false, false, true});
2081 }
2082 }
2083
2084 if (ldom.touches(xy)) {
2085 auto intersection = ldom.intersect(xy);
2086
2087 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
2088 (2 * size[0] - intersection[0].last()), -1);
2089 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
2090 (2 * size[1] - intersection[1].last()), -1);
2091
2092 ippl::NDIndex<Dim> domain2n1;
2093 domain2n1[0] = xdom;
2094 domain2n1[1] = ydom;
2095 domain2n1[2] = intersection[2];
2096
2097 if (lDomains2n1[i].touches(domain2n1)) {
2098 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2099 domain2n1[0] = ippl::Index(2 * size[0] - domain2n1[0].first(),
2100 2 * size[0] - domain2n1[0].last(), -1);
2101 domain2n1[1] = ippl::Index(2 * size[1] - domain2n1[1].first(),
2102 2 * size[1] - domain2n1[1].last(), -1);
2103
2104 intersection = intersection.intersect(domain2n1);
2105
2106 solver_recv(mpi::tag::VICO_SOLVER, 4, i, intersection, ldom, nghost, view, fd_m,
2107 {true, true, false});
2108 }
2109 }
2110
2111 if (ldom.touches(yz)) {
2112 auto intersection = ldom.intersect(yz);
2113
2114 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
2115 (2 * size[1] - intersection[1].last()), -1);
2116 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
2117 (2 * size[2] - intersection[2].last()), -1);
2118
2119 ippl::NDIndex<Dim> domain2n1;
2120 domain2n1[0] = intersection[0];
2121 domain2n1[1] = ydom;
2122 domain2n1[2] = zdom;
2123
2124 if (lDomains2n1[i].touches(domain2n1)) {
2125 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2126 domain2n1[1] = ippl::Index(2 * size[1] - domain2n1[1].first(),
2127 2 * size[1] - domain2n1[1].last(), -1);
2128 domain2n1[2] = ippl::Index(2 * size[2] - domain2n1[2].first(),
2129 2 * size[2] - domain2n1[2].last(), -1);
2130
2131 intersection = intersection.intersect(domain2n1);
2132
2133 solver_recv(mpi::tag::VICO_SOLVER, 5, i, intersection, ldom, nghost, view, fd_m,
2134 {false, true, true});
2135 }
2136 }
2137
2138 if (ldom.touches(xz)) {
2139 auto intersection = ldom.intersect(xz);
2140
2141 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
2142 (2 * size[0] - intersection[0].last()), -1);
2143 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
2144 (2 * size[2] - intersection[2].last()), -1);
2145
2146 ippl::NDIndex<Dim> domain2n1;
2147 domain2n1[0] = xdom;
2148 domain2n1[1] = intersection[1];
2149 domain2n1[2] = zdom;
2150
2151 if (lDomains2n1[i].touches(domain2n1)) {
2152 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2153 domain2n1[0] = ippl::Index(2 * size[0] - domain2n1[0].first(),
2154 2 * size[0] - domain2n1[0].last(), -1);
2155 domain2n1[2] = ippl::Index(2 * size[2] - domain2n1[2].first(),
2156 2 * size[2] - domain2n1[2].last(), -1);
2157
2158 intersection = intersection.intersect(domain2n1);
2159
2160 solver_recv(mpi::tag::VICO_SOLVER, 6, i, intersection, ldom, nghost, view, fd_m,
2161 {true, false, true});
2162 }
2163 }
2164
2165 if (ldom.touches(xyz)) {
2166 auto intersection = ldom.intersect(xyz);
2167
2168 auto xdom = ippl::Index((2 * size[0] - intersection[0].first()),
2169 (2 * size[0] - intersection[0].last()), -1);
2170 auto ydom = ippl::Index((2 * size[1] - intersection[1].first()),
2171 (2 * size[1] - intersection[1].last()), -1);
2172 auto zdom = ippl::Index((2 * size[2] - intersection[2].first()),
2173 (2 * size[2] - intersection[2].last()), -1);
2174
2175 ippl::NDIndex<Dim> domain2n1;
2176 domain2n1[0] = xdom;
2177 domain2n1[1] = ydom;
2178 domain2n1[2] = zdom;
2179
2180 if (lDomains2n1[i].touches(domain2n1)) {
2181 domain2n1 = lDomains2n1[i].intersect(domain2n1);
2182 domain2n1[0] = ippl::Index(2 * size[0] - domain2n1[0].first(),
2183 2 * size[0] - domain2n1[0].last(), -1);
2184 domain2n1[1] = ippl::Index(2 * size[1] - domain2n1[1].first(),
2185 2 * size[1] - domain2n1[1].last(), -1);
2186 domain2n1[2] = ippl::Index(2 * size[2] - domain2n1[2].first(),
2187 2 * size[2] - domain2n1[2].last(), -1);
2188
2189 intersection = intersection.intersect(domain2n1);
2190
2191 solver_recv(mpi::tag::VICO_SOLVER, 7, i, intersection, ldom, nghost, view, fd_m,
2192 {true, true, true});
2193 }
2194 }
2195 }
2196
2197 if (requests.size() > 0) {
2198 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
2199 }
2200 ippl::Comm->freeAllBuffers();
2201 };
2202
2204 // Shifted Green's function: fills grn_mr with G(r - shift) on the doubled
2205 // grid and caches FFT into grntr_m. Kokkos-parallel, mirrors the HOCKNEY
2206 // greensFunction() method in structure.
2207 //
2208 // After this call, solve() will convolve rho with the shifted kernel.
2209 // To restore the standard kernel, call greensFunction() explicitly.
2210
2211 template <typename FieldLHS, typename FieldRHS>
2213 const Vector<double, Dim>& shift) {
2214 const int alg = this->params_m.template get<int>("algorithm");
2215 const int greensFunctionType = this->params_m.template get<int>("greens_function");
2216 if (alg != Algorithm::HOCKNEY) {
2217 throw IpplException("FFTOpenPoissonSolver::shiftedGreensFunction",
2218 "Shifted Green's function is only implemented for HOCKNEY.");
2219 }
2220 if ((greensFunctionType == GreenFunction::INTEGRATED) && (Dim != 3)) {
2221 throw IpplException("FFTOpenPoissonSolver::shiftedGreensFunction",
2222 "Shifted integrated Green's function is only implemented for 3D.");
2223 }
2224
2225 // Sync mesh spacing with the current RHS mesh (same logic as solve()'s
2226 // mesh-change detection). Without this, two failure modes compound:
2227 // 1. We would compute the shifted kernel at a STALE hr_m.
2228 // 2. A subsequent solve() would see hr_m != mesh->getMeshSpacing(),
2229 // set green=true, and call greensFunction(), overwriting the
2230 // shifted kernel with the standard one.
2231 // By updating hr_m (and the dependent mesh2_m / meshComplex_m) here,
2232 // solve()'s mesh check finds no change and leaves grntr_m intact.
2233 mesh_mp = &(this->rhs_mp->get_mesh());
2234 for (unsigned int i = 0; i < Dim; ++i) {
2235 hr_m[i] = mesh_mp->getMeshSpacing(i);
2236 }
2237 mesh2_m->setMeshSpacing(hr_m);
2238 meshComplex_m->setMeshSpacing(hr_m);
2239
2240 const scalar_type pi = Kokkos::numbers::pi_v<scalar_type>;
2241
2242 typename Field_t::view_type view = grn_mr.getView();
2243 const int nghost = grn_mr.getNghost();
2244 const auto& ldom = layout2_m->getLocalNDIndex();
2245
2246 // Capture simple value arrays for the lambda.
2247 Vector<int, Dim> nr = nr_m;
2248 Vector_t hs = hr_m;
2249 Vector<double, Dim> shft = shift;
2250
2251 using index_array_type = typename ippl::RangePolicy<Dim>::index_array_type;
2252 if (greensFunctionType == GreenFunction::INTEGRATED) {
2253 ippl::parallel_for(
2254 "Shifted integrated Green's function", grn_mr.getFieldRangePolicy(),
2255 KOKKOS_LAMBDA(const index_array_type& args) {
2256 Vector<int, Dim> igVec = args - nghost;
2257 for (unsigned d = 0; d < Dim; ++d) {
2258 igVec[d] += ldom[d].first();
2259 }
2260
2261 scalar_type offset[Dim];
2262 for (unsigned int d = 0; d < Dim; ++d) {
2263 const int igSigned = (igVec[d] < nr[d]) ? igVec[d] : igVec[d] - 2 * nr[d];
2264 offset[d] = static_cast<scalar_type>(igSigned) * hs[d]
2265 - static_cast<scalar_type>(shft[d]);
2266 }
2267
2268 const scalar_type avg =
2269 integratedGreenAverage(offset[0], offset[1], offset[2], hs);
2270 apply(view, args) = -avg / (scalar_type(4) * pi);
2271 });
2272 } else {
2273 // Regularization threshold (axis-min mesh spacing, squared, quartered).
2274 scalar_type hmin2 = hs[0] * hs[0];
2275 for (unsigned int d = 1; d < Dim; ++d) {
2276 hmin2 = (hs[d] * hs[d] < hmin2) ? (hs[d] * hs[d]) : hmin2;
2277 }
2278 const scalar_type regThresh = 0.25 * hmin2;
2279
2280 ippl::parallel_for(
2281 "Shifted Green's function", grn_mr.getFieldRangePolicy(),
2282 KOKKOS_LAMBDA(const index_array_type& args) {
2283 // local -> global indices
2284 Vector<int, Dim> igVec = args - nghost;
2285 for (unsigned d = 0; d < Dim; ++d) {
2286 igVec[d] += ldom[d].first();
2287 }
2288
2289 // Hockney convention: map doubled-grid indices.
2290 // ig in [0, N) -> offset = ig * h
2291 // ig in [N, 2N) -> offset = (ig - 2N) * h
2292 // Subtract the shift to evaluate the shifted Green's function G(r-s).
2293 double rsq = 0.0;
2294 for (unsigned int d = 0; d < Dim; ++d) {
2295 const double ig_signed = (igVec[d] < nr[d])
2296 ? static_cast<double>(igVec[d])
2297 : static_cast<double>(igVec[d] - 2 * nr[d]);
2298 const double xoff = ig_signed * hs[d] - shft[d];
2299 rsq += xoff * xoff;
2300 }
2301
2302 // Sign convention matches greensFunction() (HOCKNEY): grn_mr stores -G0
2303 // so that solve()'s `rho2tr_m = -rho2tr_m * grntr_m` yields +conv(rho,G0)
2304 // where G0(r) = 1/(4 pi |r|).
2305 const bool nearSing = (rsq < regThresh);
2306 const scalar_type r = Kokkos::sqrt(rsq + nearSing * regThresh);
2307 apply(view, args) = -1.0 / (4.0 * pi * r);
2308 });
2309 }
2310
2311 // Store Fourier transform of shifted Green's function for convolution in solve()
2312 static IpplTimings::TimerRef fftsg = IpplTimings::getTimer("FFT: Shifted Green");
2313 IpplTimings::startTimer(fftsg);
2314
2315 fft_m->transform(FORWARD, grn_mr, grntr_m);
2316
2317 IpplTimings::stopTimer(fftsg);
2318 }
2319
2320} // namespace ippl
Definition IpplException.h:6
typename detail::ViewType< T, Dim, ViewArgs... >::view_type view_type
View type storing the data.
Definition BareField.h:50
Definition FFTOpenPoissonSolver.h:32
Definition FieldLayout.h:166
Definition Index.h:40
Definition NDIndex.h:21
KOKKOS_INLINE_FUNCTION NDIndex< Dim > intersect(const NDIndex< Dim > &) const
Definition NDIndex.hpp:59
Definition Vector.h:23
Definition Archive.h:20
detail::meta_hess< Field > hess(Field &u)
Definition FieldOperations.hpp:106
KOKKOS_INLINE_FUNCTION constexpr decltype(auto) apply(const View &view, const Coords &coords)
Definition IpplOperations.h:64
detail::meta_grad< Field > grad(Field &u)
Definition FieldOperations.hpp:12