Statistiques
| Révision :

root / ase / calculators / lammps.py @ 20

Historique | Voir | Annoter | Télécharger (22,97 ko)

1
#!/usr//bin/env python
2

    
3
# lammps.py (2011/02/22)
4
# An ASE calculator for the LAMMPS classical MD code available from
5
#       http://lammps.sandia.gov/
6
# The environment variable LAMMPS_COMMAND must be defined to point to the LAMMPS binary.
7
#
8
# Copyright (C) 2009 - 2011 Joerg Meyer, joerg.meyer@ch.tum.de
9
#
10
# This library is free software; you can redistribute it and/or
11
# modify it under the terms of the GNU Lesser General Public
12
# License as published by the Free Software Foundation; either
13
# version 2.1 of the License, or (at your option) any later version.
14
#
15
# This library is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
# Lesser General Public License for more details.
19
#
20
# You should have received a copy of the GNU Lesser General Public
21
# License along with this file; if not, write to the Free Software
22
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23
# or see <http://www.gnu.org/licenses/>.
24

    
25
import os
26
import shutil
27
import time
28
import math
29
import decimal as dec
30
import numpy as np
31
from ase import Atoms
32
from ase.parallel import paropen
33

    
34

    
35
class LAMMPS:
36

    
37
    def __init__(self, label="lammps", dir="LAMMPS", parameters={}, files=[], always_triclinic=False):
38
        self.label = label
39
        self.dir = dir
40
        self.parameters = parameters
41
        self.files = files
42
        self.always_triclinic = always_triclinic
43
        self.calls = 0
44
        self.potential_energy = None
45
        self.forces = None
46

    
47
        if os.path.isdir(self.dir):
48
            if (os.listdir(self.dir) == []):
49
                os.rmdir(self.dir)
50
            else:
51
                os.rename(self.dir, self.dir + ".bak-" + time.strftime("%Y%m%d-%H%M%S"))
52
        os.mkdir(self.dir, 0755)
53
        
54
        for f in files:
55
            shutil.copy(f, os.path.join(self.dir, f))
56

    
57
    def clean(self, force=False):
58
        if os.path.isdir(self.dir):
59
            files_in_dir = os.listdir(self.dir)
60
            files_copied = self.files
61
            if (len(files_in_dir) == 0):
62
                os.rmdir(self.dir)
63
            elif (set(files_in_dir).issubset(set(files_copied)) or force):
64
                shutil.rmtree(self.dir)
65

    
66
    def get_potential_energy(self, atoms):
67
        self.update(atoms)
68
        return self.potential_energy
69

    
70
    def get_forces(self, atoms):
71
        self.update(atoms)
72
        return self.forces.copy()
73

    
74
    def get_stress(self, atoms):
75
        raise NotImplementedError
76

    
77
    def update(self, atoms):
78
        # TODO: check if (re-)calculation is necessary
79
        self.calculate(atoms)
80

    
81
    def calculate(self, atoms):
82
        self.atoms = atoms.copy()
83
        self.run()
84

    
85
    def run(self):
86
        """Method which explicitely runs LAMMPS."""
87

    
88
        self.calls += 1
89

    
90
        # set LAMMPS command from environment variable
91
        if os.environ.has_key('LAMMPS_COMMAND'):
92
            lammps_command = os.path.abspath(os.environ['LAMMPS_COMMAND'])
93
        else:
94
            self.clean()
95
            raise RuntimeError('Please set LAMMPS_COMMAND environment variable')
96
        if os.environ.has_key('LAMMPS_OPTIONS'):
97
            lammps_options = os.environ['LAMMPS_OPTIONS']
98
        else:
99
            lammps_options = "-echo log -screen none"
100

    
101
        # change into subdirectory for LAMMPS calculations
102
        cwd = os.getcwd()
103
        os.chdir(self.dir)
104

    
105
        # setup file names for LAMMPS calculation
106
        label = "%s-%06d" % (self.label, self.calls)
107
        lammps_data = "data." + label
108
        lammps_in = "in." + label
109
        lammps_trj = label + ".lammpstrj"
110
        lammps_log = label + ".log"
111

    
112
        # write LAMMPS input files
113
        self.write_lammps_data(lammps_data=lammps_data)
114
        self.write_lammps_in(lammps_in=lammps_in, lammps_data=lammps_data, lammps_trj=lammps_trj, parameters=self.parameters)
115

    
116
        # run LAMMPS
117
        # TODO: check for successful completion (based on output files?!) of LAMMPS call...
118
        exitcode = os.system("%s %s -in %s -log %s" % (lammps_command, lammps_options, lammps_in, lammps_log))
119
        if exitcode != 0:
120
            cwd = os.getcwd()
121
            raise RuntimeError("LAMMPS exited in %s with exit code: %d.  " % (cwd,exitcode))
122

    
123
        # read LAMMPS output files
124
        self.read_lammps_log(lammps_log=lammps_log)
125
        self.read_lammps_trj(lammps_trj=lammps_trj)
126

    
127
        # change back to previous working directory
128
        os.chdir(cwd)
129

    
130
    def write_lammps_data(self, lammps_data=None):
131
        """Method which writes a LAMMPS data file with atomic structure."""
132
        if (lammps_data == None):
133
            lammps_data = "data." + self.label
134
        write_lammps(lammps_data, self.atoms, force_skew=self.always_triclinic)
135

    
136
    def write_lammps_in(self, lammps_in=None, lammps_data=None, lammps_trj=None, parameters={}):
137
        """Method which writes a LAMMPS in file with run parameters and settings."""
138
        if (lammps_in == None):
139
            lammps_in = "in." + self.label
140
        if (lammps_data == None):
141
            lammps_data = "data." + self.label
142
        if (lammps_trj == None):
143
            lammps_trj = self.label + ".lammpstrj"
144

    
145
        f = paropen(lammps_in, 'w')
146
        f.write("# " + f.name + " (written by ASE) \n")
147
        f.write("\n")
148

    
149
        f.write("### variables \n")
150
        f.write("variable data_file index \"%s\" \n" % lammps_data)
151
        f.write("variable dump_file index \"%s\" \n" % lammps_trj)
152
        f.write("\n\n")
153

    
154
        pbc = self.atoms.get_pbc()
155
        f.write("### simulation box \n")
156
        f.write("units metal \n")
157
        if ("boundary" in parameters):
158
            f.write("boundary %s \n" % parameters["boundary"])
159
        else:
160
            f.write("boundary %c %c %c \n" % tuple('sp'[x] for x in pbc))
161
        f.write("atom_modify sort 0 0.0 \n")
162
        for key in ("neighbor" ,"newton"):
163
            if key in parameters:
164
                f.write("%s %s \n" % (key, parameters[key]))
165
        f.write("\n")
166
        f.write("read_data ${data_file} \n")
167
        f.write("\n\n")
168

    
169
        f.write("### interactions \n")
170
        if ( ("pair_style" in parameters) and ("pair_coeff" in parameters) ):
171
            pair_style = parameters["pair_style"]
172
            f.write("pair_style %s \n" % pair_style)
173
            for pair_coeff in parameters["pair_coeff"]:
174
                f.write("pair_coeff %s \n" % pair_coeff)
175
            if "mass" in parameters:
176
                for mass in parameters["mass"]:
177
                    f.write("mass %s \n" % mass)
178
        else:
179
            # simple default parameters that should always make the LAMMPS calculation run
180
            #    pair_style         lj/cut 2.5
181
            #    pair_coeff         * * 1 1
182
            #    mass           * 1.0        else:
183
            f.write("pair_style lj/cut 2.5 \n")
184
            f.write("pair_coeff * * 1 1 \n")
185
            f.write("mass * 1.0 \n")
186
        f.write("\n")
187

    
188
        f.write("### run \n")
189
        f.write("fix fix_nve all nve \n")
190
        f.write("\n")
191
        f.write("dump dump_all all custom 1 ${dump_file} id type x y z vx vy vz fx fy fz \n")
192
        f.write("\n")
193
        f.write("thermo_style custom step temp ke pe etotal cpu \n")
194
        f.write("thermo_modify format 1 %4d format 2 %9.3f format 3 %20.6f format 4 %20.6f format 5 %20.6f format 6 %9.3f \n")
195
        f.write("thermo 1 \n")
196
        f.write("\n")
197
        if ("minimize" in parameters):
198
            f.write("minimize %s \n" % parameters["minimize"])
199
        if ("run" in parameters):
200
            f.write("run %s \n" % parameters["run"])
201
        if not ( ("minimize" in parameters) or ("run" in parameters) ):
202
            f.write("run 0 \n")
203

    
204
        f.close()
205

    
206
    def read_lammps_log(self, lammps_log=None):
207
        """Method which reads a LAMMPS output log file."""
208
        if (lammps_log == None):
209
            lammps_log = self.label + ".log"
210

    
211
        epot = 0.0
212

    
213
        f = paropen(lammps_log, 'r')
214
        while True:
215
            line = f.readline()
216
            if not line:
217
                break
218
            # get potential energy of first step (if more are done)
219
            if "PotEng" in line:
220
                i = line.split().index("PotEng")
221
                line = f.readline()
222
                epot = float(line.split()[i])
223
        f.close()
224

    
225
#        print epot
226

    
227
        self.potential_energy = epot
228

    
229
    def read_lammps_trj(self, lammps_trj=None, set_atoms=False):
230
        """Method which reads a LAMMPS dump file."""
231
        if (lammps_trj == None):
232
            lammps_trj = self.label + ".lammpstrj"
233

    
234
        f = paropen(lammps_trj, 'r')
235
        while True:
236
            line = f.readline()
237

    
238
            if not line:
239
                break
240

    
241
            #TODO: extend to proper dealing with multiple steps in one trajectory file
242
            if "ITEM: TIMESTEP" in line:
243
                n_atoms = 0
244
                lo = [] ; hi = [] ; tilt = []
245
                id = [] ; type = []
246
                positions = [] ; velocities = [] ; forces = []
247

    
248
            if "ITEM: NUMBER OF ATOMS" in line:
249
                line = f.readline()
250
                n_atoms = int(line.split()[0])
251
            
252
            if "ITEM: BOX BOUNDS" in line:
253
                # save labels behind "ITEM: BOX BOUNDS" in triclinic case (>=lammps-7Jul09)
254
                tilt_items = line.split()[3:]
255
                for i in range(3):
256
                    line = f.readline()
257
                    fields = line.split()
258
                    lo.append(float(fields[0]))
259
                    hi.append(float(fields[1]))
260
                    if (len(fields) >= 3):
261
                        tilt.append(float(fields[2]))
262
            
263
            if "ITEM: ATOMS" in line:
264
                # (reliably) identify values by labels behind "ITEM: ATOMS" - requires >=lammps-7Jul09
265
                # create corresponding index dictionary before iterating over atoms to (hopefully) speed up lookups...
266
                atom_attributes = {}
267
                for (i, x) in enumerate(line.split()[2:]):
268
                    atom_attributes[x] = i
269
                for n in range(n_atoms):
270
                    line = f.readline()
271
                    fields = line.split()
272
                    id.append( int(fields[atom_attributes['id']]) )
273
                    type.append( int(fields[atom_attributes['type']]) )
274
                    positions.append( [ float(fields[atom_attributes[x]]) for x in ['x', 'y', 'z'] ] )
275
                    velocities.append( [ float(fields[atom_attributes[x]]) for x in ['vx', 'vy', 'vz'] ] )
276
                    forces.append( [ float(fields[atom_attributes[x]]) for x in ['fx', 'fy', 'fz'] ] )
277
        f.close()
278

    
279
        # determine cell tilt (triclinic case!)
280
        if (len(tilt) >= 3):
281
            # for >=lammps-7Jul09 use labels behind "ITEM: BOX BOUNDS" to assign tilt (vector) elements ...
282
            if (len(tilt_items) >= 3):
283
                xy = tilt[tilt_items.index('xy')]
284
                xz = tilt[tilt_items.index('xz')]
285
                yz = tilt[tilt_items.index('yz')]
286
            # ... otherwise assume default order in 3rd column (if the latter was present)
287
            else:
288
                xy = tilt[0]
289
                xz = tilt[1]
290
                yz = tilt[2]
291
        else:
292
            xy = xz = yz = 0
293
        xhilo = (hi[0] - lo[0]) - xy - xz
294
        yhilo = (hi[1] - lo[1]) - yz
295
        zhilo = (hi[2] - lo[2])
296
        
297
        # The simulation box bounds are included in each snapshot and if the box is triclinic (non-orthogonal), 
298
        # then the tilt factors are also printed; see the region prism command for a description of tilt factors. 
299
        # For triclinic boxes the box bounds themselves (first 2 quantities on each line) are a true "bounding box" 
300
        # around the simulation domain, which means they include the effect of any tilt.
301
        # [ http://lammps.sandia.gov/doc/dump.html , lammps-7Jul09 ]
302
        #
303
        # This *should* extract the lattice vectors that LAMMPS uses from the true "bounding box" printed in the dump file
304
        # It might fail in some cases (negative tilts?!) due to the MIN / MAX construction of these box corners:
305
        #
306
        #        void Domain::set_global_box() 
307
        #        [...]
308
        #          if (triclinic) {
309
        #            [...]
310
        #            boxlo_bound[0] = MIN(boxlo[0],boxlo[0]+xy);
311
        #            boxlo_bound[0] = MIN(boxlo_bound[0],boxlo_bound[0]+xz);
312
        #            boxlo_bound[1] = MIN(boxlo[1],boxlo[1]+yz);
313
        #            boxlo_bound[2] = boxlo[2];
314
        #
315
        #            boxhi_bound[0] = MAX(boxhi[0],boxhi[0]+xy);
316
        #            boxhi_bound[0] = MAX(boxhi_bound[0],boxhi_bound[0]+xz);
317
        #            boxhi_bound[1] = MAX(boxhi[1],boxhi[1]+yz);
318
        #            boxhi_bound[2] = boxhi[2];
319
        #          }
320
        # [ lammps-7Jul09/src/domain.cpp ]
321
        #
322
        cell = [[xhilo,0,0],[xy,yhilo,0],[xz,yz,zhilo]]
323

    
324
#       print n_atoms
325
#       print lo
326
#       print hi
327
#       print id
328
#       print type
329
#       print positions
330
#       print velocities
331
#       print forces
332

    
333
        # assume that LAMMPS does not reorder atoms internally
334
#        print np.array(positions)
335
#        print self.atoms.get_positions()
336
#        print np.array(positions) - self.atoms.get_positions()
337

    
338
        cell_atoms = np.array(cell)
339
        type_atoms = np.array(type)
340
#      velocities_atoms = np.array(velocities)
341
        positions_atoms = np.array(positions)
342
        forces_atoms = np.array(forces)
343

    
344
        if self.atoms:
345
            cell_atoms = self.atoms.get_cell()
346

    
347
            rotation_lammps2ase = np.dot(np.linalg.inv(np.array(cell)), cell_atoms)
348
#           print "rotation_lammps2ase:"
349
#           print rotation_lammps2ase
350
#           print np.transpose(rotation_lammps2ase)
351
#           print np.linalg.det(rotation_lammps2ase)                                            # check for orthogonality of matrix 'rotation'
352
#           print np.dot(rotation_lammps2ase, np.transpose(rotation_lammps2ase))                # check for orthogonality of matrix 'rotation'
353

    
354
            type_atoms = self.atoms.get_atomic_numbers()
355
            positions_atoms = np.array( [np.dot(np.array(r), rotation_lammps2ase) for r in positions] )
356
            velocities_atoms = np.array( [np.dot(np.array(v), rotation_lammps2ase) for v in velocities] )
357
            forces_atoms = np.array( [np.dot(np.array(f), rotation_lammps2ase) for f in forces] )
358

    
359
        if (set_atoms):
360
            # assume periodic boundary conditions here (like also below in write_lammps) <- TODO:?!
361
            self.atoms = Atoms(type_atoms, positions=positions_atoms, cell=cell_atoms, pbc=True)
362

    
363
        self.forces = forces_atoms
364

    
365

    
366
# could perhaps go into io.lammps in the future...
367
#from ase.parallel import paropen
368
def write_lammps(fileobj, atoms, force_skew=False):
369
    """Method which writes atomic structure data to a LAMMPS data file."""
370
    if isinstance(fileobj, file):
371
        f = fileobj
372
    elif isinstance(fileobj, str):
373
        f = paropen(fileobj, 'w')
374
    else :
375
        raise TypeError('fileobj is not of type file or str!')
376

    
377
    if isinstance(atoms, list):
378
        if len(atoms) > 1:
379
            raise ValueError('Can only write one configuration to a lammps data file!')
380
        atoms = atoms[0]
381

    
382
    f.write(f.name + " (written by ASE) \n\n")
383

    
384
    symbols = atoms.get_chemical_symbols()
385
    n_atoms = len(symbols)
386
    f.write("%d \t atoms \n" % n_atoms)
387

    
388
    # Uniqify 'symbols' list and sort to a new list 'species'.
389
    # Sorting is important in case of multi-component systems:
390
    # This way it is assured that LAMMPS atom types are always
391
    # assigned predictively according to the alphabetic order 
392
    # of the species present in the current system. 
393
    # (Hence e.g. the mapping in interaction statements for alloy
394
    #  potentials depending on the order of the elements in the
395
    #  external potential can also be safely adjusted accordingly.)
396
    species = sorted(list(set(symbols)))
397
    n_atom_types = len(species)
398
    f.write("%d \t atom types \n" % n_atom_types)
399

    
400

    
401
    ### cell -> bounding box for LAMMPS
402

    
403
    ## A) simple version
404
    # - For orthogonal cells which are properly aligned with the reference coordinate system only.
405
#    cell = atoms.get_cell()
406
#    lo = [0.0, 0.0, 0.0]
407
#    hi = sum(cell[0:3])
408
#    f.write("%15.8f %15.8f \t xlo xhi \n" % (lo[0], hi[0]))
409
#    f.write("%15.8f %15.8f \t ylo yhi \n" % (lo[1], hi[1]))
410
#    f.write("%15.8f %15.8f \t zlo zhi \n" % (lo[2], hi[2]))
411
#    f.write("\n\n")
412

    
413
    ## B) convert to (perhaps) triclinic cells in LAMMPS
414
    
415
    # B.1) calculate lengths of and angles between cell vectors
416
    #     (conventional crystallographic nomenclature)
417
    cell = atoms.get_cell()
418
    a = np.linalg.norm(cell[0])
419
    b = np.linalg.norm(cell[1])
420
    c = np.linalg.norm(cell[2])
421
    alpha = np.arccos( np.vdot(cell[1], cell[2]) / (b * c) )
422
    beta = np.arccos( np.vdot(cell[0], cell[2]) / (a * c) )
423
    gamma = np.arccos( np.vdot(cell[0], cell[1]) / (a * b) )
424
#    print [a, b, c]
425
#    print map(np.degrees, [alpha, beta, gamma])
426

    
427
    # B.2) construct bounding box edges and skew vector of 
428
    #     corresponding (perhaps rotated!) LAMMPS cell
429
    # http://lammps.sandia.gov/doc/read_data.html :
430
    #         a_LAMMPS = (xhi-xlo,0,0); b_LAMMPS = (xy,yhi-ylo,0); c_LAMMPS = (xz,yz,zhi-zlo)
431
    xlo = ylo = zlo = 0.0
432
    # this choice of origin simplifies things:
433
    #         a_LAMMPS = (xhi,0,0); b_LAMMPS = (xy,yhi,0); c_LAMMPS = (xz,yz,zhi)
434
    xhi = a                        # a_LAMMPS
435
    xy = np.cos(gamma) * b        # b_LAMMPS
436
    yhi = np.sin(gamma) * b
437
    xz = np.cos(beta) * c        # c_LAMMPS
438
    yz = ( b * c * np.cos(alpha) - xy * xz ) / yhi
439
    zhi = np.sqrt( c**2 - xz**2 - yz**2 )
440
    
441
    # B.3) obtain rotation of original cell with respect to LAMMPS cell
442
    #     to properly tranform the atoms contained within (see below!)
443
    # IMPORTANT: use same convention as in cell of ASE atoms object, i.e.
444
    #            cell vectors are ROW VECTORS in numpy array
445
    cell_lammps = np.array([[xhi-xlo,0,0],[xy,yhi-ylo,0],[xz,yz,zhi-zlo]])
446
#    a_lammps = np.linalg.norm(cell_lammps[0])
447
#    b_lammps = np.linalg.norm(cell_lammps[1])
448
#    c_lammps = np.linalg.norm(cell_lammps[2])
449
#    alpha_lammps = np.arccos( np.vdot(cell_lammps[1], cell_lammps[2]) / (b_lammps * c_lammps) )
450
#    beta_lammps = np.arccos( np.vdot(cell_lammps[0], cell_lammps[2]) / (a_lammps * c_lammps) )
451
#    gamma_lammps = np.arccos( np.vdot(cell_lammps[0], cell_lammps[1]) / (a_lammps * b_lammps) )
452
#    print [a_lammps, b_lammps, c_lammps]
453
#    print map(np.degrees, [alpha_lammps, beta_lammps, gamma_lammps])
454
    # IMPORTANT: need vector-(rotation-)matrix product (instead of matrix-vector product) here,
455
    #            cell vectors are ROW VECTORS (also see above)
456
    rotation = np.dot(np.linalg.inv(cell), cell_lammps)
457
#    print rotation
458
#    print np.transpose(rotation)
459
#    print np.linalg.det(rotation)                                # check for orthogonality of matrix 'rotation'
460
#    print np.dot(rotation, np.transpose(rotation))                # check for orthogonality of matrix 'rotation'
461
#    print np.dot(rotation, cell[0])
462
#    print np.dot(rotation, cell[1])
463
#    print np.dot(rotation, cell[2])
464

    
465
    # B.4.1) write bounding box edges
466
    f.write("%15.8f %15.8f \t\t\t xlo xhi \n" % (xlo, xhi))
467
    f.write("%15.8f %15.8f \t\t\t ylo yhi \n" % (ylo, yhi))
468
    f.write("%15.8f %15.8f \t\t\t zlo zhi \n" % (zlo, zhi))
469
    
470
    # B.4.2) sanitize and write skew vector (if necessary)
471
    # The too simple version 
472
    #    f.write("%20.10f %20.10f %20.10f \t xy xz yz \n" % (xy, xz, yz))    
473
    # can make LAMMPS (easily) reject the definition of a triclinic box because
474
    #        a) skew vector components calculated above are outside the respective ranges 
475
    #           which LAMMPS expects as described here
476
    #                http://lammps.sandia.gov/doc/read_data.html
477
    #           and coded here
478
    #                domain.cpp -> Domain::set_initial_box()
479
    #         b) rounding up in the skew vector output can make the result violate those 
480
    #      ranges for purely numeric reasons (perhaps even more frequent problem than a)
481
    #      and definitely more stupid!)
482
    # More elaborate solution addressing the issues described above:
483
    # -> a):
484
    # Fold back skew vector components calculated above to equivalent ones complying with
485
    # what LAMMPS expects (see references above) and describing another ("less skewed") 
486
    # unit cell for the same lattice:
487
    #                t -> t_folded in [-period/2, +period/2]
488
    def fold_skew_dec(t, period) :
489
        t_dec = dec.Decimal(repr(t))
490
        period_dec = dec.Decimal(repr(period))
491
        # dec.ROUND_HALF_DOWN leaves all t in [-period/2, +period/2] unchanged whereas
492
        # dec.ROUND_HALF_UP does the same for all t in (-period/2, +period/2) but
493
        # swaps the boundary values: 
494
        #        t=-period/2 -> t_folded=+period/2
495
        #        t=+period/2 -> t_folded=-period/2
496
        t_folded_dec = t_dec - period_dec * (t_dec/period_dec).to_integral_value(dec.ROUND_HALF_DOWN)
497
        return t_folded_dec
498
    skew_folded_dec = [ fold_skew_dec(xy, xhi-xlo), fold_skew_dec(xz, xhi-xlo), fold_skew_dec(yz, yhi-ylo) ]
499
    # -> b):
500
    # This should be kept in sync with the accuracy used for writing the bounding box edges above
501
    prec_dec = dec.Decimal('1E-8')
502
    # Make sure to always round down and hence avoid numerical problems with skew vector in LAMMPS.
503
    skew_folded_and_rounded_dec = [ d.quantize(prec_dec, dec.ROUND_DOWN) for d in skew_folded_dec ]
504
    # Do not write zero skew vector. 
505
    # (I.e. calculate orthogonal cell with LAMMPS in that case!)
506
    has_skew = any( [not d.is_zero() for d in skew_folded_and_rounded_dec] )
507
    if force_skew or has_skew:
508
        f.write( "%15s %15s %15s \t xy xz yz \n" % tuple( str(d) for d in skew_folded_and_rounded_dec ) )
509
    f.write("\n\n")
510

    
511

    
512
    ### atoms
513

    
514
    ## A) simple version
515
    # - For orthogonal cells which are properly aligned with the reference coordinate system only.
516
#    f.write("Atoms \n\n")
517
#    for i, (x, y, z) in enumerate(atoms.get_positions()):
518
#        s = species.index(symbols[i]) + 1
519
#        f.write("%6d %3d %22.15f %22.15f %22.15f \n" % (i+1, s, x, y, z))
520

    
521
    ## B) convert to (perhaps) triclinic cells in LAMMPS:
522
    # - Apply rotation determined above for this case.
523
    # - Lattice translation in case of a folded skew vector above should not be necessary,
524
    #   since the resulting (perhaps) new unit cell does describe the same lattice.
525
    #   Therefore the (rotated) atoms are still at correct lattice sites, only some of them
526
    #   probably are outside of that new unit cell, which LAMMPS can hopefully deal with.
527
    f.write("Atoms \n\n")
528
    for i, r in enumerate(atoms.get_positions()):
529
        s = species.index(symbols[i]) + 1
530
        [x, y, z] = np.dot(r, rotation)
531
#        print r, [x, y, z]
532
        f.write("%6d %3d %22.15f %22.15f %22.15f \n" % (i+1, s, x, y, z))
533

    
534
    f.close()
535

    
536

    
537
if __name__ == "__main__":
538

    
539
    pair_style = "eam"
540
    Pd_eam_file = "Pd_u3.eam"
541
    pair_coeff = [ "* * " + Pd_eam_file ]
542
    parameters = { "pair_style" : pair_style, "pair_coeff" : pair_coeff }
543
    files = [ Pd_eam_file ]
544
    calc = LAMMPS(parameters=parameters, files=files)
545

    
546
    from ase import Atoms
547
    a0 = 3.93
548
    b0 = a0 / 2.0
549
    if True:
550
        bulk = Atoms(['Pd']*4,
551
                     positions=[(0,0,0),(b0,b0,0),(b0,0,b0),(0,b0,b0)],
552
                     cell=[a0]*3,
553
                     pbc=True)
554
        # test get_forces
555
        print "forces for a = %f" % a0
556
        print calc.get_forces(bulk)
557
        # single points for various lattice constants
558
        bulk.set_calculator(calc)
559
        for n in range(-5,5,1):
560
            a = a0 * (1 + n/100.0)
561
            bulk.set_cell([a]*3)
562
            print "a : %f , total energy : %f" % (a, bulk.get_potential_energy())
563

    
564
#    calc.clean(force=True)
565
    calc.clean()
566

    
567

    
568