OPAL (Object Oriented Parallel Accelerator Library) 2022.1
OPAL
P3MPoissonSolver.cpp
Go to the documentation of this file.
1//
2// Class P3MPoissonSolver
3// This class contains methods for solving Poisson's equation for the
4// space charge portion of the calculation including collisions.
5//
6// Copyright (c) 2016, Benjamin Ulmer, ETH Zürich
7// 2022, Sriramkrishnan Muralikrishnan, PSI
8// All rights reserved
9//
10// Implemented as part of the Master thesis
11// "The P3M Model on Emerging Computer Architectures With Application to Microbunching"
12// (http://amas.web.psi.ch/people/aadelmann/ETH-Accel-Lecture-1/projectscompleted/cse/thesisBUlmer.pdf)
13//
14// This file is part of OPAL.
15//
16// OPAL is free software: you can redistribute it and/or modify
17// it under the terms of the GNU General Public License as published by
18// the Free Software Foundation, either version 3 of the License, or
19// (at your option) any later version.
20//
21// You should have received a copy of the GNU General Public License
22// along with OPAL. If not, see <https://www.gnu.org/licenses/>.
23//
24
26
32#include "Physics/Physics.h"
33#include "Structure/DataSink.h"
35
36#include <cmath>
37#include <fstream>
38
40// a little helper class to specialize the action of the Green's function
41// calculation. This should be specialized for each dimension
42// to the proper action for computing the Green's function. The first
43// template parameter is the full type of the Field to compute, and the second
44// is the dimension of the data, which should be specialized.
45
46
47template<unsigned int Dim>
49
50template<>
52 template<class T, class FT, class FT2>
53 static void calculate(Vektor<T, 3>& hrsq_r, FT& grn_r, FT2* grnI_p, double alpha) {
54 grn_r = grnI_p[0] * hrsq_r[0] + grnI_p[1] * hrsq_r[1] + grnI_p[2] * hrsq_r[2];
55 grn_r = erf(alpha*sqrt(grn_r))/(sqrt(grn_r));
56 grn_r[0][0][0] = grn_r[0][0][1];
57 }
58};
59
60template<class T>
61struct ApplyField {
62 ApplyField(double alpha_, double ke_, bool isIntGreen_) : alpha(alpha_), ke(ke_),
63 isIntGreen(isIntGreen_) {}
64 void operator()(std::size_t i, std::size_t j, PartBunch& P_r) const
65 {
66 Vector_t diff = P_r.R[i] - P_r.R[j];
67 double sqr = 0;
68
69 for (unsigned d = 0; d<Dim; ++d) {
70 sqr += diff[d]*diff[d];
71 }
72
73 if(sqr!=0) {
74 double r = std::sqrt(sqr);
75
76 //compute force
77 Vector_t Fij;
78
79 //Differentiate the PP Green's function
80 //(1-erf(\alpha r))/r (for standard)
81 //(1/(2r)) * (\xi^3 - 3\xi +2) (for integrated) where
82 //xi = r/interaction_radius and multiply it with r unit vector
83 double xi = r/alpha;
84 Fij = ((double)isIntGreen * (-ke*(diff/(2*sqr))*((std::pow(xi,3) - 3*xi + 2)/r
85 + 3*(1 - std::pow(xi,2))/alpha)))
86 + ((double)(1.0 - isIntGreen)
87 * (-ke*(diff/r)*((2.*alpha*std::exp(-alpha*alpha*sqr))
88 / (std::sqrt(M_PI)*r) + (1.-std::erf(alpha*r))/(r*r))));
89
90 //Actual Force is F_ij multiplied by Qi*Qj
91 //The electrical field on particle i is E=F/q_i and hence:
92 P_r.Ef[i] -= P_r.Q[j]*Fij;
93 P_r.Ef[j] += P_r.Q[i]*Fij;
94 }
95
96 }
97 double alpha;
98 double ke;
100};
101
102
104
105// constructor
106
108 double interaction_radius,
109 double alpha, std::string greensFunction):
110 mesh_mp(mesh_p),
111 layout_mp(fl_p),
112 interaction_radius_m(interaction_radius),
113 alpha_m(alpha)
114{
115 Inform msg("P3MPoissonSolver::P3MPoissonSolver ");
116
117 integratedGreens_m = (greensFunction == std::string("INTEGRATED"));
119 ke_m = 1.0 / (4 * Physics::pi * Physics::epsilon_0);
120
121 GreensFunctionTimer_m = IpplTimings::getTimer("GreensFTotalP3M");
122 ComputePotential_m = IpplTimings::getTimer("ComputePotentialP3M");
123 CalculatePairForces_m = IpplTimings::getTimer("CalculatePairForcesP3M");
124}
125
127// destructor
129}
130
131
133
135
136 // For efficiency in the FFT's, we can use a parallel decomposition
137 // which can be serial in the first dimension.
138 e_dim_tag decomp[3];
139 e_dim_tag decomp2[3];
140 for(int d = 0; d < 3; ++ d) {
141 decomp[d] = layout_mp->getRequestedDistribution(d);
142 decomp2[d] = layout_mp->getRequestedDistribution(d);
143 }
144
145 // The FFT's require double-sized field sizes in order to
146 // simulate an isolated system. The FFT of the charge density field, rho,
147 // would otherwise mimic periodic boundary conditions, i.e. as if there were
148 // several beams set a periodic distance apart. The double-sized fields
149 // alleviate this problem.
150 for (int i = 0; i < 3; ++ i) {
152 nr_m[i] = domain_m[i].length();
153 domain2_m[i] = Index(2 * nr_m[i] + 1);
154 }
155
156 // create double sized mesh and layout objects for the use in the FFT's
157 mesh2_mp = std::unique_ptr<Mesh_t>(new Mesh_t(domain2_m));
158 layout2_mp = std::unique_ptr<FieldLayout_t>(new FieldLayout_t(*mesh2_mp, decomp));
159
161
162 // Create the domain for the transformed (complex) fields. Do this by
163 // taking the domain from the doubled mesh, permuting it to the right, and
164 // setting the 2nd dimension to have n/2 + 1 elements.
165 domain3_m[0] = Index(2 * nr_m[2] + 1);
166 domain3_m[1] = Index(nr_m[0] + 2);
167 domain3_m[2] = Index(2 * nr_m[1] + 1);
168
169 // create mesh and layout for the new real-to-complex FFT's, for the
170 // complex transformed fields
171 mesh3_mp = std::unique_ptr<Mesh_t>(new Mesh_t(domain3_m));
172 layout3_mp = std::unique_ptr<FieldLayout_t>(new FieldLayout_t(*mesh3_mp, decomp2));
173
176
177 for (int i = 0; i < 3; ++ i) {
178 domain4_m[i] = Index(nr_m[i] + 2);
179 }
180 mesh4_mp = std::unique_ptr<Mesh_t>(new Mesh_t(domain4_m));
181 layout4_mp = std::unique_ptr<FieldLayout_t>(new FieldLayout_t(*mesh4_mp, decomp));
182
184
185 // create a domain used to indicate to the FFT's how to construct it's
186 // temporary fields. This is the same as the complex field's domain,
187 // but permuted back to the left.
188 NDIndex<3> tmpdomain;
189 tmpdomain = layout3_mp->getDomain();
190 for (int i = 0; i < 3; ++ i)
191 domainFFTConstruct_m[i] = tmpdomain[(i+1) % 3];
192
193 // create the FFT object
194 fft_mp = std::unique_ptr<FFT_t>(new FFT_t(layout2_mp->getDomain(),
196
197 if(!integratedGreens_m) {
198 // these are fields that are used for calculating the Green's function.
199 // they eliminate some calculation at each time-step.
200 for (int i = 0; i < 3; ++ i) {
203 domain2_m[i] * domain2_m[i],
204 (2 * nr_m[i] - domain2_m[i]) *
205 (2 * nr_m[i] - domain2_m[i]));
206 }
207 }
208}
209
210
212
215 PartBunch& tmpBunch_r = *(dynamic_cast<PartBunch*>(bunch_p));
216 std::size_t size = tmpBunch_r.getLocalNum()+tmpBunch_r.getGhostNum();
217
218 //Take the particles to the boosted frame
219 for(std::size_t i = 0;i<size;++i)
220 {
221 tmpBunch_r.R[i](2) = tmpBunch_r.R[i](2) * gammaz;
222 }
223
224 HashPairBuilderParallel<PartBunch> HPB(tmpBunch_r,gammaz);
226 //Note: alpha_m is not used for the integrated Green's function
227 //approach
230 }
231 else {
234 }
235
236 //Bring the particles to the lab frame
237 for(std::size_t i = 0;i<size;++i)
238 {
239 tmpBunch_r.R[i](2) = tmpBunch_r.R[i](2) / gammaz;
240 }
241 }
243}
244
245
246// given a charge-density field rho and a set of mesh spacings hr,
247// compute the scalar potential in open space
250
251 // use grid of complex doubled in both dimensions
252 // and store rho in lower left quadrant of doubled grid
253 rho2_m = 0.0;
254
255 rho2_m[domain_m] = rho_r[domain_m];
256
257 // needed in greens function
258 hr_m = hr;
259
260 // FFT double-sized charge density
261 // we do a backward transformation so that we dont have to
262 // account for the normalization factor
263 // that is used in the forward transformation of the IPPL FFT
264 fft_mp->transform(-1, rho2_m, rho2tr_m);
265
266 // must be called if the mesh size has changed
270 else
273 // multiply transformed charge density
274 // and transformed Green function
275 // Don't divide by (2*nx_m)*(2*ny_m), as Ryne does;
276 // this normalization is done in IPPL's fft routine.
277 rho2tr_m *= grntr_m;
278
279 // inverse FFT, rho2_m equals to the electrostatic potential
280 fft_mp->transform(+1, rho2tr_m, rho2_m);
281 // end convolution
282
283 // back to physical grid
284 // reuse the charge density field to store the electrostatic potential
285 rho_r[domain_m] = rho2_m[domain_m];
286
287
288 rho_r *= hr[0] * hr[1] * hr[2];
289
291}
292
294// given a charge-density field rho and a set of mesh spacings hr,
295// compute the electric potential from the image charge by solving
296// the Poisson's equation
297
298void P3MPoissonSolver::computePotential(Field_t& /*rho*/, Vector_t /*hr*/, double /*zshift*/) {
299
300 throw OpalException("P3MPoissonSolver", "not implemented yet");
301
302}
303
305
306 Vector_t hrsq(hr_m * hr_m);
308
309 // we do a backward transformation so that we dont have to account
310 // for the normalization factor
311 // that is used in the forward transformation of the IPPL FFT
312 fft_mp->transform(-1, rho2_m, grntr_m);
313}
314
337 NDIndex<3> idx = layout4_mp->getLocalNDIndex();
338 double cellVolume = hr_m[0] * hr_m[1] * hr_m[2];
339 tmpgreen_m = 0.0;
340
341 for(int k = idx[2].first(); k <= idx[2].last() + 1; k++) {
342 for(int j = idx[1].first(); j <= idx[1].last() + 1; j++) {
343 for(int i = idx[0].first(); i <= idx[0].last() + 1; i++) {
344
345 Vector_t vv = Vector_t(0.0);
346 vv(0) = i * hr_m[0] - hr_m[0] / 2;
347 vv(1) = j * hr_m[1] - hr_m[1] / 2;
348 vv(2) = k * hr_m[2] - hr_m[2] / 2;
349
350 double r = std::sqrt(vv(0) * vv(0) + vv(1) * vv(1) + vv(2) * vv(2));
351 double tmpgrn = 0.0;
352 if(r >= interaction_radius_m) {
353 tmpgrn = -vv(2) * vv(2) * std::atan(vv(0) * vv(1) / (vv(2) * r)) / 2;
354 tmpgrn += -vv(1) * vv(1) * std::atan(vv(0) * vv(2) / (vv(1) * r)) / 2;
355 tmpgrn += -vv(0) * vv(0) * std::atan(vv(1) * vv(2) / (vv(0) * r)) / 2;
356 tmpgrn += vv(1) * vv(2) * std::log(vv(0) + r);
357 tmpgrn += vv(0) * vv(2) * std::log(vv(1) + r);
358 tmpgrn += vv(0) * vv(1) * std::log(vv(2) + r);
359 }
360 else {
361 tmpgrn = -(vv(0) * vv(1) * vv(2) * (-9 * std::pow(interaction_radius_m, 2)
362 + std::pow(r, 2))) / (6 * std::pow(interaction_radius_m, 3));
363 }
364
365 tmpgreen_m[i][j][k] = tmpgrn / cellVolume;
366
367 }
368 }
369 }
370
371
372 Index I = nr_m[0] + 1;
373 Index J = nr_m[1] + 1;
374 Index K = nr_m[2] + 1;
375
376 // the actual integration
377 rho2_m = 0.0;
378 rho2_m[I][J][K] = tmpgreen_m[I+1][J+1][K+1];
379 rho2_m[I][J][K] += tmpgreen_m[I+1][J][K];
380 rho2_m[I][J][K] += tmpgreen_m[I][J+1][K];
381 rho2_m[I][J][K] += tmpgreen_m[I][J][K+1];
382 rho2_m[I][J][K] -= tmpgreen_m[I+1][J+1][K];
383 rho2_m[I][J][K] -= tmpgreen_m[I+1][J][K+1];
384 rho2_m[I][J][K] -= tmpgreen_m[I][J+1][K+1];
385 rho2_m[I][J][K] -= tmpgreen_m[I][J][K];
386
388
389 fft_mp->transform(-1, rho2_m, grntr_m);
390
391}
392
394
395 Index aI(0, 2 * nr_m[0]);
396 Index aJ(0, 2 * nr_m[1]);
397
398 Index J(0, nr_m[1]);
399 Index K(0, nr_m[2]);
400
401 Index IE(nr_m[0] + 1, 2 * nr_m[0]);
402 Index JE(nr_m[1] + 1, 2 * nr_m[1]);
403 Index KE(nr_m[2] + 1, 2 * nr_m[2]);
404
405 Index mirroredIE = 2 * nr_m[0] - IE;
406 Index mirroredJE = 2 * nr_m[1] - JE;
407 Index mirroredKE = 2 * nr_m[2] - KE;
408
409 rho2_m[0][0][0] = rho2_m[0][0][1];
410
411 rho2_m[IE][J ][K ] = rho2_m[mirroredIE][J ][K ];
412 rho2_m[aI][JE][K ] = rho2_m[aI ][mirroredJE][K ];
413 rho2_m[aI][aJ][KE] = rho2_m[aI ][aJ ][mirroredKE];
414
415}
416
418 os << "* ************* P 3 M - P o i s s o n S o l v e r *************** " << endl;
419 os << "* h " << hr_m << '\n';
420 os << "* RC " << interaction_radius_m << '\n';
421 os << "* ALPHA " << alpha_m << '\n';
422 os << "* *************************************************************** " << endl;
423 return os;
424}
FTps< T, N > erf(const FTps< T, N > &x, int trunc=(FTps< T, N >::EXACT))
Error function.
Definition: FTpsMath.h:385
UniformCartesian< 3, double > Mesh_t
Definition: PBunchDefs.h:22
CenteredFieldLayout< 3, Mesh_t, Center_t > FieldLayout_t
Definition: PBunchDefs.h:28
Tps< T > log(const Tps< T > &x)
Natural logarithm.
Definition: TpsMath.h:182
Tps< T > pow(const Tps< T > &x, int y)
Integer power.
Definition: TpsMath.h:76
Tps< T > exp(const Tps< T > &x)
Exponential.
Definition: TpsMath.h:165
Tps< T > sqrt(const Tps< T > &x)
Square root.
Definition: TpsMath.h:91
const unsigned Dim
e_dim_tag
Definition: FieldLayout.h:55
PETE_TUTree< FnArcTan, typename T::PETE_Expr_t > atan(const PETE_Expr< T > &l)
Definition: PETE.h:727
PETE_TTTree< OpWhere, typename Cond_t::PETE_Expr_t, typename True_t::PETE_Expr_t, PETE_Scalar< Vektor< T, Dim > > > where(const PETE_Expr< Cond_t > &c, const PETE_Expr< True_t > &t, const Vektor< T, Dim > &f)
Inform & endl(Inform &inf)
Definition: Inform.cpp:42
constexpr double epsilon_0
The permittivity of vacuum in As/Vm.
Definition: Physics.h:51
constexpr double alpha
The fine structure constant, no dimension.
Definition: Physics.h:72
constexpr double pi
The value of.
Definition: Physics.h:30
bool lt(double x, double y)
ParticleAttrib< Vector_t > Ef
ParticlePos_t & R
size_t getLocalNum() const
ParticleAttrib< double > Q
size_t getGhostNum() const
static void calculate(Vektor< T, 3 > &hrsq_r, FT &grn_r, FT2 *grnI_p, double alpha)
ApplyField(double alpha_, double ke_, bool isIntGreen_)
void operator()(std::size_t i, std::size_t j, PartBunch &P_r) const
void calculatePairForces(PartBunchBase< double, 3 > *bunch, double gammaz) override
IpplTimings::TimerRef CalculatePairForces_m
Vektor< int, 3 > nr_m
IpplTimings::TimerRef ComputePotential_m
NDIndex< 3 > domain2_m
std::unique_ptr< FieldLayout_t > layout4_mp
FieldLayout_t * layout_mp
NDIndex< 3 > domain_m
std::unique_ptr< Mesh_t > mesh4_mp
std::unique_ptr< Mesh_t > mesh3_mp
std::unique_ptr< FFT_t > fft_mp
NDIndex< 3 > domain3_m
void computePotential(Field_t &rho, Vector_t hr) override
NDIndex< 3 > domain4_m
IpplTimings::TimerRef GreensFunctionTimer_m
std::unique_ptr< FieldLayout_t > layout2_mp
IField_t grnIField_m[3]
FFT< RCTransform, 3, double > FFT_t
Inform & print(Inform &os) const
NDIndex< 3 > domainFFTConstruct_m
std::unique_ptr< FieldLayout_t > layout3_mp
std::unique_ptr< Mesh_t > mesh2_mp
P3MPoissonSolver(Mesh_t *mesh, FieldLayout_t *fl, double interaction_radius, double alpha, std::string greensFunction)
The base class for all OPAL exceptions.
Definition: OpalException.h:28
Definition: Vektor.h:32
void initialize(Layout_t &)
const NDIndex< Dim > & getDomain() const
Definition: FieldLayout.h:325
e_dim_tag getRequestedDistribution(unsigned int d) const
Definition: FieldLayout.h:405
MFLOAT get_meshSpacing(unsigned d) const
Definition: Index.h:237
void forEach(const Pred &pred_r, const OP &op_r)
Definition: Inform.h:42
static TimerRef getTimer(const char *nm)
Definition: IpplTimings.h:182
static void stopTimer(TimerRef t)
Definition: IpplTimings.h:192
static void startTimer(TimerRef t)
Definition: IpplTimings.h:187
Vektor< double, 3 > Vector_t
Definition: Vektor.h:6