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