Statistiques
| Révision :

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