Source code for atomate2.common.utils
"""Common utilities for atomate2."""
from __future__ import annotations
import re
from importlib import import_module
from typing import TYPE_CHECKING, Any
from monty.serialization import loadfn
if TYPE_CHECKING:
from pathlib import Path
[docs]
def get_transformations(
transformations: tuple[str, ...], params: tuple[dict, ...] | None
) -> list:
"""Get instantiated transformation objects from their names and parameters.
Parameters
----------
transformations
name of the transformations
params
parameters to pass to the transformation objects
Returns
-------
A list of initiated transformation objects
"""
params = ({},) * len(transformations) if params is None else params
if len(params) != len(transformations):
raise ValueError("Number of transformations and parameters must be the same.")
transformation_objects = []
for transformation, transformation_params in zip(transformations, params):
found = False
for module in (
"advanced_transformations",
"site_transformations",
"standard_transformations",
):
mod = import_module(f"pymatgen.transformations.{module}")
try:
t_cls = getattr(mod, transformation)
found = True
continue
except AttributeError:
pass
if not found:
raise ValueError(f"Could not find transformation: {transformation}")
t_obj = t_cls(**transformation_params)
transformation_objects.append(t_obj)
return transformation_objects
[docs]
def parse_custodian(dir_name: Path) -> dict | None:
"""
Parse custodian.json file.
Calculations done using custodian have a custodian.json file which tracks the makers
performed and any errors detected and fixed.
Parameters
----------
dir_name
Path to calculation directory.
Returns
-------
Optional[dict]
The information parsed from custodian.json file.
"""
filenames = tuple(dir_name.glob("custodian.json*"))
if len(filenames) >= 1:
return loadfn(filenames[0], cls=None)
return None
[docs]
def parse_transformations(
dir_name: Path,
) -> tuple[dict, int | None, list[str] | None, str | None]:
"""Parse transformations.json file."""
transformations = {}
filenames = tuple(dir_name.glob("transformations.json*"))
icsd_id = None
if len(filenames) >= 1:
transformations = loadfn(filenames[0], cls=None)
try:
match = re.match(r"(\d+)-ICSD", transformations["history"][0]["source"])
if match:
icsd_id = int(match.group(1))
except (KeyError, IndexError):
pass
# We don't want to leave tags or authors in the
# transformations file because they'd be copied into
# every structure generated after this one.
other_parameters = transformations.get("other_parameters", {})
new_tags = other_parameters.pop("tags", None)
new_author = other_parameters.pop("author", None)
if "other_parameters" in transformations and not other_parameters:
# if dict is now empty remove it
transformations.pop("other_parameters")
return transformations, icsd_id, new_tags, new_author
[docs]
def parse_additional_json(dir_name: Path) -> dict[str, Any]:
"""Parse additional JSON files in the directory."""
additional_json = {}
for filename in dir_name.glob("*.json*"):
key = filename.name.split(".")[0]
# ignore FW.json(.gz) so jobflow doesn't try to parse prev_dir
# OutputReferences was causing atomate2 MP workflows to fail with ValueError:
# Could not resolve reference 7f5a7f14-464c-4a5b-85f9-8d11b595be3b not in store
# or cache contact @janosh in case of questions
if key not in ("custodian", "transformations", "FW"):
additional_json[key] = loadfn(filename, cls=None)
return additional_json