Statistiques
| Révision :

root / ase / calculators / lammps.py @ 20

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

1 4 tkerber
#!/usr//bin/env python
2 3 tkerber
3 4 tkerber
# lammps.py (2011/02/22)
4 4 tkerber
# An ASE calculator for the LAMMPS classical MD code available from
5 4 tkerber
#       http://lammps.sandia.gov/
6 4 tkerber
# The environment variable LAMMPS_COMMAND must be defined to point to the LAMMPS binary.
7 4 tkerber
#
8 4 tkerber
# Copyright (C) 2009 - 2011 Joerg Meyer, joerg.meyer@ch.tum.de
9 4 tkerber
#
10 4 tkerber
# This library is free software; you can redistribute it and/or
11 4 tkerber
# modify it under the terms of the GNU Lesser General Public
12 4 tkerber
# License as published by the Free Software Foundation; either
13 4 tkerber
# version 2.1 of the License, or (at your option) any later version.
14 4 tkerber
#
15 4 tkerber
# This library is distributed in the hope that it will be useful,
16 4 tkerber
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17 4 tkerber
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 4 tkerber
# Lesser General Public License for more details.
19 4 tkerber
#
20 4 tkerber
# You should have received a copy of the GNU Lesser General Public
21 4 tkerber
# License along with this file; if not, write to the Free Software
22 4 tkerber
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 4 tkerber
# or see <http://www.gnu.org/licenses/>.
24 3 tkerber
25 4 tkerber
import os
26 4 tkerber
import shutil
27 4 tkerber
import time
28 4 tkerber
import math
29 4 tkerber
import decimal as dec
30 4 tkerber
import numpy as np
31 4 tkerber
from ase import Atoms
32 4 tkerber
from ase.parallel import paropen
33 4 tkerber
34 4 tkerber
35 4 tkerber
class LAMMPS:
36 4 tkerber
37 4 tkerber
    def __init__(self, label="lammps", dir="LAMMPS", parameters={}, files=[], always_triclinic=False):
38 4 tkerber
        self.label = label
39 4 tkerber
        self.dir = dir
40 4 tkerber
        self.parameters = parameters
41 4 tkerber
        self.files = files
42 4 tkerber
        self.always_triclinic = always_triclinic
43 4 tkerber
        self.calls = 0
44 4 tkerber
        self.potential_energy = None
45 4 tkerber
        self.forces = None
46 4 tkerber
47 4 tkerber
        if os.path.isdir(self.dir):
48 4 tkerber
            if (os.listdir(self.dir) == []):
49 4 tkerber
                os.rmdir(self.dir)
50 4 tkerber
            else:
51 4 tkerber
                os.rename(self.dir, self.dir + ".bak-" + time.strftime("%Y%m%d-%H%M%S"))
52 4 tkerber
        os.mkdir(self.dir, 0755)
53 3 tkerber
54 4 tkerber
        for f in files:
55 4 tkerber
            shutil.copy(f, os.path.join(self.dir, f))
56 3 tkerber
57 4 tkerber
    def clean(self, force=False):
58 4 tkerber
        if os.path.isdir(self.dir):
59 4 tkerber
            files_in_dir = os.listdir(self.dir)
60 4 tkerber
            files_copied = self.files
61 4 tkerber
            if (len(files_in_dir) == 0):
62 4 tkerber
                os.rmdir(self.dir)
63 4 tkerber
            elif (set(files_in_dir).issubset(set(files_copied)) or force):
64 4 tkerber
                shutil.rmtree(self.dir)
65 4 tkerber
66 4 tkerber
    def get_potential_energy(self, atoms):
67 4 tkerber
        self.update(atoms)
68 4 tkerber
        return self.potential_energy
69 4 tkerber
70 4 tkerber
    def get_forces(self, atoms):
71 4 tkerber
        self.update(atoms)
72 4 tkerber
        return self.forces.copy()
73 4 tkerber
74 4 tkerber
    def get_stress(self, atoms):
75 4 tkerber
        raise NotImplementedError
76 4 tkerber
77 4 tkerber
    def update(self, atoms):
78 4 tkerber
        # TODO: check if (re-)calculation is necessary
79 4 tkerber
        self.calculate(atoms)
80 4 tkerber
81 4 tkerber
    def calculate(self, atoms):
82 4 tkerber
        self.atoms = atoms.copy()
83 4 tkerber
        self.run()
84 4 tkerber
85 4 tkerber
    def run(self):
86 4 tkerber
        """Method which explicitely runs LAMMPS."""
87 4 tkerber
88 4 tkerber
        self.calls += 1
89 4 tkerber
90 4 tkerber
        # set LAMMPS command from environment variable
91 4 tkerber
        if os.environ.has_key('LAMMPS_COMMAND'):
92 4 tkerber
            lammps_command = os.path.abspath(os.environ['LAMMPS_COMMAND'])
93 4 tkerber
        else:
94 4 tkerber
            self.clean()
95 4 tkerber
            raise RuntimeError('Please set LAMMPS_COMMAND environment variable')
96 4 tkerber
        if os.environ.has_key('LAMMPS_OPTIONS'):
97 4 tkerber
            lammps_options = os.environ['LAMMPS_OPTIONS']
98 4 tkerber
        else:
99 4 tkerber
            lammps_options = "-echo log -screen none"
100 4 tkerber
101 4 tkerber
        # change into subdirectory for LAMMPS calculations
102 4 tkerber
        cwd = os.getcwd()
103 4 tkerber
        os.chdir(self.dir)
104 4 tkerber
105 4 tkerber
        # setup file names for LAMMPS calculation
106 4 tkerber
        label = "%s-%06d" % (self.label, self.calls)
107 4 tkerber
        lammps_data = "data." + label
108 4 tkerber
        lammps_in = "in." + label
109 4 tkerber
        lammps_trj = label + ".lammpstrj"
110 4 tkerber
        lammps_log = label + ".log"
111 4 tkerber
112 4 tkerber
        # write LAMMPS input files
113 4 tkerber
        self.write_lammps_data(lammps_data=lammps_data)
114 4 tkerber
        self.write_lammps_in(lammps_in=lammps_in, lammps_data=lammps_data, lammps_trj=lammps_trj, parameters=self.parameters)
115 4 tkerber
116 4 tkerber
        # run LAMMPS
117 4 tkerber
        # TODO: check for successful completion (based on output files?!) of LAMMPS call...
118 4 tkerber
        exitcode = os.system("%s %s -in %s -log %s" % (lammps_command, lammps_options, lammps_in, lammps_log))
119 4 tkerber
        if exitcode != 0:
120 4 tkerber
            cwd = os.getcwd()
121 4 tkerber
            raise RuntimeError("LAMMPS exited in %s with exit code: %d.  " % (cwd,exitcode))
122 4 tkerber
123 4 tkerber
        # read LAMMPS output files
124 4 tkerber
        self.read_lammps_log(lammps_log=lammps_log)
125 4 tkerber
        self.read_lammps_trj(lammps_trj=lammps_trj)
126 4 tkerber
127 4 tkerber
        # change back to previous working directory
128 4 tkerber
        os.chdir(cwd)
129 4 tkerber
130 4 tkerber
    def write_lammps_data(self, lammps_data=None):
131 4 tkerber
        """Method which writes a LAMMPS data file with atomic structure."""
132 4 tkerber
        if (lammps_data == None):
133 4 tkerber
            lammps_data = "data." + self.label
134 4 tkerber
        write_lammps(lammps_data, self.atoms, force_skew=self.always_triclinic)
135 4 tkerber
136 4 tkerber
    def write_lammps_in(self, lammps_in=None, lammps_data=None, lammps_trj=None, parameters={}):
137 4 tkerber
        """Method which writes a LAMMPS in file with run parameters and settings."""
138 4 tkerber
        if (lammps_in == None):
139 4 tkerber
            lammps_in = "in." + self.label
140 4 tkerber
        if (lammps_data == None):
141 4 tkerber
            lammps_data = "data." + self.label
142 4 tkerber
        if (lammps_trj == None):
143 4 tkerber
            lammps_trj = self.label + ".lammpstrj"
144 4 tkerber
145 4 tkerber
        f = paropen(lammps_in, 'w')
146 4 tkerber
        f.write("# " + f.name + " (written by ASE) \n")
147 4 tkerber
        f.write("\n")
148 4 tkerber
149 4 tkerber
        f.write("### variables \n")
150 4 tkerber
        f.write("variable data_file index \"%s\" \n" % lammps_data)
151 4 tkerber
        f.write("variable dump_file index \"%s\" \n" % lammps_trj)
152 4 tkerber
        f.write("\n\n")
153 4 tkerber
154 4 tkerber
        pbc = self.atoms.get_pbc()
155 4 tkerber
        f.write("### simulation box \n")
156 4 tkerber
        f.write("units metal \n")
157 4 tkerber
        if ("boundary" in parameters):
158 4 tkerber
            f.write("boundary %s \n" % parameters["boundary"])
159 4 tkerber
        else:
160 4 tkerber
            f.write("boundary %c %c %c \n" % tuple('sp'[x] for x in pbc))
161 4 tkerber
        f.write("atom_modify sort 0 0.0 \n")
162 4 tkerber
        for key in ("neighbor" ,"newton"):
163 4 tkerber
            if key in parameters:
164 4 tkerber
                f.write("%s %s \n" % (key, parameters[key]))
165 4 tkerber
        f.write("\n")
166 4 tkerber
        f.write("read_data ${data_file} \n")
167 4 tkerber
        f.write("\n\n")
168 4 tkerber
169 4 tkerber
        f.write("### interactions \n")
170 4 tkerber
        if ( ("pair_style" in parameters) and ("pair_coeff" in parameters) ):
171 4 tkerber
            pair_style = parameters["pair_style"]
172 4 tkerber
            f.write("pair_style %s \n" % pair_style)
173 4 tkerber
            for pair_coeff in parameters["pair_coeff"]:
174 4 tkerber
                f.write("pair_coeff %s \n" % pair_coeff)
175 4 tkerber
            if "mass" in parameters:
176 4 tkerber
                for mass in parameters["mass"]:
177 4 tkerber
                    f.write("mass %s \n" % mass)
178 4 tkerber
        else:
179 4 tkerber
            # simple default parameters that should always make the LAMMPS calculation run
180 4 tkerber
            #    pair_style         lj/cut 2.5
181 4 tkerber
            #    pair_coeff         * * 1 1
182 4 tkerber
            #    mass           * 1.0        else:
183 4 tkerber
            f.write("pair_style lj/cut 2.5 \n")
184 4 tkerber
            f.write("pair_coeff * * 1 1 \n")
185 4 tkerber
            f.write("mass * 1.0 \n")
186 4 tkerber
        f.write("\n")
187 4 tkerber
188 4 tkerber
        f.write("### run \n")
189 4 tkerber
        f.write("fix fix_nve all nve \n")
190 4 tkerber
        f.write("\n")
191 4 tkerber
        f.write("dump dump_all all custom 1 ${dump_file} id type x y z vx vy vz fx fy fz \n")
192 4 tkerber
        f.write("\n")
193 4 tkerber
        f.write("thermo_style custom step temp ke pe etotal cpu \n")
194 4 tkerber
        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 4 tkerber
        f.write("thermo 1 \n")
196 4 tkerber
        f.write("\n")
197 4 tkerber
        if ("minimize" in parameters):
198 4 tkerber
            f.write("minimize %s \n" % parameters["minimize"])
199 4 tkerber
        if ("run" in parameters):
200 4 tkerber
            f.write("run %s \n" % parameters["run"])
201 4 tkerber
        if not ( ("minimize" in parameters) or ("run" in parameters) ):
202 4 tkerber
            f.write("run 0 \n")
203 4 tkerber
204 4 tkerber
        f.close()
205 4 tkerber
206 4 tkerber
    def read_lammps_log(self, lammps_log=None):
207 4 tkerber
        """Method which reads a LAMMPS output log file."""
208 4 tkerber
        if (lammps_log == None):
209 4 tkerber
            lammps_log = self.label + ".log"
210 4 tkerber
211 4 tkerber
        epot = 0.0
212 4 tkerber
213 4 tkerber
        f = paropen(lammps_log, 'r')
214 4 tkerber
        while True:
215 4 tkerber
            line = f.readline()
216 4 tkerber
            if not line:
217 4 tkerber
                break
218 4 tkerber
            # get potential energy of first step (if more are done)
219 4 tkerber
            if "PotEng" in line:
220 4 tkerber
                i = line.split().index("PotEng")
221 4 tkerber
                line = f.readline()
222 4 tkerber
                epot = float(line.split()[i])
223 4 tkerber
        f.close()
224 4 tkerber
225 4 tkerber
#        print epot
226 4 tkerber
227 4 tkerber
        self.potential_energy = epot
228 4 tkerber
229 4 tkerber
    def read_lammps_trj(self, lammps_trj=None, set_atoms=False):
230 4 tkerber
        """Method which reads a LAMMPS dump file."""
231 4 tkerber
        if (lammps_trj == None):
232 4 tkerber
            lammps_trj = self.label + ".lammpstrj"
233 4 tkerber
234 4 tkerber
        f = paropen(lammps_trj, 'r')
235 4 tkerber
        while True:
236 4 tkerber
            line = f.readline()
237 4 tkerber
238 4 tkerber
            if not line:
239 4 tkerber
                break
240 4 tkerber
241 4 tkerber
            #TODO: extend to proper dealing with multiple steps in one trajectory file
242 4 tkerber
            if "ITEM: TIMESTEP" in line:
243 4 tkerber
                n_atoms = 0
244 4 tkerber
                lo = [] ; hi = [] ; tilt = []
245 4 tkerber
                id = [] ; type = []
246 4 tkerber
                positions = [] ; velocities = [] ; forces = []
247 4 tkerber
248 4 tkerber
            if "ITEM: NUMBER OF ATOMS" in line:
249 4 tkerber
                line = f.readline()
250 4 tkerber
                n_atoms = int(line.split()[0])
251 4 tkerber
252 4 tkerber
            if "ITEM: BOX BOUNDS" in line:
253 4 tkerber
                # save labels behind "ITEM: BOX BOUNDS" in triclinic case (>=lammps-7Jul09)
254 4 tkerber
                tilt_items = line.split()[3:]
255 4 tkerber
                for i in range(3):
256 4 tkerber
                    line = f.readline()
257 4 tkerber
                    fields = line.split()
258 4 tkerber
                    lo.append(float(fields[0]))
259 4 tkerber
                    hi.append(float(fields[1]))
260 4 tkerber
                    if (len(fields) >= 3):
261 4 tkerber
                        tilt.append(float(fields[2]))
262 4 tkerber
263 4 tkerber
            if "ITEM: ATOMS" in line:
264 4 tkerber
                # (reliably) identify values by labels behind "ITEM: ATOMS" - requires >=lammps-7Jul09
265 4 tkerber
                # create corresponding index dictionary before iterating over atoms to (hopefully) speed up lookups...
266 4 tkerber
                atom_attributes = {}
267 4 tkerber
                for (i, x) in enumerate(line.split()[2:]):
268 4 tkerber
                    atom_attributes[x] = i
269 4 tkerber
                for n in range(n_atoms):
270 4 tkerber
                    line = f.readline()
271 4 tkerber
                    fields = line.split()
272 4 tkerber
                    id.append( int(fields[atom_attributes['id']]) )
273 4 tkerber
                    type.append( int(fields[atom_attributes['type']]) )
274 4 tkerber
                    positions.append( [ float(fields[atom_attributes[x]]) for x in ['x', 'y', 'z'] ] )
275 4 tkerber
                    velocities.append( [ float(fields[atom_attributes[x]]) for x in ['vx', 'vy', 'vz'] ] )
276 4 tkerber
                    forces.append( [ float(fields[atom_attributes[x]]) for x in ['fx', 'fy', 'fz'] ] )
277 4 tkerber
        f.close()
278 4 tkerber
279 4 tkerber
        # determine cell tilt (triclinic case!)
280 4 tkerber
        if (len(tilt) >= 3):
281 4 tkerber
            # for >=lammps-7Jul09 use labels behind "ITEM: BOX BOUNDS" to assign tilt (vector) elements ...
282 4 tkerber
            if (len(tilt_items) >= 3):
283 4 tkerber
                xy = tilt[tilt_items.index('xy')]
284 4 tkerber
                xz = tilt[tilt_items.index('xz')]
285 4 tkerber
                yz = tilt[tilt_items.index('yz')]
286 4 tkerber
            # ... otherwise assume default order in 3rd column (if the latter was present)
287 4 tkerber
            else:
288 4 tkerber
                xy = tilt[0]
289 4 tkerber
                xz = tilt[1]
290 4 tkerber
                yz = tilt[2]
291 4 tkerber
        else:
292 4 tkerber
            xy = xz = yz = 0
293 4 tkerber
        xhilo = (hi[0] - lo[0]) - xy - xz
294 4 tkerber
        yhilo = (hi[1] - lo[1]) - yz
295 4 tkerber
        zhilo = (hi[2] - lo[2])
296 4 tkerber
297 4 tkerber
        # The simulation box bounds are included in each snapshot and if the box is triclinic (non-orthogonal),
298 4 tkerber
        # then the tilt factors are also printed; see the region prism command for a description of tilt factors.
299 4 tkerber
        # For triclinic boxes the box bounds themselves (first 2 quantities on each line) are a true "bounding box"
300 4 tkerber
        # around the simulation domain, which means they include the effect of any tilt.
301 4 tkerber
        # [ http://lammps.sandia.gov/doc/dump.html , lammps-7Jul09 ]
302 4 tkerber
        #
303 4 tkerber
        # This *should* extract the lattice vectors that LAMMPS uses from the true "bounding box" printed in the dump file
304 4 tkerber
        # It might fail in some cases (negative tilts?!) due to the MIN / MAX construction of these box corners:
305 4 tkerber
        #
306 4 tkerber
        #        void Domain::set_global_box()
307 4 tkerber
        #        [...]
308 4 tkerber
        #          if (triclinic) {
309 4 tkerber
        #            [...]
310 4 tkerber
        #            boxlo_bound[0] = MIN(boxlo[0],boxlo[0]+xy);
311 4 tkerber
        #            boxlo_bound[0] = MIN(boxlo_bound[0],boxlo_bound[0]+xz);
312 4 tkerber
        #            boxlo_bound[1] = MIN(boxlo[1],boxlo[1]+yz);
313 4 tkerber
        #            boxlo_bound[2] = boxlo[2];
314 4 tkerber
        #
315 4 tkerber
        #            boxhi_bound[0] = MAX(boxhi[0],boxhi[0]+xy);
316 4 tkerber
        #            boxhi_bound[0] = MAX(boxhi_bound[0],boxhi_bound[0]+xz);
317 4 tkerber
        #            boxhi_bound[1] = MAX(boxhi[1],boxhi[1]+yz);
318 4 tkerber
        #            boxhi_bound[2] = boxhi[2];
319 4 tkerber
        #          }
320 4 tkerber
        # [ lammps-7Jul09/src/domain.cpp ]
321 4 tkerber
        #
322 4 tkerber
        cell = [[xhilo,0,0],[xy,yhilo,0],[xz,yz,zhilo]]
323 4 tkerber
324 4 tkerber
#       print n_atoms
325 4 tkerber
#       print lo
326 4 tkerber
#       print hi
327 4 tkerber
#       print id
328 4 tkerber
#       print type
329 4 tkerber
#       print positions
330 4 tkerber
#       print velocities
331 4 tkerber
#       print forces
332 4 tkerber
333 4 tkerber
        # assume that LAMMPS does not reorder atoms internally
334 4 tkerber
#        print np.array(positions)
335 4 tkerber
#        print self.atoms.get_positions()
336 4 tkerber
#        print np.array(positions) - self.atoms.get_positions()
337 4 tkerber
338 4 tkerber
        cell_atoms = np.array(cell)
339 4 tkerber
        type_atoms = np.array(type)
340 4 tkerber
#      velocities_atoms = np.array(velocities)
341 4 tkerber
        positions_atoms = np.array(positions)
342 4 tkerber
        forces_atoms = np.array(forces)
343 4 tkerber
344 4 tkerber
        if self.atoms:
345 4 tkerber
            cell_atoms = self.atoms.get_cell()
346 4 tkerber
347 4 tkerber
            rotation_lammps2ase = np.dot(np.linalg.inv(np.array(cell)), cell_atoms)
348 4 tkerber
#           print "rotation_lammps2ase:"
349 4 tkerber
#           print rotation_lammps2ase
350 4 tkerber
#           print np.transpose(rotation_lammps2ase)
351 4 tkerber
#           print np.linalg.det(rotation_lammps2ase)                                            # check for orthogonality of matrix 'rotation'
352 4 tkerber
#           print np.dot(rotation_lammps2ase, np.transpose(rotation_lammps2ase))                # check for orthogonality of matrix 'rotation'
353 4 tkerber
354 4 tkerber
            type_atoms = self.atoms.get_atomic_numbers()
355 4 tkerber
            positions_atoms = np.array( [np.dot(np.array(r), rotation_lammps2ase) for r in positions] )
356 4 tkerber
            velocities_atoms = np.array( [np.dot(np.array(v), rotation_lammps2ase) for v in velocities] )
357 4 tkerber
            forces_atoms = np.array( [np.dot(np.array(f), rotation_lammps2ase) for f in forces] )
358 4 tkerber
359 4 tkerber
        if (set_atoms):
360 4 tkerber
            # assume periodic boundary conditions here (like also below in write_lammps) <- TODO:?!
361 4 tkerber
            self.atoms = Atoms(type_atoms, positions=positions_atoms, cell=cell_atoms, pbc=True)
362 4 tkerber
363 4 tkerber
        self.forces = forces_atoms
364 4 tkerber
365 4 tkerber
366 4 tkerber
# could perhaps go into io.lammps in the future...
367 4 tkerber
#from ase.parallel import paropen
368 4 tkerber
def write_lammps(fileobj, atoms, force_skew=False):
369 4 tkerber
    """Method which writes atomic structure data to a LAMMPS data file."""
370 4 tkerber
    if isinstance(fileobj, file):
371 4 tkerber
        f = fileobj
372 4 tkerber
    elif isinstance(fileobj, str):
373 4 tkerber
        f = paropen(fileobj, 'w')
374 4 tkerber
    else :
375 4 tkerber
        raise TypeError('fileobj is not of type file or str!')
376 4 tkerber
377 4 tkerber
    if isinstance(atoms, list):
378 4 tkerber
        if len(atoms) > 1:
379 4 tkerber
            raise ValueError('Can only write one configuration to a lammps data file!')
380 4 tkerber
        atoms = atoms[0]
381 4 tkerber
382 4 tkerber
    f.write(f.name + " (written by ASE) \n\n")
383 4 tkerber
384 4 tkerber
    symbols = atoms.get_chemical_symbols()
385 4 tkerber
    n_atoms = len(symbols)
386 4 tkerber
    f.write("%d \t atoms \n" % n_atoms)
387 4 tkerber
388 4 tkerber
    # Uniqify 'symbols' list and sort to a new list 'species'.
389 4 tkerber
    # Sorting is important in case of multi-component systems:
390 4 tkerber
    # This way it is assured that LAMMPS atom types are always
391 4 tkerber
    # assigned predictively according to the alphabetic order
392 4 tkerber
    # of the species present in the current system.
393 4 tkerber
    # (Hence e.g. the mapping in interaction statements for alloy
394 4 tkerber
    #  potentials depending on the order of the elements in the
395 4 tkerber
    #  external potential can also be safely adjusted accordingly.)
396 4 tkerber
    species = sorted(list(set(symbols)))
397 4 tkerber
    n_atom_types = len(species)
398 4 tkerber
    f.write("%d \t atom types \n" % n_atom_types)
399 4 tkerber
400 4 tkerber
401 4 tkerber
    ### cell -> bounding box for LAMMPS
402 4 tkerber
403 4 tkerber
    ## A) simple version
404 4 tkerber
    # - For orthogonal cells which are properly aligned with the reference coordinate system only.
405 4 tkerber
#    cell = atoms.get_cell()
406 4 tkerber
#    lo = [0.0, 0.0, 0.0]
407 4 tkerber
#    hi = sum(cell[0:3])
408 4 tkerber
#    f.write("%15.8f %15.8f \t xlo xhi \n" % (lo[0], hi[0]))
409 4 tkerber
#    f.write("%15.8f %15.8f \t ylo yhi \n" % (lo[1], hi[1]))
410 4 tkerber
#    f.write("%15.8f %15.8f \t zlo zhi \n" % (lo[2], hi[2]))
411 4 tkerber
#    f.write("\n\n")
412 4 tkerber
413 4 tkerber
    ## B) convert to (perhaps) triclinic cells in LAMMPS
414 4 tkerber
415 4 tkerber
    # B.1) calculate lengths of and angles between cell vectors
416 4 tkerber
    #     (conventional crystallographic nomenclature)
417 4 tkerber
    cell = atoms.get_cell()
418 4 tkerber
    a = np.linalg.norm(cell[0])
419 4 tkerber
    b = np.linalg.norm(cell[1])
420 4 tkerber
    c = np.linalg.norm(cell[2])
421 4 tkerber
    alpha = np.arccos( np.vdot(cell[1], cell[2]) / (b * c) )
422 4 tkerber
    beta = np.arccos( np.vdot(cell[0], cell[2]) / (a * c) )
423 4 tkerber
    gamma = np.arccos( np.vdot(cell[0], cell[1]) / (a * b) )
424 4 tkerber
#    print [a, b, c]
425 4 tkerber
#    print map(np.degrees, [alpha, beta, gamma])
426 4 tkerber
427 4 tkerber
    # B.2) construct bounding box edges and skew vector of
428 4 tkerber
    #     corresponding (perhaps rotated!) LAMMPS cell
429 4 tkerber
    # http://lammps.sandia.gov/doc/read_data.html :
430 4 tkerber
    #         a_LAMMPS = (xhi-xlo,0,0); b_LAMMPS = (xy,yhi-ylo,0); c_LAMMPS = (xz,yz,zhi-zlo)
431 4 tkerber
    xlo = ylo = zlo = 0.0
432 4 tkerber
    # this choice of origin simplifies things:
433 4 tkerber
    #         a_LAMMPS = (xhi,0,0); b_LAMMPS = (xy,yhi,0); c_LAMMPS = (xz,yz,zhi)
434 4 tkerber
    xhi = a                        # a_LAMMPS
435 4 tkerber
    xy = np.cos(gamma) * b        # b_LAMMPS
436 4 tkerber
    yhi = np.sin(gamma) * b
437 4 tkerber
    xz = np.cos(beta) * c        # c_LAMMPS
438 4 tkerber
    yz = ( b * c * np.cos(alpha) - xy * xz ) / yhi
439 4 tkerber
    zhi = np.sqrt( c**2 - xz**2 - yz**2 )
440 4 tkerber
441 4 tkerber
    # B.3) obtain rotation of original cell with respect to LAMMPS cell
442 4 tkerber
    #     to properly tranform the atoms contained within (see below!)
443 4 tkerber
    # IMPORTANT: use same convention as in cell of ASE atoms object, i.e.
444 4 tkerber
    #            cell vectors are ROW VECTORS in numpy array
445 4 tkerber
    cell_lammps = np.array([[xhi-xlo,0,0],[xy,yhi-ylo,0],[xz,yz,zhi-zlo]])
446 4 tkerber
#    a_lammps = np.linalg.norm(cell_lammps[0])
447 4 tkerber
#    b_lammps = np.linalg.norm(cell_lammps[1])
448 4 tkerber
#    c_lammps = np.linalg.norm(cell_lammps[2])
449 4 tkerber
#    alpha_lammps = np.arccos( np.vdot(cell_lammps[1], cell_lammps[2]) / (b_lammps * c_lammps) )
450 4 tkerber
#    beta_lammps = np.arccos( np.vdot(cell_lammps[0], cell_lammps[2]) / (a_lammps * c_lammps) )
451 4 tkerber
#    gamma_lammps = np.arccos( np.vdot(cell_lammps[0], cell_lammps[1]) / (a_lammps * b_lammps) )
452 4 tkerber
#    print [a_lammps, b_lammps, c_lammps]
453 4 tkerber
#    print map(np.degrees, [alpha_lammps, beta_lammps, gamma_lammps])
454 4 tkerber
    # IMPORTANT: need vector-(rotation-)matrix product (instead of matrix-vector product) here,
455 4 tkerber
    #            cell vectors are ROW VECTORS (also see above)
456 4 tkerber
    rotation = np.dot(np.linalg.inv(cell), cell_lammps)
457 4 tkerber
#    print rotation
458 4 tkerber
#    print np.transpose(rotation)
459 4 tkerber
#    print np.linalg.det(rotation)                                # check for orthogonality of matrix 'rotation'
460 4 tkerber
#    print np.dot(rotation, np.transpose(rotation))                # check for orthogonality of matrix 'rotation'
461 4 tkerber
#    print np.dot(rotation, cell[0])
462 4 tkerber
#    print np.dot(rotation, cell[1])
463 4 tkerber
#    print np.dot(rotation, cell[2])
464 4 tkerber
465 4 tkerber
    # B.4.1) write bounding box edges
466 4 tkerber
    f.write("%15.8f %15.8f \t\t\t xlo xhi \n" % (xlo, xhi))
467 4 tkerber
    f.write("%15.8f %15.8f \t\t\t ylo yhi \n" % (ylo, yhi))
468 4 tkerber
    f.write("%15.8f %15.8f \t\t\t zlo zhi \n" % (zlo, zhi))
469 4 tkerber
470 4 tkerber
    # B.4.2) sanitize and write skew vector (if necessary)
471 4 tkerber
    # The too simple version
472 4 tkerber
    #    f.write("%20.10f %20.10f %20.10f \t xy xz yz \n" % (xy, xz, yz))
473 4 tkerber
    # can make LAMMPS (easily) reject the definition of a triclinic box because
474 4 tkerber
    #        a) skew vector components calculated above are outside the respective ranges
475 4 tkerber
    #           which LAMMPS expects as described here
476 4 tkerber
    #                http://lammps.sandia.gov/doc/read_data.html
477 4 tkerber
    #           and coded here
478 4 tkerber
    #                domain.cpp -> Domain::set_initial_box()
479 4 tkerber
    #         b) rounding up in the skew vector output can make the result violate those
480 4 tkerber
    #      ranges for purely numeric reasons (perhaps even more frequent problem than a)
481 4 tkerber
    #      and definitely more stupid!)
482 4 tkerber
    # More elaborate solution addressing the issues described above:
483 4 tkerber
    # -> a):
484 4 tkerber
    # Fold back skew vector components calculated above to equivalent ones complying with
485 4 tkerber
    # what LAMMPS expects (see references above) and describing another ("less skewed")
486 4 tkerber
    # unit cell for the same lattice:
487 4 tkerber
    #                t -> t_folded in [-period/2, +period/2]
488 4 tkerber
    def fold_skew_dec(t, period) :
489 4 tkerber
        t_dec = dec.Decimal(repr(t))
490 4 tkerber
        period_dec = dec.Decimal(repr(period))
491 4 tkerber
        # dec.ROUND_HALF_DOWN leaves all t in [-period/2, +period/2] unchanged whereas
492 4 tkerber
        # dec.ROUND_HALF_UP does the same for all t in (-period/2, +period/2) but
493 4 tkerber
        # swaps the boundary values:
494 4 tkerber
        #        t=-period/2 -> t_folded=+period/2
495 4 tkerber
        #        t=+period/2 -> t_folded=-period/2
496 4 tkerber
        t_folded_dec = t_dec - period_dec * (t_dec/period_dec).to_integral_value(dec.ROUND_HALF_DOWN)
497 4 tkerber
        return t_folded_dec
498 4 tkerber
    skew_folded_dec = [ fold_skew_dec(xy, xhi-xlo), fold_skew_dec(xz, xhi-xlo), fold_skew_dec(yz, yhi-ylo) ]
499 4 tkerber
    # -> b):
500 4 tkerber
    # This should be kept in sync with the accuracy used for writing the bounding box edges above
501 4 tkerber
    prec_dec = dec.Decimal('1E-8')
502 4 tkerber
    # Make sure to always round down and hence avoid numerical problems with skew vector in LAMMPS.
503 4 tkerber
    skew_folded_and_rounded_dec = [ d.quantize(prec_dec, dec.ROUND_DOWN) for d in skew_folded_dec ]
504 4 tkerber
    # Do not write zero skew vector.
505 4 tkerber
    # (I.e. calculate orthogonal cell with LAMMPS in that case!)
506 4 tkerber
    has_skew = any( [not d.is_zero() for d in skew_folded_and_rounded_dec] )
507 4 tkerber
    if force_skew or has_skew:
508 4 tkerber
        f.write( "%15s %15s %15s \t xy xz yz \n" % tuple( str(d) for d in skew_folded_and_rounded_dec ) )
509 4 tkerber
    f.write("\n\n")
510 4 tkerber
511 4 tkerber
512 4 tkerber
    ### atoms
513 4 tkerber
514 4 tkerber
    ## A) simple version
515 4 tkerber
    # - For orthogonal cells which are properly aligned with the reference coordinate system only.
516 4 tkerber
#    f.write("Atoms \n\n")
517 4 tkerber
#    for i, (x, y, z) in enumerate(atoms.get_positions()):
518 4 tkerber
#        s = species.index(symbols[i]) + 1
519 4 tkerber
#        f.write("%6d %3d %22.15f %22.15f %22.15f \n" % (i+1, s, x, y, z))
520 4 tkerber
521 4 tkerber
    ## B) convert to (perhaps) triclinic cells in LAMMPS:
522 4 tkerber
    # - Apply rotation determined above for this case.
523 4 tkerber
    # - Lattice translation in case of a folded skew vector above should not be necessary,
524 4 tkerber
    #   since the resulting (perhaps) new unit cell does describe the same lattice.
525 4 tkerber
    #   Therefore the (rotated) atoms are still at correct lattice sites, only some of them
526 4 tkerber
    #   probably are outside of that new unit cell, which LAMMPS can hopefully deal with.
527 4 tkerber
    f.write("Atoms \n\n")
528 4 tkerber
    for i, r in enumerate(atoms.get_positions()):
529 4 tkerber
        s = species.index(symbols[i]) + 1
530 4 tkerber
        [x, y, z] = np.dot(r, rotation)
531 4 tkerber
#        print r, [x, y, z]
532 4 tkerber
        f.write("%6d %3d %22.15f %22.15f %22.15f \n" % (i+1, s, x, y, z))
533 4 tkerber
534 4 tkerber
    f.close()
535 4 tkerber
536 4 tkerber
537 4 tkerber
if __name__ == "__main__":
538 4 tkerber
539 4 tkerber
    pair_style = "eam"
540 4 tkerber
    Pd_eam_file = "Pd_u3.eam"
541 4 tkerber
    pair_coeff = [ "* * " + Pd_eam_file ]
542 4 tkerber
    parameters = { "pair_style" : pair_style, "pair_coeff" : pair_coeff }
543 4 tkerber
    files = [ Pd_eam_file ]
544 4 tkerber
    calc = LAMMPS(parameters=parameters, files=files)
545 4 tkerber
546 4 tkerber
    from ase import Atoms
547 4 tkerber
    a0 = 3.93
548 4 tkerber
    b0 = a0 / 2.0
549 4 tkerber
    if True:
550 4 tkerber
        bulk = Atoms(['Pd']*4,
551 4 tkerber
                     positions=[(0,0,0),(b0,b0,0),(b0,0,b0),(0,b0,b0)],
552 4 tkerber
                     cell=[a0]*3,
553 4 tkerber
                     pbc=True)
554 4 tkerber
        # test get_forces
555 4 tkerber
        print "forces for a = %f" % a0
556 4 tkerber
        print calc.get_forces(bulk)
557 4 tkerber
        # single points for various lattice constants
558 4 tkerber
        bulk.set_calculator(calc)
559 4 tkerber
        for n in range(-5,5,1):
560 4 tkerber
            a = a0 * (1 + n/100.0)
561 4 tkerber
            bulk.set_cell([a]*3)
562 4 tkerber
            print "a : %f , total energy : %f" % (a, bulk.get_potential_energy())
563 4 tkerber
564 4 tkerber
#    calc.clean(force=True)
565 4 tkerber
    calc.clean()
566 4 tkerber
567 4 tkerber