From a7b857d5f2c1b3fde72bf2fdcf8ffebb60770f98 Mon Sep 17 00:00:00 2001 From: Izzy Xie <664222956@qq.com> Date: Wed, 9 Aug 2023 19:21:57 -0700 Subject: [PATCH 1/9] DEV: revise structure calculation makers to be able to use any type of maker provided by atomate2 (WIP, not tested, not doc-ed). --- WFacer/jobs.py | 109 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/WFacer/jobs.py b/WFacer/jobs.py index 6d552cd..4eca334 100644 --- a/WFacer/jobs.py +++ b/WFacer/jobs.py @@ -1,20 +1,16 @@ """Unitary jobs used by Maker.""" +import importlib import logging from copy import deepcopy from warnings import warn import numpy as np -from atomate2.vasp.jobs.core import RelaxMaker, StaticMaker, TightRelaxMaker from atomate2.vasp.schemas.calculation import Status -from atomate2.vasp.sets.core import ( - RelaxSetGenerator, - StaticSetGenerator, - TightRelaxSetGenerator, -) from jobflow import Flow, OnMissing, Response, job from pymatgen.analysis.elasticity.strain import Deformation from smol.cofe import ClusterExpansion from smol.moca import CompositionSpace +from smol.utils.class_utils import class_name_from_str from .enumeration import ( enumerate_compositions_as_counts, @@ -110,33 +106,86 @@ def _enumerate_structures( return new_structures, new_sc_matrices, new_features -def _get_vasp_makers(options): - """Get required vasp makers.""" +# TODO: write docs for this. +def _get_structure_job_maker(maker_name, generator_kwargs=None, maker_kwargs=None): + # Format for a maker name: module:name-of-maker. module name must be in full path. + maker_module, maker_name = maker_name.split(":")[:2] + maker_module = maker_module.lower() + + # Only cp2k, vasp and forcefields are supported. + if "amset" in maker_module or "common" in maker_module or "lobster" in maker_module: + raise NotImplementedError( + f"Makers in {maker_module} are not supported by WFACER!" + ) + + # Get maker from name. Note that only vasp supports tight relax (atomate2==0.0.10). + maker_name = class_name_from_str(maker_name) + try: + maker_class = getattr(importlib.import_module(maker_module), maker_name) + except AttributeError: + warn( + f"Tight relaxation is not supported for atomate module {maker_module}! Skipped." + ) + return None + + generator_kwargs = generator_kwargs or {} + maker_kwargs = maker_kwargs or {} + + # vasp and cp2k makers require input set generators, and allows stopping children. + if "forcefield" not in maker_module: + # replace jobs.core with sets.core; replace Maker with SetGenerator + generator_module = ".".join(maker_module.split(".")[:-2]) + ".sets.core" + generator_name = maker_name[:-5] + "SetGenerator" + generator_class = getattr( + importlib.import_module(generator_module), generator_name + ) + generator = generator_class(**generator_kwargs) + + maker_kwargs["stop_children_kwargs"] = {"handle_unsuccessful": "error"} + return maker_class(input_set_generator=generator, **maker_kwargs) + + # Force field makers does not require input set. Up to atomate2==0.0.10, + # they also won't support children kwargs as well as tight relaxation. + else: + return maker_class(**maker_kwargs) + + +def _get_structure_calculation_makers(options): + """Get required structure calculation makers.""" + # Here, the maker names contain module specification. + relax_maker_name = options["relax_maker_name"] relax_gen_kwargs = options["relax_generator_kwargs"] - relax_generator = RelaxSetGenerator(**relax_gen_kwargs) relax_maker_kwargs = options["relax_maker_kwargs"] - # Force throwing out an error instead of defusing children, as parsing and - # fitting jobs are children of structure jobs and will be defused - # as well if not taken care of! - - # Error handling will be taken care of by lost run detection and - # fixing functionalities in WFacer.fireworks_patches. - relax_maker_kwargs["stop_children_kwargs"] = {"handle_unsuccessful": "error"} - relax_maker = RelaxMaker(input_set_generator=relax_generator, **relax_maker_kwargs) + relax_maker = _get_structure_job_maker( + relax_maker_name, + generator_kwargs=relax_gen_kwargs, + maker_kwargs=relax_maker_kwargs, + ) + + static_maker_name = options["static_maker_name"] static_gen_kwargs = options["static_generator_kwargs"] - static_generator = StaticSetGenerator(**static_gen_kwargs) static_maker_kwargs = options["static_maker_kwargs"] - static_maker_kwargs["stop_children_kwargs"] = {"handle_unsuccessful": "error"} - static_maker = StaticMaker( - input_set_generator=static_generator, **static_maker_kwargs - ) - tight_gen_kwargs = options["tight_generator_kwargs"] - tight_generator = TightRelaxSetGenerator(**tight_gen_kwargs) - tight_maker_kwargs = options["tight_maker_kwargs"] - tight_maker_kwargs["stop_children_kwargs"] = {"handle_unsuccessful": "error"} - tight_maker = TightRelaxMaker( - input_set_generator=tight_generator, **tight_maker_kwargs + static_maker = _get_structure_job_maker( + static_maker_name, + generator_kwargs=static_gen_kwargs, + maker_kwargs=static_maker_kwargs, ) + + # Note that only vasp supports tight relax (atomate2==0.0.10). + # You can also specify tight_maker_name as None to avoid using it. + # No extra argument is needed in options to achieve that. + tight_maker_name = options["tight_maker_name"] + if tight_maker_name is not None: + tight_gen_kwargs = options["tight_generator_kwargs"] + tight_maker_kwargs = options["tight_maker_kwargs"] + tight_maker = _get_structure_job_maker( + tight_maker_name, + generator_kwargs=tight_gen_kwargs, + maker_kwargs=tight_maker_kwargs, + ) + else: + tight_maker = None + return relax_maker, static_maker, tight_maker @@ -279,7 +328,7 @@ def get_structure_calculation_flows(enum_output, last_ce_document): else: struct_id = len(last_ce_document.enumerated_structures) options = last_ce_document.ce_options - relax_maker, static_maker, tight_maker = _get_vasp_makers(options) + relax_maker, static_maker, tight_maker = _get_structure_calculation_makers(options) log.info("Performing ab-initio calculations.") new_structures = enum_output["new_structures"] @@ -296,7 +345,7 @@ def get_structure_calculation_flows(enum_output, last_ce_document): jobs = list() jobs.append(relax_maker.make(def_structure)) - if options["add_tight_relax"]: + if tight_maker is not None: tight_job = tight_maker.make( jobs[-1].output.structure, prev_vasp_dir=jobs[-1].output.dir_name ) From b5cd50b868d06422647419554905680a27d8b8ae Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 10 Aug 2023 13:25:32 -0700 Subject: [PATCH 2/9] DEV: update preprocessing according to new structure calculation maker settings. --- WFacer/jobs.py | 4 ++-- WFacer/preprocessing.py | 31 +++++++++++++++++++++++++------ tests/data/default_options.yaml | 4 +++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/WFacer/jobs.py b/WFacer/jobs.py index 4eca334..e488dc3 100644 --- a/WFacer/jobs.py +++ b/WFacer/jobs.py @@ -106,8 +106,8 @@ def _enumerate_structures( return new_structures, new_sc_matrices, new_features -# TODO: write docs for this. def _get_structure_job_maker(maker_name, generator_kwargs=None, maker_kwargs=None): + """Get a single job maker from a structure job.""" # Format for a maker name: module:name-of-maker. module name must be in full path. maker_module, maker_name = maker_name.split(":")[:2] maker_module = maker_module.lower() @@ -151,7 +151,7 @@ def _get_structure_job_maker(maker_name, generator_kwargs=None, maker_kwargs=Non def _get_structure_calculation_makers(options): - """Get required structure calculation makers.""" + """Get required calculation makers for a single structure.""" # Here, the maker names contain module specification. relax_maker_name = options["relax_maker_name"] relax_gen_kwargs = options["relax_generator_kwargs"] diff --git a/WFacer/preprocessing.py b/WFacer/preprocessing.py index 1b158f8..9b4b549 100644 --- a/WFacer/preprocessing.py +++ b/WFacer/preprocessing.py @@ -410,18 +410,24 @@ def process_calculation_options(d): Default is [1.03, 1.02, 1.01], which means to stretch the structure by 3%, 2% and 1% along a, b, and c directions, respectively. + relax_maker_name(str): + Name of relax maker. Default to atomate2.vasp.jobs.core:RelaxMaker. + cp2k and forcefields are also supported. + Maker module must be specified first, and separated from the maker + class name with a quotation mark ":". relax_generator_kwargs(dict): Additional arguments to pass into an atomate2 VaspInputGenerator that is used to initialize RelaxMaker. This is where the pymatgen vaspset arguments should go. + Note that force field makers do not have generator. relax_maker_kwargs(dict): Additional arguments to initialize an atomate2 RelaxMaker. Not frequently used. - add_tight_relax(bool): - Whether to add a tight relaxation job after a coarse - relaxation. Default to True. - You may want to disable this if your system has - difficulty converging forces or energies. + tight_maker_name(str or None): + Name of tight relax maker. + Default to atomate2.vasp.jobs.core:TightRelaxMaker, setting + to None will disable tight relaxation. + Note that only vasp supports tight relax. tight_generator_kwargs(dict): Additional arguments to pass into an atomate2 VaspInputGenerator that is used to initialize TightRelaxMaker. @@ -431,10 +437,15 @@ def process_calculation_options(d): TightRelaxMaker. A tight relax is performed after relaxation, if add_tight_relax is True. Not frequently used. + static_maker_name(str): + Name of static maker. Default to atomate2.vasp.jobs.core:StaticMaker. + Note that only vasp supports tight relax. + cp2k and forcefields are also supported. static_generator_kwargs(dict): Additional arguments to pass into an atomate2 VaspInputGenerator that is used to initialize StaticMaker. This is where the pymatgen vaspset arguments should go. + Note that force field makers do not have generator. static_maker_kwargs(dict): Additional arguments to pass into an atomate2 StaticMaker. @@ -466,11 +477,19 @@ def process_calculation_options(d): return { "apply_strain": strain_before_relax.tolist(), + "relax_maker_name": d.get( + "relax_maker_name", "atomate2.vasp.jobs.core:RelaxMaker" + ), "relax_generator_kwargs": d.get("relax_generator_kwargs", {}), "relax_maker_kwargs": d.get("relax_maker_kwargs", {}), - "add_tight_relax": d.get("add_tight_relax", True), + "tight_maker_name": d.get( + "tight_maker_name", "atomate2.vasp.jobs.core:TightRelaxMaker" + ), "tight_generator_kwargs": d.get("tight_generator_kwargs", {}), "tight_maker_kwargs": d.get("tight_maker_kwargs", {}), + "static_maker_name": d.get( + "static_maker_name", "atomate2.vasp.jobs.core:StaticMaker" + ), "static_generator_kwargs": d.get("static_generator_kwargs", {}), "static_maker_kwargs": d.get("static_maker_kwargs", {}), "other_properties": d.get("other_properties"), diff --git a/tests/data/default_options.yaml b/tests/data/default_options.yaml index 048358f..924ea55 100644 --- a/tests/data/default_options.yaml +++ b/tests/data/default_options.yaml @@ -19,11 +19,13 @@ duplicacy_criteria: "correlations" n_parallel: null keep_ground_states: true apply_strain: [[1.03, 0, 0], [0, 1.02, 0], [0, 0, 1.01]] +relax_maker_name: "atomate2.vasp.jobs.core:RelaxMaker" relax_generator_kwargs: {} relax_maker_kwargs: {} -add_tight_relax: true +tight_maker_name: "atomate2.vasp.jobs.core:TightRelaxMaker" tight_generator_kwargs: {} tight_maker_kwargs: {} +static_maker_name: "atomate2.vasp.jobs.core:StaticMaker" static_generator_kwargs: {} static_maker_kwargs: {} other_properties: null From 254aa16175b14b5604f588f68089ee371ab5d01e Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 10 Aug 2023 15:59:38 -0700 Subject: [PATCH 3/9] TST: update calculation job tests. --- WFacer/jobs.py | 6 +++- tests/test_jobs.py | 61 +++++++++++++++++++++++++++++++++-- tests/test_utils/test_occu.py | 2 +- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/WFacer/jobs.py b/WFacer/jobs.py index e488dc3..33d52b3 100644 --- a/WFacer/jobs.py +++ b/WFacer/jobs.py @@ -124,9 +124,13 @@ def _get_structure_job_maker(maker_name, generator_kwargs=None, maker_kwargs=Non maker_class = getattr(importlib.import_module(maker_module), maker_name) except AttributeError: warn( - f"Tight relaxation is not supported for atomate module {maker_module}! Skipped." + f"Maker {maker_name} is not supported by atomate module {maker_module}!" + f" Skipped." ) return None + except ModuleNotFoundError: + warn(f"Atomate module {maker_module} not found! Skipped.") + return None generator_kwargs = generator_kwargs or {} maker_kwargs = maker_kwargs or {} diff --git a/tests/test_jobs.py b/tests/test_jobs.py index d667035..644455f 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -7,14 +7,15 @@ import pytest from atomate2.vasp.schemas.calculation import Calculation from atomate2.vasp.schemas.task import TaskDocument -from jobflow import Flow, Job, OnMissing, Response +from jobflow import Flow, Job, Maker, OnMissing, Response from pymatgen.analysis.structure_matcher import StructureMatcher -from pymatgen.core import Element, Structure +from pymatgen.core import Element, Lattice, Structure from pymatgen.entries.computed_entries import ComputedEntry from smol.cofe.space.domain import Vacancy from WFacer.jobs import ( _get_iter_id_from_enum_id, + _get_structure_job_maker, calculate_structures_job, enumerate_structures, fit_calculations, @@ -179,6 +180,62 @@ def test_enumerate_structures(initial_document, enum_output): # dumpfn(structures, f"./structures_{name}.json") +def test_structure_single_job(): + # CP2K is only supported after atomate2 >= 0.0.11. + valid_makers = [ + "atomate2.vasp.jobs.core:relax-maker", + "atomate2.vasp.jobs.core:static-maker", + "atomate2.vasp.jobs.core:TightRelaxMaker", + # "atomate2.cp2k.jobs.core:relax-maker", + # "atomate2.cp2k.jobs.core:static-maker", + ] + # These should warn and return None. + wrong_makers = [ + "atomate2.whatever.jobs.core:relax-maker", + "atomate2.vasp.jobs:relax-maker", + "atomate2.cp2k.jobs.core:tight-relax-maker", + "atomate2.forcefields.jobs:CHGNet-tight-relax-maker", + ] + # These should throw NonImplementedError. + unsupported_makers = [ + "atomate2.amset.jobs:amset-maker", + "atomate2.lobster.jobs:lobster-maker", + ] + + # TODO: test these after the next atomate2 release. + # force_makers = [ + # "atomate2.forcefields.jobs:CHGNetRelaxMaker", + # "atomate2.forcefields.jobs:CHGNetStaticMaker", + # "atomate2.forcefields.jobs:M3GNetRelaxMaker", + # "atomate2.forcefields.jobs:M3GNetStaticMaker", + # ] + + for maker_name in valid_makers: + maker = _get_structure_job_maker(maker_name) + assert isinstance(maker, Maker) + assert maker.stop_children_kwargs == {"handle_unsuccessful": "error"} + assert maker.input_set_generator is not None + + for maker_name in unsupported_makers: + with pytest.raises(NotImplementedError): + _ = _get_structure_job_maker(maker_name) + + # None is returned. + for maker_name in wrong_makers: + assert _get_structure_job_maker(maker_name) is None + + # Test a specific case of input set generator. + maker = _get_structure_job_maker( + "atomate2.vasp.jobs.core:relax-maker", + generator_kwargs={ + "user_incar_settings": {"ENCUT": 1000}, + }, + ) + s = Structure(Lattice.cubic(3.0), ["Co2+", "O2-"], [[0, 0, 0], [0.5, 0.5, 0.5]]) + incar = maker.input_set_generator.get_input_set(s, potcar_spec=True).incar + assert incar["ENCUT"] == 1000 + + def test_calculate_structures(initial_document, enum_output): calc_job = calculate_structures_job(enum_output, initial_document) # Will not perform the actual calculation here, only check flow structure. diff --git a/tests/test_utils/test_occu.py b/tests/test_utils/test_occu.py index 9c59408..62e70b6 100644 --- a/tests/test_utils/test_occu.py +++ b/tests/test_utils/test_occu.py @@ -1,7 +1,7 @@ """Test occupancy generation.""" import numpy as np import numpy.testing as npt -from smol.moca.utils.occu import get_dim_ids_table, occu_to_counts +from smol.moca.occu_utils import get_dim_ids_table, occu_to_counts from WFacer.utils.occu import get_random_occupancy_from_counts From 560ae0d96c3e9ddd3befb07ef133ee6416cdf7c8 Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Wed, 27 Sep 2023 16:58:01 -0700 Subject: [PATCH 4/9] DEV: added query to other document types. Need to finish up ForcefieldTaskDocument query. --- WFacer/specie_decorators/charge.py | 2 ++ WFacer/utils/query.py | 2 +- WFacer/utils/task_document.py | 31 +++++++++++++++++++++++++----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/WFacer/specie_decorators/charge.py b/WFacer/specie_decorators/charge.py index 7a19bdb..4a62175 100644 --- a/WFacer/specie_decorators/charge.py +++ b/WFacer/specie_decorators/charge.py @@ -177,6 +177,8 @@ class MagneticChargeDecorator(GpOptimizedDecorator, ChargeDecorator): """Assign charges from magnitudes of total magentic moments on sites. Uses Gaussian process to optimize charge assignment. + + .. note:: Does not support cp2k or force field! """ decorated_prop_name = "oxi_state" diff --git a/WFacer/utils/query.py b/WFacer/utils/query.py index abec4fa..52b050b 100644 --- a/WFacer/utils/query.py +++ b/WFacer/utils/query.py @@ -34,7 +34,7 @@ def query_keypath(obj, keypath): f" of the member to refer to is not specified with" f" id-. Will query the first member in the list." ) - return query_keypath(obj[0], keypath) + return query_keypath(obj[0], keypath[1:]) elif "-" in k: if len(k.split("-")) != 2: raise ValueError( diff --git a/WFacer/utils/task_document.py b/WFacer/utils/task_document.py index 478b937..f8c703d 100644 --- a/WFacer/utils/task_document.py +++ b/WFacer/utils/task_document.py @@ -1,9 +1,17 @@ -"""Handle atomate2 taskdocument output. +"""Handle atomate2 task document output. Extend the methods in this file if more site-wise properties other than magmom should be extracted. + +.. note: + Currently only supports reading from class :class:`emmet.core.tasks.TaskDoc`, + class :class:`atomate2.cp2k.schemas.task.TaskDocument` and + class :class:`atomate2.forcefields.schemas.ForceFieldTaskDocument`. """ +from atomate2.cp2k.schemas.task import TaskDocument +from atomate2.forcefields.schemas import ForceFieldTaskDocument +from emmet.core.tasks import TaskDoc from pymatgen.entries.computed_entries import ComputedStructureEntry from ..specie_decorators.base import get_site_property_query_names_from_decorator @@ -36,12 +44,18 @@ class :class:`ComputedEntry`. ) +# TODO: Finish this function and add test. +def _get_computed_entry_from_forcefield_taskdoc(taskdoc): + return + + def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=None): """Get the computed structure entry from :class:`TaskDoc`. Args: - taskdoc(TaskDoc): - A task document generated as vasp task output by emmet-core. + taskdoc(StructureMetadata): + A task document generated as vasp task output by emmet-core, CP2K + or force fields. property_and_queries(list of (str, str) or str): optional A list of property names to be retrieved from taskdoc, and the query string to retrieve them, paired in tuples. @@ -61,10 +75,16 @@ def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=N property required by decorator, and the properties dict ready to be inserted into a :class:`CeDataWangler`. """ + if not isinstance(taskdoc, (TaskDoc, TaskDocument, ForceFieldTaskDocument)): + raise ValueError(f"Document type {type(taskdoc)} not supported!") # Final optimized structure. structure = taskdoc.structure # The computed entry, not including the structure. - computed_entry = taskdoc.entry + if isinstance(taskdoc, (TaskDoc, TaskDocument)): + computed_entry = taskdoc.entry + # Need to retrieve the entry from ionic steps. + else: + computed_entry = _get_computed_entry_from_forcefield_taskdoc(taskdoc) prop_dict = {} if property_and_queries is not None: for p in property_and_queries: @@ -84,7 +104,8 @@ def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=N site_property_query_names = get_site_property_query_names_from_decorator(d) for sp, query in site_property_query_names: # Total magnetization on each site is already read and added to - # structure by atomate2. It should be overwritten. + # structure by atomate2 for all kinds of makers (VASP, CP2K, force fields). + # There is no need to do it again. if sp != "magmom": site_props[sp] = get_property_from_object(taskdoc, query) for sp, prop in site_props.items(): From e366c9b01fa6fc3cbda7e01ce1878d3dfe10ce10 Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 28 Sep 2023 14:15:16 -0700 Subject: [PATCH 5/9] DEV: added ForcefieldTaskDocument query. --- WFacer/utils/task_document.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/WFacer/utils/task_document.py b/WFacer/utils/task_document.py index f8c703d..bf23f46 100644 --- a/WFacer/utils/task_document.py +++ b/WFacer/utils/task_document.py @@ -12,7 +12,7 @@ class :class:`atomate2.forcefields.schemas.ForceFieldTaskDocument`. from atomate2.cp2k.schemas.task import TaskDocument from atomate2.forcefields.schemas import ForceFieldTaskDocument from emmet.core.tasks import TaskDoc -from pymatgen.entries.computed_entries import ComputedStructureEntry +from pymatgen.entries.computed_entries import ComputedEntry, ComputedStructureEntry from ..specie_decorators.base import get_site_property_query_names_from_decorator from .query import get_property_from_object @@ -44,11 +44,6 @@ class :class:`ComputedEntry`. ) -# TODO: Finish this function and add test. -def _get_computed_entry_from_forcefield_taskdoc(taskdoc): - return - - def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=None): """Get the computed structure entry from :class:`TaskDoc`. @@ -82,9 +77,14 @@ def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=N # The computed entry, not including the structure. if isinstance(taskdoc, (TaskDoc, TaskDocument)): computed_entry = taskdoc.entry - # Need to retrieve the entry from ionic steps. + # In ForcefieldTaskdocument, Need to retrieve the entry from ionic steps. else: - computed_entry = _get_computed_entry_from_forcefield_taskdoc(taskdoc) + computed_entry = ComputedEntry( + composition=taskdoc.structure.composition, + energy=taskdoc.output.energy, + correction=0.0, + ) + prop_dict = {} if property_and_queries is not None: for p in property_and_queries: From 797dfc8ee6090a42ab4140ff904e506e65057020 Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 28 Sep 2023 14:22:10 -0700 Subject: [PATCH 6/9] DEV: Update emmet, monty and pydantic version. --- pyproject.toml | 4 ++-- requirements.txt | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3cc733a..01acae3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,11 +24,11 @@ dependencies = [ "sympy>=1.11.1", "jobflow>=0.1.9", "atomate2>=0.0.11", - "emmet-core>=0.51.1", + "emmet-core>=0.69.2", "scikit-learn>=1.2.0", "scikit-optimize>=0.9.0", "scipy>=1.10.0, <=1.10.1", - "pydantic>=1.10.2,<2.0", + "pydantic>=2.0", "polytope>=0.2.3", "cvxpy>=1.2.1", "cvxopt" diff --git a/requirements.txt b/requirements.txt index b5deba9..35e02d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ pyyaml==6.0.1 joblib==1.3.2 -monty==2023.9.5 +monty==2023.9.25 numpy==1.24.4 pymatgen==2023.9.10 smol==0.5.2 sparse-lm==0.5.1 sympy==1.12 -jobflow==0.1.13 +jobflow==0.1.14 atomate2==0.0.11 scikit-learn==1.3.1 -emmet-core==0.68.0 +emmet-core==0.69.5 scikit-optimize==0.9.0 -scipy==1.11.2 -pydantic==1.10.12 +scipy==1.11.3 +pydantic==2.4.2 polytope==0.2.4 cvxpy==1.3.2 cvxopt==1.3.2 From 90046ab5d8df65089306ab4b5244660d31434bf3 Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 28 Sep 2023 14:52:31 -0700 Subject: [PATCH 7/9] Revert "DEV: Update emmet, monty and pydantic version." This reverts commit 797dfc8ee6090a42ab4140ff904e506e65057020. --- pyproject.toml | 4 ++-- requirements.txt | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 01acae3..3cc733a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,11 +24,11 @@ dependencies = [ "sympy>=1.11.1", "jobflow>=0.1.9", "atomate2>=0.0.11", - "emmet-core>=0.69.2", + "emmet-core>=0.51.1", "scikit-learn>=1.2.0", "scikit-optimize>=0.9.0", "scipy>=1.10.0, <=1.10.1", - "pydantic>=2.0", + "pydantic>=1.10.2,<2.0", "polytope>=0.2.3", "cvxpy>=1.2.1", "cvxopt" diff --git a/requirements.txt b/requirements.txt index 35e02d6..b5deba9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ pyyaml==6.0.1 joblib==1.3.2 -monty==2023.9.25 +monty==2023.9.5 numpy==1.24.4 pymatgen==2023.9.10 smol==0.5.2 sparse-lm==0.5.1 sympy==1.12 -jobflow==0.1.14 +jobflow==0.1.13 atomate2==0.0.11 scikit-learn==1.3.1 -emmet-core==0.69.5 +emmet-core==0.68.0 scikit-optimize==0.9.0 -scipy==1.11.3 -pydantic==2.4.2 +scipy==1.11.2 +pydantic==1.10.12 polytope==0.2.4 cvxpy==1.3.2 cvxopt==1.3.2 From dd5de0f79a5e78e181e41d761226affab499abe4 Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Thu, 28 Sep 2023 15:57:00 -0700 Subject: [PATCH 8/9] TST: add test case to validate ForceFieldTaskDocument parsing. --- WFacer/jobs.py | 2 +- tests/conftest.py | 10 ++++++++-- tests/data/zns_ff_taskdoc.json | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 tests/data/zns_ff_taskdoc.json diff --git a/WFacer/jobs.py b/WFacer/jobs.py index cbfbbc4..6953b95 100644 --- a/WFacer/jobs.py +++ b/WFacer/jobs.py @@ -115,7 +115,7 @@ def _get_structure_job_maker(maker_name, generator_kwargs=None, maker_kwargs=Non # Only cp2k, vasp and forcefields are supported. if "amset" in maker_module or "common" in maker_module or "lobster" in maker_module: raise NotImplementedError( - f"Makers in {maker_module} are not supported by WFACER!" + f"Makers in {maker_module} are not supported by WFacer!" ) # Get maker from name. Note that only vasp supports tight relax (atomate2==0.0.10). diff --git a/tests/conftest.py b/tests/conftest.py index 2e0bc96..cd6a9e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from atomate2.forcefields.schemas import ForceFieldTaskDocument from emmet.core.tasks import TaskDoc # emmet-core >= 0.60.0. from monty.serialization import loadfn from pydantic import parse_file_as @@ -180,6 +181,11 @@ def single_wrangler_sin(single_ensemble_sin): return gen_random_wrangler(single_ensemble_sin, 30) -@pytest.fixture(scope="package", params=["zns_taskdoc.json"]) +@pytest.fixture(scope="package", params=["zns_taskdoc.json", "zns_ff_taskdoc.json"]) def single_taskdoc(request): - return parse_file_as(TaskDoc, os.path.join(DATA_DIR, request.param)) + if "ff" not in request.param: + return parse_file_as(TaskDoc, os.path.join(DATA_DIR, request.param)) + else: + return parse_file_as( + ForceFieldTaskDocument, os.path.join(DATA_DIR, request.param) + ) diff --git a/tests/data/zns_ff_taskdoc.json b/tests/data/zns_ff_taskdoc.json new file mode 100644 index 0000000..151c938 --- /dev/null +++ b/tests/data/zns_ff_taskdoc.json @@ -0,0 +1 @@ +{"builder_meta": {"emmet_version": "0.66.0", "pymatgen_version": "2023.7.17", "pull_request": null, "database_version": null, "build_date": "2023-09-28 22:34:09.404000"}, "nsites": 8, "elements": ["S", "Zn"], "nelements": 2, "composition": {"Zn": 4.0, "S": 4.0}, "composition_reduced": {"Zn": 1.0, "S": 1.0}, "formula_pretty": "ZnS", "formula_anonymous": "AB", "chemsys": "S-Zn", "volume": 154.13189020078053, "density": 4.200542399549414, "density_atomic": 19.266486275097567, "symmetry": {"crystal_system": "Cubic", "symbol": "F-43m", "number": 216, "point_group": "-43m", "symprec": 0.1, "version": "2.1.0"}, "structure": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[5.36163816, 0.0, 3.2830565054151524e-16], [8.622171764466567e-16, 5.36163816, 3.2830565054151524e-16], [0.0, 0.0, 5.36163816]], "pbc": [true, true, true], "a": 5.36163816, "b": 5.36163816, "c": 5.36163816, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 154.13189020078053}, "sites": [{"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {"magmom": 0.010701864957809448}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.5, -1.232595164407831e-32], "xyz": [2.6808190800000005, 2.68081908, 3.283056505415152e-16], "properties": {"magmom": 0.01070202887058258}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.0, 0.49999999999999994], "xyz": [2.68081908, 0.0, 2.6808190799999996], "properties": {"magmom": 0.010701984167098999}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.5, 0.49999999999999994], "xyz": [4.3110858822332833e-16, 2.68081908, 2.6808190799999996], "properties": {"magmom": 0.010701984167098999}, "label": "Zn"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.25, 0.25], "xyz": [1.3404095400000002, 1.34040954, 1.3404095400000002], "properties": {"magmom": 0.003248557448387146}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.7500000000000001, 0.25], "xyz": [4.021228620000001, 4.0212286200000005, 1.3404095400000005], "properties": {"magmom": 0.0032484978437423706}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.25, 0.75], "xyz": [4.0212286200000005, 1.34040954, 4.0212286200000005], "properties": {"magmom": 0.0032485276460647583}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.7500000000000001, 0.75], "xyz": [1.3404095400000007, 4.0212286200000005, 4.0212286200000005], "properties": {"magmom": 0.003248468041419983}, "label": "S"}]}, "input": {"structure": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[5.36163816, 0.0, 3.2830565054151524e-16], [8.622171764466567e-16, 5.36163816, 3.2830565054151524e-16], [0.0, 0.0, 5.36163816]], "pbc": [true, true, true], "a": 5.36163816, "b": 5.36163816, "c": 5.36163816, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 154.13189020078053}, "sites": [{"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.5, -1.232595164407831e-32], "xyz": [2.6808190800000005, 2.68081908, 3.283056505415152e-16], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.0, 0.49999999999999994], "xyz": [2.68081908, 0.0, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.5, 0.49999999999999994], "xyz": [4.3110858822332833e-16, 2.68081908, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.25, 0.25], "xyz": [1.3404095400000002, 1.34040954, 1.3404095400000002], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.7500000000000001, 0.25], "xyz": [4.021228620000001, 4.0212286200000005, 1.3404095400000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.25, 0.75], "xyz": [4.0212286200000005, 1.34040954, 4.0212286200000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.7500000000000001, 0.75], "xyz": [1.3404095400000007, 4.0212286200000005, 4.0212286200000005], "properties": {}, "label": "S"}]}, "relax_cell": false, "steps": 500, "relax_kwargs": {}, "optimizer_kwargs": {}}, "output": {"structure": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[5.36163816, 0.0, 3.2830565054151524e-16], [8.622171764466567e-16, 5.36163816, 3.2830565054151524e-16], [0.0, 0.0, 5.36163816]], "pbc": [true, true, true], "a": 5.36163816, "b": 5.36163816, "c": 5.36163816, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 154.13189020078053}, "sites": [{"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {"magmom": 0.010701864957809448}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.5, -1.232595164407831e-32], "xyz": [2.6808190800000005, 2.68081908, 3.283056505415152e-16], "properties": {"magmom": 0.01070202887058258}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.0, 0.49999999999999994], "xyz": [2.68081908, 0.0, 2.6808190799999996], "properties": {"magmom": 0.010701984167098999}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.5, 0.49999999999999994], "xyz": [4.3110858822332833e-16, 2.68081908, 2.6808190799999996], "properties": {"magmom": 0.010701984167098999}, "label": "Zn"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.25, 0.25], "xyz": [1.3404095400000002, 1.34040954, 1.3404095400000002], "properties": {"magmom": 0.003248557448387146}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.7500000000000001, 0.25], "xyz": [4.021228620000001, 4.0212286200000005, 1.3404095400000005], "properties": {"magmom": 0.0032484978437423706}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.25, 0.75], "xyz": [4.0212286200000005, 1.34040954, 4.0212286200000005], "properties": {"magmom": 0.0032485276460647583}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.7500000000000001, 0.75], "xyz": [1.3404095400000007, 4.0212286200000005, 4.0212286200000005], "properties": {"magmom": 0.003248468041419983}, "label": "S"}]}, "energy": -29.856157302856445, "energy_per_atom": -3.7320196628570557, "forces": [[-9.75094735622406e-07, -9.676441550254822e-07, -1.0095536708831787e-06], [3.916211426258087e-07, -1.2991949915885925e-06, -4.4563785195350647e-07], [8.763745427131653e-07, 7.175840437412262e-07, -4.5727938413619995e-07], [1.5222467482089996e-06, 1.0505318641662598e-06, 6.943009793758392e-07], [1.0021030902862549e-06, 8.996576070785522e-07, 1.6312114894390106e-06], [-1.1594966053962708e-07, 7.692724466323853e-07, 5.443580448627472e-07], [-8.163042366504669e-07, -1.3364478945732117e-07, 1.457519829273224e-07], [-1.8654391169548035e-06, -1.026783138513565e-06, -1.098494976758957e-06]], "stress": [0.24108503013849258, 0.24108514189720154, 0.24108506739139557, -2.0968975888990826e-07, 2.008615718196438e-08, 6.171633426532708e-08], "ionic_steps": [{"energy": -29.856157302856445, "forces": [[-9.75094735622406e-07, -9.676441550254822e-07, -1.0095536708831787e-06], [3.916211426258087e-07, -1.2991949915885925e-06, -4.4563785195350647e-07], [8.763745427131653e-07, 7.175840437412262e-07, -4.5727938413619995e-07], [1.5222467482089996e-06, 1.0505318641662598e-06, 6.943009793758392e-07], [1.0021030902862549e-06, 8.996576070785522e-07, 1.6312114894390106e-06], [-1.1594966053962708e-07, 7.692724466323853e-07, 5.443580448627472e-07], [-8.163042366504669e-07, -1.3364478945732117e-07, 1.457519829273224e-07], [-1.8654391169548035e-06, -1.026783138513565e-06, -1.098494976758957e-06]], "stress": [0.24108503013849258, 0.24108514189720154, 0.24108506739139557, -2.0968975888990826e-07, 2.008615718196438e-08, 6.171633426532708e-08], "structure": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[5.36163816, 0.0, 3.2830565054151524e-16], [8.622171764466567e-16, 5.36163816, 3.2830565054151524e-16], [0.0, 0.0, 5.36163816]], "pbc": [true, true, true], "a": 5.36163816, "b": 5.36163816, "c": 5.36163816, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 154.13189020078053}, "sites": [{"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.5, -1.232595164407831e-32], "xyz": [2.6808190800000005, 2.68081908, 3.283056505415152e-16], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.0, 0.49999999999999994], "xyz": [2.68081908, 0.0, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.5, 0.49999999999999994], "xyz": [4.3110858822332833e-16, 2.68081908, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.25, 0.25], "xyz": [1.3404095400000002, 1.34040954, 1.3404095400000002], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.7500000000000001, 0.25], "xyz": [4.021228620000001, 4.0212286200000005, 1.3404095400000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.25, 0.75], "xyz": [4.0212286200000005, 1.34040954, 4.0212286200000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.7500000000000001, 0.75], "xyz": [1.3404095400000007, 4.0212286200000005, 4.0212286200000005], "properties": {}, "label": "S"}]}, "magmoms": [0.010701864957809448, 0.01070202887058258, 0.010701984167098999, 0.010701984167098999, 0.003248557448387146, 0.0032484978437423706, 0.0032485276460647583, 0.003248468041419983]}, {"energy": -29.856157302856445, "forces": [[-9.75094735622406e-07, -9.676441550254822e-07, -1.0095536708831787e-06], [3.916211426258087e-07, -1.2991949915885925e-06, -4.4563785195350647e-07], [8.763745427131653e-07, 7.175840437412262e-07, -4.5727938413619995e-07], [1.5222467482089996e-06, 1.0505318641662598e-06, 6.943009793758392e-07], [1.0021030902862549e-06, 8.996576070785522e-07, 1.6312114894390106e-06], [-1.1594966053962708e-07, 7.692724466323853e-07, 5.443580448627472e-07], [-8.163042366504669e-07, -1.3364478945732117e-07, 1.457519829273224e-07], [-1.8654391169548035e-06, -1.026783138513565e-06, -1.098494976758957e-06]], "stress": [0.24108503013849258, 0.24108514189720154, 0.24108506739139557, -2.0968975888990826e-07, 2.008615718196438e-08, 6.171633426532708e-08], "structure": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[5.36163816, 0.0, 3.2830565054151524e-16], [8.622171764466567e-16, 5.36163816, 3.2830565054151524e-16], [0.0, 0.0, 5.36163816]], "pbc": [true, true, true], "a": 5.36163816, "b": 5.36163816, "c": 5.36163816, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 154.13189020078053}, "sites": [{"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.5, -1.232595164407831e-32], "xyz": [2.6808190800000005, 2.68081908, 3.283056505415152e-16], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.5, 0.0, 0.49999999999999994], "xyz": [2.68081908, 0.0, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "Zn", "occu": 1}], "abc": [0.0, 0.5, 0.49999999999999994], "xyz": [4.3110858822332833e-16, 2.68081908, 2.6808190799999996], "properties": {}, "label": "Zn"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.25, 0.25], "xyz": [1.3404095400000002, 1.34040954, 1.3404095400000002], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.7500000000000001, 0.25], "xyz": [4.021228620000001, 4.0212286200000005, 1.3404095400000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.7500000000000001, 0.25, 0.75], "xyz": [4.0212286200000005, 1.34040954, 4.0212286200000005], "properties": {}, "label": "S"}, {"species": [{"element": "S", "occu": 1}], "abc": [0.25, 0.7500000000000001, 0.75], "xyz": [1.3404095400000007, 4.0212286200000005, 4.0212286200000005], "properties": {}, "label": "S"}]}, "magmoms": [0.010701864957809448, 0.01070202887058258, 0.010701984167098999, 0.010701984167098999, 0.003248557448387146, 0.0032484978437423706, 0.0032485276460647583, 0.003248468041419983]}], "n_steps": 2}, "forcefield_name": "Forcefield", "forcefield_version": "Unknown", "dir_name": null, "@module": "atomate2.forcefields.schemas", "@class": "ForceFieldTaskDocument", "@version": "0.0.11"} From 7d005f3ffba19fe5bac8a8d2ca768af7ed1af20b Mon Sep 17 00:00:00 2001 From: qchempku2017 Date: Fri, 29 Sep 2023 16:41:28 -0700 Subject: [PATCH 9/9] TST: correct test cases for ForceFieldTaskDocument. --- WFacer/utils/query.py | 2 +- WFacer/utils/task_document.py | 2 +- tests/test_utils/test_query.py | 60 ++++++++++++++------------ tests/test_utils/test_task_document.py | 17 ++++++-- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/WFacer/utils/query.py b/WFacer/utils/query.py index 52b050b..abec4fa 100644 --- a/WFacer/utils/query.py +++ b/WFacer/utils/query.py @@ -34,7 +34,7 @@ def query_keypath(obj, keypath): f" of the member to refer to is not specified with" f" id-. Will query the first member in the list." ) - return query_keypath(obj[0], keypath[1:]) + return query_keypath(obj[0], keypath) elif "-" in k: if len(k.split("-")) != 2: raise ValueError( diff --git a/WFacer/utils/task_document.py b/WFacer/utils/task_document.py index bf23f46..bc41e7a 100644 --- a/WFacer/utils/task_document.py +++ b/WFacer/utils/task_document.py @@ -51,7 +51,7 @@ def get_entry_from_taskdoc(taskdoc, property_and_queries=None, decorator_names=N taskdoc(StructureMetadata): A task document generated as vasp task output by emmet-core, CP2K or force fields. - property_and_queries(list of (str, str) or str): optional + property_and_queries(list of (str, str) or list of str): optional A list of property names to be retrieved from taskdoc, and the query string to retrieve them, paired in tuples. If only strings are given, will also query with the given diff --git a/tests/test_utils/test_query.py b/tests/test_utils/test_query.py index 5c53bc6..c198b98 100644 --- a/tests/test_utils/test_query.py +++ b/tests/test_utils/test_query.py @@ -2,6 +2,7 @@ import numpy as np import numpy.testing as npt import pytest +from emmet.core.tasks import TaskDoc from WFacer.utils.query import ( get_property_from_object, @@ -33,43 +34,48 @@ def test_query(single_taskdoc): _ = query_keypath(d_test, "students.test_set".split(".")) with pytest.raises(ValueError): _ = query_keypath(single_taskdoc, ["whatever"]) - assert isinstance( - query_keypath(single_taskdoc, "calcs_reversed.0-output.outcar".split(".")), dict - ) - d_mags = query_keypath( - single_taskdoc, "calcs_reversed.0-output.outcar.magnetization".split(".") - ) - assert d_mags is not None - assert isinstance(d_mags, list) - assert isinstance(d_mags[0], dict) - assert "tot" in d_mags[0] - mags = query_keypath( - single_taskdoc, "calcs_reversed.0-output.outcar.magnetization.^tot".split(".") - ) - assert isinstance(mags, list) - assert not isinstance(mags[0], dict) - npt.assert_array_almost_equal([d["tot"] for d in d_mags], mags) + assert query_name_iteratively(single_taskdoc, "structure") is not None + if isinstance(single_taskdoc, TaskDoc): + assert isinstance( + query_keypath(single_taskdoc, "calcs_reversed.0-output.outcar".split(".")), + dict, + ) + d_mags = query_keypath( + single_taskdoc, "calcs_reversed.0-output.outcar.magnetization".split(".") + ) + assert d_mags is not None + assert isinstance(d_mags, list) + assert isinstance(d_mags[0], dict) + assert "tot" in d_mags[0] + mags = query_keypath( + single_taskdoc, + "calcs_reversed.0-output.outcar.magnetization.^tot".split("."), + ) + assert isinstance(mags, list) + assert not isinstance(mags[0], dict) + npt.assert_array_almost_equal([d["tot"] for d in d_mags], mags) + assert query_name_iteratively(single_taskdoc, "volume") is not None assert query_name_iteratively(d_test, "age") == 114514 assert query_name_iteratively(d_test, "name") == "luis" assert query_name_iteratively(d_test, "whatever") is None - assert query_name_iteratively(single_taskdoc, "volume") is not None def test_get_property(single_taskdoc): # Currently only testing "energy" and "magmom". - magmom = get_property_from_object(single_taskdoc, "magnetization") - volume = get_property_from_object(single_taskdoc, "bandgap") energy = get_property_from_object(single_taskdoc, "energy") - assert len(magmom) == len(single_taskdoc.structure) - assert volume > 0 - assert np.isclose(energy, single_taskdoc.entry.energy) # Check this. assert np.isclose(energy, single_taskdoc.output.energy) - assert np.isclose(energy, single_taskdoc.calcs_reversed[0].output.energy) + if isinstance(single_taskdoc, TaskDoc): + magmom = get_property_from_object(single_taskdoc, "magnetization") + bandgap = get_property_from_object(single_taskdoc, "bandgap") + assert len(magmom) == len(single_taskdoc.structure) + assert bandgap > 0 + assert np.isclose(energy, single_taskdoc.entry.energy) # Check this. + assert np.isclose(energy, single_taskdoc.calcs_reversed[0].output.energy) + entry = get_property_from_object(single_taskdoc, "entry") + assert entry is not None + assert entry.data["aspherical"] + assert get_property_from_object(single_taskdoc, "aspherical") with pytest.raises(ValueError): _ = get_property_from_object(single_taskdoc, "whatever") # Should be able to look into entry.data as well. Should give true. - entry = get_property_from_object(single_taskdoc, "entry") - assert entry is not None - assert entry.data["aspherical"] - assert get_property_from_object(single_taskdoc, "aspherical") diff --git a/tests/test_utils/test_task_document.py b/tests/test_utils/test_task_document.py index 993c30a..d4f3159 100644 --- a/tests/test_utils/test_task_document.py +++ b/tests/test_utils/test_task_document.py @@ -1,20 +1,29 @@ """Test single_taskdocument utilities.""" import numpy as np import pytest +from emmet.core.tasks import TaskDoc from WFacer.utils.task_document import get_entry_from_taskdoc def test_get_entry(single_taskdoc): + if isinstance(single_taskdoc, TaskDoc): + property_and_queries = ["volume"] + else: + property_and_queries = [] entry, props = get_entry_from_taskdoc( single_taskdoc, - property_and_queries=["volume"], + property_and_queries=property_and_queries, decorator_names=["magnetic-charge"], ) - assert "volume" in props + if len(property_and_queries) > 0: + assert "volume" in props + assert np.isclose(entry.energy, single_taskdoc.entry.energy) + assert np.isclose( + entry.uncorrected_energy, single_taskdoc.entry.uncorrected_energy + ) + assert np.isclose(entry.energy, single_taskdoc.output.energy) assert "magmom" in entry.structure[0].properties - assert np.isclose(entry.energy, single_taskdoc.entry.energy) - assert np.isclose(entry.uncorrected_energy, single_taskdoc.entry.uncorrected_energy) with pytest.raises(ValueError): _, _ = get_entry_from_taskdoc(