Simpatico  v1.10
NptIntegrator.cpp
1 /*
2 * Simpatico - Simulation Package for Polymeric and Molecular Liquids
3 *
4 * Copyright 2010 - 2017, The Regents of the University of Minnesota
5 * Distributed under the terms of the GNU General Public License.
6 */
7 
8 #include "NptIntegrator.h"
9 #include <ddMd/simulation/Simulation.h>
10 #include <ddMd/storage/AtomStorage.h>
11 #include <ddMd/storage/AtomIterator.h>
12 #include <ddMd/communicate/Exchanger.h>
13 #include <ddMd/potentials/pair/PairPotential.h>
14 #include <simp/ensembles/EnergyEnsemble.h>
15 #include <simp/ensembles/BoundaryEnsemble.h>
16 #include <util/mpi/MpiLoader.h>
17 #include <util/space/Vector.h>
18 #include <util/format/Dbl.h>
19 #include <util/global.h>
20 
21 #include <iostream>
22 
23 namespace DdMd
24 {
25 
26  using namespace Util;
27  using namespace Simp;
28 
29  /*
30  * Constructor.
31  */
33  : TwoStepIntegrator(simulation)
34  { setClassName("NptIntegrator"); }
35 
36  /*
37  * Destructor.
38  */
40  {}
41 
42  /*
43  * Read time step dt.
44  */
45  void NptIntegrator::readParameters(std::istream& in)
46  {
47  read<double>(in, "dt", dt_);
48  read<double>(in, "tauT", tauT_);
49  read<double>(in, "tauP", tauP_);
50  read<LatticeSystem>(in, "mode", mode_);
52 
53  // Allocate memory
54  int nAtomType = simulation().nAtomType();
55  if (!prefactors_.isAllocated()) {
56  prefactors_.allocate(nAtomType);
57  }
58 
59  }
60 
65  {
66  loadParameter<double>(ar, "dt", dt_);
67  loadParameter<double>(ar, "tauT", tauT_);
68  loadParameter<double>(ar, "tauP", tauP_);
69  loadParameter<LatticeSystem>(ar, "mode", mode_);
71 
72  MpiLoader<Serializable::IArchive> loader(*this, ar);
73  loader.load(xi_);
74  loader.load(eta_);
75  loader.load(nu_);
76 
77  int nAtomType = simulation().nAtomType();
78  if (!prefactors_.isAllocated()) {
79  prefactors_.allocate(nAtomType);
80  }
81  }
82 
83  /*
84  * Read time step dt.
85  */
87  {
88  ar << dt_;
89  ar << tauT_;
90  ar << tauP_;
91  ar << mode_;
92  Integrator::save(ar);
93  ar << xi_;
94  ar << eta_;
95  ar << nu_;
96  }
97 
98  /*
99  * Initialize dynamical state variables to zero.
100  */
102  {
103  xi_ = 0.0;
104  eta_ = 0.0;
105  nu_ = Vector(0.0, 0.0, 0.0);
106  }
107 
109  {
110  // Initialize state and clear statistics on first usage.
111  if (!isSetup()) {
112  clear();
113  setIsSetup();
114  }
115 
116  // Exchange atoms, build pair list, compute forces.
117  setupAtoms();
118 
119  // Set prefactors for acceleration
120  double dtHalf = 0.5*dt_;
121  double mass;
122  int nAtomType = prefactors_.capacity();
123  for (int i = 0; i < nAtomType; ++i) {
124  mass = simulation().atomType(i).mass();
125  prefactors_[i] = dtHalf/mass;
126  }
127 
130  #ifdef UTIL_MPI
131  atomStorage().computeNAtomTotal(domain().communicator());
132  #endif
133  if (domain().isMaster()) {
134  T_target_ = simulation().energyEnsemble().temperature();
135  P_target_ = simulation().boundaryEnsemble().pressure();
136  ndof_ = atomStorage().nAtomTotal()*3;
137  }
138  #ifdef UTIL_MPI
139  bcast(domain().communicator(), ndof_, 0);
140  #endif
141  }
142 
143  /*
144  * First half of velocity-verlet update.
145  */
147  {
148  Vector dv;
149  double prefactor; // = 0.5*dt/mass
150  double xi_prime;
151  AtomIterator atomIter;
152  Simulation& sim = simulation();
153 
154  sim.computeVirialStress();
155  sim.computeKineticStress();
156  sim.computeKineticEnergy();
157 
158  if (sim.domain().isMaster()) {
159  T_target_ = sim.energyEnsemble().temperature();
160  P_target_ = sim.boundaryEnsemble().pressure();
161  T_kinetic_ = sim.kineticEnergy()*2.0/ndof_;
162  Tensor stress = sim.virialStress();
163  stress += sim.kineticStress();
164 
165  P_curr_diag_ = Vector(stress(0, 0), stress(1,1), stress(2,2));
166  double P_curr = (1.0/3.0)*stress.trace();
167 
168  double W = (1.0/2.0)*ndof_*T_target_*tauP_*tauP_;
169  double mtk_term = (1.0/2.0)*dt_*T_kinetic_/W;
170 
171  // Advance barostat (first half of update)
172  double V = sim.boundary().volume();
173  if (mode_ == Cubic) {
174  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr - P_target_) + mtk_term;
175  nu_[1] = nu_[2] = nu_[0];
176  } else if (mode_ == Tetragonal) {
177  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[0] - P_target_) + mtk_term;
178  nu_[1] += (1.0/2.0)*dt_*V/W*((1.0/2.0)*(P_curr_diag_[1] + P_curr_diag_[2]) - P_target_)
179  + mtk_term;
180  nu_[2] = nu_[1];
181  } else if (mode_ == Orthorhombic) {
182  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[0] - P_target_) + mtk_term;
183  nu_[1] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[1] - P_target_) + mtk_term;
184  nu_[2] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[2] - P_target_) + mtk_term;
185  }
186 
187  // Advance thermostat
188  xi_prime = xi_ + (1.0/4.0)*dt_/tauT_/tauT_*(T_kinetic_/T_target_ - 1.0);
189  xi_ = xi_prime + (1.0/4.0)*dt_/tauT_/tauT_*(T_kinetic_/T_target_*exp(-xi_prime*dt_) - 1.0);
190  eta_ += (1.0/2.0)*xi_prime*dt_;
191  }
192 
193  #ifdef UTIL_MPI
194  bcast(domain().communicator(), xi_, 0);
195  bcast(domain().communicator(), xi_prime, 0);
196  bcast(domain().communicator(), nu_, 0);
197  #endif
198 
199  // Precompute loop invariant quantities
200  mtk_term_2_ = (nu_[0]+nu_[1]+nu_[2])/ndof_;
201  Vector v_fac = Vector((1.0/4.0)*(nu_[0]+mtk_term_2_),
202  (1.0/4.0)*(nu_[1]+mtk_term_2_),
203  (1.0/4.0)*(nu_[2]+mtk_term_2_));
204  exp_v_fac_ = Vector(exp(-v_fac[0]*dt_),
205  exp(-v_fac[1]*dt_),
206  exp(-v_fac[2]*dt_));
207  Vector exp_v_fac_2 = Vector(exp(-(2.0*v_fac[0]+(1.0/2.0)*xi_prime)*dt_),
208  exp(-(2.0*v_fac[1]+(1.0/2.0)*xi_prime)*dt_),
209  exp(-(2.0*v_fac[2]+(1.0/2.0)*xi_prime)*dt_));
210  Vector r_fac = Vector((1.0/2.0)*nu_[0],
211  (1.0/2.0)*nu_[1],
212  (1.0/2.0)*nu_[2]);
213  Vector exp_r_fac = Vector(exp(r_fac[0]*dt_),
214  exp(r_fac[1]*dt_),
215  exp(r_fac[2]*dt_));
216 
217  //Coefficients of sinh(x)/x = a_0 + a_2 * x^2 + a_4 * x^4 + a_6 * x^6 + a_8 * x^8 + a_10 * x^10
218  const double a[] = { double(1.0), double(1.0/6.0), double(1.0/120.0),
219  double(1.0/5040.0), double(1.0/362880.0), double(1.0/39916800.0)};
220 
221  Vector arg_v = Vector(v_fac[0]*dt_, v_fac[1]*dt_, v_fac[2]*dt_);
222  Vector arg_r = Vector(r_fac[0]*dt_, r_fac[1]*dt_, r_fac[2]*dt_);
223 
224  sinhx_fac_v_ = Vector(0.0, 0.0, 0.0);
225  Vector sinhx_fac_r = Vector(0.0, 0.0, 0.0);
226 
227  Vector term_v = Vector(1.0, 1.0, 1.0);
228  Vector term_r = Vector(1.0, 1.0, 1.0);
229 
230  for (unsigned int i = 0; i < 6; i++) {
231  sinhx_fac_v_ += Vector(a[i]*term_v[0],
232  a[i]*term_v[1],
233  a[i]*term_v[2]);
234  sinhx_fac_r += Vector(a[i]*term_r[0],
235  a[i]*term_r[1],
236  a[i]*term_r[2]);
237  term_v = Vector(term_v[0] * arg_v[0] * arg_v[0],
238  term_v[1] * arg_v[1] * arg_v[1],
239  term_v[2] * arg_v[2] * arg_v[2]);
240  term_r = Vector(term_r[0] * arg_r[0] * arg_r[0],
241  term_r[1] * arg_r[1] * arg_r[1],
242  term_r[2] * arg_r[2] * arg_r[2]);
243  }
244 
245  // 1st half of NPT
246  Vector vtmp;
247  atomStorage().begin(atomIter);
248  for ( ; atomIter.notEnd(); ++atomIter) {
249  prefactor = prefactors_[atomIter->typeId()];
250 
251  dv.multiply(atomIter->force(), prefactor);
252  dv[0] = dv[0] * exp_v_fac_[0]*sinhx_fac_v_[0];
253  dv[1] = dv[1] * exp_v_fac_[1]*sinhx_fac_v_[1];
254  dv[2] = dv[2] * exp_v_fac_[2]*sinhx_fac_v_[2];
255 
256  Vector& v = atomIter->velocity();
257  v[0] = v[0] * exp_v_fac_2[0] + dv[0];
258  v[1] = v[1] * exp_v_fac_2[1] + dv[1];
259  v[2] = v[2] * exp_v_fac_2[2] + dv[2];
260 
261  vtmp[0] = v[0]*exp_r_fac[0] *sinhx_fac_r[0];
262  vtmp[1] = v[1]*exp_r_fac[1] *sinhx_fac_r[1];
263  vtmp[2] = v[2]*exp_r_fac[2] *sinhx_fac_r[2];
264 
265  Vector& r = atomIter->position();
266  r[0] = r[0]*exp_r_fac[0]*exp_r_fac[0] + vtmp[0]*dt_;
267  r[1] = r[1]*exp_r_fac[1]*exp_r_fac[1] + vtmp[1]*dt_;
268  r[2] = r[2]*exp_r_fac[2]*exp_r_fac[2] + vtmp[2]*dt_;
269  }
270 
271  // Advance box lengths
272  Vector box_len_scale = Vector(exp(nu_[0]*dt_),
273  exp(nu_[1]*dt_),
274  exp(nu_[2]*dt_));
275 
276  Vector L = sim.boundary().lengths();
277  L[0] *= box_len_scale[0];
278  L[1] *= box_len_scale[1];
279  L[2] *= box_len_scale[2];
280 
281  // Update box lengths
282  sim.boundary().setOrthorhombic(L);
283  }
284 
286  {
287  Vector dv;
288  double prefactor; // = 0.5*dt/mass
289  AtomIterator atomIter;
290  Simulation& sim = simulation();
291 
292  Vector v_fac_2 = Vector((1.0/2.0)*(nu_[0]+mtk_term_2_),
293  (1.0/2.0)*(nu_[1]+mtk_term_2_),
294  (1.0/2.0)*(nu_[2]+mtk_term_2_));
295  Vector exp_v_fac_2 = Vector(exp(-v_fac_2[0]*dt_),
296  exp(-v_fac_2[1]*dt_),
297  exp(-v_fac_2[2]*dt_));
298 
299  double v2_sum = 0.0;
300 
301  // 2nd half of NPT
302  atomStorage().begin(atomIter);
303  for ( ; atomIter.notEnd(); ++atomIter) {
304  prefactor = prefactors_[atomIter->typeId()];
305  dv.multiply(atomIter->force(), prefactor);
306  dv[0] = dv[0] * exp_v_fac_[0]*sinhx_fac_v_[0];
307  dv[1] = dv[1] * exp_v_fac_[1]*sinhx_fac_v_[1];
308  dv[2] = dv[2] * exp_v_fac_[2]*sinhx_fac_v_[2];
309 
310  Vector& v = atomIter->velocity();
311  v[0] = v[0] * exp_v_fac_2[0] + dv[0];
312  v[1] = v[1] * exp_v_fac_2[1] + dv[1];
313  v[2] = v[2] * exp_v_fac_2[2] + dv[2];
314 
315  // total up 2*kinetic energy
316  double mass = simulation().atomType(atomIter->typeId()).mass();
317  v2_sum += v.dot(v)*mass;
318  }
319 
320  // Advance thermostat (second half of update)
321  double T_prime;
322  double xi_prime;
323  #ifdef UTIL_MPI
324  //reduce vs_sum
325  domain().communicator().Reduce(&v2_sum, &T_prime, 1, MPI::DOUBLE, MPI::SUM,0);
326  #endif
327 
328  if (domain().isMaster()) {
329  T_prime /= ndof_;
330  xi_prime = xi_ + (1.0/4.0)*dt_/tauT_/tauT_*(T_prime/T_target_ - 1.0);
331  xi_ = xi_prime + (1.0/4.0)*dt_/tauT_/tauT_*(T_prime/T_target_*exp(-xi_prime*dt_) - 1.0);
332  eta_ += (1.0/2.0)*xi_prime*dt_;
333  }
334 
335  #ifdef UTIL_MPI
336  bcast(domain().communicator(), xi_prime,0);
337  #endif
338 
339  // Rescale velocities
340  double exp_v_fac_thermo = exp(-(1.0/2.0)*xi_prime*dt_);
341  atomStorage().begin(atomIter);
342  for ( ; atomIter.notEnd(); ++atomIter) {
343  atomIter->velocity().multiply(atomIter->velocity(), exp_v_fac_thermo);
344  }
345 
346  sim.velocitySignal().notify();
347  sim.computeKineticStress();
348  sim.computeKineticEnergy();
349  sim.computeVirialStress();
350 
351  // Advance barostat
352  if (sim.domain().isMaster()) {
353  T_kinetic_ = sim.kineticEnergy()*2.0/ndof_;
354  Tensor stress = sim.virialStress();
355  stress += sim.kineticStress();
356 
357  P_curr_diag_ = Vector(stress(0,0), stress(1,1), stress(2,2));
358  double P_curr = (1.0/3.0)*stress.trace();
359 
360  double W = (1.0/2.0)*ndof_*T_target_*tauP_*tauP_;
361  double mtk_term = (1.0/2.0)*dt_*T_kinetic_/W;
362 
363  double V = sim.boundary().volume();
364  if (mode_ == Cubic) {
365  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr - P_target_) + mtk_term;
366  nu_[1] = nu_[2] = nu_[0];
367  } else if (mode_ == Tetragonal) {
368  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[0] - P_target_) + mtk_term;
369  nu_[1] += (1.0/2.0)*dt_*V/W*((1.0/2.0)*(P_curr_diag_[1]+P_curr_diag_[2]) - P_target_) + mtk_term;
370  nu_[2] = nu_[1];
371  } else if (mode_ == Orthorhombic) {
372  nu_[0] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[0] - P_target_) + mtk_term;
373  nu_[1] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[1] - P_target_) + mtk_term;
374  nu_[2] += (1.0/2.0)*dt_*V/W*(P_curr_diag_[2] - P_target_) + mtk_term;
375  }
376  }
377 
378  #if 0
379  // Output conserved quantity
381  if (sim.domain().isMaster()) {
382  std::ofstream file;
383  file.open("NPT.log", std::ios::out | std::ios::app);
384  double thermostat_energy = ndof_ * T_target_ * (eta_ + tauT_*tauT_*xi_*xi_/2.0);
385  double W = (1.0/2.0)*ndof_*T_target_*tauP_*tauP_;
386  double V = sim.boundary().volume();
387  double barostat_energy = W*(nu_[0]*nu_[0]+ nu_[1]*nu_[1] + nu_[2]*nu_[2]);
388  double pe = sim.potentialEnergy();
389  double ke = sim.kineticEnergy();
390  file << Dbl(V,20)
391  << Dbl(pe,20)
392  << Dbl(ke,20)
393  << Dbl(barostat_energy,20)
394  << Dbl(thermostat_energy,20)
395  << std::endl;
396  file.close();
397  }
398  #endif
399 
400  #ifdef UTIL_MPI
401  // Broadcast barostat and thermostat state variables
402  bcast(domain().communicator(), xi_, 0);
403  bcast(domain().communicator(), nu_, 0);
404  #endif
405 
406  }
407 
408 }
void loadParameters(Serializable::IArchive &ar)
Load saveInterval and saveFileName from restart archive.
Definition: Integrator.cpp:83
Signal & velocitySignal()
Signal to indicate change in atomic velocities.
A Vector is a Cartesian vector.
Definition: Vector.h:75
int nAtomTotal() const
Get total number of atoms on all processors.
virtual void integrateStep2()
Execute secodn step of two-step integrator.
double kineticEnergy()
Return precomputed total kinetic energy.
double pressure() const
Get the target pressure.
double volume() const
Return unit cell volume.
double dot(const Vector &v) const
Return dot product of this vector and vector v.
Definition: Vector.h:632
void setOrthorhombic(const Vector &lengths)
Set unit cell dimensions for orthorhombic boundary.
const Vector & lengths() const
Get Vector of unit cell lengths by const reference.
Vector & multiply(const Vector &v, double s)
Multiply a vector v by a scalar s.
Definition: Vector.h:686
void readParameters(std::istream &in)
Read saveInterval and saveFileName.
Definition: Integrator.cpp:65
BoundaryEnsemble & boundaryEnsemble()
Get the BoundaryEnsemble by reference.
void setIsSetup()
Mark the integrator as having been setup at least once.
Definition: Integrator.h:240
double mass() const
Get the mass.
virtual void integrateStep1()
Execute first step of two-step integrator.
Wrapper for a double precision number, for formatted ostream output.
Definition: Dbl.h:39
File containing preprocessor macros for error handling.
Parallel domain decomposition (DD) MD simulation.
Classes used by all simpatico molecular simulations.
double trace() const
Return the trace of this tensor.
Definition: Tensor.h:621
Main object for a domain-decomposition MD simulation.
A Tensor represents a Cartesian tensor.
Definition: Tensor.h:32
MPI::Intracomm & communicator() const
Return Cartesian communicator by reference.
Definition: Domain.h:257
AtomType & atomType(int i)
Get an AtomType descriptor by reference.
Saving / output archive for binary ostream.
~NptIntegrator()
Destructor.
void setupAtoms()
Setup state of atoms just before integration.
Definition: Integrator.cpp:118
void bcast(MPI::Intracomm &comm, T &data, int root)
Broadcast a single T value.
Definition: MpiSendRecv.h:134
void computePotentialEnergies()
Calculate and store total potential energy on all processors.
void notify(const T &t)
Notify all observers.
Definition: Signal.cpp:22
bool notEnd() const
Is the current pointer not at the end of the PArray?
Utility classes for scientific computation.
Definition: accumulators.mod:1
void load(Data &value)
Load and broadcast a single Data value.
Definition: MpiLoader.h:137
NptIntegrator(Simulation &simulation)
Constructor.
Domain & domain()
Get the Domain.
Tensor kineticStress() const
Return total kinetic stress.
bool isAllocated() const
Return true if the DArray has been allocated, false otherwise.
Definition: DArray.h:222
bool isSetup() const
Has the setup() method been called at least once previously?
Definition: Integrator.h:234
AtomStorage & atomStorage()
Get the AtomStorage.
void save(Serializable::OArchive &ar)
Save saveInterval and saveFileName from restart archive.
Definition: Integrator.cpp:105
void computeNAtomTotal(MPI::Intracomm &communicator)
Compute the total number of local atoms on all processors.
bool isMaster() const
Is this the master processor (gridRank == 0) ?
Definition: Domain.h:313
void computeVirialStress()
Calculate and store all virial stress contributions.
Simulation & simulation()
Get the parent simulation.
void readParameters(std::istream &in)
Read required parameters.
double potentialEnergy() const
Return sum of precomputed total potential energies.
int nAtomType()
Get maximum number of atom types.
Saving archive for binary istream.
Provides methods for MPI-aware loading of data from input archive.
Definition: MpiLoader.h:43
A two-step velocity-Verlet style integrator.
void setClassName(const char *className)
Set class name string.
int nAtomType()
Get maximum number of atom types.
Boundary & boundary()
Get the Boundary by reference.
int capacity() const
Return allocated size.
Definition: Array.h:153
void allocate(int capacity)
Allocate the underlying C array.
Definition: DArray.h:191
virtual void loadParameters(Serializable::IArchive &ar)
Load internal state from an archive.
double temperature() const
Return the temperature.
void computeKineticEnergy()
Compute total kinetic energy.
Domain & domain()
Get the Domain by reference.
virtual void save(Serializable::OArchive &ar)
Save internal state to an archive.
void setup()
Setup state just before main loop.
Tensor virialStress() const
Return total virial stress.
Iterator for all atoms owned by an AtomStorage.
Definition: AtomIterator.h:24
void computeKineticStress()
Calculate and store kinetic stress.
virtual void clear()
Set integrator to initial state and clears all statistics.
Definition: Integrator.cpp:539
void begin(AtomIterator &iterator)
Set iterator to beginning of the set of atoms.
virtual void initDynamicalState()
Initialize internal state variables xi, eta, and nu to zero.
EnergyEnsemble & energyEnsemble()
Get the EnergyEnsemble by reference.