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