Statistiques
| Révision :

root / ase / calculators / dftb.py @ 1

Historique | Voir | Annoter | Télécharger (18,46 ko)

1
"""This module defines an ASE interface to DftbPlus
2

3
http://http://www.dftb-plus.info//
4
http://www.dftb.org/
5

6
-Markus Kaukonen markus.kaukonen@iki.fi
7
"""
8

    
9

    
10
import numpy as np
11
import os, string
12

    
13
#from ase.data import chemical_symbols
14
from ase.units import Hartree, Bohr
15

    
16

    
17
class Dftb:
18
    """Class for doing DFTB+ calculations.
19
    """
20
    def __init__(self, label='dftb', write_dftb=False,
21
                 charge=0.0, include_dispersion=False,
22
                 do_spin_polarized=False, 
23
                 unpaired_electrons=0.0,
24
                 fermi_temperature=0.0, scc=False):
25
        """Construct DFTB-calculator object.
26

27

28
        For example:
29
        calc = Dftb(label='dftb',write_dftb=True,include_dispersion=True )
30

31
        Parameters
32
        ==========
33
        label: str
34
            Prefix to use for filenames (label.txt, ...).
35
            Default is 'dftb'.
36
        write_dftb: boolean
37
            True: a minimal input file (name of which is always 'dftb_in.hsd')
38
            is written based on values given here.
39
            False: input file for dftb+ is not written. User must have
40
            generated file 'dftb_in.hsd' in the working directory.
41
            Use write_dftb=False to use your own 'dftb_in.hsd'-file.
42
        charge: float
43
            Total charge of the system.
44
        include_dispersion: boolean
45
            True: Default dispersion parameters are written in the 
46
            file 'dftb_in.hsd' (requires that also write_dftb_input_file==True)
47
            False: dispersion parameters are not written here.
48
        do_spin_polarized: boolean
49
            True: Spin polarized calculation
50
            (requires that also write_dftb_input_file==True)
51
            False: Spin unpolarized calculation
52
        unpaired_electrons: float
53
            Number of spin unpaired electrons in the system.
54
            Relevant only if do_spin_polarized==True
55
        fermi_temperature: float
56
            Fermi temperature for electrons.
57
        scc: boolean
58
            True: Do charge self consistent dftb+
59
            False: No SCC, charges on atoms are not iterated
60
            
61
        Input file for DFTB+ file is 'dftb_in.hsd'. Keywords in it are
62
        written here or read from an existing file. The atom positions
63
        in file 'dftb_in.hsd' are updated during ASE geometry
64
        optimization.
65
        """
66

    
67

    
68
        if not(write_dftb):
69
            if os.path.isfile('dftb_in.hsd'):
70
                f = open('dftb_in.hsd')
71
            else:
72
                print 'Input file for DFTB+ dftb_in.hsd is missing'
73
                raise RuntimeError, \
74
                    'Provide it or set write_dftb=True '
75
            #lines = f.readlines()
76
            f.close()
77

    
78
        self.label = label
79
        self.write_dftb = write_dftb
80
        self.charge = charge
81
        self.include_dispersion = include_dispersion
82
        self.do_spin_polarized = do_spin_polarized
83
        self.unpaired_electrons = unpaired_electrons
84
        #if (do_spin_polarized):
85
        #    print 'Sorry, generation of file "dftb_in.hsd" with spin '
86
        #    print 'polarization is not inplemented for DFTB+'
87
        #    print 'Set write_dftb=False and'
88
        #    raise RuntimeError, \
89
        #        'Generate file "dftb_in.hsd by hand"'
90
        
91
        self.etotal = 0.0
92
        self.cell = None
93
        self.fermi_temperature = fermi_temperature
94
        self.scc = scc 
95
        self.converged = False
96

    
97
        #dftb has no stress
98
        self.stress = np.empty((3, 3))
99
        
100

    
101
    def update(self, atoms):
102
        """Energy and forces are calculated when atoms have moved
103
        by calling self.calculate
104
        """
105
        if (not self.converged or
106
            len(self.typenumber) != len(atoms)):
107
            self.initialize(atoms)
108
            self.calculate(atoms)
109
        elif ((self.positions != atoms.get_positions()).any() or
110
              (self.pbc != atoms.get_pbc()).any() or
111
              (self.cell != atoms.get_cell()).any()):
112
            self.calculate(atoms)
113

    
114
    def initialize(self, atoms):
115
        #from ase.io.dftb import read_dftb
116

    
117
        atomtypes = atoms.get_chemical_symbols()
118
        self.allspecies = []
119
        self.typenumber = []
120
        self.max_angular_momentum = []
121
        for species in (atomtypes):
122
            if species not in self.allspecies:
123
                self.allspecies.append(species)
124
        for species in (atomtypes):
125
            myindex = 1 + self.allspecies.index(species)
126
            self.typenumber.append(myindex)
127
        for i in self.allspecies:
128
            if i == 'H':
129
                self.max_angular_momentum.append('s')
130
            elif i in ['C','N','O']:
131
                self.max_angular_momentum.append('p')
132
            elif i in ['Si','S','Fe','Ni']:
133
                self.max_angular_momentum.append('d')
134
            else:
135
                print 'anglular momentum is not imlemented in ASE-DFTB+'
136
                print 'for species '+i
137
                raise RuntimeError('Use option write_dftb=False')
138
        self.converged = False
139
        
140
        #write DFTB input file if desired 
141
        self.positions = atoms.get_positions().copy()
142
        if self.write_dftb:
143
            self.write_dftb_input_file(atoms)
144
        
145
        
146
    def get_potential_energy(self, atoms):
147
        self.update(atoms)
148
        return self.etotal
149

    
150
    def get_forces(self, atoms):
151
        self.update(atoms)
152
        return self.forces.copy()
153
    
154
    def get_stress(self, atoms):
155
        self.update(atoms)
156
        return self.stress.copy()
157

    
158
    def calculate(self, atoms):
159
        """Total DFTB energy is calculated (to file 'energy'
160
        also forces are calculated (to file 'gradient')
161
        """
162
        
163
        self.positions = atoms.get_positions().copy()
164
        self.cell = atoms.get_cell().copy()
165
        self.pbc = atoms.get_pbc().copy()
166

    
167
        if self.write_dftb:
168
            self.write_dftb_input_file(atoms)
169
        else:
170
            #write current coordinates to file 'dftb_in.hsd' for DFTB+
171
            self.change_atom_positions_dftb(atoms)
172

    
173

    
174
        #DFTB energy&forces calculation
175
        if (os.environ.has_key('DFTB_COMMAND') and
176
            (os.environ.has_key('DFTB_PREFIX'))):
177
            dftb = os.environ['DFTB_COMMAND']
178
            exitcode = os.system(dftb+ '> dftb.output' )
179
        elif not(os.environ.has_key('DFTB_COMMAND')):
180
            raise RuntimeError('Please set DFTB_COMMAND environment variable')
181
        elif not(os.environ.has_key('DFTB_PREFIX')):
182
            print 'Path for DFTB+ slater koster files is missing'    
183
            raise RuntimeError('Please set DFTB_PREFIX environment variable')
184
        else:
185
            pass
186
        if exitcode != 0:
187
            raise RuntimeError('Dftb exited with exit code: %d.  ' % exitcode)
188

    
189
        self.read_energy()
190

    
191
        #DFTB atomic forces calculation, to be read in file detailed.out
192
        #os.system(self.dftb_program_forces +'> output.forces.dummy')
193

    
194
        self.read_forces(atoms)
195

    
196
        self.converged = True
197

    
198
        
199
    def read_energy(self):
200
        """Read Energy from DFTB energy file."""
201
        text = open('dftb.output', 'r').read().lower()
202
        lines = iter(text.split('\n'))
203

    
204
        # Energy:
205
        for line in lines:
206
            if 'total energy' in line:
207
                energy_tmp = float(line.split()[2])
208
                #print 'energy_tmp', energy_tmp
209
        self.etotal = energy_tmp * Hartree
210

    
211

    
212
    def read_forces(self, atoms):
213
        """Read Forces from DFTB+ detailed.out output file"""
214

    
215
        myfile = open('detailed.out','r')
216
        line = myfile.readline()
217
        line = myfile.readline()
218
        tmpforces = np.array([[0, 0, 0]])
219
        while line:
220
            if 'Total Forces' in line:
221
                for i, dummy in enumerate(atoms):
222
                    line = myfile.readline()
223
                    line2 = string.replace(line, 'D', 'E')
224
                    tmp = np.array\
225
                        ([[float(f) for f in line2.split()[0:3]]])
226
                    tmpforces = np.concatenate((tmpforces, tmp))  
227
            line = myfile.readline()
228
            
229

    
230
        self.forces = (np.delete(tmpforces, np.s_[0:1], axis=0))*Hartree/Bohr
231

    
232
        #print 'forces', self.forces
233

    
234
    def read(self):
235
        """Dummy stress for dftb"""
236
        self.stress = np.empty((3, 3))
237

    
238
    def write_dftb_input_file(self, atoms):
239
        """Write input parameters to DFTB+ input file 'dftb_in.hsd'."""
240
        import sys
241
        fh = open('dftb_in.hsd', 'w')
242
        # geometry
243
        fh.write('Geometry = {\n')
244
        fh.write('TypeNames = {')
245
        for i in self.allspecies:
246
            fh.write(' "'+i+'"')
247
        fh.write(' }\n')
248
        fh.write('TypesAndCoordinates [Angstrom] = {\n')
249
        self.positions = atoms.get_positions().copy()
250
        for i, pos in zip(self.typenumber, self.positions):
251
            fh.write('%6d ' % (i))
252
            fh.write('%20.14f %20.14f %20.14f' %  tuple(pos))
253
            fh.write('\n')
254
        fh.write(' }\n')
255

    
256
        #is it periodic and when is write also lattice vectors
257
        periodic = atoms.get_pbc().any()
258
        if periodic:
259
            fh.write('Periodic = Yes\n')
260
        else:
261
            fh.write('Periodic = No\n')
262
        if periodic:
263
            cell = atoms.get_cell().copy()
264
            fh.write('LatticeVectors [Angstrom] = {\n')
265
            for v in cell:
266
                fh.write('%20.14f %20.14f %20.14f \n' %  tuple(v))
267
            fh.write('  }\n')
268

    
269
        #end of geometry session
270
        fh.write('}\n')
271

    
272
        #zero step CG relaxation to get forces and energies
273
        # these are dummies because ASE takes care of these things
274
        fh.write('\n') 
275
        fh.write('Driver = ConjugateGradient {\n')
276
        fh.write('MovedAtoms = Range { 1 -1 }\n')
277
        fh.write('  MaxForceComponent = 1.0e-4\n')
278
        fh.write('  MaxSteps = 0\n')
279
        fh.write('  OutputPrefix = '+self.label+ '\n')
280
        fh.write('}\n')
281

    
282
        #Hamiltonian
283
        fh.write('\n') 
284
        fh.write('Hamiltonian = DFTB { # DFTB Hamiltonian\n')
285
        if (self.scc):
286
            fh.write('  SCC = Yes')
287
            fh.write(' # Use self consistent charges\n')               
288
            fh.write('  SCCTolerance = 1.0e-5')
289
            fh.write(' # Tolerance for charge consistence\n')            
290
            fh.write('  MaxSCCIterations = 1000')
291
            fh.write(' # Nr. of maximal SCC iterations\n')          
292
            fh.write('  Mixer = Broyden {') 
293
            fh.write(' # Broyden mixer for charge mixing\n')          
294
            fh.write('    MixingParameter = 0.2')  
295
            fh.write(' # Mixing parameter\n')
296
            fh.write('  }\n')
297
        else:
298
            fh.write('  SCC = No # NO self consistent charges\n')
299
        fh.write('  SlaterKosterFiles = Type2FileNames {')
300
        fh.write(' # File names from two atom type names\n')
301
        sk_prefix = os.environ['DFTB_PREFIX']
302
        fh.write('    Prefix = "'+sk_prefix+'"')
303
        fh.write(' # Path as prefix\n')
304
        fh.write('    Separator = "-"')
305
        fh.write(' # Dash between type names\n')
306
        fh.write('    Suffix = ".skf"')
307
        fh.write(' # Suffix after second type name\n')
308
        fh.write('  }\n')
309
        fh.write('  MaxAngularMomentum = {')
310
        fh.write(' # Maximal l-value of the various species\n')
311
        for i, j in zip(self.allspecies, self.max_angular_momentum):
312
            fh.write('   '+i+' = "'+j+'"\n')
313
        fh.write('  }\n')
314
        fh.write('  Charge = ')
315
        fh.write('%10.6f' % (self.charge))
316
        fh.write(' # System neutral\n')
317
        if self.do_spin_polarized:
318
            fh.write('  SpinPolarisation = Colinear {\n') 
319
            fh.write('  UnpairedElectrons = '+str(self.unpaired_electrons)+'\n')
320
            fh.write('  } \n')
321
            fh.write('  SpinConstants = {\n') 
322
            for i in self.allspecies:
323
                if i == 'H':
324
                    fh.write('   H={\n') 
325
                    fh.write('    # Wss\n') 
326
                    fh.write('    -0.072\n') 
327
                    fh.write('    }\n')
328
                elif i == 'C': 
329
                    fh.write('   C={\n') 
330
                    fh.write('    # Wss Wsp Wps Wpp\n') 
331
                    fh.write('    -0.031 -0.025 -0.025 -0.023\n') 
332
                    fh.write('    }\n') 
333
                elif i == 'N': 
334
                    fh.write('   N={\n') 
335
                    fh.write('    # Wss Wsp Wps Wpp\n')
336
                    fh.write('    -0.033 -0.027 -0.027 -0.026\n') 
337
                    fh.write('     }\n') 
338
                elif i == 'O':
339
                    fh.write('   O={\n') 
340
                    fh.write('    # Wss Wsp Wps Wpp\n') 
341
                    fh.write('    -0.035 -0.030 -0.030 -0.028\n') 
342
                    fh.write('     }\n') 
343
                elif (i == 'Si' or i == 'SI'):
344
                    fh.write('   Si={\n') 
345
                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n')
346
                    fh.write('    -0.020 -0.015 0.000 -0.015 -0.014 0.000 0.002 0.002 -0.032\n')
347
                    fh.write('    }\n')
348
                elif (i == 'S'):
349
                    fh.write('   S={\n') 
350
                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
351
                    fh.write('    -0.021 -0.017 0.000 -0.017 -0.016 0.000 0.000 0.000 -0.080\n')
352
                    fh.write('    }\n')
353
                elif (i == 'Fe' or i == 'FE'):
354
                    fh.write('   Fe={\n') 
355
                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
356
                    fh.write('    -0.016 -0.012 -0.003 -0.012 -0.029 -0.001 -0.003 -0.001 -0.015\n')
357
                    fh.write('    }\n')
358
                elif (i == 'Ni' or i == 'NI'):
359
                    fh.write('   Ni={\n') 
360
                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
361
                    fh.write('    -0.016 -0.012 -0.003 -0.012 -0.022 -0.001 -0.003 -0.001 -0.018\n')
362
                    fh.write('    }\n')
363
                else:
364
                    print 'Missing spin polarisation parameters for species'+i
365
                    raise RuntimeError, \
366
                        'Run spin unpolarised calculation'
367
                fh.write('   }\n') 
368
        else:
369
            fh.write('  SpinPolarisation = {}')
370
            fh.write(' # No spin polarisation\n')
371
        fh.write('  Filling = Fermi {\n')
372
        fh.write('    Temperature [Kelvin] = ')
373
        fh.write('%10.6f\n' % (self.fermi_temperature))
374
        fh.write('  }\n')
375
        if periodic:
376
            fh.write('# gamma only\n')
377
            fh.write('KPointsAndWeights = { \n')
378
            fh.write('  0.000000    0.000000    0.000000       1.000000 \n')
379
            fh.write('}\n')
380

    
381
        #Dispersion parameters
382
        if (self.include_dispersion):
383
            fh.write('Dispersion = SlaterKirkwood {\n')
384
            fh.write(' PolarRadiusCharge = HybridDependentPol {\n')
385
            fh.write('\n')
386
            fh.write('  C={\n')
387
            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
388
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
389
            fh.write('      1.382 1.382 1.382 1.064 1.064 1.064 3.8 3.8 3.8 3.8 3.8 3.8 2.5\n')
390
            fh.write('    }\n')
391
            fh.write('  }\n')
392
            fh.write('\n')
393
            fh.write('  N={\n')
394
            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
395
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
396
            fh.write('      1.030 1.030 1.090 1.090 1.090 1.090 3.8 3.8 3.8 3.8 3.8 3.8 2.82\n')
397
            fh.write('    }\n')
398
            fh.write('  }\n')
399
            fh.write('\n')
400
            fh.write('  O={\n')
401
            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
402
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
403
            fh.write('    # All polarisabilities and radii set the same\n')
404
            fh.write('      0.560 0.560 0.000 0.000 0.000 0.000 3.8 3.8 3.8 3.8 3.8 3.8 3.15\n')
405
            fh.write('    }\n')
406
            fh.write('  }\n')
407
            fh.write('\n')
408

    
409

    
410
            fh.write('  H={\n')
411
            fh.write('    CovalentRadius [Angstrom] = 0.4\n')
412
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
413
            fh.write('    # Different polarisabilities depending on the hybridisation\n')
414
            fh.write('      0.386 0.396 0.000 0.000 0.000 0.000 3.5 3.5 3.5 3.5 3.5 3.5 0.8\n')
415
            fh.write('    }\n')
416
            fh.write('  }\n')
417
            fh.write('\n')
418

    
419
            fh.write('  P={\n')
420
            fh.write('    CovalentRadius [Angstrom] = 0.9\n')
421
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
422
            fh.write('    # Different polarisabilities depending on the hybridisation\n')
423
            fh.write('      1.600 1.600 1.600 1.600 1.600 1.600 4.7 4.7 4.7 4.7 4.7 4.7 4.50\n')
424
            fh.write('    }\n')
425
            fh.write('  }\n')
426
            fh.write('\n')
427

    
428
            fh.write('  S={\n')
429
            fh.write('    CovalentRadius [Angstrom] = 0.9\n')
430
            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
431
            fh.write('    # Different polarisabilities depending on the hybridisation\n')
432
            fh.write('      3.000 3.000 3.000 3.000 3.000 3.000 4.7 4.7 4.7 4.7 4.7 4.7 4.80\n')
433
            fh.write('    }\n')
434
            fh.write('  }\n')
435
            fh.write(' }\n')        
436
            fh.write('}\n') 
437
        fh.write('}\n') 
438
        fh.write('Options = {}\n')
439
        fh.write('ParserOptions = {\n')
440
        fh.write('  ParserVersion = 3\n')
441
        fh.write('}\n')
442
 
443
        fh.close()
444

    
445
    def change_atom_positions_dftb(self, atoms):
446
        """Write coordinates in DFTB+ input file dftb_in.hsd
447
        """
448

    
449
        filename = 'dftb_in.hsd'
450
        if isinstance(filename, str):
451
            myfile = open(filename)
452

    
453
        lines = myfile.readlines()
454

    
455
        if type(filename) == str:
456
            myfile.close()
457

    
458
        if isinstance(filename, str):
459
            myfile = open(filename, 'w')
460
        else: # Assume it's a 'file-like object'
461
            myfile = filename
462

    
463
        coord = atoms.get_positions()
464

    
465
        start_writing_coords = False
466
        stop_writing_coords = False
467
        i = 0
468
        for line in lines:
469
            if ('TypesAndCoordinates' in line):
470
                start_writing_coords = True
471
            if (start_writing_coords and not(stop_writing_coords)):
472
                if ('}' in line):
473
                    stop_writing_coords = True
474
            if (start_writing_coords and not(stop_writing_coords)and 
475
                not ('TypesAndCoordinates' in line)):
476
                atom_type_index = line.split()[0]
477
                myfile.write('%6s  %20.14f  %20.14f  %20.14f\n'
478
                        % (atom_type_index,coord[i][0],coord[i][1],coord[i][2]))
479
                i = i + 1
480
            else:
481
                myfile.write(line)