root / ase / md / npt.py @ 13
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 |
|