Simpatico  v1.10
ddMd/integrators/NphIntegrator.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 "NphIntegrator.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/space/Vector.h>
17 #include <util/mpi/MpiLoader.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("NphIntegrator"); }
35 
36  /*
37  * Destructor.
38  */
40  {}
41 
42  /*
43  * Read parameters and initialize.
44  */
45  void NphIntegrator::readParameters(std::istream& in)
46  {
47  read<double>(in, "dt", dt_);
48  read<double>(in, "W", W_);
49  read<LatticeSystem>(in, "mode", mode_);
51 
52  // Allocate memory
53  int nAtomType = simulation().nAtomType();
54  if (!prefactors_.isAllocated()) {
55  prefactors_.allocate(nAtomType);
56  }
57  }
58 
59  /*
60  * Load state from restart archive.
61  */
63  {
64  loadParameter<double>(ar, "dt", dt_);
65  loadParameter<double>(ar, "W", W_);
66  loadParameter<LatticeSystem>(ar, "mode", mode_);
68 
69  MpiLoader<Serializable::IArchive> loader(*this, ar);
70  loader.load(nu_);
71 
72  // Allocate memory
73  int nAtomType = simulation().nAtomType();
74  if (!prefactors_.isAllocated()) {
75  prefactors_.allocate(nAtomType);
76  }
77  }
78 
79  /*
80  * Save state to output archive.
81  */
83  {
84  ar << dt_;
85  ar << W_;
86  ar << mode_;
87  Integrator::save(ar);
88  ar << nu_;
89  }
90 
91  /*
92  * Initialize extended ensemble state variables.
93  */
95  { nu_ = Vector(0.0,0.0,0.0); }
96 
98  {
99  // Initialize state and clear statistics on first usage.
100  if (!isSetup()) {
101  clear();
102  setIsSetup();
103  }
104 
105  // Exchange atoms, build pair list, compute forces.
106  setupAtoms();
107 
108  // Set prefactors for acceleration
109  double dtHalf = 0.5*dt_;
110  double mass;
111  int nAtomType = prefactors_.capacity();
112  for (int i = 0; i < nAtomType; ++i) {
113  mass = simulation().atomType(i).mass();
114  prefactors_[i] = dtHalf/mass;
115  }
116 
118  #ifdef UTIL_MPI
119  atomStorage().computeNAtomTotal(domain().communicator());
120  #endif
121  if (domain().isMaster()) {
122  P_target_ = simulation().boundaryEnsemble().pressure();
123  ndof_ = atomStorage().nAtomTotal()*3;
124  }
125  #ifdef UTIL_MPI
126  bcast(domain().communicator(), ndof_,0);
127  #endif
128  }
129 
130  /*
131  * First half of two-step velocity-Verlet integrator.
132  */
134  {
135  Vector dv;
136  AtomIterator atomIter;
137 
138  Simulation& sys = simulation();
139  sys.computeVirialStress();
140  sys.computeKineticStress();
141  sys.computeKineticEnergy();
142 
143  if (sys.domain().isMaster()) {
144  P_target_ = simulation().boundaryEnsemble().pressure();
145  T_kinetic_ = sys.kineticEnergy()*2.0/ndof_;
146  Tensor stress = sys.virialStress();
147  stress += sys.kineticStress();
148 
149  P_curr_diag_ = Vector(stress(0, 0), stress(1,1), stress(2,2));
150  double P_curr = (1.0/3.0)*stress.trace();
151 
152  double mtk_term = (1.0/2.0)*dt_*T_kinetic_/W_;
153 
154  // Advance barostat
155  double V = sys.boundary().volume();
156  if (mode_ == Cubic) {
157  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr - P_target_) + mtk_term;
158  nu_[1] = nu_[2] = nu_[0];
159  } else if (mode_ == Tetragonal) {
160  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[0] - P_target_) + mtk_term;
161  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;
162  nu_[2] = nu_[1];
163  } else if (mode_ == Orthorhombic) {
164  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[0] - P_target_) + mtk_term;
165  nu_[1] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[1] - P_target_) + mtk_term;
166  nu_[2] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[2] - P_target_) + mtk_term;
167  }
168 
169  }
170 
171  #ifdef UTIL_MPI
172  bcast(domain().communicator(), nu_,0);
173  #endif
174 
175  // Precompute loop invariant quantities
176  mtk_term_2_ = (nu_[0]+nu_[1]+nu_[2])/ndof_;
177  Vector v_fac = Vector((1.0/4.0)*(nu_[0]+mtk_term_2_),
178  (1.0/4.0)*(nu_[1]+mtk_term_2_),
179  (1.0/4.0)*(nu_[2]+mtk_term_2_));
180  exp_v_fac_ = Vector(exp(-v_fac[0]*dt_),
181  exp(-v_fac[1]*dt_),
182  exp(-v_fac[2]*dt_));
183  Vector exp_v_fac_2 = Vector(exp(-(2.0*v_fac[0])*dt_),
184  exp(-(2.0*v_fac[1])*dt_),
185  exp(-(2.0*v_fac[2])*dt_));
186  Vector r_fac = Vector((1.0/2.0)*nu_[0],
187  (1.0/2.0)*nu_[1],
188  (1.0/2.0)*nu_[2]);
189  Vector exp_r_fac = Vector(exp(r_fac[0]*dt_),
190  exp(r_fac[1]*dt_),
191  exp(r_fac[2]*dt_));
192 
193  // 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
194  const double a[] = {double(1.0), double(1.0/6.0), double(1.0/120.0),
195  double(1.0/5040.0), double(1.0/362880.0), double(1.0/39916800.0)};
196 
197  Vector arg_v = Vector(v_fac[0]*dt_, v_fac[1]*dt_, v_fac[2]*dt_);
198  Vector arg_r = Vector(r_fac[0]*dt_, r_fac[1]*dt_, r_fac[2]*dt_);
199 
200  sinhx_fac_v_ = Vector(0.0,0.0,0.0);
201  Vector sinhx_fac_r = Vector(0.0,0.0,0.0);
202 
203  Vector term_v = Vector(1.0,1.0,1.0);
204  Vector term_r = Vector(1.0,1.0,1.0);
205 
206  for (unsigned int i = 0; i < 6; i++) {
207  sinhx_fac_v_ += Vector(a[i]*term_v[0],
208  a[i]*term_v[1],
209  a[i]*term_v[2]);
210  sinhx_fac_r += Vector(a[i]*term_r[0],
211  a[i]*term_r[1],
212  a[i]*term_r[2]);
213  term_v = Vector(term_v[0] * arg_v[0] * arg_v[0],
214  term_v[1] * arg_v[1] * arg_v[1],
215  term_v[2] * arg_v[2] * arg_v[2]);
216  term_r = Vector(term_r[0] * arg_r[0] * arg_r[0],
217  term_r[1] * arg_r[1] * arg_r[1],
218  term_r[2] * arg_r[2] * arg_r[2]);
219  }
220 
221  // 1st half of NPH
222  Vector vtmp;
223  double prefactor; // = 0.5*dt/mass
224  atomStorage().begin(atomIter);
225  for ( ; atomIter.notEnd(); ++atomIter) {
226  prefactor = prefactors_[atomIter->typeId()];
227 
228  dv.multiply(atomIter->force(), prefactor);
229  dv[0] = dv[0] * exp_v_fac_[0]*sinhx_fac_v_[0];
230  dv[1] = dv[1] * exp_v_fac_[1]*sinhx_fac_v_[1];
231  dv[2] = dv[2] * exp_v_fac_[2]*sinhx_fac_v_[2];
232 
233  Vector& v = atomIter->velocity();
234  v[0] = v[0] * exp_v_fac_2[0] + dv[0];
235  v[1] = v[1] * exp_v_fac_2[1] + dv[1];
236  v[2] = v[2] * exp_v_fac_2[2] + dv[2];
237 
238  vtmp[0] = v[0]*exp_r_fac[0] *sinhx_fac_r[0];
239  vtmp[1] = v[1]*exp_r_fac[1] *sinhx_fac_r[1];
240  vtmp[2] = v[2]*exp_r_fac[2] *sinhx_fac_r[2];
241 
242  Vector& r = atomIter->position();
243  r[0] = r[0]*exp_r_fac[0]*exp_r_fac[0] + vtmp[0]*dt_;
244  r[1] = r[1]*exp_r_fac[1]*exp_r_fac[1] + vtmp[1]*dt_;
245  r[2] = r[2]*exp_r_fac[2]*exp_r_fac[2] + vtmp[2]*dt_;
246  }
247 
248  // Advance box lengths
249  Vector box_len_scale = Vector(exp(nu_[0]*dt_),
250  exp(nu_[1]*dt_),
251  exp(nu_[2]*dt_));
252 
253  Vector L = sys.boundary().lengths();
254  L[0] *= box_len_scale[0];
255  L[1] *= box_len_scale[1];
256  L[2] *= box_len_scale[2];
257 
258  // Update box lengths
259  sys.boundary().setOrthorhombic(L);
260  }
261 
262  /*
263  * Second half of two-step velocity-Verlet integrator.
264  */
266  {
267  Vector dv;
268  double prefactor; // = 0.5*dt/mass
269  AtomIterator atomIter;
270 
271  Vector v_fac_2 = Vector((1.0/2.0)*(nu_[0]+mtk_term_2_),
272  (1.0/2.0)*(nu_[1]+mtk_term_2_),
273  (1.0/2.0)*(nu_[2]+mtk_term_2_));
274  Vector exp_v_fac_2 = Vector(exp(-v_fac_2[0]*dt_),
275  exp(-v_fac_2[1]*dt_),
276  exp(-v_fac_2[2]*dt_));
277 
278  // 2nd half of NPH
279  atomStorage().begin(atomIter);
280  for ( ; atomIter.notEnd(); ++atomIter) {
281  prefactor = prefactors_[atomIter->typeId()];
282  dv.multiply(atomIter->force(), prefactor);
283  dv[0] = dv[0] * exp_v_fac_[0]*sinhx_fac_v_[0];
284  dv[1] = dv[1] * exp_v_fac_[1]*sinhx_fac_v_[1];
285  dv[2] = dv[2] * exp_v_fac_[2]*sinhx_fac_v_[2];
286 
287  Vector& v = atomIter->velocity();
288  v[0] = v[0] * exp_v_fac_2[0] + dv[0];
289  v[1] = v[1] * exp_v_fac_2[1] + dv[1];
290  v[2] = v[2] * exp_v_fac_2[2] + dv[2];
291  }
292 
293  Simulation& sys = simulation();
294  sys.velocitySignal().notify();
295  sys.computeKineticStress();
296  sys.computeKineticEnergy();
297  sys.computeVirialStress();
298 
299  // Advance barostat
300  if (sys.domain().isMaster()) {
301  T_kinetic_ = sys.kineticEnergy()*2.0/ndof_;
302  Tensor stress = sys.virialStress();
303  stress += sys.kineticStress();
304 
305  P_curr_diag_ = Vector(stress(0,0), stress(1,1), stress(2,2));
306  double P_curr = (1.0/3.0)*stress.trace();
307 
308  double mtk_term = (1.0/2.0)*dt_*T_kinetic_/W_;
309 
310  double V = sys.boundary().volume();
311  if (mode_ == Cubic) {
312  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr - P_target_) + mtk_term;
313  nu_[1] = nu_[2] = nu_[0];
314  } else if (mode_ == Tetragonal) {
315  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[0] - P_target_) + mtk_term;
316  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;
317  nu_[2] = nu_[1];
318  } else if (mode_ == Orthorhombic) {
319  nu_[0] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[0] - P_target_) + mtk_term;
320  nu_[1] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[1] - P_target_) + mtk_term;
321  nu_[2] += (1.0/2.0)*dt_*V/W_*(P_curr_diag_[2] - P_target_) + mtk_term;
322  }
323  }
324 
325  #if 0
326  // output conserved quantity
328  if (sys.domain().isMaster()) {
329  std::ofstream file;
330  file.open("NPH.log", std::ios::out | std::ios::app);
331  double V = sys.boundary().volume();
332  double barostat_energy = W_*(nu_[0]*nu_[0]+ nu_[1]*nu_[1] + nu_[2]*nu_[2]);
333  double pe = sys.potentialEnergy();
334  double ke = sys.kineticEnergy();
335  file << Dbl(V,20)
336  << Dbl(pe,20)
337  << Dbl(ke,20)
338  << Dbl(barostat_energy,20)
339  << std::endl;
340  file.close();
341  }
342  #endif
343 
344  #ifdef UTIL_MPI
345  bcast(domain().communicator(), nu_,0);
346  #endif
347 
348  }
349 
350 }
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
void save(Serializable::OArchive &ar)
Save state to an input restart archive.
NphIntegrator(Simulation &simulation)
Constructor.
int nAtomTotal() const
Get total number of atoms on all processors.
double kineticEnergy()
Return precomputed total kinetic energy.
double pressure() const
Get the target pressure.
double volume() const
Return unit cell volume.
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
void readParameters(std::istream &in)
Read required parameters.
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.
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
AtomType & atomType(int i)
Get an AtomType descriptor by reference.
Saving / output archive for binary ostream.
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
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
virtual void integrateStep2()
Execute secodn step of two-step integrator.
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.
void initDynamicalState()
Initialize Vector nu to zero.
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.
double potentialEnergy() const
Return sum of precomputed total potential energies.
virtual void integrateStep1()
Execute first step of two-step integrator.
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
void computeKineticEnergy()
Compute total kinetic energy.
Domain & domain()
Get the Domain by reference.
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.
void setup()
Setup state just before integration.
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.
void loadParameters(Serializable::IArchive &ar)
Load parameters from an input restart archive.