root / ase / calculators / fleur.py @ 4
Historique | Voir | Annoter | Télécharger (14,15 ko)
1 |
"""This module defines an ASE interface to FLAPW code FLEUR.
|
---|---|
2 |
|
3 |
http://www.flapw.de
|
4 |
"""
|
5 |
|
6 |
import os |
7 |
try:
|
8 |
from subprocess import Popen, PIPE |
9 |
except ImportError: |
10 |
from os import popen3 |
11 |
else:
|
12 |
def popen3(cmd): |
13 |
p = Popen(cmd, shell=True, close_fds=True, |
14 |
stdin=PIPE, stdout=PIPE, stderr=PIPE) |
15 |
return p.stdin, p.stdout, p.stderr
|
16 |
import re |
17 |
|
18 |
import numpy as np |
19 |
|
20 |
from ase.units import Hartree, Bohr |
21 |
|
22 |
class FLEUR: |
23 |
"""Class for doing FLEUR calculations.
|
24 |
|
25 |
In order to use fleur one has to define the following environment
|
26 |
variables:
|
27 |
|
28 |
FLEUR_INPGEN path to the input generator (inpgen.x) of fleur
|
29 |
|
30 |
FLEUR path to the fleur executable. Note that fleur uses different
|
31 |
executable for real and complex cases (systems with/without inversion
|
32 |
symmetry), so FLEUR must point to the correct executable.
|
33 |
|
34 |
It is probable that user needs to tune manually the input file before
|
35 |
the actual calculation, so in addition to the standard
|
36 |
get_potential_energy function this class defines the following utility
|
37 |
functions:
|
38 |
|
39 |
write_inp
|
40 |
generate the input file `inp`
|
41 |
initialize_density
|
42 |
creates the initial density after possible manual edits of `inp`
|
43 |
calculate
|
44 |
convergence the total energy. With fleur, one specifies always
|
45 |
only the number of SCF-iterations so this function launches
|
46 |
the executable several times and monitors the convergence.
|
47 |
relax
|
48 |
Uses fleur's internal algorithm for structure
|
49 |
optimization. Requires that the proper optimization parameters
|
50 |
(atoms to optimize etc.) are specified by hand in `inp`
|
51 |
|
52 |
"""
|
53 |
def __init__(self, xc='LDA', kpts=None, nbands=None, convergence=None, |
54 |
width=None, kmax=None, mixer=None, maxiter=40, |
55 |
maxrelax=20, workdir=None): |
56 |
|
57 |
"""Construct FLEUR-calculator object.
|
58 |
|
59 |
Parameters
|
60 |
==========
|
61 |
xc: str
|
62 |
Exchange-correlation functional. Must be one of LDA, PBE,
|
63 |
RPBE.
|
64 |
kpts: list of three int
|
65 |
Monkhost-Pack sampling.
|
66 |
nbands: int
|
67 |
Number of bands. (not used at the moment)
|
68 |
convergence: dictionary
|
69 |
Convergence parameters (currently only energy in eV)
|
70 |
{'energy' : float}
|
71 |
width: float
|
72 |
Fermi-distribution width in eV.
|
73 |
kmax: float
|
74 |
Plane wave cutoff in a.u.
|
75 |
mixer: dictionary
|
76 |
Mixing parameters imix, alpha, spinf
|
77 |
{'imix' : int, 'alpha' : float, 'spinf' : float}
|
78 |
maxiter: int
|
79 |
Maximum number of SCF iterations
|
80 |
maxrelax: int
|
81 |
Maximum number of relaxation steps
|
82 |
workdir: str
|
83 |
Working directory for the calculation
|
84 |
"""
|
85 |
|
86 |
self.xc = xc
|
87 |
self.kpts = kpts
|
88 |
self.nbands = nbands
|
89 |
self.width = width
|
90 |
self.kmax = kmax
|
91 |
self.maxiter = maxiter
|
92 |
self.maxrelax = maxrelax
|
93 |
self.mixer = mixer
|
94 |
|
95 |
if convergence:
|
96 |
self.convergence = convergence
|
97 |
self.convergence['energy'] /= Hartree |
98 |
else:
|
99 |
self.convergence = {'energy' : 0.0001} |
100 |
|
101 |
|
102 |
|
103 |
self.start_dir = None |
104 |
self.workdir = workdir
|
105 |
if self.workdir: |
106 |
self.start_dir = os.getcwd()
|
107 |
if not os.path.isdir(workdir): |
108 |
os.mkdir(workdir) |
109 |
else:
|
110 |
self.workdir = '.' |
111 |
self.start_dir = '.'
|
112 |
|
113 |
self.converged = False
|
114 |
|
115 |
def update(self, atoms): |
116 |
"""Update a FLEUR calculation."""
|
117 |
|
118 |
if (not self.converged or |
119 |
len(self.numbers) != len(atoms) or |
120 |
(self.numbers != atoms.get_atomic_numbers()).any()):
|
121 |
self.initialize(atoms)
|
122 |
self.calculate(atoms)
|
123 |
elif ((self.positions != atoms.get_positions()).any() or |
124 |
(self.pbc != atoms.get_pbc()).any() or |
125 |
(self.cell != atoms.get_cell()).any()):
|
126 |
self.converged = False |
127 |
self.initialize(atoms)
|
128 |
self.calculate(atoms)
|
129 |
|
130 |
def initialize(self, atoms): |
131 |
"""Create an input file inp and generate starting density."""
|
132 |
|
133 |
self.converged = False |
134 |
self.initialize_inp(atoms)
|
135 |
self.initialize_density()
|
136 |
|
137 |
def initialize_inp(self, atoms): |
138 |
"""Create a inp file"""
|
139 |
os.chdir(self.workdir)
|
140 |
|
141 |
self.numbers = atoms.get_atomic_numbers().copy()
|
142 |
self.positions = atoms.get_positions().copy()
|
143 |
self.cell = atoms.get_cell().copy()
|
144 |
self.pbc = atoms.get_pbc().copy()
|
145 |
|
146 |
# create the input
|
147 |
self.write_inp(atoms)
|
148 |
|
149 |
os.chdir(self.start_dir)
|
150 |
|
151 |
def initialize_density(self): |
152 |
"""Creates a new starting density."""
|
153 |
|
154 |
os.chdir(self.workdir)
|
155 |
# remove possible conflicting files
|
156 |
files2remove = ['cdn1', 'fl7para', 'stars', 'wkf2', |
157 |
'kpts', 'broyd', 'broyd.7', 'tmat', 'tmas'] |
158 |
|
159 |
for f in files2remove: |
160 |
if os.path.isfile(f):
|
161 |
os.remove(f) |
162 |
|
163 |
# generate the starting density
|
164 |
os.system("sed -i -e 's/strho=./strho=T/' inp")
|
165 |
try:
|
166 |
fleur_exe = os.environ['FLEUR']
|
167 |
except KeyError: |
168 |
raise RuntimeError('Please set FLEUR') |
169 |
cmd = popen3(fleur_exe)[2]
|
170 |
stat = cmd.read() |
171 |
if '!' in stat: |
172 |
raise RuntimeError('FLEUR exited with a code %s' % stat) |
173 |
os.system("sed -i -e 's/strho=./strho=F/' inp")
|
174 |
|
175 |
os.chdir(self.start_dir)
|
176 |
|
177 |
def get_potential_energy(self, atoms, force_consistent=False): |
178 |
self.update(atoms)
|
179 |
|
180 |
if force_consistent:
|
181 |
return self.efree * Hartree |
182 |
else:
|
183 |
# Energy extrapolated to zero Kelvin:
|
184 |
return (self.etotal + self.efree) / 2 * Hartree |
185 |
|
186 |
def get_forces(self, atoms): |
187 |
self.update(atoms)
|
188 |
# electronic structure is converged, so let's calculate forces:
|
189 |
# TODO
|
190 |
return np.array((0.0, 0.0, 0.0)) |
191 |
|
192 |
def get_stress(self, atoms): |
193 |
raise NotImplementedError |
194 |
|
195 |
def get_dipole_moment(self, atoms): |
196 |
"""Returns total dipole moment of the system."""
|
197 |
raise NotImplementedError |
198 |
|
199 |
def calculate(self, atoms): |
200 |
"""Converge a FLEUR calculation to self-consistency.
|
201 |
|
202 |
Input files should be generated before calling this function
|
203 |
FLEUR performs always fixed number of SCF steps. This function
|
204 |
reduces the number of iterations gradually, however, a minimum
|
205 |
of five SCF steps is always performed.
|
206 |
"""
|
207 |
|
208 |
os.chdir(self.workdir)
|
209 |
try:
|
210 |
fleur_exe = os.environ['FLEUR']
|
211 |
except KeyError: |
212 |
raise RuntimeError('Please set FLEUR') |
213 |
|
214 |
self.niter = 0 |
215 |
out = ''
|
216 |
err = ''
|
217 |
while not self.converged: |
218 |
if self.niter > self.maxiter: |
219 |
raise RuntimeError('FLEUR failed to convergence in %d iterations' % self.maxiter) |
220 |
|
221 |
p = Popen(fleur_exe, shell=True, stdin=PIPE, stdout=PIPE,
|
222 |
stderr=PIPE) |
223 |
stat = p.wait() |
224 |
out = p.stdout.read() |
225 |
err = p.stderr.read() |
226 |
print err.strip()
|
227 |
if stat != 0: |
228 |
raise RuntimeError('FLEUR exited with a code %d' % stat) |
229 |
# catenate new output with the old one
|
230 |
os.system('cat out >> out.old')
|
231 |
self.read()
|
232 |
self.check_convergence()
|
233 |
|
234 |
os.rename('out.old', 'out') |
235 |
# After convergence clean up broyd* files
|
236 |
os.system('rm -f broyd*')
|
237 |
os.chdir(self.start_dir)
|
238 |
return out, err
|
239 |
|
240 |
def relax(self, atoms): |
241 |
"""Currently, user has to manually define relaxation parameters
|
242 |
(atoms to relax, relaxation directions, etc.) in inp file
|
243 |
before calling this function."""
|
244 |
|
245 |
nrelax = 0
|
246 |
relaxed = False
|
247 |
while not relaxed: |
248 |
# Calculate electronic structure
|
249 |
self.calculate(atoms)
|
250 |
# Calculate the Pulay forces
|
251 |
os.system("sed -i -e 's/l_f=./l_f=T/' inp")
|
252 |
while True: |
253 |
self.converged = False |
254 |
out, err = self.calculate(atoms)
|
255 |
if 'GEO new' in err: |
256 |
os.chdir(self.workdir)
|
257 |
os.rename('inp_new', 'inp') |
258 |
os.chdir(self.start_dir)
|
259 |
break
|
260 |
if 'GEO: Des woas' in err: |
261 |
relaxed = True
|
262 |
break
|
263 |
nrelax += 1
|
264 |
# save the out and cdn1 files
|
265 |
os.system('cp out out_%d' % nrelax)
|
266 |
os.system('cp cdn1 cdn1_%d' % nrelax)
|
267 |
if nrelax > self.maxrelax: |
268 |
raise RuntimeError('Failed to relax in %d iterations' % self.maxrelax) |
269 |
self.converged = False |
270 |
|
271 |
|
272 |
def write_inp(self, atoms): |
273 |
"""Write the `inp` input file of FLEUR.
|
274 |
|
275 |
First, the information from Atoms is written to the simple input
|
276 |
file and the actual input file `inp` is then generated with the
|
277 |
FLEUR input generator. The location of input generator is specified
|
278 |
in the environment variable FLEUR_INPGEN.
|
279 |
|
280 |
Finally, the `inp` file is modified according to the arguments of
|
281 |
the FLEUR calculator object.
|
282 |
"""
|
283 |
|
284 |
fh = open('inp_simple', 'w') |
285 |
fh.write('FLEUR input generated with ASE\n')
|
286 |
fh.write('\n')
|
287 |
|
288 |
if atoms.pbc[2]: |
289 |
film = 'f'
|
290 |
else:
|
291 |
film = 't'
|
292 |
fh.write('&input film=%s /' % film)
|
293 |
fh.write('\n')
|
294 |
|
295 |
for vec in atoms.get_cell(): |
296 |
fh.write(' ')
|
297 |
for el in vec: |
298 |
fh.write(' %21.16f' % (el/Bohr))
|
299 |
fh.write('\n')
|
300 |
fh.write(' %21.16f\n' % 1.0) |
301 |
fh.write(' %21.16f %21.16f %21.16f\n' % (1.0, 1.0, 1.0)) |
302 |
fh.write('\n')
|
303 |
|
304 |
natoms = len(atoms)
|
305 |
fh.write(' %6d\n' % natoms)
|
306 |
positions = atoms.get_scaled_positions() |
307 |
if not atoms.pbc[2]: |
308 |
# in film calculations z position has to be in absolute
|
309 |
# coordinates and symmetrical
|
310 |
cart_pos = atoms.get_positions() |
311 |
cart_pos[:, 2] -= atoms.get_cell()[2, 2]/2.0 |
312 |
positions[:, 2] = cart_pos[:, 2] / Bohr |
313 |
atomic_numbers = atoms.get_atomic_numbers() |
314 |
for Z, pos in zip(atomic_numbers, positions): |
315 |
fh.write('%3d' % Z)
|
316 |
for el in pos: |
317 |
fh.write(' %21.16f' % el)
|
318 |
fh.write('\n')
|
319 |
|
320 |
fh.close() |
321 |
try:
|
322 |
inpgen = os.environ['FLEUR_INPGEN']
|
323 |
except KeyError: |
324 |
raise RuntimeError('Please set FLEUR_INPGEN') |
325 |
|
326 |
# rename the previous inp if it exists
|
327 |
if os.path.isfile('inp'): |
328 |
os.rename('inp', 'inp.bak') |
329 |
os.system('%s < inp_simple' % inpgen)
|
330 |
|
331 |
# read the whole inp-file for possible modifications
|
332 |
fh = open('inp', 'r') |
333 |
lines = fh.readlines() |
334 |
fh.close() |
335 |
|
336 |
|
337 |
for ln, line in enumerate(lines): |
338 |
# XC potential
|
339 |
if line.startswith('pbe'): |
340 |
if self.xc == 'PBE': |
341 |
pass
|
342 |
elif self.xc == 'RPBE': |
343 |
lines[ln] = 'rpbe non-relativi\n'
|
344 |
elif self.xc == 'LDA': |
345 |
lines[ln] = 'mjw non-relativic\n'
|
346 |
del lines[ln+1] |
347 |
else:
|
348 |
raise RuntimeError('XC-functional %s is not supported' % self.xc) |
349 |
# kmax
|
350 |
if self.kmax and line.startswith('Window'): |
351 |
line = '%10.5f\n' % self.kmax |
352 |
lines[ln+2] = line
|
353 |
|
354 |
# Fermi width
|
355 |
if self.width and line.startswith('gauss'): |
356 |
line = 'gauss=F %7.5ftria=F\n' % (self.width / Hartree) |
357 |
lines[ln] = line |
358 |
# kpts
|
359 |
if self.kpts and line.startswith('nkpt'): |
360 |
line = 'nkpt= nx=%2d,ny=%2d,nz=%2d\n' % (self.kpts[0], |
361 |
self.kpts[1], |
362 |
self.kpts[2]) |
363 |
lines[ln] = line |
364 |
# Mixing
|
365 |
if self.mixer and line.startswith('itmax'): |
366 |
imix = self.mixer['imix'] |
367 |
alpha = self.mixer['alpha'] |
368 |
spinf = self.mixer['spinf'] |
369 |
line_end = 'imix=%2d,alpha=%6.2f,spinf=%6.2f\n' % (imix,
|
370 |
alpha, |
371 |
spinf) |
372 |
line = line[:21] + line_end
|
373 |
lines[ln] = line |
374 |
|
375 |
# write everything back to inp
|
376 |
fh = open('inp', 'w') |
377 |
for line in lines: |
378 |
fh.write(line) |
379 |
fh.close() |
380 |
|
381 |
def read(self): |
382 |
"""Read results from FLEUR's text-output file `out`."""
|
383 |
|
384 |
lines = open('out', 'r').readlines() |
385 |
|
386 |
# total energies
|
387 |
self.total_energies = []
|
388 |
pat = re.compile('(.*total energy=)(\s)*([-0-9.]*)')
|
389 |
for line in lines: |
390 |
m = pat.match(line) |
391 |
if m:
|
392 |
self.total_energies.append(float(m.group(3))) |
393 |
self.etotal = self.total_energies[-1] |
394 |
|
395 |
# free_energies
|
396 |
self.free_energies = []
|
397 |
pat = re.compile('(.*free energy=)(\s)*([-0-9.]*)')
|
398 |
for line in lines: |
399 |
m = pat.match(line) |
400 |
if m:
|
401 |
self.free_energies.append(float(m.group(3))) |
402 |
self.efree = self.free_energies[-1] |
403 |
|
404 |
# TODO forces, charge density difference...
|
405 |
|
406 |
def check_convergence(self): |
407 |
"""Check the convergence of calculation"""
|
408 |
energy_error = np.ptp(self.total_energies[-3:]) |
409 |
self.converged = energy_error < self.convergence['energy'] |
410 |
|
411 |
# TODO check charge convergence
|
412 |
|
413 |
# reduce the itmax in inp
|
414 |
lines = open('inp', 'r').readlines() |
415 |
pat = re.compile('(itmax=)([ 0-9]*)')
|
416 |
fh = open('inp', 'w') |
417 |
for line in lines: |
418 |
m = pat.match(line) |
419 |
if m:
|
420 |
itmax = int(m.group(2)) |
421 |
self.niter += itmax
|
422 |
itmax_new = itmax / 2
|
423 |
itmax = max(5, itmax_new) |
424 |
line = 'itmax=%2d' % itmax + line[8:] |
425 |
fh.write(line) |
426 |
fh.close() |
427 |
|