Statistiques
| Révision :

root / ase / md / npt.py

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