root / ase / calculators / lammps.py @ 8
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 |