Skip to content

Commit

Permalink
just borrow pymatgen implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Han Lin Mai committed Dec 26, 2023
1 parent f4ceb52 commit 7f7035b
Showing 1 changed file with 87 additions and 63 deletions.
150 changes: 87 additions & 63 deletions pyiron_atomistics/vasp/vasprun.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def from_file(self, filename="vasprun.xml"):
"""
if not (os.path.isfile(filename)):
raise AssertionError()
self.filename=filename
try:
self.parse_root_to_dict(filename)
except ParseError:
Expand All @@ -83,8 +84,7 @@ def parse_root_to_dict(self, filename):
d["stress_tensors"] = list()
for _, leaf in ETree.iterparse(filename):
if leaf.tag in ["generator", "incar"]:
d[leaf.tag] = dict()
d[leaf.tag] = self.parse_container_element(leaf, d[leaf.tag])
d[leaf.tag] = self._parse_INCAR_and_generator(leaf)
if leaf.tag in ["kpoints"]:
d[leaf.tag] = dict()
self.parse_kpoints_to_dict(leaf, d[leaf.tag])
Expand Down Expand Up @@ -537,28 +537,95 @@ def parse_structure_to_dict(self, node, d):

if leaf.tag == "varray" and leaf.attrib["name"] == "selective":
d["selective_dynamics"] = self._parse_2d_matrix(leaf, vec_type=bool)

def parse_container_element(self, container, d):

def _parse_INCAR_generator_parameters(self, val_type, val):
"""
Helper function to convert a Vasprun parameter into the proper type.
Boolean, int and float types are converted.
Args:
val_type: Value type parsed from vasprun.xml.
val: Actual string value parsed for vasprun.xml.
"""
if val_type == "logical":
return val == "T"
if val_type == "int":
return int(val)
if val_type == "string":
return val.strip()
return float(val)


def _parse_INCAR_generator_vector_parameters(self, val_type, val, filename, param_name):
"""
Parses XML container elements into a dictionary. This method is specifically
designed to handle elements within 'generator' and 'incar' tags of a VASP XML file.
It processes elements with 'i' and 'v' tags, using different parsing methods for each.
Helper function to convert a Vasprun array-type parameter into the proper
type. Boolean, int and float types are converted.
Args:
container (xml.etree.Element): An XML element that contains child elements to be parsed.
Typically, this would be a 'generator' or 'incar' element.
val_type: Value type parsed from vasprun.xml.
val: Actual string value parsed for vasprun.xml.
filename: Fullpath of vasprun.xml. Used for robust error handling.
E.g., if vasprun.xml contains *** for some Incar parameters,
the code will try to read from an INCAR file present in the same
directory.
param_name: Name of parameter.
Returns:
dict: A dictionary containing parsed data from the XML container. Keys are the names
of the elements, and values are the parsed data, which could be a simple value
or a list, depending on the element type.
"""
for item in container:
if item.tag == 'i':
d.update(self.parse_item_to_dict(item, d))
elif item.tag == 'v':
d[item.attrib["name"]] = self.parse_INCAR_vector_to_list(item)
return d
Parsed value.
"""
err = ValueError("Error in parsing vasprun.xml")
if val_type == "logical":
val = [i == "T" for i in val.split()]
elif val_type == "int":
try:
val = [int(i) for i in val.split()]
except ValueError:
# Fix for stupid error in vasprun sometimes which displays
# LDAUL/J as 2****
val = self._parse_from_incar(filename, param_name)
if val is None:
raise err
elif val_type == "string":
val = val.split()
else:
try:
val = [float(i) for i in val.split()]
except ValueError:
# Fix for stupid error in vasprun sometimes which displays
# MAGMOM as 2****
# val = self._parse_from_incar(filename, param_name)
va
if val is None:
raise err
return val

def _parse_INCAR_and_generator(self, elem):
params = {}
for c in elem:
name = c.attrib.get("name")
if c.tag not in ("i", "v"):
p = self._parse_INCAR_and_generator(c)
if name == "response functions":
# Delete duplicate fields from "response functions",
# which overrides the values in the root params.
p = {k: v for k, v in p.items() if k not in params}
params.update(p)
else:
ptype = c.attrib.get("type")
val = c.text.strip() if c.text else ""
try:
if c.tag == "i":
params[name] = self._parse_INCAR_generator_parameters(ptype, val)
else:
params[name] = self._parse_INCAR_generator_vector_parameters(ptype, val, self.filename, name)
except Exception as exc:
if name == "RANDOM_SEED":
# Handles the case where RANDOM SEED > 99999, which results in *****
params[name] = None
else:
raise exc
elem.clear()
return params

@staticmethod
def parse_item_to_dict(node, d):
Expand All @@ -584,50 +651,7 @@ def parse_item_to_dict(node, d):
d[node.attrib["name"]] = node.text
return d

@staticmethod
def parse_INCAR_vector_to_list(node, filename=None):
"""
Parses an XML element with the tag 'v' into a list. This method is robust to conversion errors and
is specifically tailored for parsing vector data from the INCAR section of a VASP XML file. In case of
conversion errors, it can optionally read correct values from an INCAR file.
Args:
node (xml.etree.Element): An XML element with the tag 'v', containing vector data to be parsed.
filename (str, optional): The path to an INCAR file used for error handling. If provided,
the method will attempt to read correct values from this file in case
of conversion errors. Defaults to None.
Returns:
list: A list of parsed values. Each value is converted to the appropriate Python data type
based on the 'type' attribute of the element.
"""
val_type = node.attrib.get("type", "string")
values = node.text.strip().split() if node.text else []
parsed_values = []
for val in values:
try:
if val_type == "logical":
parsed_values.append(val == "T")
elif val_type == "int":
parsed_values.append(int(val))
elif val_type == "float":
parsed_values.append(float(val))
else: # Default case for string type
parsed_values.append(val)
except ValueError:
# Handle conversion error - placeholder for special handling
# For example, read from an INCAR file or set to a default value
# This part needs to be adapted based on your specific requirements
parsed_value = Vasprun.read_from_incar(filename, node.attrib["name"], val_type, val)
if parsed_value is not None:
parsed_values.append(parsed_value)
else:
raise ValueError(f"Error parsing value '{val}' in {node.attrib['name']}")

return parsed_values

@staticmethod
def read_from_incar(filename, param_name, val_type, val):
def _parse_from_incar(self, param_name, val_type, val):
"""
Placeholder method for reading and parsing parameter values from an INCAR file. This method is intended
to be used for handling special cases where values in the VASP XML file are not correctly formatted.
Expand Down

0 comments on commit 7f7035b

Please sign in to comment.