Source code for aiida_fleur.tools.extract_corelevels
###############################################################################
# Copyright (c), Forschungszentrum Jülich GmbH, IAS-1/PGI-1, Germany. #
# All rights reserved. #
# This file is part of the AiiDA-FLEUR package. #
# #
# The code is hosted on GitHub at https://github.com/JuDFTteam/aiida-fleur #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.flapw.de or #
# http://aiida-fleur.readthedocs.io/en/develop/ #
###############################################################################
"""
In this module you find methods to parse/extract corelevel shifts from an
out.xml file of FLEUR.
"""
# TODO clean up
# TODO together with xml_util, parser info handling, has to be also a return value of everything
# or rather throw exception on lowest level and catch at higher levels?
from lxml import etree #, objectify
import warnings
from masci_tools.util.xml.common_functions import eval_xpath, get_xml_attribute
#convert_to_float
#import time
#start_time = time.time()
##
#calculation to extract from:
# either from list given here or system argument
#calcs_pks = []
#calcs_pks = [1464, 1462, 1399, 1403]#, 1059]#, 1414
####
#if not calcs_pks:
# try:
# for arg in sys.argv[1:]:
# calc_t = arg
# calcs_pks.append(int(calc_t))
# except:
# pass
#####
'''
# check if calculation pks belong to successful fleur calculations
for pk in calcs_pks:
calc = load_node(pk)
if (not isinstance(calc, FleurCalc)):
raise ValueError("Calculation with pk {} must be a FleurCalculation".format(pk))
if calc.get_state() != 'FINISHED':
raise ValueError("Calculation with pk {} must be in state FINISHED".format(pk))
'''
def extract_lo_energies(outxmlfile, options=None):
pass
#TODO: how? out of DOS?
[docs]def extract_corelevels(outxmlfile, options=None):
"""
Extracts corelevels out of out.xml files
:params outxmlfile: path to out.xml file
:param options: A dict: 'iteration' : X/'all'
:returns corelevels: A list of the form:
.. code-block:: python
[atomtypes][spin][dict={atomtype : '', corestates : list_of_corestates}]
[atomtypeNumber][spin]['corestates'][corestate number][attribute]
get corelevel energy of first atomtype, spin1, corelevels[0][0]['corestates'][i]['energy']
:example of output:
.. code-block:: python
[[{'atomtype': ' 1',
'corestates': [{'energy': -3.6489930627,
'j': ' 0.5',
'l': ' 0',
'n': ' 1',
'weight': 2.0}],
'eigenvalue_sum': ' -7.2979861254',
'kin_energy': ' 13.4757066163',
'spin': '1'}],
[{'atomtype': ' 2',
'corestates': [{'energy': -3.6489930627,
'j': ' 0.5',
'l': ' 0',
'n': ' 1',
'weight': 2.0}],
'eigenvalue_sum': ' -7.2979861254',
'kin_energy': ' 13.4757066163',
'spin': '1'}]]
"""
##########################################
#1. read out.xml in etree
#2. get all species
#3. get number of atom types and their species
#4 get corelevel dimension from atoms types.
#5 init saving arrays:
#list length number of atom types, which contains dictionaries:
# in the form { 'species' : species, 'coresetup' : '', 'atom' : W , 'corelevels' : []} lists of corelevels from last iteration (Do i want all iterations, optional?) Or do I even want a dictionaries of corelevels? (but coresetup is in atom type info
#6 parse corelevels:
# get last iteration
# fill corelevel list
#######################################
########################
#XPATHS to maintain
warnings.warn(
'extract_corelevels is deprecated. You can use the outxml_parser\n'
"in masci_tools.io.parsers.fleur instead with outxml_parser(outxmlfile, optional_tasks=('corelevels'))\n"
'To get this information', DeprecationWarning)
species_xpath = '/fleurOutput/inputData/atomSpecies'
iteration_xpath = '/fleurOutput/scfLoop/iteration'
atomgroup_xpath = '/fleurOutput/inputData/atomGroups'
coreconfig_xpath = 'electronConfig/coreConfig/text()'
valenceconfig_xpath = 'electronConfig/valenceConfig/text()'
state_occ_xpath = 'electronConfig/stateOccupation'
new_species_xpath = '/fleurOutput/fleurInput/atomSpecies'
new_atomgroup_xpath = '/fleurOutput/fleurInput/atomGroups'
relcoreStates_xpath = 'coreStates'
relpos_xpath = 'relPos'
abspos_xpath = 'absPos'
filmpos_xpath = 'filmPos'
#TODO all the attribute names...
######################
#1. read out.xml in etree
# TODO this should be common, moved somewhere else and importet
parsed_data = {}
outfile_broken = False
parse_xml = True
parser = etree.XMLParser(recover=False) #, remove_blank_text=True)
parser_info = {'parser_warnings': [], 'unparsed': []}
try:
tree = etree.parse(outxmlfile, parser)
except etree.XMLSyntaxError:
outfile_broken = True
#print 'broken xml'
parser_info['parser_warnings'].append('The out.xml file is broken I try to repair it.')
if outfile_broken:
#repair xmlfile and try to parse what is possible.
parser = etree.XMLParser(recover=True) #, remove_blank_text=True)
try:
tree = etree.parse(outxmlfile, parser)
except etree.XMLSyntaxError:
parser_info['parser_warnings'].append('Skipping the parsing of the xml file. Repairing was not possible.')
parse_xml = False
#if parse_xml:
root = tree.getroot()
# 2. get all species from input
# get element, name, coreStates
# TODO why can this not be eval_xpath2?
species_nodes = eval_xpath(root, species_xpath)
if not species_nodes:
species_nodes = eval_xpath(root, new_species_xpath)
species_atts = {}
species_names = []
for species in species_nodes:
species_name = species.get('name')
species_corestates = species.get('coreStates')
species_element = species.get('element')
species_atomicnumber = species.get('atomicNumber')
species_magMom = species.get('magMom')
#TODO sometimes not in inp.xml... what if it is not there
coreconfig = eval_xpath(species, coreconfig_xpath)
valenceconfig = eval_xpath(species, valenceconfig_xpath)
state_occ = eval_xpath(species, state_occ_xpath, list_return=True)
#parse state occ
state_results = []
for tag in state_occ: #always a list?
state = tag.get('state')
spinUp = tag.get('spinUp')
spinDown = tag.get('spinDown')
state_results.append({state: [spinUp, spinDown]})
species_atts[species_name] = {
'name': species_name,
'corestates': species_corestates,
'element': species_element,
'atomgroups': [],
'mag_mom': species_magMom,
'atomic_number': species_atomicnumber,
'coreconfig': coreconfig,
'valenceconfig': valenceconfig,
'stateOccupation': state_results
}
species_names.append(species_name)
#3. get number of atom types and their species from input
atomtypes = []
atomgroup_nodes = eval_xpath(root, atomgroup_xpath) #/fleurinp/
if not atomgroup_nodes:
atomgroup_nodes = eval_xpath(root, new_atomgroup_xpath)
# always a list?
for atomgroup in atomgroup_nodes:
types_dict = {}
group_species = atomgroup.get('species')
if group_species in species_names:
species_atts[group_species]['atomgroups'].append(atomgroup)
element = species_atts[group_species]['element']
atomicnumber = int(species_atts[group_species]['atomic_number'])
coreconf = species_atts[group_species]['coreconfig']
valenceconf = species_atts[group_species]['valenceconfig']
stateocc = species_atts[group_species]['stateOccupation']
a = eval_xpath(atomgroup, relpos_xpath, list_return=True) \
+ eval_xpath(atomgroup, abspos_xpath, list_return=True) \
+ eval_xpath(atomgroup, filmpos_xpath, list_return=True) # always list
natoms = len(a)
types_dict = {
'species': group_species,
'element': element,
'atomic_number': atomicnumber,
'coreconfig': coreconf,
'valenceconfig': valenceconf,
'stateOccupation': stateocc,
'natoms': natoms
}
atomtypes.append(types_dict)
#natomgroup = len(atomgroup_nodes)
#print(natomgroup)
corelevels = []
#4 get corelevel dimension from atoms types.
#5 init saving arrays:
#6 parse corelevels:
iteration_nodes = eval_xpath(root, iteration_xpath, list_return=True)
nIteration = len(iteration_nodes)
if nIteration >= 1:
iteration_to_parse = iteration_nodes[-1] #TODO:Optional all or other
#print iteration_to_parse
corestatescards = eval_xpath(iteration_to_parse, relcoreStates_xpath, list_return=True)
# maybe does not return a list...
for atype in atomtypes: # spin=2 is already in there
corelevels.append([])
for corestatescard in corestatescards:
corelv = parse_state_card(corestatescard, iteration_to_parse, parser_info)
corelevels[int(corelv['atomtype']) - 1].append(corelv) # is corelv['atomtype'] always an integer?
#print parser_info
#pprint(corelevels[0][1]['corestates'][2]['energy'])
#corelevels[atomtypeNumber][spin]['corestates'][corestate number][attribute]
return corelevels, atomtypes
[docs]def parse_state_card(corestateNode, iteration_node, parser_info=None):
"""
Parses the ONE core state card
:params corestateNode: an etree element (node), of a fleur output corestate card
:params iteration_node: an etree element, iteration node
:params jspin: integer 1 or 2
:returns: a pythondict of type:
.. code-block:: python
{'eigenvalue_sum' : eigenvalueSum,
'corestates': states,
'spin' : spin,
'kin_energy' : kinEnergy,
'atomtype' : atomtype}
"""
##### all xpath of density convergence card (maintain) ########
coreStates_xpath = 'coreStates'
state_xpath = 'state'
units_name = 'units'
value_name = 'value'
distance_name = 'distance'
n_name = 'n'
j_name = 'j'
l_name = 'l'
energy_name = 'energy'
weight_name = 'weight'
spin_name = 'spin'
kinEnergy_name = 'kinEnergy'
eigenvalueSum_name = 'eigValSum'
lostElectrons_name = 'lostElectrons'
atomtype_name = 'atomType'
#######
if parser_info is None:
parser_info = {'parser_warnings': []}
atomtype = get_xml_attribute(corestateNode, atomtype_name)
kinEnergy = get_xml_attribute(corestateNode, kinEnergy_name)
vE2, suc = convert_to_float(kinEnergy)
eigenvalueSum = get_xml_attribute(corestateNode, eigenvalueSum_name)
vE2, suc = convert_to_float(eigenvalueSum)
spin = get_xml_attribute(corestateNode, spin_name)
#print('spin {}'.format(spin))
#states = corestateNode.xpath(
#for state in states:
# get all corestate tags, (atomtypes * spin)
#corestateNodes = eval_xpath(iteration_node, coreStates_xpath, parser_info)
# for every corestate tag parse the attributes
# some only the first interation, then get all state tags of the corestate tag (atom depended)
# parse each core state #Attention to spin
states = []
corestates = eval_xpath(corestateNode, state_xpath, list_return=True) #, parser_info)
for corestate in corestates: # be careful that corestates is a list
state_dict = {}
n_state = get_xml_attribute(corestate, n_name)
l_state = get_xml_attribute(corestate, l_name)
j_state = get_xml_attribute(corestate, j_name)
energy, suc = convert_to_float(get_xml_attribute(corestate, energy_name))
weight, suc = convert_to_float(get_xml_attribute(corestate, weight_name))
state_dict = {'n': n_state, 'l': l_state, 'j': j_state, 'energy': energy, 'weight': weight}
states.append(state_dict)
core_states = {
'eigenvalue_sum': eigenvalueSum,
'corestates': states,
'spin': spin,
'kin_energy': kinEnergy,
'atomtype': atomtype
}
return core_states
# TODO should be used from somewhere else, probably double
[docs]def convert_to_float(value_string, parser_info=None):
"""
Tries to make a float out of a string. If it can't it logs a warning
and returns True or False if convertion worked or not.
:param value_string: a string
:returns value: the new float or value_string: the string given
:retruns: True or False
"""
if parser_info is None:
parser_info = {'parser_warnings': []}
try:
value = float(value_string)
except TypeError:
parser_info['parser_warnings'].append(f'Could not convert: "{value_string}" to float, TypeError')
return value_string, False
except ValueError:
parser_info['parser_warnings'].append(f'Could not convert: "{value_string}" to float, ValueError')
return value_string, False
return value, True
'''
### call
test_outxmlfiles = ['./out.xml', './test_outxml/outCuF.xml', './test_outxml/outFe.xml', './test_outxml/outHg.xml', './test_outxml/outO.xml']
outxmlfile = test_outxmlfiles[2]
corelevels = extract_corelevels(outxmlfile)
for i in range(0,len(corelevels[0][1]['corestates'])):
print corelevels[0][1]['corestates'][i]['energy']
for calc in calcs_pks:
pass
# get out.xml file of calculation
outxml = load_node(pk).out.retrieved.folder.get_abs_path('out.xml')
extract_corelevels(outxml)
print("--- %s seconds ---" % (time.time() - start_time))
'''
# TODO this is prob doubled is also in init_cls wc
[docs]def clshifts_to_be(coreleveldict, reference_dict, warn=False):
"""
This methods converts corelevel shifts to binding energies, if a reference is given.
These can than be used for plotting.
:params reference_dict: An example:
.. code-block:: python
reference_dict = {'W' : {'4f7/2' : [124],
'4f5/2' : [102]},
'Be' : {'1s': [117]}}
:params coreleveldict: An example:
.. code-block:: python
coreleveldict = {'W' : {'4f7/2' : [0.4, 0.3, 0.4 ,0.1],
'4f5/2' : [0, 0.3, 0.4, 0.1]},
'Be' : {'1s': [0, 0.2, 0.4, 0.1, 0.3]}
"""
# this block of comments was extracted from the docstring
# I did not understand where it belongs
# {'Be': {'1s': [117, 117.2, 117.4, 117.1, 117.3]},
# 'W': {'4f5/2': [102, 102.3, 102.4, 102.1],
# '4f7/2': [124.4, 124.3, 124.4, 124.1]}}
return_corelevel_dict = {}
for elem, corelevel_dict in coreleveldict.items():
ref_el = reference_dict.get(elem, {})
if not ref_el: # no refernce for that element given
if warn:
print(f"WARNING: Reference for element: '{elem}' not given. I ignore these.")
continue
return_corelevel_dict[elem] = {}
for corelevel_name, corelevel_list in corelevel_dict.items():
ref_cl = ref_el.get(corelevel_name, [])
if not ref_cl: # no reference corelevel given for that element
if warn:
print("WARNING: Reference corelevel '{}' for element: '{}' "
'not given. I ignore these.'.format(corelevel_name, elem))
continue
be_all = []
nref = len(ref_cl)
ncl = len(corelevel_list)
if nref == ncl:
for i, corelevel in enumerate(corelevel_list):
be = corelevel + ref_cl[i]
be_all.append(be)
else:
for corelevel in corelevel_list:
be = corelevel + ref_cl[0]
be_all.append(be)
return_corelevel_dict[elem][corelevel_name] = be_all
return return_corelevel_dict