OPAL (Object Oriented Parallel Accelerator Library)  2024.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 
28 #include "Algorithms/PartBunch.h"
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 
47 template<unsigned int Dim>
48 struct P3MGreensFunction { };
49 
50 template<>
51 struct P3MGreensFunction<3> {
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 
60 template<class T>
61 struct 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;
99  bool isIntGreen;
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) {
151  hr_m[i] = mesh_mp->get_meshSpacing(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 
160  rho2_m.initialize(*mesh2_mp, *layout2_mp);
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 
174  rho2tr_m.initialize(*mesh3_mp, *layout3_mp);
175  grntr_m.initialize(*mesh3_mp, *layout3_mp);
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 
183  tmpgreen_m.initialize(*mesh4_mp, *layout4_mp);
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) {
201  grnIField_m[i].initialize(*mesh2_mp, *layout2_mp);
202  grnIField_m[i][domain2_m] = where(lt(domain2_m[i], nr_m[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 
214  if (interaction_radius_m>0){
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);
225  if(integratedGreens_m) {
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
271  greensFunction();
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 
298 void 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 
387  mirrorRhoField();
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 }
NDIndex< 3 > domain_m
P3MPoissonSolver(Mesh_t *mesh, FieldLayout_t *fl, double interaction_radius, double alpha, std::string greensFunction)
Tps< T > sqrt(const Tps< T > &x)
Square root.
Definition: TpsMath.h:91
e_dim_tag getRequestedDistribution(unsigned int d) const
Definition: FieldLayout.h:405
void initialize(Layout_t &)
std::unique_ptr< FFT_t > fft_mp
IpplTimings::TimerRef GreensFunctionTimer_m
Vektor< int, 3 > nr_m
void calculatePairForces(PartBunchBase< double, 3 > *bunch, double gammaz) override
FTps< T, N > erf(const FTps< T, N > &x, int trunc=(FTps< T, N >::EXACT))
Error function.
Definition: FTpsMath.h:385
constexpr double epsilon_0
The permittivity of vacuum in As/Vm.
Definition: Physics.h:51
IField_t grnIField_m[3]
Inform & print(Inform &os) const
Vektor< double, 3 > Vector_t
Definition: Vektor.h:6
NDIndex< 3 > domainFFTConstruct_m
FieldLayout_t * layout_mp
Definition: TSVMeta.h:24
ParticleAttrib< Vector_t > Ef
FFT< RCTransform, 3, double > FFT_t
Tps< T > exp(const Tps< T > &x)
Exponential.
Definition: TpsMath.h:165
NDIndex< 3 > domain3_m
constexpr double pi
The value of .
Definition: Physics.h:30
Definition: Index.h:236
Inform & endl(Inform &inf)
Definition: Inform.cpp:42
size_t getGhostNum() const
std::unique_ptr< FieldLayout_t > layout2_mp
PETE_TUTree< FnArcTan, typename T::PETE_Expr_t > atan(const PETE_Expr< T > &l)
Definition: PETE.h:727
CenteredFieldLayout< 3, Mesh_t, Center_t > FieldLayout_t
Definition: PBunchDefs.h:28
NDIndex< 3 > domain4_m
static void calculate(Vektor< T, 3 > &hrsq_r, FT &grn_r, FT2 *grnI_p, double alpha)
static void startTimer(TimerRef t)
Definition: IpplTimings.h:187
bool lt(double x, double y)
std::unique_ptr< FieldLayout_t > layout4_mp
The base class for all OPAL exceptions.
Definition: OpalException.h:28
size_t getLocalNum() const
void operator()(std::size_t i, std::size_t j, PartBunch &P_r) const
NDIndex< 3 > domain2_m
Definition: Inform.h:42
UniformCartesian< 3, double > Mesh_t
Definition: PBunchDefs.h:22
e_dim_tag
Definition: FieldLayout.h:55
std::unique_ptr< FieldLayout_t > layout3_mp
ParticleAttrib< double > Q
const unsigned Dim
ApplyField(double alpha_, double ke_, bool isIntGreen_)
constexpr double alpha
The fine structure constant, no dimension.
Definition: Physics.h:72
Tps< T > log(const Tps< T > &x)
Natural logarithm.
Definition: TpsMath.h:182
ParticlePos_t & R
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)
MFLOAT get_meshSpacing(unsigned d) const
IpplTimings::TimerRef ComputePotential_m
void forEach(const Pred &pred_r, const OP &op_r)
static TimerRef getTimer(const char *nm)
Definition: IpplTimings.h:182
std::unique_ptr< Mesh_t > mesh3_mp
std::unique_ptr< Mesh_t > mesh4_mp
Tps< T > pow(const Tps< T > &x, int y)
Integer power.
Definition: TpsMath.h:76
static void stopTimer(TimerRef t)
Definition: IpplTimings.h:192
const NDIndex< Dim > & getDomain() const
Definition: FieldLayout.h:325
void computePotential(Field_t &rho, Vector_t hr) override
IpplTimings::TimerRef CalculatePairForces_m
std::unique_ptr< Mesh_t > mesh2_mp