OPAL (Object Oriented Parallel Accelerator Library)  2021.1.99
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.
5 //
6 // Copyright (c) 2016, Benjamin Ulmer, ETH Zürich
7 // All rights reserved
8 //
9 // Implemented as part of the Master thesis
10 // "The P3M Model on Emerging Computer Architectures With Application to Microbunching"
11 // (http://amas.web.psi.ch/people/aadelmann/ETH-Accel-Lecture-1/projectscompleted/cse/thesisBUlmer.pdf)
12 //
13 // This file is part of OPAL.
14 //
15 // OPAL is free software: you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation, either version 3 of the License, or
18 // (at your option) any later version.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with OPAL. If not, see <https://www.gnu.org/licenses/>.
22 //
24 #include "Algorithms/PartBunch.h"
28 //#include "Particle/PairBuilder/HashPairBuilderPeriodicParallel_globCHaining.h"
30 #include "Structure/DataSink.h"
32 #include "Physics/Physics.h"
33 #include <fstream>
34 #include <cmath>
36 // a little helper class to specialize the action of the Green's function
37 // calculation. This should be specialized for each dimension
38 // to the proper action for computing the Green's function. The first
39 // template parameter is the full type of the Field to compute, and the second
40 // is the dimension of the data, which should be specialized.
41 
42 //const double ke=1./(4.*M_PI*8.8e-14);
43 const double ke=2.532638e8;
44 
45 template<unsigned int Dim>
46 struct SpecializedGreensFunction { };
47 
48 template<>
49 struct SpecializedGreensFunction<3> {
50  template<class T, class FT, class FT2>
51  static void calculate(Vektor<T, 3> &hrsq, FT &grn, FT2 *grnI, double alpha,double eps) {
52  double r;
53  NDIndex<3> elem0=NDIndex<3>(Index(0,0), Index(0,0),Index(0,0));
54  grn = grnI[0] * hrsq[0] + grnI[1] * hrsq[1] + grnI[2] * hrsq[2];
55  NDIndex<3> lDomain_m = grn.getLayout().getLocalNDIndex();
56  NDIndex<3> elem;
57  for (int i=lDomain_m[0].min(); i<=lDomain_m[0].max(); ++i) {
58  elem[0]=Index(i,i);
59  for (int j=lDomain_m[1].min(); j<=lDomain_m[1].max(); ++j) {
60  elem[1]=Index(j,j);
61  for (int k=lDomain_m[2].min(); k<=lDomain_m[2].max(); ++k) {
62  elem[2]=Index(k,k);
63  r = std::real(std::sqrt(grn.localElement(elem)));
64  if(elem==elem0) {
65  grn.localElement(elem) = 0 ;
66  }
67  else
68  grn.localElement(elem) = ke*std::complex<double>(std::erf(alpha*r)/(r+eps));
69  }
70  }
71  }
72  }
73 };
74 
75 template<class T>
76 struct ApplyField {
77  ApplyField(T c, double r, double epsilon, double alpha) : C(c), R(r), eps(epsilon), a(alpha) {}
78  void operator()(std::size_t i, std::size_t j, PartBunch &P,Vektor<double,3> &shift) const
79  {
80  Vector_t diff = P.R[i] - (P.R[j]+shift);
81  double sqr = 0;
82 
83  for (unsigned d = 0; d<Dim; ++d)
84  sqr += diff[d]*diff[d];
85 
86  //compute r with softening parameter, unsoftened r is obtained by sqrt(sqr)
87  if(sqr!=0) {
88  double r = std::sqrt(sqr+eps*eps);
89  //for order two transition
90  if (P.Q[i]!=0 && P.Q[j]!=0) {
91  //compute potential energy
92  double phi =ke*(1.-std::erf(a*std::sqrt(sqr)))/r;
93 
94  //compute force
95  Vector_t Fij = ke*C*(diff/std::sqrt(sqr))*((2.*a*std::exp(-a*a*sqr))/(std::sqrt(M_PI)*r)+(1.-std::erf(a*std::sqrt(sqr)))/(r*r));
96 
97  //Actual Force is F_ij multiplied by Qi*Qj
98  //The electrical field on particle i is E=F/q_i and hence:
99  P.Ef[i] -= P.Q[j]*Fij;
100  P.Ef[j] += P.Q[i]*Fij;
101  //update potential per particle
102  P.Phi[i] += P.Q[j]*phi;
103  P.Phi[j] += P.Q[i]*phi;
104  }
105  }
106  }
107  T C;
108  double R;
109  double eps;
110  double a;
111 };
112 
113 
115 
116 // constructor
117 
118 
119 P3MPoissonSolver::P3MPoissonSolver(Mesh_t *mesh, FieldLayout_t *fl, double interaction_radius, double alpha, double eps):
120  mesh_m(mesh),
121  layout_m(fl),
122  interaction_radius_m(interaction_radius),
123  alpha_m(alpha),
124  eps_m(eps)
125 {
126  Inform msg("P3MPoissonSolver::P3MPoissonSolver ");
127 
129 
130  for (unsigned int i = 0; i < 2 * Dim; ++i) {
131  if (Ippl::getNodes()>1) {
133  //std periodic boundary conditions for gradient computations etc.
136  }
137  else {
139  //std periodic boundary conditions for gradient computations etc.
142  }
143  }
144 
145  for (unsigned int i = 0; i < 2 * Dim; ++i) {
146  if (Ippl::getNodes()>1) {
148  //std periodic boundary conditions for gradient computations etc.
151  }
152  else {
154  //std periodic boundary conditions for gradient computations etc.
157  }
158  }
159 
160 
161  // For efficiency in the FFT's, we can use a parallel decomposition
162  // which can be serial in the first dimension.
163  // e_dim_tag decomp[3];
164  // for(int d = 0; d < 3; ++d) {
165  // decomp[d] = layout_m->getRequestedDistribution(d);
166  // }
167 
168  // The FFT's require double-sized field sizes in order to (more closely
169  // do not understand this ...)
170  // simulate an isolated system. The FFT of the charge density field, rho,
171  // would otherwise mimic periodic boundary conditions, i.e. as if there were
172  // several beams set a periodic distance apart. The double-sized fields
173  // alleviate this problem.
174  for(unsigned int i = 0; i < 3; i++) {
175  hr_m[i] = mesh_m->get_meshSpacing(i);
176  nr_m[i] = domain_m[i].length();
177  }
178 
181 
185 
186  bool compressTemps = true;
187  // create the FFT object
188  fft_m = std::unique_ptr<FFTC_t>(new FFTC_t(layout_m->getDomain(), compressTemps));
189  fft_m->setDirectionName(+1, "forward");
190  fft_m->setDirectionName(-1, "inverse");
191 }
192 
194 
196 
197  for (unsigned int i = 0; i < 2 * Dim; ++i) {
198  if (Ippl::getNodes()>1) {
200  //std periodic boundary conditions for gradient computations etc.
203  }
204  else {
206  //std periodic boundary conditions for gradient computations etc.
209  }
210  }
211 
212  for (unsigned int i = 0; i < 2 * Dim; ++i) {
213  if (Ippl::getNodes()>1) {
215  //std periodic boundary conditions for gradient computations etc.
218  }
219  else {
221  //std periodic boundary conditions for gradient computations etc.
224  }
225  }
226 
227  for(unsigned int i = 0; i < 3; i++) {
228  hr_m[i] = mesh_m->get_meshSpacing(i);
229  nr_m[i] = domain_m[i].length();
230  }
231 
234 
238 
239  bool compressTemps = true;
240  if (fft_m)
241  fft_m.reset();
242 
243  // create the FFT object
244  fft_m = std::unique_ptr<FFTC_t>(new FFTC_t(layout_m->getDomain(), compressTemps));
245  fft_m->setDirectionName(+1, "forward");
246  fft_m->setDirectionName(-1, "inverse");
247 
248 }
249 
250 
251 
253 // destructor
255 }
256 
257 void P3MPoissonSolver::calculatePairForces(PartBunchBase<double, 3> *bunch, double interaction_radius, double alpha, double eps) {
258  if (interaction_radius>0){
259  if (Ippl::getNodes() > 1) {
260  PartBunch &tmpBunch = *(dynamic_cast<PartBunch*>(bunch));
262  HPB.for_each(RadiusCondition<double, Dim>(interaction_radius), ApplyField<double>(-1,interaction_radius,eps,alpha),extend_l, extend_r);
263  }
264  else {
265  PartBunch &tmpBunch = *(dynamic_cast<PartBunch*>(bunch));
267  HPB.for_each(RadiusCondition<double, Dim>(interaction_radius), ApplyField<double>(-1,interaction_radius,eps,alpha),extend_l, extend_r);
268  }
269  }
270 
271 }
272 
273 void P3MPoissonSolver::calculateGridForces(PartBunchBase<double, 3> *bunch, double /*interaction_radius*/, double alpha, double eps){
274 
275  Inform msg ("calculateGridForces ");
276  Vector_t l,h;
277 
278  // (1) scatter charge to charge density grid and transform to fourier space
279  rho_m[domain_m]=0;
280  bunch->Q.scatter(rho_m, bunch->R, IntrplCIC_t());
281 
282  rhocmpl_m[domain_m] = rho_m[domain_m]/(hr_m[0]*hr_m[1]*hr_m[2]);
283 
284  fft_m->transform("inverse", rhocmpl_m);
285 
286  // (2) compute Greens function in real space and transform to fourier space
287  IField_t grnIField[3];
288 
289  // This loop stores in grnIField_m[i] the index of the ith dimension mirrored at the central axis. e.g.
290  // grnIField_m[0]=[(0 1 2 3 ... 3 2 1) ; (0 1 2 3 ... 3 2 1; ...)]
291  for (int i = 0; i < 3; ++i) {
292  grnIField[i].initialize(*mesh_m, *layout_m);
293  grnIField[i][domain_m] = where(lt(domain_m[i], nr_m[i]/2),
294  domain_m[i] * domain_m[i],
295  (nr_m[i]-domain_m[i]) *
296  (nr_m[i]-domain_m[i]));
297  }
298  Vector_t hrsq(hr_m * hr_m);
300 
301  //transform G -> Ghat and store in grncmpl_m
302  fft_m->transform("inverse", grncmpl_m);
303  //multiply in fourier space and obtain PhiHat in rhocmpl_m
304  rhocmpl_m *= grncmpl_m;
305 
306  // (3) Backtransformation: compute potential field in real space and E=-Grad Phi
307  //compute electrostatic potential Phi in real space by FFT PhiHat -> Phi and store it in rhocmpl_m
308  fft_m->transform("forward", rhocmpl_m);
309 
310  //take only the real part and store in phi_m (has periodic bc instead of interpolation bc)
311  phi_m = real(rhocmpl_m)*hr_m[0]*hr_m[1]*hr_m[2];
312  //dumpVTKScalar( phi_m, this,it, "Phi_m") ;
313 
314  //compute Electric field on the grid by -Grad(Phi) store in eg_m
315  eg_m = -Grad1Ord(phi_m, eg_m);
316 
317  //interpolate the electric field to the particle positions
318  bunch->Ef.gather(eg_m, bunch->R, IntrplCIC_t());
319  //interpolate electrostatic potenital to the particle positions
320  bunch->Phi.gather(phi_m, bunch->R, IntrplCIC_t());
321 }
322 
323 
324 
326 // given a charge-density field rho and a set of mesh spacings hr,
327 // compute the electric potential from the image charge by solving
328 // the Poisson's equation
329 
330 void P3MPoissonSolver::computePotential(Field_t &/*rho*/, Vector_t /*hr*/, double /*zshift*/) {
331 
332 
333 }
334 
336 
337  Inform m("computeAvgSpaceChargeForces ");
338 
339  const double N = static_cast<double>(bunch->getTotalNum());
340  double locAvgEf[Dim]={};
341  for (unsigned i=0; i<bunch->getLocalNum(); ++i) {
342  locAvgEf[0]+=std::abs(bunch->Ef[i](0));
343  locAvgEf[1]+=std::abs(bunch->Ef[i](1));
344  locAvgEf[2]+=std::abs(bunch->Ef[i](2));
345  }
346 
347  reduce(&(locAvgEf[0]), &(locAvgEf[0]) + Dim,
348  &(globSumEf_m[0]), OpAddAssign());
349 
350  // m << "globSumEF = " << globSumEf_m[0] << "\t" << globSumEf_m[1] << "\t" << globSumEf_m[2] << endl;
351 
352  avgEF_m[0]=globSumEf_m[0]/N;
353  avgEF_m[1]=globSumEf_m[1]/N;
354  avgEF_m[2]=globSumEf_m[2]/N;
355 
356 }
357 
358 
360  Vektor<double,Dim> beam_center(0,0,0);
361  Vector_t Rrel;
362  double scFoc = std::sqrt(dot(avgEF_m,avgEF_m));
363  for (unsigned i=0; i<bunch->getLocalNum(); ++i) {
364  Rrel=bunch->R[i] - beam_center;
365  bunch->Ef[i] += Rrel/r*f*scFoc;
366  }
367 
368 }
369 
370 // given a charge-density field rho and a set of mesh spacings hr,
371 // compute the scalar potential in open space
373 
374 
375 }
376 
378 
379  Inform m("compute_temperature ");
380  // double loc_temp[Dim]={0.0,0.0,0.0};
381  double loc_avg_vel[Dim]={0.0,0.0,0.0};
382  double avg_vel[Dim]={0.0,0.0,0.0};
383 
384  for(unsigned long k = 0; k < bunch->getLocalNum(); ++k) {
385  for(unsigned i = 0; i < Dim; i++) {
386  loc_avg_vel[i] += bunch->P[k](i);
387  }
388  }
389  reduce(&(loc_avg_vel[0]), &(loc_avg_vel[0]) + Dim,
390  &(avg_vel[0]), OpAddAssign());
391 
392  const double N = static_cast<double>(bunch->getTotalNum());
393  avg_vel[0]=avg_vel[0]/N;
394  avg_vel[1]=avg_vel[1]/N;
395  avg_vel[2]=avg_vel[2]/N;
396 
397  m << "avg_vel[0]= " << avg_vel[0] << " avg_vel[1]= " << avg_vel[1] << " avg_vel[2]= " << avg_vel[2] << endl;
398 
399  /*
400  for(unsigned long k = 0; k < bunch.getLocalNum(); ++k) {
401  for(unsigned i = 0; i < Dim; i++) {
402  loc_temp[i] += (bunch.P[k](i)-avg_vel[i])*(bunch.P[k](i)-avg_vel[i]);
403  }
404  }
405  reduce(&(loc_temp[0]), &(loc_temp[0]) + Dim,
406  &(temperature[0]), OpAddAssign());
407  temperature[0]=temperature[0]/N;
408  temperature[1]=temperature[1]/N;
409  temperature[2]=temperature[2]/N;
410  */
411 }
412 
414  Inform msg("P3MPoissonSolver::test ");
415 
416  // set special conditions for this test
417  const double mi = 1.0;
418  const double qi = -1.0;
419  const double qom = qi/mi;
420  const double beam_radius = 0.001774;
421  const double f = 1.5;
422  const double dt = bunch->getdT();
423 
425  DataSink *ds = opal->getDataSink();
426 
427  // std::vector<std::pair<std::string, unsigned int> > collimatorLosses; // just empty
428  Vector_t FDext[6];
429 
430  bunch->Q = qi;
431  bunch->M = mi;
432 
433  bunch->calcBeamParameters();
434 
435  initFields();
436 
437  for (int i=0; i<3; i++) {
438  extend_r[i] = hr_m[i]*nr_m[i]/2;
439  extend_l[i] = -hr_m[i]*nr_m[i]/2;
440  }
441 
442  msg << *this << endl;
443 
444  // calculate initial space charge forces
447 
448  //avg space charge forces for constant focusing
450 
451  for (int it=0; it<1000; it++) {
452 
453  // advance the particle positions
454  // basic leapfrogging timestep scheme. velocities are offset
455  // by half a timestep from the positions.
456 
457  assign(bunch->R, bunch->R + dt * bunch->P);
458 
459  bunch->update();
460 
463  applyConstantFocusing(bunch,f,beam_radius);
464 
465  assign(bunch->P, bunch->P + dt * qom * bunch->Ef);
466 
467  if (it%10 == 0){
468  bunch->calcBeamParameters();
469  ds->dumpSDDS(bunch, FDext, it);
470  }
471  msg << "Finished iteration " << it << endl;
472  }
473 }
474 
475 
477  os << "* ************* P 3 M - P o i s s o n S o l v e r *************** " << endl;
478  os << "* h " << hr_m << '\n';
479  os << "* RC " << interaction_radius_m << '\n';
480  os << "* ALPHA " << alpha_m << '\n';
481  os << "* EPSILON " << eps_m << '\n';
482  os << "* Extend L " << extend_l << '\n';
483  os << "* Extend R " << extend_r << '\n';
484  os << "* hr " << hr_m << '\n';
485  os << "* nr " << nr_m << '\n';
486  os << "* *************************************************************** " << endl;
487  return os;
488 }
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
double dot(const Vector3D &lhs, const Vector3D &rhs)
Vector dot product.
Definition: Vector3D.cpp:118
FLieGenerator< T, N > real(const FLieGenerator< std::complex< T >, N > &)
Take real part of a complex generator.
FTps< T, N > erf(const FTps< T, N > &x, int trunc=(FTps< T, N >::EXACT))
Error function.
Definition: FTpsMath.h:385
const double ke
const unsigned Dim
void assign(const BareField< T, Dim > &a, RHS b, OP op, ExprTag< true >)
T::PETE_Expr_t::PETE_Return_t min(const PETE_Expr< T > &expr, NDIndex< D > &loc)
Definition: ReductionLoc.h:76
Field< Vektor< T, 3U >, 3U, UniformCartesian< 3U, MFLOAT >, Cell > & Grad1Ord(Field< T, 3U, UniformCartesian< 3U, MFLOAT >, Vert > &x, Field< Vektor< T, 3U >, 3U, UniformCartesian< 3U, MFLOAT >, Cell > &r)
Old Grad operator.
bool reduce(Communicate &, InputIterator, InputIterator, OutputIterator, const ReduceOp &, bool *IncludeVal=0)
Definition: GlobalComm.hpp:55
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)
PETE_TUTree< FnAbs, typename T::PETE_Expr_t > abs(const PETE_Expr< T > &l)
Inform & endl(Inform &inf)
Definition: Inform.cpp:42
constexpr double alpha
The fine structure constant, no dimension.
Definition: Physics.h:78
constexpr double c
The velocity of light in m/s.
Definition: Physics.h:51
bool lt(double x, double y)
ParticleAttrib< Vector_t > Ef
ParticlePos_t & R
size_t getLocalNum() const
ParticleAttrib< double > M
size_t getTotalNum() const
ParticleAttrib< Vector_t > P
double getdT() const
ParticleAttrib< double > Q
void calcBeamParameters()
ParticleAttrib< double > Phi
The global OPAL structure.
Definition: OpalData.h:49
DataSink * getDataSink()
Definition: OpalData.cpp:389
static OpalData * getInstance()
Definition: OpalData.cpp:195
static void calculate(Vektor< T, 3 > &hrsq, FT &grn, FT2 *grnI, double alpha, double eps)
void operator()(std::size_t i, std::size_t j, PartBunch &P, Vektor< double, 3 > &shift) const
ApplyField(T c, double r, double epsilon, double alpha)
Vektor< double, 3 > extend_l
BConds< Vector_t, Dim, Mesh_t, Center_t > vbc_m
Vektor< int, 3 > nr_m
void compute_temperature(PartBunchBase< double, 3 > *bunch)
void test(PartBunchBase< double, 3 > *bunch)
BConds< double, Dim, Mesh_t, Center_t > bc_m
BConds< double, Dim, Mesh_t, Center_t > bcp_m
NDIndex< 3 > domain_m
void calculatePairForces(PartBunchBase< double, 3 > *bunch, double interaction_radius, double alpha, double eps)
void applyConstantFocusing(PartBunchBase< double, 3 > *bunch, double f, double r)
double globSumEf_m[Dim]
Vektor< double, 3 > extend_r
void calculateGridForces(PartBunchBase< double, 3 > *bunch, double interaction_radius, double alpha, double eps)
Vektor< double, Dim > avgEF_m
std::unique_ptr< FFTC_t > fft_m
Inform & print(Inform &os) const
void computePotential(Field_t &rho, Vector_t hr, double zshift)
FFT< CCTransform, 3, double > FFTC_t
FieldLayout_t * layout_m
void computeAvgSpaceChargeForces(PartBunchBase< double, 3 > *bunch)
P3MPoissonSolver(Mesh_t *mesh, FieldLayout_t *fl, double interaction_radius, double alpha, double eps)
void dumpSDDS(PartBunchBase< double, 3 > *beam, Vector_t FDext[], const double &azimuth=-1) const
Definition: DataSink.cpp:109
Definition: Vektor.h:32
Definition: Field.h:33
void initialize(Layout_t &)
void scatter(Field< T, Dim, M, C > &f, const ParticleAttrib< Vektor< PT, Dim > > &pp, const IntOp &) const
void gather(const Field< T, Dim, M, C > &f, const ParticleAttrib< Vektor< PT, Dim > > &pp, const IntOp &)
const NDIndex< Dim > & getDomain() const
Definition: FieldLayout.h:325
MFLOAT get_meshSpacing(unsigned d) const
Definition: Index.h:237
void for_each(const Pred &pred, const OP &op, Vektor< double, 3 > extend_l, Vektor< double, 3 > extend_r)
void for_each(const Pred &pred, const OP &op, Vektor< double, 3 > extend_l, Vektor< double, 3 > extend_r)
Definition: Inform.h:42
static int getNodes()
Definition: IpplInfo.cpp:670