# -*- coding: utf-8 -*-
"""
A collection of function that are used to parse the output of Quantum Espresso PW.
The function that needs to be called from outside is parse_raw_output().
The functions mostly work without aiida specific functionalities.
The parsing will try to convert whatever it can in some dictionary, which
by operative decision doesn't have much structure encoded, [the values are simple ]
"""
import xml.dom.minidom
import os
import string
from aiida.parsers.plugins.quantumespresso.constants import ry_to_ev, hartree_to_ev, bohr_to_ang, ry_si, bohr_si
from aiida.parsers.plugins.quantumespresso import QEOutputParsingError
# TODO: it could be possible to use info of the input file to parse output.
# but atm the output has all the informations needed for the parsing.
# parameter that will be used later for comparisons
__copyright__ = u"Copyright (c), 2015, ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE (Theory and Simulation of Materials (THEOS) and National Centre for Computational Design and Discovery of Novel Materials (NCCR MARVEL)), Switzerland and ROBERT BOSCH LLC, USA. All rights reserved."
__license__ = "MIT license, see LICENSE.txt file"
__version__ = "0.5.0"
__contributors__ = "Andrea Cepellotti, Andrius Merkys, Giovanni Pizzi, Martin Uhrin, Nicolas Mounet"
lattice_tolerance = 1.e-5
default_energy_units = 'eV'
units_suffix = '_units'
k_points_default_units = '2 pi / Angstrom'
default_length_units = 'Angstrom'
default_dipole_units = 'Debye'
default_magnetization_units = 'Bohrmag / cell'
default_force_units = 'ev / angstrom'
default_stress_units = 'GPascal'
default_polarization_units = 'C / m^2'
[docs]def parse_raw_output(out_file, input_dict, parser_opts=None, xml_file=None, dir_with_bands=None):
"""
Parses the output of a calculation
Receives in input the paths to the output file and the xml file.
:param out_file: path to pw std output
:param input_dict: not used
:param parser_opts: not used
:param dir_with_bands: path to directory with all k-points (Kxxxxx) folders
:param xml_file: path to QE data-file.xml
:returns out_dict: a dictionary with parsed data
:return successful: a boolean that is False in case of failed calculations
:raises QEOutputParsingError: for errors in the parsing,
:raises AssertionError: if two keys in the parsed dicts are found to be qual
3 different keys to check in output: parser_warnings, xml_warnings and warnings.
On an upper level, these flags MUST be checked.
The first two are expected to be empty unless QE failures or unfinished jobs.
"""
import copy
# TODO: a lot of ifs could be cleaned out
# TODO: input_dict should be used as well
job_successful = True
parser_version = '0.1'
parser_info = {}
parser_info['parser_warnings'] = []
parser_info['parser_info'] = 'AiiDA QE Basic Parser v{}'.format(parser_version)
# if xml_file is not given in input, skip its parsing
if xml_file is not None:
try:
with open(xml_file, 'r') as f:
xml_lines = f.read() # Note: read() and not readlines()
except IOError:
raise QEOutputParsingError("Failed to open xml file: {}.".format(xml_file))
xml_data, structure_data = parse_pw_xml_output(xml_lines, dir_with_bands)
# Note the xml file should always be consistent.
else:
parser_info['parser_warnings'].append('Skipping the parsing of the xml file.')
xml_data = {}
bands_data = {}
structure_data = {}
# load QE out file
try:
with open(out_file, 'r') as f:
out_lines = f.read()
except IOError: # non existing output file -> job crashed
raise QEOutputParsingError("Failed to open output file: {}.".format(out_file))
if not out_lines: # there is an output file, but it's empty -> crash
job_successful = False
# check if the job has finished (that doesn't mean without errors)
finished_run = False
for line in out_lines.split('\n')[::-1]:
if 'JOB DONE' in line:
finished_run = True
break
if not finished_run: # error if the job has not finished
warning = 'QE pw run did not reach the end of the execution.'
parser_info['parser_warnings'].append(warning)
job_successful = False
# parse
try:
out_data, trajectory_data, critical_messages = parse_pw_text_output(out_lines, xml_data, structure_data,
input_dict)
except QEOutputParsingError:
if not finished_run: # I try to parse it as much as possible
parser_info['parser_warnings'].append('Error while parsing the output file')
out_data = {}
trajectory_data = {}
critical_messages = []
else: # if it was finished and I got error, it's a mistake of the parser
raise QEOutputParsingError('Error while parsing QE output')
# I add in the out_data all the last elements of trajectory_data values.
# Safe for some large arrays, that I will likely never query.
skip_keys = ['forces', 'lattice_vectors_relax',
'atomic_positions_relax', 'atomic_species_name']
tmp_trajectory_data = copy.copy(trajectory_data)
for x in tmp_trajectory_data.iteritems():
if x[0] in skip_keys:
continue
out_data[x[0]] = x[1][-1]
if len(x[1]) == 1: # delete eventual keys that are not arrays (scf cycles)
trajectory_data.pop(x[0])
# note: if an array is empty, there will be KeyError
for key in ['k_points', 'k_points_weights']:
try:
trajectory_data[key] = xml_data.pop(key)
except KeyError:
pass
# As the k points are an array that is rather large, and again it's not something I'm going to parse likely
# since it's an info mainly contained in the input file, I move it to the trajectory data
# if there is a severe error, the calculation is FAILED
if any([x in out_data['warnings'] for x in critical_messages]):
job_successful = False
for key in out_data.keys():
if key in xml_data.keys():
if key == 'fermi_energy' or key == 'fermi_energy_units': # an exception for the (only?) key that may be found on both
del out_data[key]
else:
raise AssertionError('{} found in both dictionaries, '
'values: {} vs. {}'.format(
key, out_data[key], xml_data[key])) # this shouldn't happen!
# out_data keys take precedence and overwrite xml_data keys,
# if the same key name is shared by both
# dictionaries (but this should not happen!)
parameter_data = dict(xml_data.items() + out_data.items() + parser_info.items())
# return various data.
# parameter data will be mapped in ParameterData
# trajectory_data in ArrayData
# structure_data in a Structure
# bands_data should probably be merged in ArrayData
return parameter_data, trajectory_data, structure_data, job_successful
[docs]def cell_volume(a1, a2, a3):
"""
returns the volume of the primitive cell: \|a1.(a2xa3)\|
"""
a_mid_0 = a2[1] * a3[2] - a2[2] * a3[1]
a_mid_1 = a2[2] * a3[0] - a2[0] * a3[2]
a_mid_2 = a2[0] * a3[1] - a2[1] * a3[0]
return abs(float(a1[0] * a_mid_0 + a1[1] * a_mid_1 + a1[2] * a_mid_2))
# In the following, some functions that helps the parsing of
# the xml file of QE v5.0.x (version below not tested)
def read_xml_card(dom, cardname):
try:
root_node = [_ for _ in dom.childNodes if
isinstance(_, xml.dom.minidom.Element)
and _.nodeName == "Root"][0]
the_card = [_ for _ in root_node.childNodes if _.nodeName == cardname][0]
# the_card = dom.getElementsByTagName(cardname)[0]
return the_card
except Exception as e:
print e
raise QEOutputParsingError('Error parsing tag {}'.format(cardname))
def parse_xml_child_integer(tagname, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.childNodes[0]
return int(b.data)
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}'
.format(tagname, target_tags.tagName))
def parse_xml_child_float(tagname, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.childNodes[0]
return float(b.data)
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}' \
.format(tagname, target_tags.tagName))
def parse_xml_child_bool(tagname, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.childNodes[0]
return str2bool(b.data)
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}' \
.format(tagname, target_tags.tagName))
def str2bool(string):
try:
false_items = ["f", "0", "false", "no"]
true_items = ["t", "1", "true", "yes"]
string = str(string.lower().strip())
if string in false_items:
return False
if string in true_items:
return True
else:
raise QEOutputParsingError('Error converting string '
'{} to boolean value.'.format(string))
except Exception:
raise QEOutputParsingError('Error converting string to boolean.')
def parse_xml_child_str(tagname, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.childNodes[0]
return str(b.data).rstrip().replace('\n', '')
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}' \
.format(tagname, target_tags.tagName))
def parse_xml_child_attribute_str(tagname, attributename, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
value = str(a.getAttribute(attributename))
return value.rstrip().replace('\n', '').lower()
except Exception:
raise QEOutputParsingError('Error parsing attribute {}, tag {} inside {}'
.format(attributename, tagname, target_tags.tagName))
def parse_xml_child_attribute_int(tagname, attributename, target_tags):
try:
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
value = int(a.getAttribute(attributename))
return value
except Exception:
raise QEOutputParsingError('Error parsing attribute {}, tag {} inside {}'
.format(attributename, tagname, target_tags.tagName))
def grep_energy_from_line(line):
try:
return float(line.split('=')[1].split('Ry')[0]) * ry_to_ev
except Exception:
raise QEOutputParsingError('Error while parsing energy')
[docs]def convert_qe_time_to_sec(timestr):
"""
Given the walltime string of Quantum Espresso, converts it in a number of
seconds (float).
"""
rest = timestr.strip()
if 'd' in rest:
days, rest = rest.split('d')
else:
days = '0'
if 'h' in rest:
hours, rest = rest.split('h')
else:
hours = '0'
if 'm' in rest:
minutes, rest = rest.split('m')
else:
minutes = '0'
if 's' in rest:
seconds, rest = rest.split('s')
else:
seconds = '0.'
if rest.strip():
raise ValueError("Something remained at the end of the string '{}': '{}'"
.format(timestr, rest))
num_seconds = (
float(seconds) + float(minutes) * 60. +
float(hours) * 3600. + float(days) * 86400.)
return num_seconds
[docs]def convert_list_to_matrix(in_matrix, n_rows, n_columns):
"""
converts a list into a list of lists (a matrix like) with n_rows and n_columns
"""
return [in_matrix[j:j + n_rows] for j in range(0, n_rows * n_columns, n_rows)]
def xml_card_cell(parsed_data, dom):
# CARD CELL of QE output
cardname = 'CELL'
target_tags = read_xml_card(dom, cardname)
for tagname in ['NON-PERIODIC_CELL_CORRECTION', 'BRAVAIS_LATTICE']:
parsed_data[tagname.replace('-', '_').lower()] = parse_xml_child_str(tagname, target_tags)
tagname = 'LATTICE_PARAMETER'
value = parse_xml_child_float(tagname, target_tags)
parsed_data[tagname.replace('-', '_').lower() + '_xml'] = value
attrname = 'UNITS'
metric = parse_xml_child_attribute_str(tagname, attrname, target_tags)
if metric not in ['bohr', 'angstrom']:
raise QEOutputParsingError('Error parsing attribute {}, tag {} inside {}, units not found'
.format(attrname, tagname, target_tags.tagName))
if metric == 'bohr':
value *= bohr_to_ang
parsed_data[tagname.replace('-', '_').lower()] = value
tagname = 'CELL_DIMENSIONS'
try:
#a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.childNodes[0]
c = b.data.replace('\n', '').split()
value = [float(i) for i in c]
parsed_data[tagname.replace('-', '_').lower()] = value
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}.'.format(tagname, target_tags.tagName))
tagname = 'DIRECT_LATTICE_VECTORS'
lattice_vectors = []
try:
second_tagname = 'UNITS_FOR_DIRECT_LATTICE_VECTORS'
#a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
b = a.getElementsByTagName('UNITS_FOR_DIRECT_LATTICE_VECTORS')[0]
value = str(b.getAttribute('UNITS')).lower()
parsed_data[second_tagname.replace('-', '_').lower()] = value
metric = value
if metric not in ['bohr', 'angstroms']: # REMEMBER TO CHECK THE UNITS AT THE END OF THE FUNCTION
raise QEOutputParsingError('Error parsing tag {} inside {}: units not supported: {}'
.format(tagname, target_tags.tagName, metric))
lattice_vectors = []
for second_tagname in ['a1', 'a2', 'a3']:
#b = a.getElementsByTagName(second_tagname)[0]
b = [_ for _ in a.childNodes if _.nodeName == second_tagname][0]
c = b.childNodes[0]
d = c.data.replace('\n', '').split()
value = [float(i) for i in d]
if metric == 'bohr':
value = [bohr_to_ang * float(s) for s in value]
lattice_vectors.append(value)
volume = cell_volume(lattice_vectors[0], lattice_vectors[1], lattice_vectors[2])
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {} inside {}.'
.format(tagname, target_tags.tagName, cardname))
# NOTE: lattice_vectors will be saved later together with card IONS.atom
tagname = 'RECIPROCAL_LATTICE_VECTORS'
try:
#a = target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
second_tagname = 'UNITS_FOR_RECIPROCAL_LATTICE_VECTORS'
b = a.getElementsByTagName(second_tagname)[0]
value = str(b.getAttribute('UNITS')).lower()
parsed_data[second_tagname.replace('-', '_').lower()] = value
metric = value
# NOTE: output is given in 2 pi / a [ang ^ -1]
if metric not in ['2 pi / a']:
raise QEOutputParsingError('Error parsing tag {} inside {}: units {} not supported'
.format(tagname, target_tags.tagName, metric))
# reciprocal_lattice_vectors
this_matrix = []
for second_tagname in ['b1', 'b2', 'b3']:
b = a.getElementsByTagName(second_tagname)[0]
c = b.childNodes[0]
d = c.data.replace('\n', '').split()
value = [float(i) for i in d]
if metric == '2 pi / a':
value = [float(s) / parsed_data['lattice_parameter'] for s in value]
this_matrix.append(value)
parsed_data['reciprocal_lattice_vectors'] = this_matrix
except Exception:
raise QEOutputParsingError('Error parsing tag {} inside {}.'
.format(tagname, target_tags.tagName))
return parsed_data, lattice_vectors, volume
def xml_card_ions(parsed_data, dom, lattice_vectors, volume):
cardname = 'IONS'
target_tags = read_xml_card(dom, cardname)
for tagname in ['NUMBER_OF_ATOMS', 'NUMBER_OF_SPECIES']:
parsed_data[tagname.lower()] = parse_xml_child_integer(tagname, target_tags)
tagname = 'UNITS_FOR_ATOMIC_MASSES'
attrname = 'UNITS'
parsed_data[tagname.lower()] = parse_xml_child_attribute_str(tagname, attrname, target_tags)
try:
parsed_data['species'] = {}
parsed_data['species']['index'] = []
parsed_data['species']['type'] = []
parsed_data['species']['mass'] = []
parsed_data['species']['pseudo'] = []
for i in range(parsed_data['number_of_species']):
tagname = 'SPECIE.' + str(i + 1)
parsed_data['species']['index'].append(i + 1)
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
tagname2 = 'ATOM_TYPE'
parsed_data['species']['type'].append(parse_xml_child_str(tagname2, a))
tagname2 = 'MASS'
parsed_data['species']['mass'].append(parse_xml_child_float(tagname2, a))
tagname2 = 'PSEUDO'
parsed_data['species']['pseudo'].append(parse_xml_child_str(tagname2, a))
tagname = 'UNITS_FOR_ATOMIC_POSITIONS'
attrname = 'UNITS'
parsed_data[tagname.lower()] = parse_xml_child_attribute_str(tagname, attrname, target_tags)
except:
raise QEOutputParsingError('Error parsing tag SPECIE.# inside %s.' % (target_tags.tagName ))
# TODO convert the units
# if parsed_data['units_for_atomic_positions'] not in ['alat','bohr','angstrom']:
try:
atomlist = []
atoms_index_list = []
atoms_if_pos_list = []
tagslist = []
for i in range(parsed_data['number_of_atoms']):
tagname = 'ATOM.' + str(i + 1)
# USELESS AT THE MOMENT, I DON'T SAVE IT
# parsed_data['atoms']['list_index']=i
# a=target_tags.getElementsByTagName(tagname)[0]
a = [_ for _ in target_tags.childNodes if _.nodeName == tagname][0]
tagname2 = 'INDEX'
b = int(a.getAttribute(tagname2))
atoms_index_list.append(b)
tagname2 = 'SPECIES'
chem_symbol = str(a.getAttribute(tagname2)).rstrip().replace("\n", "")
# I check if it is a subspecie
chem_symbol_digits = "".join([i for i in chem_symbol if i in string.digits])
try:
tagslist.append(int(chem_symbol_digits))
except ValueError:
# If I can't parse the digit, it is probably not there: I add a None to the tagslist
tagslist.append(None)
# I remove the symbols
chem_symbol = chem_symbol.translate(None, string.digits)
tagname2 = 'tau'
b = a.getAttribute(tagname2)
tau = [float(s) for s in b.rstrip().replace("\n", "").split()]
metric = parsed_data['units_for_atomic_positions']
if metric not in ['alat', 'bohr', 'angstrom']: # REMEMBER TO CONVERT AT THE END
raise QEOutputParsingError('Error parsing tag %s inside %s' % (tagname, target_tags.tagName ))
if metric == 'alat':
tau = [parsed_data['lattice_parameter_xml'] * float(s) for s in tau]
elif metric == 'bohr':
tau = [bohr_to_ang * float(s) for s in tau]
atomlist.append([chem_symbol, tau])
tagname2 = 'if_pos'
b = a.getAttribute(tagname2)
if_pos = [int(s) for s in b.rstrip().replace("\n", "").split()]
atoms_if_pos_list.append(if_pos)
parsed_data['atoms'] = atomlist
parsed_data['atoms_index_list'] = atoms_index_list
parsed_data['atoms_if_pos_list'] = atoms_if_pos_list
cell = {}
cell['lattice_vectors'] = lattice_vectors
cell['volume'] = volume
cell['atoms'] = atomlist
cell['tagslist'] = tagslist
parsed_data['cell'] = cell
except Exception:
raise QEOutputParsingError('Error parsing tag ATOM.# inside %s.' % (target_tags.tagName ))
# saving data together with cell parameters. Did so for better compatibility with ASE.
# correct some units that have been converted in
parsed_data['atomic_positions' + units_suffix] = default_length_units
parsed_data['direct_lattice_vectors' + units_suffix] = default_length_units
return parsed_data
[docs]def parse_pw_xml_output(data, dir_with_bands=None):
"""
Parse the xml data of QE v5.0.x
Input data must be a single string, as returned by file.read()
Returns a dictionary with parsed values
"""
import copy
from xml.parsers.expat import ExpatError
# NOTE : I often assume that if the xml file has been written, it has no
# internal errors.
try:
dom = xml.dom.minidom.parseString(data)
except ExpatError:
return {'xml_warnings': "Error in XML parseString: bad format"}, {}, {}
parsed_data = {}
parsed_data['xml_warnings'] = []
structure_dict = {}
# CARD CELL
structure_dict, lattice_vectors, volume = copy.deepcopy(xml_card_cell(structure_dict, dom))
# CARD IONS
structure_dict = copy.deepcopy(xml_card_ions(structure_dict, dom, lattice_vectors, volume))
# fermi energy
cardname = 'BAND_STRUCTURE_INFO'
target_tags = read_xml_card(dom, cardname)
tagname = 'FERMI_ENERGY'
parsed_data[tagname.replace('-', '_').lower()] = \
parse_xml_child_float(tagname, target_tags) * hartree_to_ev
parsed_data[tagname.lower() + units_suffix] = default_energy_units
return parsed_data, structure_dict
[docs]def parse_pw_text_output(data, xml_data=None, structure_data=None, input_dict=None):
"""
Parses the text output of QE-PWscf.
:param data: a string, the file as read by read()
:param xml_data: the dictionary with the keys read from xml.
:param structure_data: dictionary, coming from the xml, with info on the structure
:return parsed_data: dictionary with key values, referring to quantities
at the last scf step.
:return trajectory_data: key,values referring to intermediate scf steps,
as in the case of vc-relax. Empty dictionary if no
value is present.
:return critical_messages: a list with critical messages. If any is found in
parsed_data['warnings'], the calculation is FAILED!
"""
parsed_data = {}
parsed_data['warnings'] = []
vdw_correction = False
trajectory_data = {}
# critical warnings: if any is found, the calculation status is FAILED
critical_warnings = {
'The maximum number of steps has been reached.': "The maximum step of the ionic/electronic relaxation has been reached.",
'convergence NOT achieved after': "The scf cycle did not reach convergence.",
# 'eigenvalues not converged':None, # special treatment
'iterations completed, stopping': 'Maximum number of iterations reached in Wentzcovitch Damped Dynamics.',
'Maximum CPU time exceeded': 'Maximum CPU time exceeded',
'%%%%%%%%%%%%%%': None,
}
minor_warnings = {'Warning:': None,
'DEPRECATED:': None,
'incommensurate with FFT grid': 'The FFT is incommensurate: some symmetries may be lost.',
'SCF correction compared to forces is too large, reduce conv_thr': "Forces are inaccurate (SCF correction is large): reduce conv_thr.",
}
all_warnings = dict(critical_warnings.items() + minor_warnings.items())
# Find some useful quantities.
try:
for line in data.split('\n'):
if 'lattice parameter (alat)' in line:
alat = float(line.split('=')[1].split('a.u')[0])
elif 'number of atoms/cell' in line:
nat = int(line.split('=')[1])
elif 'number of atomic types' in line:
ntyp = int(line.split('=')[1])
elif 'unit-cell volume' in line:
volume = float(line.split('=')[1].split('(a.u.)^3')[0])
elif 'number of Kohn-Sham states' in line:
nbnd = int(line.split('=')[1])
break
alat *= bohr_to_ang
volume *= bohr_to_ang ** 3
parsed_data['number_of_bands'] = nbnd
except NameError: # nat or other variables where not found, and thus not initialized
# try to get some error message
for count, line in enumerate(data.split('\n')):
if any(i in line for i in all_warnings):
messages = [all_warnings[i] if all_warnings[i] is not None
else line for i in all_warnings.keys()
if i in line]
if '%%%%%%%%%%%%%%' in line:
messages = parse_QE_errors(data.split('\n'), count,
parsed_data['warnings'])
# if it found something, add to log
if len(messages) > 0:
parsed_data['warnings'].extend(messages)
if len(parsed_data['warnings']) > 0:
return parsed_data, trajectory_data, critical_warnings.values()
else:
# did not find any error message -> raise an Error and do not
# return anything
raise QEOutputParsingError("Parser can't load basic info.")
# Save these two quantities in the parsed_data, because they will be
# useful for queries (maybe), and structure_data will not be stored as a ParameterData
parsed_data['number_of_atoms'] = nat
parsed_data['number_of_species'] = ntyp
parsed_data['volume'] = volume
c_bands_error = False
# now grep quantities that can be considered isolated informations.
for count, line in enumerate(data.split('\n')):
# special parsing of c_bands error
if 'c_bands' in line and 'eigenvalues not converged' in line:
c_bands_error = True
elif "iteration #" in line and c_bands_error:
# if there is another iteration, c_bands is not necessarily a problem
# I put a warning only if c_bands error appears in the last iteration
c_bands_error = False
# Parsing of errors
elif any(i in line for i in all_warnings):
message = [all_warnings[i] for i in all_warnings.keys() if i in line][0]
if message is None:
message = line
# if the run is a molecular dynamics, I ignore that I reached the
# last iteration step.
if ('The maximum number of steps has been reached.' in line and
'md' in input_dict['CONTROL']['calculation']):
message = None
if 'iterations completed, stopping' in line:
value = message
message = None
if 'Wentzcovitch Damped Dynamics:' in line:
dynamic_iterations = int(line.split()[3])
if max_dynamic_iterations == dynamic_iterations:
message = value
if '%%%%%%%%%%%%%%' in line:
message = None
messages = parse_QE_errors(data.split('\n'), count, parsed_data['warnings'])
# if it found something, add to log
try:
parsed_data['warnings'].extend(messages)
except UnboundLocalError:
pass
if message is not None:
parsed_data['warnings'].append(message)
if c_bands_error:
parsed_data['warnings'].append("c_bands: at least 1 eigenvalues not converged")
# I split the output text in the atomic SCF calculations.
# the initial part should be things already contained in the xml.
# (cell, initial positions, kpoints, ...) and I skip them.
# In case, parse for them before this point.
# Put everything in a trajectory_data dictionary
relax_steps = data.split('Self-consistent Calculation')[1:]
relax_steps = [i.split('\n') for i in relax_steps]
# now I create a bunch of arrays for every step.
for data_step in relax_steps:
for count, line in enumerate(data_step):
# NOTE: in the above, the chemical symbols are not those of AiiDA
# since the AiiDA structure is different. So, I assume now that the
# order of atoms is the same of the input atomic structure.
# Computed dipole correction in slab geometries.
# save dipole in debye units, only at last iteration of scf cycle
# grep energy and eventually, magnetization
if '!' in line:
try:
for key in ['energy', 'energy_accuracy']:
if key not in trajectory_data:
trajectory_data[key] = []
En = float(line.split('=')[1].split('Ry')[0]) * ry_to_ev
E_acc = float(data_step[count + 2].split('<')[1].split('Ry')[0]) * ry_to_ev
for key, value in [['energy', En], ['energy_accuracy', E_acc]]:
trajectory_data[key].append(value)
parsed_data[key + units_suffix] = default_energy_units
except Exception:
parsed_data['warnings'].append('Error while parsing the energy')
elif 'the Fermi energy is' in line:
try:
value = line.split('is')[1].split('ev')[0]
try:
trajectory_data['fermi_energy'].append(value)
except KeyError:
trajectory_data['fermi_energy'] = [value]
parsed_data['fermi_energy' + units_suffix] = default_energy_units
except Exception:
parsed_data['warnings'].append('Error while parsing Fermi energy from the output file.')
elif 'Forces acting on atoms (Ry/au):' in line:
try:
forces = []
j = 0
while True:
j += 1
line2 = data_step[count + j]
if 'atom ' in line2:
line2 = line2.split('=')[1].split()
# CONVERT FORCES IN eV/Ang
vec = [float(s) * ry_to_ev / \
bohr_to_ang for s in line2]
forces.append(vec)
if len(forces) == nat:
break
try:
trajectory_data['forces'].append(forces)
except KeyError:
trajectory_data['forces'] = [forces]
parsed_data['forces' + units_suffix] = default_force_units
except Exception:
parsed_data['warnings'].append('Error while parsing forces.')
# TODO: adding the parsing support for the decomposition of the forces
elif 'Total force =' in line:
try: # note that I can't check the units: not written in output!
value = float(line.split('=')[1].split('Total')[0]) * ry_to_ev / bohr_to_ang
try:
trajectory_data['total_force'].append(value)
except KeyError:
trajectory_data['total_force'] = [value]
parsed_data['total_force' + units_suffix] = default_force_units
except Exception:
parsed_data['warnings'].append('Error while parsing total force.')
elif 'entering subroutine stress ...' in line:
try:
stress = []
for k in range(10):
if "P=" in data_step[count + k + 1]:
count2 = count + k + 1
if '(Ry/bohr**3)' not in data_step[count2]:
raise QEOutputParsingError('Error while parsing stress: unexpected units.')
for k in range(3):
line2 = data_step[count2 + k + 1].split()
vec = [float(s) * 10 ** (-9) * ry_si / (bohr_si) ** 3 for s in line2[0:3]]
stress.append(vec)
try:
trajectory_data['stress'].append(stress)
except KeyError:
trajectory_data['stress'] = [stress]
parsed_data['stress' + units_suffix] = default_stress_units
except Exception:
parsed_data['warnings'].append('Error while parsing stress tensor.')
return parsed_data, trajectory_data, critical_warnings.values()
[docs]def parse_QE_errors(lines, count, warnings):
"""
Parse QE errors messages (those appearing between some lines with
``'%%%%%%%%'``)
:param lines: list of strings, the output text file as read by readlines()
or as obtained by data.split('\\n') when data is the text file read by read()
:param count: the line at which we identified some ``'%%%%%%%%'``
:param warnings: the warnings already parsed in the file
:return messages: a list of QE error messages
"""
# find the indices of the lines with problems
found_endpoint = False
init_problem = count
for count2, line2 in enumerate(lines[count + 1:]):
end_problem = count + count2 + 1
if "%%%%%%%%%%%%" in line2:
found_endpoint = True
break
messages = []
if found_endpoint:
# build a dictionary with the lines
prob_list = lines[init_problem:end_problem + 1]
irred_list = list(set(prob_list))
for v in prob_list:
if ( len(v) > 0 and (v in irred_list and v not in warnings) ):
messages.append(irred_list.pop(irred_list.index(v)))
return messages