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