root / ase / md / npt.py @ 14
Historique | Voir | Annoter | Télécharger (27,35 ko)
| 1 | 1 | tkerber | '''Constant pressure/stress and temperature dynamics.
|
|---|---|---|---|
| 2 | 1 | tkerber |
|
| 3 | 1 | tkerber | Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an NPT
|
| 4 | 1 | tkerber | (or N,stress,T) ensemble.
|
| 5 | 1 | tkerber |
|
| 6 | 1 | tkerber | The method is the one proposed by Melchionna et al. [1] and later
|
| 7 | 1 | tkerber | modified by Melchionna [2]. The differential equations are integrated
|
| 8 | 1 | tkerber | using a centered difference method [3].
|
| 9 | 1 | tkerber |
|
| 10 | 1 | tkerber | 1. S. Melchionna, G. Ciccotti and B. L. Holian, "Hoover NPT dynamics
|
| 11 | 1 | tkerber | for systems varying in shape and size", Molecular Physics 78, p. 533
|
| 12 | 1 | tkerber | (1993).
|
| 13 | 1 | tkerber |
|
| 14 | 1 | tkerber | 2. S. Melchionna, "Constrained systems and statistical distribution",
|
| 15 | 1 | tkerber | Physical Review E, 61, p. 6165 (2000).
|
| 16 | 1 | tkerber |
|
| 17 | 1 | tkerber | 3. B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover,
|
| 18 | 1 | tkerber | "Time-reversible equilibrium and nonequilibrium isothermal-isobaric
|
| 19 | 1 | tkerber | simulations with centered-difference Stoermer algorithms.", Physical
|
| 20 | 1 | tkerber | Review A, 41, p. 4552 (1990).
|
| 21 | 1 | tkerber | '''
|
| 22 | 1 | tkerber | |
| 23 | 1 | tkerber | __docformat__ = 'reStructuredText'
|
| 24 | 1 | tkerber | |
| 25 | 1 | tkerber | from numpy import * |
| 26 | 1 | tkerber | import sys |
| 27 | 1 | tkerber | import time |
| 28 | 1 | tkerber | import weakref |
| 29 | 1 | tkerber | from ase.md.md import MolecularDynamics |
| 30 | 1 | tkerber | #from ASE.Trajectories.NetCDFTrajectory import NetCDFTrajectory
|
| 31 | 1 | tkerber | |
| 32 | 1 | tkerber | # Delayed imports: If the trajectory object is reading a special ASAP version
|
| 33 | 1 | tkerber | # of HooverNPT, that class is imported from Asap.Dynamics.NPTDynamics.
|
| 34 | 1 | tkerber | |
| 35 | 1 | tkerber | class NPT(MolecularDynamics): |
| 36 | 1 | tkerber | '''Constant pressure/stress and temperature dynamics.
|
| 37 | 1 | tkerber |
|
| 38 | 1 | tkerber | Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an
|
| 39 | 1 | tkerber | NPT (or N,stress,T) ensemble.
|
| 40 | 1 | tkerber |
|
| 41 | 1 | tkerber | The method is the one proposed by Melchionna et al. [1] and later
|
| 42 | 1 | tkerber | modified by Melchionna [2]. The differential equations are integrated
|
| 43 | 1 | tkerber | using a centered difference method [3]. See also NPTdynamics.tex
|
| 44 | 1 | tkerber |
|
| 45 | 1 | tkerber | The dynamics object is called with the following parameters:
|
| 46 | 1 | tkerber |
|
| 47 | 1 | tkerber | atoms
|
| 48 | 1 | tkerber | The list of atoms.
|
| 49 | 1 | tkerber |
|
| 50 | 1 | tkerber | dt
|
| 51 | 1 | tkerber | The timestep in units matching eV, A, u.
|
| 52 | 1 | tkerber |
|
| 53 | 1 | tkerber | temperature
|
| 54 | 1 | tkerber | The desired temperature in eV.
|
| 55 | 1 | tkerber |
|
| 56 | 1 | tkerber | externalstress
|
| 57 | 1 | tkerber | The external stress in eV/A^3. Either a symmetric
|
| 58 | 1 | tkerber | 3x3 tensor, a 6-vector representing the same, or a
|
| 59 | 1 | tkerber | scalar representing the pressure. Note that the
|
| 60 | 1 | tkerber | stress is positive in tension whereas the pressure is
|
| 61 | 1 | tkerber | positive in compression: giving a scalar p is
|
| 62 | 1 | tkerber | equivalent to giving the tensor (-p, -p, -p, 0, 0, 0).
|
| 63 | 1 | tkerber |
|
| 64 | 1 | tkerber | ttime
|
| 65 | 1 | tkerber | Characteristic timescale of the thermostat.
|
| 66 | 1 | tkerber | Set to None to disable the thermostat.
|
| 67 | 1 | tkerber |
|
| 68 | 1 | tkerber | pfactor
|
| 69 | 1 | tkerber | A constant in the barostat differential equation. If
|
| 70 | 1 | tkerber | a characteristic barostat timescale of ptime is
|
| 71 | 1 | tkerber | desired, set pfactor to ptime^2 * B (where B is the
|
| 72 | 1 | tkerber | Bulk Modulus). Set to None to disable the barostat.
|
| 73 | 1 | tkerber | Typical metallic bulk moduli are of the order of
|
| 74 | 1 | tkerber | 100 GPa or 0.6 eV/A^3.
|
| 75 | 1 | tkerber |
|
| 76 | 1 | tkerber | mask=None
|
| 77 | 1 | tkerber | Optional argument. A tuple of three integers (0 or 1),
|
| 78 | 1 | tkerber | indicating if the system can change size along the
|
| 79 | 1 | tkerber | three Cartesian axes. Set to (1,1,1) or None to allow
|
| 80 | 1 | tkerber | a fully flexible computational box. Set to (1,1,0)
|
| 81 | 1 | tkerber | to disallow elongations along the z-axis etc.
|
| 82 | 1 | tkerber |
|
| 83 | 1 | tkerber | Useful parameter values:
|
| 84 | 1 | tkerber |
|
| 85 | 1 | tkerber | * The same timestep can be used as in Verlet dynamics, i.e. 5 fs is fine
|
| 86 | 1 | tkerber | for bulk copper.
|
| 87 | 1 | tkerber |
|
| 88 | 1 | tkerber | * The ttime and pfactor are quite critical[4], too small values may
|
| 89 | 1 | tkerber | cause instabilites and/or wrong fluctuations in T / p. Too
|
| 90 | 1 | tkerber | large values cause an oscillation which is slow to die. Good
|
| 91 | 1 | tkerber | values for the characteristic times seem to be 25 fs for ttime,
|
| 92 | 1 | tkerber | and 75 fs for ptime (used to calculate pfactor), at least for
|
| 93 | 1 | tkerber | bulk copper with 15000-200000 atoms. But this is not well
|
| 94 | 1 | tkerber | tested, it is IMPORTANT to monitor the temperature and
|
| 95 | 1 | tkerber | stress/pressure fluctuations.
|
| 96 | 1 | tkerber |
|
| 97 | 1 | tkerber | It has the following methods:
|
| 98 | 1 | tkerber |
|
| 99 | 1 | tkerber | __call__(n)
|
| 100 | 1 | tkerber | Perform n timesteps.
|
| 101 | 1 | tkerber | initialize()
|
| 102 | 1 | tkerber | Estimates the dynamic variables for time=-1 to start
|
| 103 | 1 | tkerber | the algorithm. This is automatically called before
|
| 104 | 1 | tkerber | the first timestep.
|
| 105 | 1 | tkerber | set_stress()
|
| 106 | 1 | tkerber | Set the external stress. Use with care. It is
|
| 107 | 1 | tkerber | preferable to set the right value when creating the
|
| 108 | 1 | tkerber | object.
|
| 109 | 1 | tkerber | set_mask()
|
| 110 | 1 | tkerber | Change the mask. Use with care, as you may "freeze"
|
| 111 | 1 | tkerber | a fluctuation in the strain rate.
|
| 112 | 1 | tkerber | get_gibbs_free_energy()
|
| 113 | 1 | tkerber | Gibbs free energy is supposed to be preserved by this
|
| 114 | 1 | tkerber | dynamics. This is mainly intended as a diagnostic
|
| 115 | 1 | tkerber | tool.
|
| 116 | 1 | tkerber |
|
| 117 | 1 | tkerber | References:
|
| 118 | 1 | tkerber |
|
| 119 | 1 | tkerber | 1) S. Melchionna, G. Ciccotti and B. L. Holian, Molecular
|
| 120 | 1 | tkerber | Physics 78, p. 533 (1993).
|
| 121 | 1 | tkerber |
|
| 122 | 1 | tkerber | 2) S. Melchionna, Physical
|
| 123 | 1 | tkerber | Review E 61, p. 6165 (2000).
|
| 124 | 1 | tkerber |
|
| 125 | 1 | tkerber | 3) B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover,
|
| 126 | 1 | tkerber | Physical Review A 41, p. 4552 (1990).
|
| 127 | 1 | tkerber |
|
| 128 | 1 | tkerber | 4) F. D. Di Tolla and M. Ronchetti, Physical
|
| 129 | 1 | tkerber | Review E 48, p. 1726 (1993).
|
| 130 | 1 | tkerber |
|
| 131 | 1 | tkerber | '''
|
| 132 | 1 | tkerber | |
| 133 | 1 | tkerber | classname = "NPT" # Used by the trajectory. |
| 134 | 1 | tkerber | def __init__(self, atoms, |
| 135 | 1 | tkerber | timestep, temperature, externalstress, ttime, pfactor, |
| 136 | 1 | tkerber | mask=None, trajectory=None, logfile=None, loginterval=1): |
| 137 | 1 | tkerber | MolecularDynamics.__init__(self, atoms, timestep, trajectory,
|
| 138 | 1 | tkerber | logfile, loginterval) |
| 139 | 1 | tkerber | #self.atoms = atoms
|
| 140 | 1 | tkerber | #self.timestep = timestep
|
| 141 | 1 | tkerber | self.zero_center_of_mass_momentum(verbose=1) |
| 142 | 1 | tkerber | self.temperature = temperature
|
| 143 | 1 | tkerber | self.set_stress(externalstress)
|
| 144 | 1 | tkerber | self.set_mask(mask)
|
| 145 | 1 | tkerber | self.eta = zeros((3,3), float) |
| 146 | 1 | tkerber | self.zeta = 0.0 |
| 147 | 1 | tkerber | self.zeta_integrated = 0.0 |
| 148 | 1 | tkerber | self.initialized = 0 |
| 149 | 1 | tkerber | self.ttime = ttime
|
| 150 | 1 | tkerber | self.pfactor_given = pfactor
|
| 151 | 1 | tkerber | self._calculateconstants()
|
| 152 | 1 | tkerber | self.timeelapsed = 0.0 |
| 153 | 1 | tkerber | self.frac_traceless = 1 |
| 154 | 1 | tkerber | |
| 155 | 1 | tkerber | def set_temperature(self, temperature): |
| 156 | 1 | tkerber | self.temperature = temperature
|
| 157 | 1 | tkerber | self._calculateconstants()
|
| 158 | 1 | tkerber | |
| 159 | 1 | tkerber | def set_stress(self, stress): |
| 160 | 1 | tkerber | """Set the applied stress.
|
| 161 | 1 | tkerber |
|
| 162 | 1 | tkerber | Must be a symmetric 3x3 tensor, a 6-vector representing a symmetric
|
| 163 | 1 | tkerber | 3x3 tensor, or a number representing the pressure.
|
| 164 | 1 | tkerber | """
|
| 165 | 1 | tkerber | if type(stress) == type(1.0) or type(stress) == type(1): |
| 166 | 1 | tkerber | stress = array((-stress, -stress, -stress, 0.0, 0.0, 0.0)) |
| 167 | 1 | tkerber | elif stress.shape == (3,3): |
| 168 | 1 | tkerber | if not self._issymmetric(stress): |
| 169 | 1 | tkerber | raise ValueError, "The external stress must be a symmetric tensor." |
| 170 | 1 | tkerber | stress = array((stress[0,0], stress[1,1], stress[2,2], stress[1,2], |
| 171 | 1 | tkerber | stress[0,2], stress[0,1])) |
| 172 | 1 | tkerber | elif stress.shape != (6,): |
| 173 | 1 | tkerber | raise ValueError, "The external stress has the wrong shape." |
| 174 | 1 | tkerber | self.externalstress = stress
|
| 175 | 1 | tkerber | |
| 176 | 1 | tkerber | def set_mask(self, mask): |
| 177 | 1 | tkerber | """Set the mask indicating dynamic elements of the computational box.
|
| 178 | 1 | tkerber |
|
| 179 | 1 | tkerber | If set to None, all elements may change. If set to a 3-vector
|
| 180 | 1 | tkerber | of ones and zeros, elements which are zero specify directions
|
| 181 | 1 | tkerber | along which the size of the computational box cannot change.
|
| 182 | 1 | tkerber | For example, if mask = {1,1,0} the length of the system along
|
| 183 | 1 | tkerber | the z-axis cannot change, although xz and yz shear is still
|
| 184 | 1 | tkerber | possible. To disable shear globally, set the mode to diagonal
|
| 185 | 1 | tkerber | (not yet implemented).
|
| 186 | 1 | tkerber | """
|
| 187 | 1 | tkerber | if mask is None: |
| 188 | 1 | tkerber | mask = ones((3,))
|
| 189 | 1 | tkerber | if not hasattr(mask, "shape"): |
| 190 | 1 | tkerber | mask = array(mask) |
| 191 | 1 | tkerber | if mask.shape != (3,) and mask.shape != (3,3): |
| 192 | 1 | tkerber | raise "The mask has the wrong shape (must be a 3-vector or 3x3 matrix)" |
| 193 | 1 | tkerber | else:
|
| 194 | 1 | tkerber | mask = not_equal(mask, 0) # Make sure it is 0/1 |
| 195 | 1 | tkerber | |
| 196 | 1 | tkerber | if mask.shape == (3,): |
| 197 | 1 | tkerber | self.mask = outer(mask, mask)
|
| 198 | 1 | tkerber | else:
|
| 199 | 1 | tkerber | self.mask = mask
|
| 200 | 1 | tkerber | |
| 201 | 1 | tkerber | def set_fraction_traceless(self, fracTraceless): |
| 202 | 1 | tkerber | """set what fraction of the traceless part of the force
|
| 203 | 1 | tkerber | on eta is kept.
|
| 204 | 1 | tkerber |
|
| 205 | 1 | tkerber | By setting this to zero, the volume may change but the shape may not.
|
| 206 | 1 | tkerber | """
|
| 207 | 1 | tkerber | self.frac_traceless = fracTraceless
|
| 208 | 1 | tkerber | |
| 209 | 1 | tkerber | def get_strain_rate(self): |
| 210 | 1 | tkerber | "Get the strain rate as an upper-triangular 3x3 matrix"
|
| 211 | 1 | tkerber | return array(self.eta, copy=1) |
| 212 | 1 | tkerber | |
| 213 | 1 | tkerber | def set_strain_rate(self, rate): |
| 214 | 1 | tkerber | "Set the strain rate. Must be an upper triangular 3x3 matrix."
|
| 215 | 1 | tkerber | if not (rate.shape == (3,3) and self._isuppertriangular(rate)): |
| 216 | 1 | tkerber | raise ValueError, "Strain rate must be an upper triangular matrix." |
| 217 | 1 | tkerber | self.eta = rate
|
| 218 | 1 | tkerber | if self.initialized: |
| 219 | 1 | tkerber | # Recalculate h_past and eta_past so they match the current value.
|
| 220 | 1 | tkerber | self._initialize_eta_h()
|
| 221 | 1 | tkerber | |
| 222 | 1 | tkerber | def get_time(self): |
| 223 | 1 | tkerber | "Get the elapsed time."
|
| 224 | 1 | tkerber | return self.timeelapsed |
| 225 | 1 | tkerber | |
| 226 | 1 | tkerber | def run(self, steps): |
| 227 | 1 | tkerber | """Perform a number of time steps."""
|
| 228 | 1 | tkerber | if not self.initialized: |
| 229 | 1 | tkerber | self.initialize()
|
| 230 | 1 | tkerber | else:
|
| 231 | 1 | tkerber | if self.have_the_atoms_been_changed(): |
| 232 | 1 | tkerber | raise NotImplementedError, "You have modified the atoms since the last timestep." |
| 233 | 1 | tkerber | |
| 234 | 1 | tkerber | for i in xrange(steps): |
| 235 | 1 | tkerber | self.step()
|
| 236 | 1 | tkerber | self.nsteps += 1 |
| 237 | 1 | tkerber | self.call_observers()
|
| 238 | 1 | tkerber | |
| 239 | 1 | tkerber | def have_the_atoms_been_changed(self): |
| 240 | 1 | tkerber | "Checks if the user has modified the positions or momenta of the atoms"
|
| 241 | 1 | tkerber | limit = 1e-10
|
| 242 | 1 | tkerber | h = self._getbox()
|
| 243 | 1 | tkerber | if max(abs((h - self.h).ravel())) > limit: |
| 244 | 1 | tkerber | self._warning("The computational box has been modified.") |
| 245 | 1 | tkerber | return 1 |
| 246 | 1 | tkerber | expected_r = dot(self.q + 0.5, h) |
| 247 | 1 | tkerber | err = max(abs((expected_r - self.atoms.get_positions()).ravel())) |
| 248 | 1 | tkerber | if err > limit:
|
| 249 | 1 | tkerber | self._warning("The atomic positions have been modified: "+ str(err)) |
| 250 | 1 | tkerber | return 1 |
| 251 | 1 | tkerber | return 0 |
| 252 | 1 | tkerber | |
| 253 | 1 | tkerber | def step(self): |
| 254 | 1 | tkerber | """Perform a single time step.
|
| 255 | 1 | tkerber |
|
| 256 | 1 | tkerber | Assumes that the forces and stresses are up to date, and that
|
| 257 | 1 | tkerber | the positions and momenta have not been changed since last
|
| 258 | 1 | tkerber | timestep.
|
| 259 | 1 | tkerber | """
|
| 260 | 1 | tkerber | |
| 261 | 1 | tkerber | ## Assumes the following variables are OK
|
| 262 | 1 | tkerber | # q_past, q, q_future, p, eta, eta_past, zeta, zeta_past, h, h_past
|
| 263 | 1 | tkerber | #
|
| 264 | 1 | tkerber | # q corresponds to the current positions
|
| 265 | 1 | tkerber | # p must be equal to self.atoms.GetCartesianMomenta()
|
| 266 | 1 | tkerber | # h must be equal to self.atoms.GetUnitCell()
|
| 267 | 1 | tkerber | #
|
| 268 | 1 | tkerber | #print "Making a timestep"
|
| 269 | 1 | tkerber | dt = self.dt
|
| 270 | 1 | tkerber | h_future = self.h_past + 2*dt * dot(self.h, self.eta) |
| 271 | 1 | tkerber | if self.pfactor_given is None: |
| 272 | 1 | tkerber | deltaeta = zeros(6, float) |
| 273 | 1 | tkerber | else:
|
| 274 | 1 | tkerber | deltaeta = -2*dt * (self.pfact * linalg.det(self.h) |
| 275 | 1 | tkerber | * (self.atoms.get_stress()
|
| 276 | 1 | tkerber | - self.externalstress))
|
| 277 | 1 | tkerber | |
| 278 | 1 | tkerber | if self.frac_traceless == 1: |
| 279 | 1 | tkerber | eta_future = self.eta_past + self.mask * self._makeuppertriangular(deltaeta) |
| 280 | 1 | tkerber | else:
|
| 281 | 1 | tkerber | trace_part, traceless_part = self._separatetrace(self._makeuppertriangular(deltaeta)) |
| 282 | 1 | tkerber | eta_future = self.eta_past + trace_part + self.frac_traceless * traceless_part |
| 283 | 1 | tkerber | |
| 284 | 1 | tkerber | deltazeta = 2*dt*self.tfact * (self.atoms.get_kinetic_energy() |
| 285 | 1 | tkerber | - self.desiredEkin)
|
| 286 | 1 | tkerber | zeta_future = self.zeta_past + deltazeta
|
| 287 | 1 | tkerber | # Advance time
|
| 288 | 1 | tkerber | #print "Max change in scaled positions:", max(abs(self.q_future.flat - self.q.flat))
|
| 289 | 1 | tkerber | #print "Max change in basis set", max(abs((h_future - self.h).flat))
|
| 290 | 1 | tkerber | self.timeelapsed += dt
|
| 291 | 1 | tkerber | self.h_past = self.h |
| 292 | 1 | tkerber | self.h = h_future
|
| 293 | 1 | tkerber | self.inv_h = linalg.inv(self.h) |
| 294 | 1 | tkerber | # Do not throw away the q arrays, they are "magical" on parallel
|
| 295 | 1 | tkerber | # simulations (the contents migrate along with the atoms).
|
| 296 | 1 | tkerber | (self.q_past, self.q, self.q_future) = (self.q, self.q_future, |
| 297 | 1 | tkerber | self.q_past)
|
| 298 | 1 | tkerber | self._setbox_and_positions(self.h,self.q) |
| 299 | 1 | tkerber | self.eta_past = self.eta |
| 300 | 1 | tkerber | self.eta = eta_future
|
| 301 | 1 | tkerber | self.zeta_past = self.zeta |
| 302 | 1 | tkerber | self.zeta = zeta_future
|
| 303 | 1 | tkerber | self._synchronize() # for parallel simulations. |
| 304 | 1 | tkerber | self.zeta_integrated += dt * self.zeta |
| 305 | 1 | tkerber | #self.forcecalculator()
|
| 306 | 1 | tkerber | force = self.atoms.get_forces()
|
| 307 | 1 | tkerber | # The periodic boundary conditions may have moved the atoms.
|
| 308 | 1 | tkerber | self.post_pbc_fix(fixfuture=0) |
| 309 | 1 | tkerber | self._calculate_q_future(force)
|
| 310 | 1 | tkerber | self.atoms.set_momenta(dot(self.q_future-self.q_past, self.h/(2*dt)) * |
| 311 | 1 | tkerber | self._getmasses())
|
| 312 | 1 | tkerber | #self.stresscalculator()
|
| 313 | 1 | tkerber | |
| 314 | 1 | tkerber | def initialize(self): |
| 315 | 1 | tkerber | """Initialize the dynamics.
|
| 316 | 1 | tkerber |
|
| 317 | 1 | tkerber | The dynamics requires positions etc for the two last times to
|
| 318 | 1 | tkerber | do a timestep, so the algorithm is not self-starting. This
|
| 319 | 1 | tkerber | method performs a 'backwards' timestep to generate a
|
| 320 | 1 | tkerber | configuration before the current.
|
| 321 | 1 | tkerber | """
|
| 322 | 1 | tkerber | #print "Initializing the NPT dynamics."
|
| 323 | 1 | tkerber | dt = self.dt
|
| 324 | 1 | tkerber | atoms = self.atoms
|
| 325 | 1 | tkerber | self.h = self._getbox() |
| 326 | 1 | tkerber | if not self._isuppertriangular(self.h): |
| 327 | 1 | tkerber | print "I am", self |
| 328 | 1 | tkerber | print "self.h:" |
| 329 | 1 | tkerber | print self.h |
| 330 | 1 | tkerber | print "Min:", min((self.h[1,0], self.h[2,0], self.h[2,1])) |
| 331 | 1 | tkerber | print "Max:", max((self.h[1,0], self.h[2,0], self.h[2,1])) |
| 332 | 1 | tkerber | raise NotImplementedError, "Can (so far) only operate on lists of atoms where the computational box is an upper triangular matrix." |
| 333 | 1 | tkerber | self.inv_h = linalg.inv(self.h) |
| 334 | 1 | tkerber | # The contents of the q arrays should migrate in parallel simulations.
|
| 335 | 1 | tkerber | self._make_special_q_arrays()
|
| 336 | 1 | tkerber | self.q[:] = dot(self.atoms.get_positions(), |
| 337 | 1 | tkerber | self.inv_h) - 0.5 |
| 338 | 1 | tkerber | # zeta and eta were set in __init__
|
| 339 | 1 | tkerber | self._initialize_eta_h()
|
| 340 | 1 | tkerber | deltazeta = dt * self.tfact * (atoms.get_kinetic_energy() -
|
| 341 | 1 | tkerber | self.desiredEkin)
|
| 342 | 1 | tkerber | self.zeta_past = self.zeta - deltazeta |
| 343 | 1 | tkerber | self._calculate_q_past_and_future()
|
| 344 | 1 | tkerber | self.initialized = 1 |
| 345 | 1 | tkerber | |
| 346 | 1 | tkerber | def get_gibbs_free_energy(self): |
| 347 | 1 | tkerber | """Return the Gibb's free energy, which is supposed to be conserved.
|
| 348 | 1 | tkerber |
|
| 349 | 1 | tkerber | Requires that the energies of the atoms are up to date.
|
| 350 | 1 | tkerber |
|
| 351 | 1 | tkerber | This is mainly intended as a diagnostic tool. If called before the
|
| 352 | 1 | tkerber | first timestep, Initialize will be called.
|
| 353 | 1 | tkerber | """
|
| 354 | 1 | tkerber | if not self.initialized: |
| 355 | 1 | tkerber | self.Initialize()
|
| 356 | 1 | tkerber | n = self._getnatoms()
|
| 357 | 1 | tkerber | #tretaTeta = sum(diagonal(matrixmultiply(transpose(self.eta),
|
| 358 | 1 | tkerber | # self.eta)))
|
| 359 | 1 | tkerber | contractedeta = sum((self.eta*self.eta).ravel()) |
| 360 | 1 | tkerber | gibbs = (self.atoms.get_potential_energy() +
|
| 361 | 1 | tkerber | self.atoms.get_kinetic_energy()
|
| 362 | 1 | tkerber | - sum(self.externalstress[0:3]) * linalg.det(self.h) / 3.0) |
| 363 | 1 | tkerber | if self.ttime is not None: |
| 364 | 1 | tkerber | gibbs += (1.5 * n * self.temperature * (self.ttime * self.zeta)**2 |
| 365 | 1 | tkerber | + 3 * self.temperature * (n-1) * self.zeta_integrated) |
| 366 | 1 | tkerber | else:
|
| 367 | 1 | tkerber | assert self.zeta == 0.0 |
| 368 | 1 | tkerber | if self.pfactor_given is not None: |
| 369 | 1 | tkerber | gibbs += 0.5 / self.pfact * contractedeta |
| 370 | 1 | tkerber | else:
|
| 371 | 1 | tkerber | assert contractedeta == 0.0 |
| 372 | 1 | tkerber | return gibbs
|
| 373 | 1 | tkerber | |
| 374 | 1 | tkerber | def get_center_of_mass_momentum(self): |
| 375 | 1 | tkerber | "Get the center of mass momentum."
|
| 376 | 1 | tkerber | return self.atoms.get_momenta().sum(0) |
| 377 | 1 | tkerber | |
| 378 | 1 | tkerber | def zero_center_of_mass_momentum(self, verbose=0): |
| 379 | 1 | tkerber | "Set the center of mass momentum to zero."
|
| 380 | 1 | tkerber | cm = self.get_center_of_mass_momentum()
|
| 381 | 1 | tkerber | abscm = sqrt(sum(cm*cm))
|
| 382 | 1 | tkerber | if verbose and abscm > 1e-4: |
| 383 | 1 | tkerber | self._warning(self.classname+": Setting the center-of-mass momentum to zero (was %.6g %.6g %.6g)" % tuple(cm)) |
| 384 | 1 | tkerber | self.atoms.set_momenta(self.atoms.get_momenta() |
| 385 | 1 | tkerber | - cm / self._getnatoms())
|
| 386 | 1 | tkerber | |
| 387 | 1 | tkerber | def post_pbc_fix(self, fixfuture=1): |
| 388 | 1 | tkerber | """Correct for atoms moved by the boundary conditions.
|
| 389 | 1 | tkerber |
|
| 390 | 1 | tkerber | If the fixfuture argument is 1 (the default), q_future is also
|
| 391 | 1 | tkerber | corrected. This is not necessary when post_pbc_fix() is called from
|
| 392 | 1 | tkerber | within Timestep(), but must be done when the user calls post_pbc_fix
|
| 393 | 1 | tkerber | (for example if a CNA calculation may have triggered a migration).
|
| 394 | 1 | tkerber | """
|
| 395 | 1 | tkerber | q = dot(self.atoms.get_positions(),
|
| 396 | 1 | tkerber | self.inv_h) - 0.5 |
| 397 | 1 | tkerber | delta_q = floor(0.5 + (q - self.q)) |
| 398 | 1 | tkerber | self.q += delta_q
|
| 399 | 1 | tkerber | self.q_past += delta_q
|
| 400 | 1 | tkerber | if fixfuture:
|
| 401 | 1 | tkerber | self.q_future += delta_q
|
| 402 | 1 | tkerber | |
| 403 | 1 | tkerber | def attach_atoms(self, atoms): |
| 404 | 1 | tkerber | """Assign atoms to a restored dynamics object.
|
| 405 | 1 | tkerber |
|
| 406 | 1 | tkerber | This function must be called to set the atoms immediately after the
|
| 407 | 1 | tkerber | dynamics object has been read from a trajectory.
|
| 408 | 1 | tkerber | """
|
| 409 | 1 | tkerber | try:
|
| 410 | 1 | tkerber | self.atoms
|
| 411 | 1 | tkerber | except AttributeError: |
| 412 | 1 | tkerber | pass
|
| 413 | 1 | tkerber | else:
|
| 414 | 1 | tkerber | raise RuntimeError, "Cannot call attach_atoms on a dynamics which already has atoms." |
| 415 | 1 | tkerber | MolecularDynamics.__init__(self, atoms, self.dt) |
| 416 | 1 | tkerber | ####self.atoms = atoms
|
| 417 | 1 | tkerber | limit = 1e-6
|
| 418 | 1 | tkerber | h = self._getbox()
|
| 419 | 1 | tkerber | if max(abs((h - self.h).ravel())) > limit: |
| 420 | 1 | tkerber | raise RuntimeError, "The unit cell of the atoms does not match the unit cell stored in the file." |
| 421 | 1 | tkerber | self.inv_h = linalg.inv(self.h) |
| 422 | 1 | tkerber | self._make_special_q_arrays()
|
| 423 | 1 | tkerber | self.q[:] = dot(self.atoms.get_positions(), |
| 424 | 1 | tkerber | self.inv_h) - 0.5 |
| 425 | 1 | tkerber | self._calculate_q_past_and_future()
|
| 426 | 1 | tkerber | self.initialized = 1 |
| 427 | 1 | tkerber | |
| 428 | 1 | tkerber | def _getbox(self): |
| 429 | 1 | tkerber | "Get the computational box."
|
| 430 | 1 | tkerber | return self.atoms.get_cell() |
| 431 | 1 | tkerber | |
| 432 | 1 | tkerber | def _getmasses(self): |
| 433 | 1 | tkerber | "Get the masses as an Nx1 array."
|
| 434 | 1 | tkerber | return reshape(self.atoms.get_masses(), (-1,1)) |
| 435 | 1 | tkerber | |
| 436 | 1 | tkerber | # def _getcartesianpositions(self):
|
| 437 | 1 | tkerber | # "Get the cartesian positions of the atoms"
|
| 438 | 1 | tkerber | # return self.atoms.get_positions()
|
| 439 | 1 | tkerber | |
| 440 | 1 | tkerber | # def _getmomenta(self):
|
| 441 | 1 | tkerber | # "Get the (cartesian) momenta of the atoms"
|
| 442 | 1 | tkerber | # return self.atoms.GetCartesianMomenta()
|
| 443 | 1 | tkerber | |
| 444 | 1 | tkerber | # def _getforces(self):
|
| 445 | 1 | tkerber | # "Get the (cartesian) forces of the atoms"
|
| 446 | 1 | tkerber | # return self.atoms.GetCartesianForces()
|
| 447 | 1 | tkerber | |
| 448 | 1 | tkerber | # def _setmomenta(self, momenta):
|
| 449 | 1 | tkerber | # "Set the (cartesian) momenta of the atoms"
|
| 450 | 1 | tkerber | # self.atoms.SetCartesianMomenta(momenta)
|
| 451 | 1 | tkerber | |
| 452 | 1 | tkerber | def _separatetrace(self, mat): |
| 453 | 1 | tkerber | """return two matrices, one proportional to the identity
|
| 454 | 1 | tkerber | the other traceless, which sum to the given matrix
|
| 455 | 1 | tkerber | """
|
| 456 | 1 | tkerber | tracePart = ((mat[0][0] + mat[1][1] + mat[2][2]) / 3.) * identity(3) |
| 457 | 1 | tkerber | return tracePart, mat - tracePart
|
| 458 | 1 | tkerber | |
| 459 | 1 | tkerber | # A number of convenient helper methods
|
| 460 | 1 | tkerber | def _warning(self, text): |
| 461 | 1 | tkerber | "Emit a warning."
|
| 462 | 1 | tkerber | sys.stderr.write("WARNING: "+text+"\n") |
| 463 | 1 | tkerber | sys.stderr.flush() |
| 464 | 1 | tkerber | |
| 465 | 1 | tkerber | def _calculate_q_future(self, force): |
| 466 | 1 | tkerber | "Calculate future q. Needed in Timestep and Initialization."
|
| 467 | 1 | tkerber | dt = self.dt
|
| 468 | 1 | tkerber | id3 = identity(3)
|
| 469 | 1 | tkerber | alpha = (dt * dt) * dot(force / self._getmasses(),
|
| 470 | 1 | tkerber | self.inv_h)
|
| 471 | 1 | tkerber | beta = dt * dot(self.h, dot(self.eta + 0.5 * self.zeta * id3, |
| 472 | 1 | tkerber | self.inv_h))
|
| 473 | 1 | tkerber | inv_b = linalg.inv(beta + id3) |
| 474 | 1 | tkerber | self.q_future[:] = dot(2*self.q + dot(self.q_past, beta - id3) + alpha, |
| 475 | 1 | tkerber | inv_b) |
| 476 | 1 | tkerber | |
| 477 | 1 | tkerber | def _calculate_q_past_and_future(self): |
| 478 | 1 | tkerber | def ekin(p, m = self.atoms.get_masses()): |
| 479 | 1 | tkerber | p2 = sum(p*p, -1) |
| 480 | 1 | tkerber | return 0.5 * sum(p2 / m) / len(m) |
| 481 | 1 | tkerber | p0 = self.atoms.get_momenta()
|
| 482 | 1 | tkerber | m = self._getmasses()
|
| 483 | 1 | tkerber | e0 = ekin(p0) |
| 484 | 1 | tkerber | p = array(p0, copy=1)
|
| 485 | 1 | tkerber | dt = self.dt
|
| 486 | 1 | tkerber | for i in range(2): |
| 487 | 1 | tkerber | self.q_past[:] = self.q - dt * dot(p / m, self.inv_h) |
| 488 | 1 | tkerber | self._calculate_q_future(self.atoms.get_forces()) |
| 489 | 1 | tkerber | p = dot(self.q_future - self.q_past, self.h/(2*dt)) * m |
| 490 | 1 | tkerber | e = ekin(p) |
| 491 | 1 | tkerber | if e < 1e-5: |
| 492 | 1 | tkerber | # The kinetic energy and momenta are virtually zero
|
| 493 | 1 | tkerber | return
|
| 494 | 1 | tkerber | p = (p0 - p) + p0 |
| 495 | 1 | tkerber | |
| 496 | 1 | tkerber | def _initialize_eta_h(self): |
| 497 | 1 | tkerber | self.h_past = self.h - self.dt * dot(self.h, self.eta) |
| 498 | 1 | tkerber | if self.pfactor_given is None: |
| 499 | 1 | tkerber | deltaeta = zeros(6, float) |
| 500 | 1 | tkerber | else:
|
| 501 | 1 | tkerber | deltaeta = (-self.dt * self.pfact * linalg.det(self.h) |
| 502 | 1 | tkerber | * (self.atoms.get_stress() - self.externalstress)) |
| 503 | 1 | tkerber | if self.frac_traceless == 1: |
| 504 | 1 | tkerber | self.eta_past = self.eta - self.mask * self._makeuppertriangular(deltaeta) |
| 505 | 1 | tkerber | else:
|
| 506 | 1 | tkerber | trace_part, traceless_part = self._separatetrace(self._makeuppertriangular(deltaeta)) |
| 507 | 1 | tkerber | self.eta_past = self.eta - trace_part - self.frac_traceless * traceless_part |
| 508 | 1 | tkerber | |
| 509 | 1 | tkerber | |
| 510 | 1 | tkerber | def _makeuppertriangular(self, sixvector): |
| 511 | 1 | tkerber | "Make an upper triangular matrix from a 6-vector."
|
| 512 | 1 | tkerber | return array(((sixvector[0], sixvector[5], sixvector[4]), |
| 513 | 1 | tkerber | (0, sixvector[1], sixvector[3]), |
| 514 | 1 | tkerber | (0, 0, sixvector[2]))) |
| 515 | 1 | tkerber | |
| 516 | 1 | tkerber | def _isuppertriangular(self, m): |
| 517 | 1 | tkerber | "Check that a matrix is on upper triangular form."
|
| 518 | 1 | tkerber | return m[1,0] == m[2,0] == m[2,1] == 0.0 |
| 519 | 1 | tkerber | |
| 520 | 1 | tkerber | def _calculateconstants(self): |
| 521 | 1 | tkerber | "(Re)calculate some constants when pfactor, ttime or temperature have been changed."
|
| 522 | 1 | tkerber | n = self._getnatoms()
|
| 523 | 1 | tkerber | if self.ttime is None: |
| 524 | 1 | tkerber | self.tfact = 0.0 |
| 525 | 1 | tkerber | else:
|
| 526 | 1 | tkerber | self.tfact = 2.0 / (3 * n * self.temperature * |
| 527 | 1 | tkerber | self.ttime * self.ttime) |
| 528 | 1 | tkerber | if self.pfactor_given is None: |
| 529 | 1 | tkerber | self.pfact = 0.0 |
| 530 | 1 | tkerber | else:
|
| 531 | 1 | tkerber | self.pfact = 1.0 / (self.pfactor_given |
| 532 | 1 | tkerber | * linalg.det(self._getbox()))
|
| 533 | 1 | tkerber | #self.pfact = 1.0/(n * self.temperature * self.ptime * self.ptime)
|
| 534 | 1 | tkerber | self.desiredEkin = 1.5 * (n - 1) * self.temperature |
| 535 | 1 | tkerber | |
| 536 | 1 | tkerber | def _setbox_and_positions(self, h, q): |
| 537 | 1 | tkerber | """Set the computational box and the positions."""
|
| 538 | 1 | tkerber | self.atoms.set_cell(h, scale_atoms=True) |
| 539 | 1 | tkerber | r = dot(q + 0.5, h)
|
| 540 | 1 | tkerber | self.atoms.set_positions(r)
|
| 541 | 1 | tkerber | |
| 542 | 1 | tkerber | # A few helper methods, which have been placed in separate methods
|
| 543 | 1 | tkerber | # so they can be replaced in the parallel version.
|
| 544 | 1 | tkerber | def _synchronize(self): |
| 545 | 1 | tkerber | """Synchronizes eta, h and zeta on all processors in a parallel simulation.
|
| 546 | 1 | tkerber |
|
| 547 | 1 | tkerber | In a parallel simulation, eta, h and zeta are communicated
|
| 548 | 1 | tkerber | from the master to all slaves, to prevent numerical noise from
|
| 549 | 1 | tkerber | causing them to diverge.
|
| 550 | 1 | tkerber |
|
| 551 | 1 | tkerber | In a serial simulation, do nothing.
|
| 552 | 1 | tkerber | """
|
| 553 | 1 | tkerber | pass # This is a serial simulation object. Do nothing. |
| 554 | 1 | tkerber | |
| 555 | 1 | tkerber | def _getnatoms(self): |
| 556 | 1 | tkerber | """Get the number of atoms.
|
| 557 | 1 | tkerber |
|
| 558 | 1 | tkerber | In a parallel simulation, this is the total number of atoms on all
|
| 559 | 1 | tkerber | processors.
|
| 560 | 1 | tkerber | """
|
| 561 | 1 | tkerber | return len(self.atoms) |
| 562 | 1 | tkerber | |
| 563 | 1 | tkerber | def _make_special_q_arrays(self): |
| 564 | 1 | tkerber | """Make the arrays used to store data about the atoms.
|
| 565 | 1 | tkerber |
|
| 566 | 1 | tkerber | In a parallel simulation, these are migrating arrays. In a
|
| 567 | 1 | tkerber | serial simulation they are ordinary Numeric arrays.
|
| 568 | 1 | tkerber | """
|
| 569 | 1 | tkerber | natoms = len(self.atoms) |
| 570 | 1 | tkerber | self.q = zeros((natoms,3), float) |
| 571 | 1 | tkerber | self.q_past = zeros((natoms,3), float) |
| 572 | 1 | tkerber | self.q_future = zeros((natoms,3), float) |
| 573 | 1 | tkerber | |
| 574 | 1 | tkerber | |
| 575 | 1 | tkerber | |
| 576 | 1 | tkerber | # class _HooverNPTTrajectory:
|
| 577 | 1 | tkerber | # """A Trajectory-like object storing data in a HooverNPT object."""
|
| 578 | 1 | tkerber | # def InitForWrite(self):
|
| 579 | 1 | tkerber | # """Does initialization related to write mode."""
|
| 580 | 1 | tkerber | # self.CreateDimension('unlim', None)
|
| 581 | 1 | tkerber | # self.nc.history = 'ASE NPT trajectory'
|
| 582 | 1 | tkerber | # self.nc.version = '0.1'
|
| 583 | 1 | tkerber | # self.nc.classname = self.atoms.classname
|
| 584 | 1 | tkerber | # self.unlim = 0
|
| 585 | 1 | tkerber | # self.nc.lengthunit = units.GetLengthUnit()
|
| 586 | 1 | tkerber | # self.nc.energyunit = units.GetEnergyUnit()
|
| 587 | 1 | tkerber | # self.conversion = (1, 1)
|
| 588 | 1 | tkerber | |
| 589 | 1 | tkerber | # def InitForWriteOrAppend(self):
|
| 590 | 1 | tkerber | # """Does initialization related to write and append mode.
|
| 591 | 1 | tkerber | |
| 592 | 1 | tkerber | # Either InitForWrite or InitForReadOrAppend will have been
|
| 593 | 1 | tkerber | # called before calling this method.
|
| 594 | 1 | tkerber | # """
|
| 595 | 1 | tkerber | # names = copy.copy(self.known_names)
|
| 596 | 1 | tkerber | # if self.atoms.ttime is None:
|
| 597 | 1 | tkerber | # del names['ttime']
|
| 598 | 1 | tkerber | # if self.atoms.pfactor_given is None:
|
| 599 | 1 | tkerber | # del names['pfactor_given']
|
| 600 | 1 | tkerber | # for d in names.keys():
|
| 601 | 1 | tkerber | # def getdata(atoms=self.atoms, name=d):
|
| 602 | 1 | tkerber | # return getattr(atoms, name)
|
| 603 | 1 | tkerber | # self.Add(d, data = getdata)
|
| 604 | 1 | tkerber | |
| 605 | 1 | tkerber | # known_names = {
|
| 606 | 1 | tkerber | # # name shape typecode once units
|
| 607 | 1 | tkerber | # # ----------------------------------------------------------------
|
| 608 | 1 | tkerber | # 'dt': ((), Float, True, (1, -0.5)),
|
| 609 | 1 | tkerber | # 'temperature': ((), Float, True, (0, 1)),
|
| 610 | 1 | tkerber | # 'desiredEkin': ((), Float, True, (0, 1)),
|
| 611 | 1 | tkerber | # 'externalstress': ((6,), Float, True, (-3, 1)),
|
| 612 | 1 | tkerber | # 'mask': ((3, 3), Float, True, (0, 0)),
|
| 613 | 1 | tkerber | # 'ttime': ((), Float, True, (1, -0.5)),
|
| 614 | 1 | tkerber | # 'tfact': ((), Float, True, (-2, 0)),
|
| 615 | 1 | tkerber | # 'pfactor_given': ((), Float, True, (-1, 0)),
|
| 616 | 1 | tkerber | # 'pfact': ((), Float, True, (-2, 0)),
|
| 617 | 1 | tkerber | # 'frac_traceless': ((), Float, True, (0, 0)),
|
| 618 | 1 | tkerber | # 'eta': ((3, 3), Float, False, (-1, 0.5)),
|
| 619 | 1 | tkerber | # 'eta_past': ((3, 3), Float, False, (-1, 0.5)),
|
| 620 | 1 | tkerber | # 'zeta': ((), Float, False, (-1, 0.5)),
|
| 621 | 1 | tkerber | # 'zeta_past': ((), Float, False, (-1, 0.5)),
|
| 622 | 1 | tkerber | # 'zeta_integrated': ((), Float, False, (0, 0)),
|
| 623 | 1 | tkerber | # 'h': ((3, 3), Float, False, (1, 0)),
|
| 624 | 1 | tkerber | # 'h_past': ((3, 3), Float, False, (1, 0)),
|
| 625 | 1 | tkerber | # 'timeelapsed': ((), Float, False, (1, -0.5))
|
| 626 | 1 | tkerber | # }
|
| 627 | 1 | tkerber | |
| 628 | 1 | tkerber | # # This trajectory does not store a list of atoms
|
| 629 | 1 | tkerber | # def GetListOfAtoms(self, frame=None):
|
| 630 | 1 | tkerber | # raise AttributeError, "GetListOfAtoms makes no sense in a HooverNPTTrajectory"
|
| 631 | 1 | tkerber | |
| 632 | 1 | tkerber | # # Instead, we store a dynamics
|
| 633 | 1 | tkerber | # def GetDynamics(self, frame=None):
|
| 634 | 1 | tkerber | # """Get a HooverNPT Dynamics object.
|
| 635 | 1 | tkerber | |
| 636 | 1 | tkerber | # If a frame number is not given, the current frame is used.
|
| 637 | 1 | tkerber | |
| 638 | 1 | tkerber | # The variant of the object (ASE HooverNPT, ASAP Serial/Parallel NPT)
|
| 639 | 1 | tkerber | # will be the same as the stored object.
|
| 640 | 1 | tkerber | |
| 641 | 1 | tkerber | # After getting the dynamics, the atoms should be attached with the
|
| 642 | 1 | tkerber | # dynamics.attach_atoms(atoms) method.
|
| 643 | 1 | tkerber | # """
|
| 644 | 1 | tkerber | # # Bypass calling the normal constructor
|
| 645 | 1 | tkerber | # class Dummy:
|
| 646 | 1 | tkerber | # pass
|
| 647 | 1 | tkerber | # dyn = Dummy()
|
| 648 | 1 | tkerber | # dyn.__class__ = self.getClass(self.nc.classname)
|
| 649 | 1 | tkerber | # vars = self.nc.variables
|
| 650 | 1 | tkerber | # for q in self.known_names.keys():
|
| 651 | 1 | tkerber | # if vars.has_key(q):
|
| 652 | 1 | tkerber | # once = self.known_names[q][2]
|
| 653 | 1 | tkerber | # if once:
|
| 654 | 1 | tkerber | # setattr(dyn, q, vars[q].getValue())
|
| 655 | 1 | tkerber | # else:
|
| 656 | 1 | tkerber | # setattr(dyn, q, vars[q][frame])
|
| 657 | 1 | tkerber | # return dyn
|
| 658 | 1 | tkerber | |
| 659 | 1 | tkerber | # def getClass(self, classname):
|
| 660 | 1 | tkerber | # "Internal function: turns a class name into a class object."
|
| 661 | 1 | tkerber | # if self.nc.classname == "HooverNPT":
|
| 662 | 1 | tkerber | # return HooverNPT
|
| 663 | 1 | tkerber | # else:
|
| 664 | 1 | tkerber | # raise RuntimeError, ("Cannot create a dynamics of type "
|
| 665 | 1 | tkerber | # + self.nc.classname)
|
| 666 | 1 | tkerber | |
| 667 | 1 | tkerber | # class HooverNPTTrajectory(_HooverNPTTrajectory,NetCDFTrajectory):
|
| 668 | 1 | tkerber | # """A Trajectory-like object storing data in a HooverNPT object."""
|
| 669 | 1 | tkerber | # def __init__(self, filename, dynamics=None, mode=None, interval=1):
|
| 670 | 1 | tkerber | # """Open the NetCDF file.
|
| 671 | 1 | tkerber | |
| 672 | 1 | tkerber | # If there is no ``dynamics`` argument, then the file is opened
|
| 673 | 1 | tkerber | # in read mode - otherwise, write or append mode is used. The
|
| 674 | 1 | tkerber | # ``interval`` argument determines how often the configurations
|
| 675 | 1 | tkerber | # are written to file."""
|
| 676 | 1 | tkerber | # # Call the original constructor, but passing the dynamics instead of
|
| 677 | 1 | tkerber | # # the atoms.
|
| 678 | 1 | tkerber | # if dynamics is not None:
|
| 679 | 1 | tkerber | # # Prevents a circular reference when the trajectory is attached
|
| 680 | 1 | tkerber | # # to the dynamics it observes.
|
| 681 | 1 | tkerber | # dynamics = weakref.proxy(dynamics)
|
| 682 | 1 | tkerber | # NetCDFTrajectory.__init__(self, filename,
|
| 683 | 1 | tkerber | # atoms=dynamics,
|
| 684 | 1 | tkerber | # mode=mode, interval=interval)
|
| 685 | 1 | tkerber | |
| 686 | 1 | tkerber |