Simpatico  v1.10
MdSpmePotential.cpp
1 /*
2 * Simpatico - Simulation Package for Polymeric and Molecular Liquids
3 *
4 * Copyright 2010 - 2017, David Morse (morse012@umn.edu)
5 * Distributed under the terms of the GNU General Public License.
6 */
7 
8 #include "MdSpmePotential.h"
9 #include <mcMd/simulation/System.h>
10 #include <mcMd/simulation/Simulation.h>
11 #include <mcMd/chemistry/AtomType.h>
12 
13 #include <simp/boundary/Boundary.h>
14 
15 #include <util/space/Vector.h>
16 #include <util/space/Tensor.h>
17 #include <util/math/Constants.h>
18 #include <util/containers/Array.h>
19 
20 #include <stdlib.h>
21 #include <cmath>
22 
23 namespace McMd
24 {
25 
26  using namespace Util;
27  using namespace Simp;
28 
29  /*
30  * Constructor.
31  */
34  ewaldInteraction_(),
35  simulationPtr_(&system.simulation()),
36  systemPtr_(&system),
37  boundaryPtr_(&system.boundary()),
38  atomTypesPtr_(&system.simulation().atomTypes()),
39  gridDimensions_(),
40  rhoR_(),
41  rhoK_(),
42  g_(),
43  sqWaves_(),
44  vecWaves_(),
45  xfield_(),
46  yfield_(),
47  zfield_(),
48  order_(5)
49  {
50  // Note: Don't setClassName - using "CoulombPotential" base class name
51  }
52 
53  /*
54  * Destructor
55  */
57  {
58  fftw_destroy_plan(forward_plan);
59  fftw_destroy_plan(xfield_backward_plan);
60  fftw_destroy_plan(yfield_backward_plan);
61  fftw_destroy_plan(zfield_backward_plan);
62  }
63 
64  /*
65  * Read parameters and initialize.
66  */
67  void MdSpmePotential::readParameters(std::istream& in)
68  {
69  // Read EwaldInteraction block containing parameters
70  bool nextIndent = false;
71  addParamComposite(ewaldInteraction_, nextIndent);
72  ewaldInteraction_.readParameters(in);
73  read<IntVector>(in, "gridDimensions", gridDimensions_);
74  setGridDimensions();
75  }
76 
77  /*
78  * Load internal state from an archive.
79  */
81  {
82  bool nextIndent = false;
83  addParamComposite(ewaldInteraction_, nextIndent);
84  ewaldInteraction_.loadParameters(ar);
85  loadParameter<IntVector>(ar, "gridDimensions", gridDimensions_);
86  setGridDimensions();
87  }
88 
89  /*
90  * Save internal state to an archive.
91  */
93  {
94  ewaldInteraction_.save(ar);
95  ar << gridDimensions_;
96  }
97 
98  /*
99  * Set a parameter value, identified by a string.
100  */
101  void MdSpmePotential::set(std::string name, double value)
102  {
103  ewaldInteraction_.set(name, value);
104  unsetWaves();
105  }
106 
107  /*
108  * Get a parameter value, identified by a string.
109  */
110  double MdSpmePotential::get(std::string name) const
111  {
112  double value;
113  value = ewaldInteraction_.get(name);
114  return value;
115  }
116 
117  /*
118  * Number of grid points, or waves.
119  */
120  inline
122  { return gridDimensions_[0]*gridDimensions_[1]*gridDimensions_[2]; }
123 
124  /*
125  * Precompute waves and influence function.
126  */
128  {
129  // Allocate memory if not done previously
130  if (g_.size() == 0) {
131  setGridDimensions();
132  }
133 
134  // Compute waves and influence function.
135  computeWaves();
136 
137  // Mark wave data as up to date.
138  hasWaves_ = true;
139  }
140 
141  void MdSpmePotential::setGridDimensions()
142  {
143  // Allocate memory
144  rhoR_.allocate(gridDimensions_);
145  rhoK_.allocate(gridDimensions_);
146  g_.allocate(gridDimensions_);
147  sqWaves_.allocate(gridDimensions_);
148  vecWaves_.allocate(gridDimensions_);
149  xfield_.allocate(gridDimensions_);
150  yfield_.allocate(gridDimensions_);
151  zfield_.allocate(gridDimensions_);
152 
153  // Initialize fft plan for charge grid
154  fftw_complex* inf;
155  fftw_complex* outf;
156  inf = reinterpret_cast<fftw_complex*>(rhoR_.data());
157  outf = reinterpret_cast<fftw_complex*>(rhoK_.data());
158  forward_plan = fftw_plan_dft_3d(gridDimensions_[0],
159  gridDimensions_[1],
160  gridDimensions_[2],
161  inf, outf,
162  FFTW_FORWARD,FFTW_MEASURE);
163 
164  // Initialize fft plans for electric field component grids
165  fftw_complex* inxf;
166  fftw_complex* outxf;
167  inxf = reinterpret_cast<fftw_complex*>(xfield_.data());
168  outxf = reinterpret_cast<fftw_complex*>(xfield_.data());
169  xfield_backward_plan =
170  fftw_plan_dft_3d(gridDimensions_[0],
171  gridDimensions_[1],
172  gridDimensions_[2],
173  inxf, outxf,FFTW_BACKWARD, FFTW_MEASURE);
174  fftw_complex* inyf;
175  fftw_complex* outyf;
176  inyf = reinterpret_cast<fftw_complex*>(yfield_.data());
177  outyf = reinterpret_cast<fftw_complex*>(yfield_.data());
178  yfield_backward_plan =
179  fftw_plan_dft_3d(gridDimensions_[0],
180  gridDimensions_[1],
181  gridDimensions_[2],
182  inyf, outyf,FFTW_BACKWARD, FFTW_MEASURE);
183  fftw_complex* inzf;
184  fftw_complex* outzf;
185  inzf = reinterpret_cast<fftw_complex*>(zfield_.data());
186  outzf = reinterpret_cast<fftw_complex*>(zfield_.data());
187  zfield_backward_plan =
188  fftw_plan_dft_3d(gridDimensions_[0],
189  gridDimensions_[1],
190  gridDimensions_[2],
191  inzf, outzf,FFTW_BACKWARD, FFTW_MEASURE);
192 
193  }
194 
195  /*
196  * Set elements of grid to all zero.
197  */
198  template<class T>
199  void MdSpmePotential::setGridToZero(GridArray<T>& grid)
200  {
201  IntVector temp;
202 
203  for (int i = 0; i < grid.dimension(0); ++i) {
204  temp[0] = i;
205  for (int j = 0; j < grid.dimension(1); ++j) {
206  temp[1] = j;
207  for (int k = 0; k < grid.dimension(2); ++k) {
208  temp[2] = k;
209  grid(temp) = 0 ;
210  }
211  }
212  }
213  }
214 
215  /*
216  * Compute waves and influence function.
217  */
218  void MdSpmePotential::computeWaves()
219  {
220  Vector b0, b1, b2;
221  Vector q0, q1, q;
222  double qSq, b, c;
223  IntVector pos;
224  int i, j, k;
225  int m0, m1, m2;
226 
227  setGridToZero(g_);
228 
229  b0 = boundaryPtr_->reciprocalBasisVector(0);
230  b1 = boundaryPtr_->reciprocalBasisVector(1);
231  b2 = boundaryPtr_->reciprocalBasisVector(2);
232 
233  // Loop over grid points
234  for (i = 0; i < gridDimensions_[0]; ++i) {
235  pos[0] = i;
236  m0 = (i <= gridDimensions_[0]/2) ? i : i - gridDimensions_[0];
237  q0.multiply(b0, m0);
238 
239  for (j = 0; j < gridDimensions_[1]; ++j) {
240  pos[1] = j;
241  m1 = (j <= gridDimensions_[1]/2) ? j : j - gridDimensions_[1];
242  q1.multiply(b1, m1);
243  q1 += q0;
244 
245  for (k = 0; k < gridDimensions_[2]; ++k) {
246  pos[2] = k;
247  m2 = (k <= gridDimensions_[2]/2) ? k : k - gridDimensions_[2];
248  q.multiply(b2, m2);
249  q += q1;
250 
251  vecWaves_(pos) = q;
252  qSq = q.square();
253  sqWaves_(pos) = qSq;
254  if (qSq > 1.0E-10) {
255  b = bfactor(i, 0) * bfactor(j, 1) *bfactor(k, 2);
256  c = ewaldInteraction_.kSpacePotential(qSq);
257  g_(pos) = b * c;
258  } else {
259  g_(pos) = 0.0;
260  }
261 
262  }
263  }
264  }
265  }
266 
267  /*
268  * Compute bfactor associated with one direction.
269  */
270  double MdSpmePotential::bfactor(double m, int dim)
271  {
272  // If order of spline is odd, this interpolation result fails,
273  // when 2*m = gridDimensions[dim], since 1 / 0 in this function.
274  if (order_%2 == 1 && m == gridDimensions_[dim]/2) {
275  return 0.0;
276  }
277 
278  double pi = Constants::Pi;
279  double gridDimensions(gridDimensions_[dim]);
280  DCMPLX I(0.0,1.0);
281  DCMPLX denom(0.0, 0.0);
282  for (double k = 0.0; k <= order_ - 2.0; k++) {
283  denom += basisSpline(k + 1.0)*exp(2.0 * pi * I * m * k / gridDimensions) ;
284  }
285  return std::norm(exp(2.0 * pi * I * (order_ -1.0) * m/gridDimensions) / denom);
286  }
287 
288  /*
289  * Assign charges to grid points.
290  */
291  void MdSpmePotential::assignCharges()
292  {
293  if (!hasWaves()) {
294  makeWaves();
295  }
296 
297  System::MoleculeIterator molIter;
298  Molecule::AtomIterator atomIter;
299  Vector gpos; //general coordination of atom
300  double xdistance, ydistance, zdistance; // distance from atom to node
301  double charge;
302  IntVector knot;
303  IntVector floorGridIdx;
304  int ximg, yimg, zimg; // grid point coordinates
305  int xknot, yknot, zknot;
306 
307  setGridToZero(rhoR_);
308 
309  // Loop over atoms.
310  int nSpecies = simulationPtr_->nSpecies();
311  for (int iSpecies = 0; iSpecies < nSpecies; ++iSpecies) {
312  systemPtr_->begin(iSpecies, molIter);
313  for ( ; molIter.notEnd(); ++molIter) {
314  for (molIter->begin(atomIter); atomIter.notEnd(); ++atomIter) {
315  charge = (*atomTypesPtr_)[atomIter->typeId()].charge();
316 
317  // Compute generalized position with components in [0,1]
318  boundaryPtr_->transformCartToGen(atomIter->position(), gpos);
319  boundaryPtr_->shiftGen(gpos);
320 
321  // Find the floor grid point.
322  floorGridIdx[0]=floor(gpos[0]*gridDimensions_[0]);
323  floorGridIdx[1]=floor(gpos[1]*gridDimensions_[1]);
324  floorGridIdx[2]=floor(gpos[2]*gridDimensions_[2]);
325 
326  // Have not incorporated reciprocal vector * lattice vector
327  // for other lattice. Need to modify the expression of distance.
328 
329  for (int x = 0 ; x < order_ ; ++x) {
330  ximg = floorGridIdx[0] + x - (order_ - 1);
331  xdistance = (gpos[0]*gridDimensions_[0]-ximg);
332  xknot = ximg < 0 ? ximg + gridDimensions_[0] : ximg;
333  knot[0] = xknot;
334 
335  for (int y = 0 ; y < order_ ; ++y) {
336  yimg = floorGridIdx[1] + y - (order_ - 1);
337  ydistance = (gpos[1]*gridDimensions_[1]-yimg) ;
338  yknot = yimg < 0 ? yimg + gridDimensions_[1] : yimg;
339  knot[1] = yknot;
340 
341  for (int z = 0; z < order_; ++z) {
342  zimg = floorGridIdx[2] + z - (order_ - 1);
343  zdistance = (gpos[2]*gridDimensions_[2]-zimg) ;
344  zknot = zimg < 0 ? zimg + gridDimensions_[2] : zimg;
345  knot[2] = zknot;
346 
347  rhoR_(knot) += charge
348  * basisSpline(xdistance)
349  * basisSpline(ydistance)
350  * basisSpline(zdistance);
351  } //loop z
352  } //loop y
353  } //loop x
354  } //loop atom
355  } //loop molecule
356  } //loop over species
357  }
358 
359  /*
360  * basisSpline function.
361  */
362  double MdSpmePotential::basisSpline(double x)
363  {
364  if (0.0 < x && x < 1.0)
365  return 1.0 / 24.0 * x * x * x *x;
366  else if (1.0 <= x && x < 2.0)
367  return -5.0/24.0 + (5.0/6.0 + (-5.0/4.0 + (5.0 / 6.0 -1.0/6.0*x) *x) *x) *x;
368  else if (2.0 <= x && x < 3.0)
369  return 155.0/24.0 + (-25.0/2.0 + (35.0/4.0 + (-5.0/2.0 +1.0/4.0*x)*x)*x)*x;
370  else if (3.0 <= x && x < 4.0)
371  return -655.0/24.0 + (65.0/2.0 + ( -55.0/4.0 + (5.0/2.0 - 1.0/6.0*x)*x)*x)*x;
372  else if (4.0 <= x && x < 5.0)
373  return 625.0/24.0 + (-125.0/6.0 + (25.0/4.0 + (-5.0/6.0 + 1/24.0 *x)*x)*x)*x;
374  else
375  return 0.0;
376  }
377 
378  /*
379  * Add k-space Coulomb forces for all atoms. standard Ewald Summation.
380  */
382  {
383  assignCharges();
384  setGridToZero(rhoK_);
385  fftw_execute(forward_plan);
386 
387  // Compute field components in k-space, multiplying by i*q
388  Vector qv; // Wavevector
389  DCMPLX ci = Constants::Im / boundaryPtr_->volume();
390  for (int rank = 0 ; rank < g_.size() ; ++rank) {
391  qv = vecWaves_[rank];
392  xfield_[rank] = ci*qv[0]*rhoK_[rank]*g_[rank];
393  yfield_[rank] = ci*qv[1]*rhoK_[rank]*g_[rank];
394  zfield_[rank] = ci*qv[2]*rhoK_[rank]*g_[rank];
395  }
396 
397  // Inverse transform to obtain fields on r-space grid
398  fftw_execute(xfield_backward_plan);
399  fftw_execute(yfield_backward_plan);
400  fftw_execute(zfield_backward_plan);
401 
402  System::MoleculeIterator molIter;
403  Molecule::AtomIterator atomIter;
404  Vector fatom(0.0);
405  Vector floorGridIdx, gpos;
406  IntVector m;
407  IntVector knot;
408  double xdistance, ydistance, zdistance; // b-spline
409  double EPS = 1.0E-10; // Tiny number to check if is charged
410  double charge;
411  int type;
412  int ximg, yimg, zimg;
413  int xknot, yknot, zknot;
414 
415  // Loop over species, molecules atoms
416  int nSpecies(simulationPtr_->nSpecies());
417  for (int iSpecies = 0; iSpecies < nSpecies; ++iSpecies) {
418  systemPtr_->begin(iSpecies, molIter);
419  for ( ; molIter.notEnd(); ++molIter) {
420  for (molIter->begin(atomIter); atomIter.notEnd(); ++atomIter) {
421  type = atomIter->typeId();
422  charge = (*atomTypesPtr_)[type].charge();
423 
424  // If atom has nonzero charge
425  if( fabs(charge) > EPS) {
426 
427  // Compute generalized position with components in [0,1]
428  boundaryPtr_->transformCartToGen(atomIter->position(), gpos);
429  boundaryPtr_->shiftGen(gpos);
430 
431  // Find the floor grid point.
432  floorGridIdx[0]=floor(gpos[0]*gridDimensions_[0]);
433  floorGridIdx[1]=floor(gpos[1]*gridDimensions_[1]);
434  floorGridIdx[2]=floor(gpos[2]*gridDimensions_[2]);
435 
436  // Compute the force on the atom by interpolation
437  fatom.zero();
438  for (int x = 0 ; x < order_ ; ++x) {
439  ximg = floorGridIdx[0] + x - (order_ - 1);
440  xdistance = (gpos[0]*gridDimensions_[0]-ximg) ;
441  xknot = ximg < 0 ? ximg + gridDimensions_[0] : ximg;
442  knot[0] = xknot;
443 
444  for (int y = 0 ; y < order_ ; ++y) {
445  yimg = floorGridIdx[1] + y - (order_ - 1);
446  ydistance = (gpos[1]*gridDimensions_[1]-yimg) ;
447  yknot = yimg < 0 ? yimg + gridDimensions_[1] : yimg;
448  knot[1] = yknot;
449 
450  for (int z = 0; z < order_; ++z) {
451  zimg = floorGridIdx[2] + z - (order_ - 1);
452  zdistance = (gpos[2]*gridDimensions_[2]-zimg) ;
453  zknot = zimg < 0 ? zimg + gridDimensions_[2] : zimg;
454  knot[2] = zknot;
455 
456  fatom[0] +=basisSpline(xdistance)
457  *basisSpline(ydistance)
458  *basisSpline(zdistance)
459  *std::real(xfield_(knot));
460  fatom[1] +=basisSpline(xdistance)
461  *basisSpline(ydistance)
462  *basisSpline(zdistance)
463  *std::real(yfield_(knot));
464  fatom[2] +=basisSpline(xdistance)
465  *basisSpline(ydistance)
466  *basisSpline(zdistance)
467  *std::real(zfield_(knot));
468  }
469  }
470  }
471  fatom *= -1.0*charge;
472  atomIter->force() += fatom;
473  } // if charged
474  } // loop over atom
475  } // loop over mol
476  } // loop over species
477  }
478 
479  /*
480  * Calculate the k-space contribution to the Coulomb energy.
481  */
483  {
484  assignCharges();
485  setGridToZero(rhoK_);
486  fftw_execute(forward_plan);
487 
488  // Loop over all waves in Fourier grid
489  double energy = 0.0;
490  for (int i = 0; i < g_.size(); ++i) {
491  energy += g_[i] * std::norm(rhoK_[i]);
492  }
493  double volume = boundaryPtr_->volume();
494  energy /= 2.0*volume;
495 
496  // Calculate self-energy correction to Ewald summation.
497  System::MoleculeIterator molIter;
498  Molecule::AtomIterator atomIter;
499  double charge;
500  double selfEnergy = 0.0;
501  int nSpecies = simulationPtr_->nSpecies();
502  for (int iSpecies = 0; iSpecies < nSpecies; ++iSpecies) {
503  systemPtr_->begin(iSpecies, molIter);
504  for ( ; molIter.notEnd(); ++molIter) {
505  molIter->begin(atomIter);
506  for ( ; atomIter.notEnd(); ++atomIter) {
507  charge = (*atomTypesPtr_)[atomIter->typeId()].charge();
508  selfEnergy += charge*charge;
509  }
510  }
511  }
512  double pi = Constants::Pi;
513  double alpha = ewaldInteraction_.alpha();
514  double epsilon = ewaldInteraction_.epsilon();
515  selfEnergy *= alpha/(4.0*sqrt(pi)*pi*epsilon);
516  energy -= selfEnergy;
517 
518  kSpaceEnergy_.set(energy);
519  }
520 
521  /*
522  * Compute the k-space contribution to Coulomb stress.
523  */
525  {
526  assignCharges();
527  setGridToZero(rhoK_);
528  fftw_execute(forward_plan);
529 
530  Tensor K, stress;
531  Vector qv;
532  double alpha = ewaldInteraction_.alpha();
533  double ca = 0.25/(alpha*alpha);
534  double qSq;
535 
536  // Loop over all waves in Fourier grid
537  stress.zero();
538  for (int i = 0; i < g_.size(); ++i) {
539  qSq = sqWaves_[i];
540  if (qSq > 1.0E-10) {
541  qv = vecWaves_[i];
542  K.dyad(qv, qv);
543  K *= -2.0 * (ca + (1.0/qSq));
544  K.add(Tensor::Identity, K);
545  K *= g_[i]*std::norm(rhoK_[i]);
546  stress += K;
547  }
548  }
549  double volume = boundaryPtr_->volume();
550  stress /= 2.0*volume*volume;
551 
552  kSpaceStress_.set(stress);
553  }
554 }
Vector & zero()
Set all elements of a 3D vector to zero.
Definition: Vector.h:514
Coulomb potential for an Md simulation.
virtual ~MdSpmePotential()
Destructor (destroy fftw plan).
int size() const
Get total number of grid points.
Definition: GridArray.h:371
double kSpacePotential(double kSq) const
Return regularized Fourier-space potential.
void shiftGen(Vector &r) const
Shift generalized Vector r to its primary image.
Setable< double > kSpaceEnergy_
K-space part of Coulomb energy.
A Vector is a Cartesian vector.
Definition: Vector.h:75
int dimension(int i) const
Get number of grid points along direction i.
Definition: GridArray.h:364
void begin(int speciesId, MoleculeIterator &iterator)
Initialize an iterator for molecules of one species in this System.
Definition: System.h:1147
bool notEnd() const
Is the current pointer not at the end of the array?
Definition: ArrayIterator.h:83
double volume() const
Return unit cell volume.
void set(const T &value)
Set the value and mark as set.
Definition: Setable.h:107
Vector & multiply(const Vector &v, double s)
Multiply a vector v by a scalar s.
Definition: Vector.h:686
Tensor & zero()
Set all elements of this tensor to zero.
Definition: Tensor.h:441
A set of interacting Molecules enclosed by a Boundary.
Definition: System.h:115
virtual void save(Serializable::OArchive &ar)
Save internal state to an archive.
virtual int nWave() const
Total number of waves or grid points.
Classes used by all simpatico molecular simulations.
MdSpmePotential(System &system)
Constructor.
A Tensor represents a Cartesian tensor.
Definition: Tensor.h:32
Tensor & add(const Tensor &t1, const Tensor &t2)
Add tensors t1 and t2.
Definition: Tensor.h:567
Tensor stress()
Get total Coulomb stress.
Saving / output archive for binary ostream.
Setable< Tensor > kSpaceStress_
K-space part of Coulomb stress.
virtual void makeWaves()
Precompute waves and influence function.
void set(std::string name, double value)
Set a parameter value, identified by a string.
const Vector & reciprocalBasisVector(int i) const
Return reciprocal lattice basis vector i.
virtual void computeStress()
place holder.
double alpha() const
Get Ewald smearing parameter alpha (inverse length).
virtual void computeEnergy()
Calculate the long range kspace part of Coulomb energy.
bool notEnd() const
Is the current pointer not at the end of the PArray?
Utility classes for scientific computation.
Definition: accumulators.mod:1
void allocate(const IntVector &dimensions)
Allocate memory for a matrix.
Definition: GridArray.h:312
Forward iterator for an Array or a C array.
Definition: ArrayIterator.h:39
virtual void loadParameters(Serializable::IArchive &ar)
Load internal state from an archive.
Forward iterator for a PArray.
Definition: ArraySet.h:19
void set(std::string name, double value)
Modify a parameter, identified by a string.
double get(std::string name) const
Get a parameter value, identified by a string.
Saving archive for binary istream.
virtual void readParameters(std::istream &in)
Read parameters and initialize.
Single-processor Monte Carlo (MC) and molecular dynamics (MD).
double get(std::string name) const
Get a parameter value, identified by a string.
int nSpecies() const
Get the number of Species in this Simulation.
bool hasWaves_
Are waves and k-space potential up to date? Unset if boundary or parameters change.
void addParamComposite(ParamComposite &child, bool next=true)
Add a child ParamComposite object to the format array.
Multi-dimensional array with the dimensionality of space.
Definition: GridArray.h:28
static const double Pi
Trigonometric constant Pi.
Definition: Constants.h:35
double epsilon() const
Get dielectric permittivity.
An IntVector is an integer Cartesian vector.
Definition: IntVector.h:73
static const Tensor Identity
Constant idenity Tensor (diagonal diagonal elements all 1).
Definition: Tensor.h:302
Tensor & dyad(const Vector &v1, const Vector &v2)
Create dyad of two vectors.
Definition: Tensor.h:763
double energy()
Get total Coulomb energy.
static const std::complex< double > Im
Square root of -1.
Definition: Constants.h:40
virtual void loadParameters(Serializable::IArchive &ar)
Load internal state from an archive.
virtual void save(Serializable::OArchive &ar)
Save internal state to an archive.
void unsetWaves()
Unset all data that depends on the Boundary.
double square() const
Return square magnitude of this vector.
Definition: Vector.h:616
void readParameters(std::istream &in)
Read epsilon, alpha, and rCutoff.
bool hasWaves()
Are wavevectors and k-space influence function up to date?
virtual void addForces()
Add k-space Coulomb forces for all atoms.
void transformCartToGen(const Vector &Rc, Vector &Rg) const
Transform Cartesian Vector to scaled / generalized coordinates.