Statistiques
| Révision :

root / ase / calculators / aims.py @ 13

Historique | Voir | Annoter | Télécharger (17,02 ko)

1
"""This module defines an ASE interface to FHI-aims.
2

3
Felix Hanke hanke@liverpool.ac.uk
4
Jonas Bjork j.bjork@liverpool.ac.uk
5
"""
6

    
7
from general import Calculator
8
import os
9
import sys
10
from os.path import join, isfile, islink
11

    
12
import numpy as np
13

    
14
import ase
15

    
16
float_keys = [
17
    'charge',
18
    'charge_mix_param',
19
    'hartree_convergence_parameter',
20
    'ini_linear_mix_param',
21
    'ini_spin_mix_parma',
22
    'initial_moment',
23
    'MD_MB_init',
24
    'MD_time_step',
25
    'prec_mix_param',
26
    'set_vacuum_level',
27
    'spin_mix_param',
28
]
29

    
30
exp_keys = [
31
    'sc_accuracy_eev',
32
    'sc_accuracy_etot',
33
    'sc_accuracy_forces',
34
    'sc_accuracy_rho',
35
]
36

    
37
string_keys = [
38
    'communication_type',
39
    'KS_method',
40
    'mixer',
41
    'output_level',
42
    'packed_matrix_format',
43
    'restart',
44
    'restart_read_only',
45
    'restart_write_only',
46
    'spin',
47
    'total_energy_method',
48
    'qpe_calc',
49
    'xc',
50
]
51

    
52
int_keys = [
53
    'empty_states',
54
    'ini_linear_mixing',
55
    'max_relaxation_steps',    
56
    'n_max_pulay',   
57
    'sc_iter_limit',
58
    'walltime',
59
]
60

    
61
bool_keys = [
62
    'collect_eigenvectors',
63
    'compute_forces',
64
    'compute_kinetic',
65
    'distributed_spline_storage',
66
    'evaluate_work_function',
67
    'final_forces_cleaned',
68
    'hessian_to_restart_geometry',
69
    'MD_clean_rotations',
70
    'restart_relaxations',
71
    'squeeze_memory',
72
    'use_density_matrix',
73
    'use_dipole_correction',
74
    'use_local_index',
75
    'vdw_correction_hirshfeld',
76
]
77

    
78
list_keys = [
79
    'k_grid',
80
    'k_offset',
81
    'MD_run',
82
    'MD_schedule',
83
    'MD_segment',
84
    'mixer_threshold',
85
    'occupation_type',
86
    'output',
87
    'preconditioner',
88
    'relativistic',
89
    'relax_geometry',
90
]
91

    
92
input_keys = [
93
    'run_command',
94
    'run_dir',
95
    'species_dir',
96
    'cubes',
97
    'output_template',
98
    'track_output',
99
] 
100

    
101
input_parameters_default = {'run_command':None,
102
                            'run_dir':None,
103
                            'species_dir':None,
104
                            'cubes':None,
105
                            'output_template':'aims',
106
                            'track_output':False}
107

    
108
class Aims(Calculator):
109
    def __init__(self, **kwargs):
110
        self.name = 'Aims'
111
        self.float_params = {}
112
        self.exp_params = {}
113
        self.string_params = {}
114
        self.int_params = {}
115
        self.bool_params = {}
116
        self.list_params = {}
117
        self.input_parameters = {}
118
        for key in float_keys:
119
            self.float_params[key] = None
120
        for key in exp_keys:
121
            self.exp_params[key] = None
122
        for key in string_keys:
123
            self.string_params[key] = None
124
        for key in int_keys:
125
            self.int_params[key] = None
126
        for key in bool_keys:
127
            self.bool_params[key] = None
128
        for key in list_keys:
129
            self.list_params[key] = None
130
        for key in input_keys:
131
            self.input_parameters[key] = input_parameters_default[key]
132
        if os.environ.has_key('AIMS_SPECIES_DIR'):
133
            self.input_parameters['species_dir'] = os.environ['AIMS_SPECIES_DIR']
134
        if os.environ.has_key('AIMS_COMMAND'):
135
            self.input_parameters['run_command'] = os.environ['AIMS_COMMAND']
136

    
137
        self.positions = None
138
        self.atoms = None
139
        self.run_counts = 0
140
        self.set(**kwargs)
141

    
142
    def set(self, **kwargs):
143
        if 'control' in kwargs:
144
            fname = kwargs['control']
145
            from ase.io.aims import read_aims_calculator
146
            file = open(fname, 'r')
147
            calc_temp = None
148
            while True:
149
                line = file.readline()
150
                if "List of parameters used to initialize the calculator:" in line:
151
                   file.readline()
152
                   calc_temp = read_aims_calculator(file)
153
                   break
154
            if calc_temp is not None:
155
                self.float_params      = calc_temp.float_params
156
                self.exp_params        = calc_temp.exp_params
157
                self.string_params     = calc_temp.string_params
158
                self.int_params        = calc_temp.int_params
159
                self.bool_params       = calc_temp.bool_params
160
                self.list_params       = calc_temp.list_params
161
                self.input_parameters  = calc_temp.input_parameters
162
            else:
163
                raise TypeError("Control file to be imported can not be read by ASE = " + fname)
164
        for key in kwargs:
165
            if self.float_params.has_key(key):
166
                self.float_params[key] = kwargs[key]
167
            elif self.exp_params.has_key(key):
168
                self.exp_params[key] = kwargs[key]
169
            elif self.string_params.has_key(key):
170
                self.string_params[key] = kwargs[key]
171
            elif self.int_params.has_key(key):
172
                self.int_params[key] = kwargs[key]
173
            elif self.bool_params.has_key(key):
174
                self.bool_params[key] = kwargs[key]
175
            elif self.list_params.has_key(key):
176
                self.list_params[key] = kwargs[key]
177
            elif self.input_parameters.has_key(key):
178
                self.input_parameters[key] = kwargs[key]
179
            elif key is not 'control':
180
                raise TypeError('Parameter not defined: ' + key)
181

    
182
    def update(self, atoms):
183
        if self.calculation_required(atoms,[]):
184
            self.calculate(atoms)
185

    
186
    def calculation_required(self, atoms,quantities):
187
        if (self.positions is None or
188
            (self.atoms != atoms) or
189
            (self.atoms != self.old_atoms) or 
190
            (self.float_params != self.old_float_params) or
191
            (self.exp_params != self.old_exp_params) or
192
            (self.string_params != self.old_string_params) or
193
            (self.int_params != self.old_int_params) or
194
            (self.bool_params != self.old_bool_params) or
195
            (self.list_params != self.old_list_params) or
196
            (self.input_parameters != self.old_input_parameters)):
197
            return True
198
        else:
199
            return False
200

    
201
    def calculate(self, atoms):
202
        """Generate necessary files in the working directory.
203
        
204
        If the directory does not exist it will be created.
205

206
        """
207
        positions = atoms.get_positions()
208
        have_lattice_vectors = atoms.get_pbc().any()        
209
        have_k_grid = self.list_params['k_grid']
210
        if have_lattice_vectors and not have_k_grid:
211
            raise RuntimeError("Found lattice vectors but no k-grid!")
212
        if not have_lattice_vectors and have_k_grid:
213
            raise RuntimeError("Found k-grid but no lattice vectors!")
214
        from ase.io.aims import write_aims
215
        write_aims('geometry.in', atoms) 
216
        self.write_control()
217
        self.write_species()
218
        self.run()
219
        self.converged = self.read_convergence()
220
        if not self.converged:
221
            os.system("tail -20 "+self.out)
222
            raise RuntimeError("FHI-aims did not converge!\n"+
223
                               "The last lines of output are printed above "+
224
                               "and should give an indication why.")
225

    
226
        self.set_results(atoms)
227

    
228
    def set_results(self,atoms):
229
        self.read(atoms)
230
        self.old_float_params = self.float_params.copy()
231
        self.old_exp_params = self.exp_params.copy()
232
        self.old_string_params = self.string_params.copy()
233
        self.old_int_params = self.int_params.copy()
234
        self.old_input_parameters = self.input_parameters.copy()
235
        self.old_bool_params = self.bool_params.copy()
236
        self.old_list_params = self.list_params.copy()
237
        self.old_atoms = self.atoms.copy()
238

    
239
    def run(self):
240
        if self.input_parameters['track_output']:
241
            self.out = self.input_parameters['output_template']+str(self.run_counts)+'.out'
242
            self.run_counts += 1
243
        else:
244
            self.out = self.input_parameters['output_template']+'.out'            
245
        if self.input_parameters['run_command']:
246
            aims_command = self.input_parameters['run_command'] 
247
        elif os.environ.has_key('AIMS_COMMAND'):
248
            aims_command = os.environ['AIMS_COMMAND']
249
        else:
250
            raise RuntimeError("No specification for running FHI-aims. Aborting!")
251
        aims_command = aims_command + ' >> ' 
252
        if self.input_parameters['run_dir']:
253
            aims_command = aims_command + self.input_parameters['run_dir'] + '/'
254
        aims_command = aims_command + self.out
255
        self.write_parameters('#',self.out)
256
        exitcode = os.system(aims_command)
257
        if exitcode != 0:
258
            raise RuntimeError('FHI-aims exited with exit code: %d.  ' % exitcode)
259
        if self.input_parameters['cubes'] and self.input_parameters['track_output']:
260
            self.input_parameters['cubes'].move_to_base_name(self.input_parameters['output_template']+str(self.run_counts-1))
261

    
262
    def write_parameters(self,prefix,filename):
263
        output = open(filename,'w')
264
        output.write(prefix+'=======================================================\n')
265
        output.write(prefix+'FHI-aims file: '+filename+'\n')
266
        output.write(prefix+'Created using the Atomic Simulation Environment (ASE)\n'+prefix+'\n')
267
        output.write(prefix+'List of parameters used to initialize the calculator:\n')
268
        output.write(prefix+'=======================================================\n')
269
        for key, val in self.float_params.items():
270
            if val is not None:
271
                output.write('%-35s%5.6f\n' % (key, val))        
272
        for key, val in self.exp_params.items():
273
            if val is not None:
274
                output.write('%-35s%5.2e\n' % (key, val))
275
        for key, val in self.string_params.items():
276
            if val is not None:
277
                output.write('%-35s%s\n' % (key, val))
278
        for key, val in self.int_params.items():
279
            if val is not None:
280
                output.write('%-35s%d\n' % (key, val))
281
        for key, val in self.bool_params.items():
282
            if val is not None:
283
                if key == 'vdw_correction_hirshfeld' and val:
284
                    output.write('%-35s\n' % (key))
285
                elif val:
286
                    output.write('%-35s.true.\n' % (key))
287
                elif key != 'vdw_correction_hirshfeld':
288
                    output.write('%-35s.false.\n' % (key))
289
        for key, val in self.list_params.items():
290
            if val is not None:
291
                if key == 'output':
292
                    if not isinstance(val,(list,tuple)): 
293
                        val = [val]
294
                    for output_type in val:
295
                        output.write('%-35s%s\n' % (key,str(output_type)))
296
                else:
297
                    output.write('%-35s' % (key))
298
                    if isinstance(val,str): 
299
                        output.write(val)
300
                    else:
301
                        for sub_value in val:
302
                            output.write(str(sub_value)+' ')
303
                    output.write('\n')
304
        for key, val in self.input_parameters.items():
305
            if key is  'cubes':
306
                if val:
307
                    val.write(output)
308
            elif val and val != input_parameters_default[key]:
309
                output.write(prefix+'%-34s%s\n' % (key,val))
310
        output.write(prefix+'=======================================================\n\n')
311
        output.close()
312

    
313
    def write_control(self, file = 'control.in'):
314
        """Writes the control.in file."""
315
        self.write_parameters('#',file)
316

    
317
    def write_species(self, file = 'control.in'):
318
        from ase.data import atomic_numbers
319

    
320
        if not self.input_parameters['species_dir']:
321
            raise RuntimeError('Missing species directory, THIS MUST BE SPECIFIED!')
322

    
323
        control = open(file, 'a')
324
        species_path = self.input_parameters['species_dir']
325
        symbols = self.atoms.get_chemical_symbols()
326
        symbols2 = []
327
        for n, symbol in enumerate(symbols):
328
            if symbol not in symbols2:
329
                symbols2.append(symbol)
330
        for symbol in symbols2:
331
            fd = join(species_path, '%02i_%s_default' % (atomic_numbers[symbol], symbol))
332
            for line in open(fd, 'r'):
333
                control.write(line)
334
        control.close()
335

    
336
    def get_dipole_moment(self, atoms):
337
        if self.list_params['output'] is None or 'dipole' not in self.list_params['output']:
338
            raise RuntimeError('output=[\'dipole\'] has to be set.')
339
        elif atoms.get_pbc().any():
340
            raise RuntimeError('FHI-aims does not allow this for systems with periodic boundary conditions.')
341
        self.update(atoms)
342
        return self.dipole
343

    
344
    def read_dipole(self):
345
        """Method that reads the electric dipole moment from the output file."""
346

    
347
        dipolemoment=np.zeros([1,3])
348
        for line in open(self.out, 'r'):
349
            if line.rfind('Total dipole moment [eAng]') > -1:
350
                dipolemoment=np.array([float(f) for f in line.split()[6:10]])
351
        return dipolemoment
352

    
353
    def read_energy(self, all=None):
354
        for line in open(self.out, 'r'):
355
            if line.rfind('Total energy corrected') > -1:
356
                E0 = float(line.split()[-2])
357
            elif line.rfind('Total energy uncorrected') > -1:
358
                F = float(line.split()[-2])
359
        energy_free, energy_zero = F, E0
360
        return [energy_free, energy_zero]
361

    
362
    def read_forces(self, atoms, all=False):
363
        """Method that reads forces from the output file.
364

365
        If 'all' is switched on, the forces for all ionic steps
366
        in the output file will be returned, in other case only the
367
        forces for the last ionic configuration are returned."""
368
        lines = open(self.out, 'r').readlines()
369
        forces = np.zeros([len(atoms), 3])
370
        for n, line in enumerate(lines):
371
            if line.rfind('Total atomic forces') > -1:
372
                for iatom in range(len(atoms)):
373
                    data = lines[n+iatom+1].split()
374
                    for iforce in range(3):
375
                        forces[iatom, iforce] = float(data[2+iforce])
376
        return forces
377

    
378
    def get_stress(self, atoms):
379
        raise NotImplementedError('Stresses are not currently available in FHI-aims, sorry. ')
380

    
381
# methods that should be quickly implemented some time, haven't had time yet:
382
    def read_fermi(self):
383
        """Method that reads Fermi energy from output file"""
384
        return
385

    
386
    def read_magnetic_moment(self):
387
        return
388

    
389
    def read_convergence(self):
390
        converged = False
391
        lines = open(self.out, 'r').readlines()
392
        for n, line in enumerate(lines):
393
            if line.rfind('Have a nice day') > -1:
394
                converged = True
395
        return converged
396

    
397
    def read_eigenvalues(self, kpt=0, spin=0):
398
        return 
399

    
400
class AimsCube:
401
    """ object to ensure the output of cube files, can be attached to Aims object"""
402
    def __init__(self,origin=(0,0,0),
403
                 edges=[(0.1,0.0,0.0),(0.0,0.1,0.0),(0.0,0.0,0.1)],
404
                 points=(50,50,50),plots=None):
405
        """ parameters: 
406
        origin, edges, points = same as in the FHI-aims output
407
        plots: what to print, same names as in FHI-aims """
408

    
409
        self.name   = 'AimsCube'
410
        self.origin = origin
411
        self.edges  = edges
412
        self.points = points
413
        self.plots  = plots
414
         
415
    def ncubes(self):
416
        """returns the number of cube files to output """
417
        if self.plots:
418
            number = len(self.plots)
419
        else:
420
            number = 0
421
        return number
422

    
423
    def set(self,**kwargs):
424
        """ set any of the parameters ... """
425
        # NOT IMPLEMENTED AT THE MOMENT!
426

    
427
    def move_to_base_name(self,basename):
428
        """ when output tracking is on or the base namem is not standard,
429
        this routine will rename add the base to the cube file output for 
430
        easier tracking """
431
        for plot in self.plots:
432
            found = False
433
            cube = plot.split()
434
            if cube[0] == 'total_density' or cube[0] == 'spin_density' or cube[0] == 'delta_density':
435
                found = True
436
                old_name = cube[0]+'.cube'
437
                new_name = basename+'.'+old_name
438
            if cube[0] == 'eigenstate' or cube[0] == 'eigenstate_density':
439
                found = True
440
                state = int(cube[1])
441
                s_state = cube[1]
442
                for i in [10,100,1000,10000]:
443
                    if state < i:
444
                        s_state = '0'+s_state
445
                old_name = cube[0]+'_'+s_state+'_spin_1.cube'
446
                new_name = basename+'.'+old_name
447
            if found:
448
                os.system("mv "+old_name+" "+new_name)
449

    
450
    def add_plot(self,name):
451
        """ in case you forgot one ... """
452
        plots += [name]
453

    
454
    def write(self,file):
455
        """ write the necessary output to the already opened control.in """
456
        file.write('output cube '+self.plots[0]+'\n')
457
        file.write('   cube origin ')
458
        for ival in self.origin:
459
            file.write(str(ival)+' ')
460
        file.write('\n')
461
        for i in range(3):
462
            file.write('   cube edge '+str(self.points[i])+' ')
463
            for ival in self.edges[i]:
464
                file.write(str(ival)+' ')
465
            file.write('\n')
466
        if self.ncubes() > 1:
467
            for i in range(self.ncubes()-1):
468
                file.write('output cube '+self.plots[i+1]+'\n')
469

    
470
                    
471