Source code for masci_tools.io.parsers.kkrimp_parser_functions

###############################################################################
# Copyright (c), Forschungszentrum Jülich GmbH, IAS-1/PGI-1, Germany.         #
#                All rights reserved.                                         #
# This file is part of the Masci-tools package.                               #
# (Material science tools)                                                    #
#                                                                             #
# The code is hosted on GitHub at https://github.com/judftteam/masci-tools.   #
# For further information on the license, see the LICENSE.txt file.           #
# For further information please visit http://judft.de/.                      #
#                                                                             #
###############################################################################
"""
Tools for the impurity calculation plugin and its workflows
"""
import numpy as np
import traceback
from masci_tools.io.common_functions import search_string, get_outfile_txt, get_version_info, convert_to_pystd
from masci_tools.io.parsers.kkrparser_functions import get_rms, find_warnings, get_charges_per_atom, get_core_states
from masci_tools.io.common_functions import get_Ry2eV

__copyright__ = ('Copyright (c), 2018, Forschungszentrum Jülich GmbH,'
                 'IAS-1/PGI-1, Germany. All rights reserved.')
__license__ = 'MIT license, see LICENSE.txt file'
__version__ = '0.8.1'
__contributors__ = ('Philipp Rüßmann', 'Fabian Bertoldo', 'David Antognini Silva')

####################################################################################


[docs]class KkrimpParserFunctions: """ Class of parser functions for KKRimp calculation :usage: success, msg_list, out_dict = parse_kkrimp_outputfile().parse_kkrimp_outputfile(out_dict, files) """ ### some helper functions ### def _get_econt_info(self, out_log): """ extract energy contour information from out_log file :param out_log: file that is parsed :returns: econt (dict), dictionary containing the energy contour info :note: econt contains the following keys * 'emin', bottom of energy contour * 'Nepts', number of points in energy contour * 'epts', list of complex valued energy points * 'weights', list of complex valued weights for energy integration """ tmptxt = get_outfile_txt(out_log) econt = {} itmp = search_string('[read_energy] number of energy points', tmptxt) if itmp >= 0: econt['Nepts'] = int(tmptxt.pop(itmp).split()[-1]) itmp = search_string('energies and weights are:', tmptxt) if itmp >= 0: tmp = [] for ie in range(econt['Nepts']): tmpline = tmptxt[itmp + 4 + ie].split()[1:] tmp.append([float(tmpline[0]), float(tmpline[1]), float(tmpline[2]), float(tmpline[3])]) tmp = np.array(tmp) econt['epts'] = tmp[:, :2] econt['weights'] = tmp[:, 2:] econt['emin'] = tmp[0, 0] return econt def _get_scfinfo(self, file): """ extract scf infos (nunmber of iterations, max number of iterations, mixing info) from file :param file: :returns: niter (int), nitermax (int), converged (bool), nmax_reached (bool), mixinfo (dict) :note: mixinfo contains information on mixing scheme and mixing factor used in the calculation """ tmptxt = get_outfile_txt(file) # get rms and number of iterations itmp, niter, rms = 0, -1, -1 while itmp >= 0: itmp = search_string('average rms-error', tmptxt) if itmp >= 0: tmp = tmptxt.pop(itmp).replace('D', 'E').split() niter = int(tmp[1]) rms = float(tmp[-1]) # get max number of scf steps itmp = search_string('SCFSTEPS', tmptxt) if itmp >= 0: nitermax = int(tmptxt.pop(itmp).split()[-1]) # get qbound itmp = search_string('QBOUND', tmptxt) if itmp >= 0: qbound = float(tmptxt.pop(itmp).split()[-1]) # get imix itmp = search_string('IMIX', tmptxt) if itmp >= 0: imix = int(tmptxt.pop(itmp).split()[-1]) # get mixfac itmp = search_string('MIXFAC', tmptxt) if itmp >= 0: mixfac = float(tmptxt.pop(itmp).split()[-1]) # get fcm itmp = search_string('FCM', tmptxt) if itmp >= 0: fcm = float(tmptxt.pop(itmp).split()[-1]) # set mixinfo mixinfo = [imix, mixfac, qbound, fcm] # set converged and nmax_reached logicals converged, nmax_reached = False, False if nitermax == niter: nmax_reached = True if rms < qbound: converged = True # return values return niter, nitermax, converged, nmax_reached, mixinfo def _get_newsosol(self, file): """ Check if spin orbit coupling solver is used :param file: absolute path to out_log.000.txt of KKRimp calculation :returns: True(False) if SOC solver is (not) used """ tmptxt = get_outfile_txt(file) itmp = search_string('Spin orbit coupling used?', tmptxt) itmp = int(tmptxt.pop(itmp).split()[-1]) newsosol = itmp == 1 return newsosol def _get_natom(self, file): """ Extract number of atoms in impurity cluster :param file: file that is parsed to find number of atoms :returns: natom (int), number of atoms in impurity cluster """ tmptxt = get_outfile_txt(file) itmp = search_string('NATOM is', tmptxt) natom = int(tmptxt.pop(itmp).split()[-1]) return natom def _get_magtot(self, file, natom, debug=False): """ Extract total magnetic moment of all atoms in imp. cluster, magnetic moment for each atom in the cluster and magn. moment for all atoms and all iterations of the calculation :param file: file that is parsed to find magnetic moments :param natom: number of atoms in the cluster :returns: magn. moment for all atoms in the cluster for the last iteration (saved in z-comp. of 3d vector) magn. moment for all atoms in the cluster for all iterations (saved in z-comp. of 3d vector) total magnetic moments of all atoms for last iteration """ tmptxt = get_outfile_txt(file) itmp = 0 spinmom_all = [] while itmp >= 0: itmp = search_string('spin magnetic moment =', tmptxt) if itmp >= 0: spinmom_all.append(float(tmptxt.pop(itmp).split()[-1])) # if no spin spinmom = spinmom_all[len(spinmom_all) - natom:] if len(spinmom) > 0: # this means we found something spinmom_vec = np.array([[0, 0, spinmom[0]]]) spinmom_vec_all = np.array([[0, 0, spinmom_all[0]]]) for i in range(1, natom): spinmom_vec = np.append(spinmom_vec, [[0, 0, spinmom[i]]], axis=0) for i in range(1, len(spinmom_all)): spinmom_vec_all = np.append(spinmom_vec_all, [[0, 0, spinmom_all[i]]], axis=0) magtot = sum(spinmom) else: # otherwise return empty lists spinmom_vec, spinmom_vec_all, magtot = [], [], [] return spinmom_vec, spinmom_vec_all, magtot def _extract_timings(self, outfile): """ Extract timings for the different parts in the KKRimp code :param outfile: timing file of the KKRimp run :returns: res (dict) timings in seconds, averaged over iterations """ tmptxt = get_outfile_txt(outfile) search_keys = [ 'time until scf starts', 'vpot->tmat', 'gref->gmat', 'gonsite->density', 'energyloop', 'Iteration number', 'Total running time' ] res = {} for isearch in search_keys: tmpval = [] itmp = 0 while itmp >= 0: itmp = search_string(isearch, tmptxt) if itmp >= 0: tmpval.append(float(tmptxt.pop(itmp).split()[-1])) if len(tmpval) > 0: res[isearch] = tmpval # average over iterations niter = len(res.get(search_keys[-2], [])) if niter > 0: for key in search_keys[1:6]: if key in res: res[key] = sum(res[key]) / niter for key in [search_keys[0], search_keys[-1]]: if key in res: res[key] = res[key][0] return res def _get_nspin(self, file): """ Extract nspin from file :param file: file that is parsed :returns: 1 if calculation is paramagnetic, 2 otherwise """ tmptxt = get_outfile_txt(file) itmp = search_string('NSPIN', tmptxt) nspin = int(tmptxt.pop(itmp).split()[-1]) return nspin def _get_spinmom_per_atom(self, file, natom): """ Extract spin moment for all atoms :param file: file that is parsed :param natom: number of atoms in impurity cluster :returns: spinmom_at (array of spin moments for all atoms and the last iteration), spinmom_at_all (array of spin moments for all atoms and iterations), spinmom_at_tot (total spinmoment for the last iteration) """ from math import sqrt #pylint: disable=no-name-in-module lines = get_outfile_txt(file) startline = len(lines) - natom spinmom_at = np.array([lines[startline].split()]) spinmom_at_all = np.array([lines[1].split()]) for i in range(1, natom): spinmom_at = np.append(spinmom_at, [lines[startline + i].split()], axis=0) for j in range(2, len(lines)): spinmom_at_all = np.append(spinmom_at_all, [lines[j].split()], axis=0) spinmom_at_tot = 0 for i in range(0, natom): spinmom_at_tot += sqrt(float(spinmom_at[i][0])**2 + float(spinmom_at[i][1])**2 + float(spinmom_at[i][2])**2) # make sure the values are converted from string to float spinmom_at = np.array(spinmom_at, dtype=float) spinmom_at_all = np.array(spinmom_at_all, dtype=float) return spinmom_at, spinmom_at_all, spinmom_at_tot def _get_orbmom_per_atom(self, file, natom): """ Extract orbital moment for all atoms (orbmom_at: all atoms in last iteration, orbmom_at_all: all atoms in all iterations). For each atom there are six values: first -> x-component real part, second -> x-component imaginary part, third -> y-component real part, ... sixth -> z-component imaginary part. :param file: file that is parsed :param natom: number of atoms in impurity cluster :returns: orbmom_at (list), orbital moments for all atoms """ lines = get_outfile_txt(file) startline = len(lines) - natom orbmom_at = [] for i in range(natom): tmp = lines[startline + i].split() orbmom_at.append([tmp[1], tmp[3], tmp[5]]) # [1,3,5] needed since full complex number is written orbmom_at = np.array(orbmom_at, dtype=float) # convert to float array # do the same for all iterations orbmom_at_all = [] for i in range(1, len(lines)): tmp = lines[i].split() orbmom_at_all.append([tmp[1], tmp[3], tmp[5]]) orbmom_at_all = np.array(orbmom_at_all, dtype=float) # convert to float array return orbmom_at, orbmom_at_all def _get_EF_potfile(self, potfile): """ Extract EF value from potential file :param potfile: file that is parsed :returns: EF (float), value of the Fermi energy in Ry """ tmptxt = get_outfile_txt(potfile) EF = float(tmptxt[3].split()[1]) return EF def _get_Etot(self, file): """ Extract total energy file :param file: file that is parsed :returns: Etot (list), values of the total energy in Ry for all iterations """ tmptxt = get_outfile_txt(file) itmp = 0 Etot = [] while itmp >= 0: itmp = search_string('TOTAL ENERGY', tmptxt) if itmp >= 0: Etot.append(float(tmptxt.pop(itmp).split()[-1])) return Etot def _get_energies_atom(self, file1, file2, natom): """ Extract single particle and total energies in Ry for all atoms from file 1 and file 2 :param file1: file containing all single particle energies :param file2: file containing all total energies :returns: esp_at (list), etot_at (list) """ esp = np.loadtxt(file1) etot = np.loadtxt(file2) if natom > 1: esp_at = esp[-natom:, 1] etot_at = etot[-natom:, 1] else: esp_at = esp etot_at = etot return esp_at, etot_at ### end helper functions ###
[docs] def parse_kkrimp_outputfile(self, out_dict, file_dict, debug=False, ignore_nan=False): """ Main parser function for kkrimp, read information from files in file_dict and fills out_dict :param out_dict: dictionary that is filled with parsed output of the KKRimp calculation :param file_dict: dictionary of files that are parsed :param debug: True/False to activate debug output :param ignore_nan: bool replace NaN by zero with numpy's nan_to_num function :returns: success (bool), msg_list(list of error/warning messages of parser), out_dict (filled dict of parsed output) :note: file_dict should contain the following keys - 'outfile', the std_out of the KKRimp calculation - 'out_log', the out_log.000.txt file - 'out_pot', the output potential - 'out_enersp_at', the out_energysp_per_atom_eV file - 'out_enertot_at', the out_energytotal_per_atom_eV file - 'out_timing', the timing file - 'kkrflex_llyfac', the file for the Lloyd factor - 'kkrflex_angles', the nonco_angles file for the KKRimp calculation - 'out_spinmoms', the output spin moments file - 'out_orbmoms', the output orbital moments file """ msg_list = [] files = file_dict try: code_version, compile_options, serial_number = get_version_info(files['out_log']) tmp_dict = {} tmp_dict['code_version'] = code_version tmp_dict['compile_options'] = compile_options tmp_dict['calculation_serial_number'] = serial_number out_dict['code_info_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: Version Info' msg_list.append(msg) if debug: traceback.print_exc() tmp_dict = {} # used to group convergence info (rms, rms per atom, charge neutrality) # also initialize convegence_group where all info stored for all iterations is kept out_dict['convergence_group'] = tmp_dict try: rms_charge, rms_ldau, rms_spin, result_atoms_last_charge, result_atoms_last_spin = get_rms(files['outfile'], files['out_log'], debug=debug, is_imp_calc=True) tmp_dict['rms'] = rms_charge[-1] tmp_dict['rms_all_iterations'] = rms_charge tmp_dict['rms_LDAU'] = rms_ldau[-1] tmp_dict['rms_LDAU_all_iterations'] = rms_ldau tmp_dict['rms_per_atom'] = result_atoms_last_charge if len(rms_spin) > 0: tmp_dict['rms_spin'] = rms_spin[-1] else: tmp_dict['rms_spin'] = None tmp_dict['rms_spin_all_iterations'] = rms_spin tmp_dict['rms_spin_per_atom'] = result_atoms_last_spin tmp_dict['rms_unit'] = 'unitless' out_dict['convergence_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: rms-error' msg_list.append(msg) if debug: traceback.print_exc() try: nspin = self._get_nspin(files['out_log']) natom = self._get_natom(files['out_log']) newsosol = self._get_newsosol(files['out_log']) out_dict['nspin'] = nspin out_dict['number_of_atoms_in_unit_cell'] = natom out_dict['use_newsosol'] = newsosol except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: nspin/natom' msg_list.append(msg) if debug: traceback.print_exc() tmp_dict = {} # used to group magnetism info (spin and orbital moments) try: result = self._get_magtot(files['out_log'], natom, debug=debug) if len(result) > 0: tmp_dict['total_spin_moment'] = result[-1] out_dict['convergence_group']['total_spin_moment_all_iterations'] = result tmp_dict['total_spin_moment_unit'] = 'mu_Bohr' out_dict['magnetism_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: total magnetic moment' msg_list.append(msg) if debug: traceback.print_exc() try: if nspin > 1 and newsosol: #result, vec, angles = get_spinmom_per_atom(outfile, natom, nonco_out_file) spinmom_atom, spinmom_atom_vec_all_iter, spin_tot_abs = self._get_spinmom_per_atom( files['out_spinmoms'], natom) if ignore_nan: spinmom_atom = np.nan_to_num(spinmom_atom) spinmom_atom_vec_all_iter = np.nan_to_num(spinmom_atom_vec_all_iter) spin_tot_abs = np.nan_to_num(spin_tot_abs) if len(result) > 0: tmp_dict['total_abs_spin_moment'] = spin_tot_abs tmp_dict['spin_moment_per_atom'] = spinmom_atom out_dict['convergence_group']['spin_moment_per_atom_all_iterations'] = spinmom_atom_vec_all_iter tmp_dict['spin_moment_unit'] = 'mu_Bohr' out_dict['magnetism_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: spin moment per atom' msg_list.append(msg) if debug: traceback.print_exc() # add orbital moments to magnetis group in parser output try: if nspin > 1 and newsosol: orbmom_atom, orbmom_atom_all_iter = self._get_orbmom_per_atom(files['out_orbmoms'], natom) if len(result) > 0: tmp_dict['total_orbital_moment'] = sum(orbmom_atom) tmp_dict['orbital_moment_per_atom'] = orbmom_atom out_dict['convergence_group']['orbital_moment_per_atom_all_iterations'] = orbmom_atom_all_iter tmp_dict['orbital_moment_unit'] = 'mu_Bohr' out_dict['magnetism_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: orbital moment' msg_list.append(msg) if debug: traceback.print_exc() try: result = self._get_EF_potfile(files['out_pot']) out_dict['fermi_energy'] = result out_dict['fermi_energy_units'] = 'Ry' except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: EF' msg_list.append(msg) if debug: traceback.print_exc() try: result = self._get_Etot(files['out_log']) out_dict['energy'] = result[-1] * get_Ry2eV() out_dict['energy_unit'] = 'eV' out_dict['total_energy_Ry'] = result[-1] out_dict['total_energy_Ry_unit'] = 'Rydberg' out_dict['convergence_group']['total_energy_Ry_all_iterations'] = result except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: total energy' msg_list.append(msg) if debug: traceback.print_exc() try: result = find_warnings(files['outfile']) tmp_dict = {} tmp_dict['number_of_warnings'] = len(result) tmp_dict['warnings_list'] = result out_dict['warnings_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: search for warnings' msg_list.append(msg) if debug: traceback.print_exc() try: result = self._extract_timings(files['out_timing']) out_dict['timings_group'] = result out_dict['timings_unit'] = 'seconds' except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: timings' msg_list.append(msg) if debug: traceback.print_exc() try: esp_at, etot_at = self._get_energies_atom(files['out_enersp_at'], files['out_enertot_at'], natom) out_dict['single_particle_energies'] = esp_at * get_Ry2eV() out_dict['single_particle_energies_unit'] = 'eV' out_dict['total_energies_atom'] = etot_at * get_Ry2eV() out_dict['total_energies_atom_unit'] = 'eV' except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: single particle energies' msg_list.append(msg) if debug: traceback.print_exc() try: result_WS, result_tot, result_C = get_charges_per_atom(files['out_log']) niter = len(out_dict['convergence_group']['rms_all_iterations']) natyp = int(len(result_tot) / niter) out_dict['total_charge_per_atom'] = result_WS[-natyp:] out_dict['charge_core_states_per_atom'] = result_C[-natyp:] # this check deals with the DOS case where output is slightly different if len(result_WS) == len(result_C): out_dict['charge_valence_states_per_atom'] = result_WS[-natyp:] - result_C[-natyp:] out_dict['total_charge_per_atom_unit'] = 'electron charge' out_dict['charge_core_states_per_atom_unit'] = 'electron charge' out_dict['charge_valence_states_per_atom_unit'] = 'electron charge' except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: charges' msg_list.append(msg) if debug: traceback.print_exc() try: econt = self._get_econt_info(files['out_log']) tmp_dict = {} tmp_dict['emin'] = econt.get('emin') tmp_dict['emin_unit'] = 'Rydberg' tmp_dict['number_of_energy_points'] = econt.get('Nepts') tmp_dict['epoints_contour'] = econt.get('epts') tmp_dict['epoints_contour_unit'] = 'Rydberg' tmp_dict['epoints_weights'] = econt.get('weights') out_dict['energy_contour_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: energy contour' msg_list.append(msg) if debug: traceback.print_exc() try: ncore, emax, lmax, descr_max = get_core_states(files['out_pot']) tmp_dict = {} tmp_dict['number_of_core_states_per_atom'] = ncore tmp_dict['energy_highest_lying_core_state_per_atom'] = emax tmp_dict['energy_highest_lying_core_state_per_atom_unit'] = 'Rydberg' tmp_dict['descr_highest_lying_core_state_per_atom'] = descr_max out_dict['core_states_group'] = tmp_dict except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: core_states' msg_list.append(msg) if debug: traceback.print_exc() try: niter, nitermax, converged, nmax_reached, mixinfo = self._get_scfinfo(files['out_log']) out_dict['convergence_group']['number_of_iterations'] = niter out_dict['convergence_group']['number_of_iterations_max'] = nitermax out_dict['convergence_group']['calculation_converged'] = converged out_dict['convergence_group']['nsteps_exhausted'] = nmax_reached out_dict['convergence_group']['imix'] = mixinfo[0] out_dict['convergence_group']['strmix'] = mixinfo[1] out_dict['convergence_group']['qbound'] = mixinfo[2] out_dict['convergence_group']['fcm'] = mixinfo[3] out_dict['convergence_group']['brymix'] = mixinfo[1] except: # pylint: disable=bare-except msg = 'Error parsing output of KKRimp: scfinfo' msg_list.append(msg) if debug: traceback.print_exc() #convert numpy arrays to standard python lists out_dict = convert_to_pystd(out_dict) # return output with error messages if there are any return len(msg_list) == 0, msg_list, out_dict