From 16c8232a58cb50cb2de95f98042da027ea37e6bf Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 01:47:27 +0000 Subject: [PATCH 1/6] first commit --- src/quacc/atoms/deformation.py | 11 +-- src/quacc/recipes/common/elastic.py | 83 +++++++++++++++++-- src/quacc/recipes/emt/elastic.py | 13 +-- src/quacc/recipes/mlp/elastic.py | 81 ++++++++++++++++++ src/quacc/types.py | 8 ++ .../recipes/emt_recipes/test_emt_elastic.py | 20 +++-- .../recipes/mlp_recipes/test_mlp_elastic.py | 47 +++++++++++ 7 files changed, 238 insertions(+), 25 deletions(-) create mode 100644 src/quacc/recipes/mlp/elastic.py create mode 100644 tests/core/recipes/mlp_recipes/test_mlp_elastic.py diff --git a/src/quacc/atoms/deformation.py b/src/quacc/atoms/deformation.py index 1bb53febb2..ac44fb218c 100644 --- a/src/quacc/atoms/deformation.py +++ b/src/quacc/atoms/deformation.py @@ -7,18 +7,21 @@ from pymatgen.analysis.elasticity.strain import DeformedStructureSet from pymatgen.io.ase import AseAtomsAdaptor +from quacc import job + if TYPE_CHECKING: from collections.abc import Sequence from ase.atoms import Atoms +@job def make_deformations_from_bulk( atoms: Atoms, norm_strains: Sequence[float] = (-0.01, -0.005, 0.005, 0.01), shear_strains: Sequence[float] = (-0.06, -0.03, 0.03, 0.06), symmetry: bool = False, -) -> list[Atoms]: +) -> DeformedStructureSet: """ Function to generate deformed structures from a bulk atoms object. @@ -38,13 +41,11 @@ def make_deformations_from_bulk( list[Atoms] All generated deformed structures """ - struct = AseAtomsAdaptor.get_structure(atoms) + struct = AseAtomsAdaptor.get_structure(atoms) # type: ignore - deformed_set = DeformedStructureSet( + return DeformedStructureSet( struct, norm_strains=norm_strains, shear_strains=shear_strains, symmetry=symmetry, ) - - return [structure.to_ase_atoms() for structure in deformed_set] diff --git a/src/quacc/recipes/common/elastic.py b/src/quacc/recipes/common/elastic.py index 33d5535cd5..0b8fb50e60 100644 --- a/src/quacc/recipes/common/elastic.py +++ b/src/quacc/recipes/common/elastic.py @@ -4,24 +4,67 @@ from typing import TYPE_CHECKING -from quacc import subflow +from ase import units +from ase.stress import voigt_6_to_full_3x3_stress +from emmet.core.elasticity import ElasticityDoc +from emmet.core.mpid import MPID +from pymatgen.analysis.elasticity.stress import Stress +from pymatgen.io.ase import AseAtomsAdaptor + +from quacc import job from quacc.atoms.deformation import make_deformations_from_bulk if TYPE_CHECKING: from typing import Any from ase.atoms import Atoms + from pymatgen.analysis.elasticity.strain import DeformedStructureSet from quacc import Job + from quacc.types import ElasticSchema, OptSchema, RunSchema + + +@job +def deformations_to_elastic_tensor( + undeformed_result: OptSchema | RunSchema, + deformed_structure_set: DeformedStructureSet, + results: list[dict], +) -> ElasticityDoc: + structure = AseAtomsAdaptor.get_structure(undeformed_result["atoms"]) # type: ignore + return ElasticityDoc.from_deformations_and_stresses( + structure, + material_id=MPID("quacc-00"), + deformations=deformed_structure_set.deformations, + equilibrium_stress=Stress( + ( + voigt_6_to_full_3x3_stress(undeformed_result["results"]["stress"]) + if len(undeformed_result["results"]["stress"]) == 6 + else undeformed_result["results"]["stress"] + ) + / units.GPa + ), + stresses=[ + Stress( + ( + voigt_6_to_full_3x3_stress(relax_result["results"]["stress"]) + if len(undeformed_result["results"]["stress"]) == 6 + else undeformed_result["results"]["stress"] + ) + / units.GPa + ) + for relax_result in results + ], + ) -@subflow def bulk_to_deformations_subflow( atoms: Atoms, relax_job: Job, - static_job: Job | None = None, + static_job: Job, + run_static: bool = False, + pre_relax: bool = True, deform_kwargs: dict[str, Any] | None = None, -) -> list[dict]: +) -> ElasticSchema: """ Workflow consisting of: @@ -37,6 +80,10 @@ def bulk_to_deformations_subflow( Atoms object relax_job The relaxation function. + static_job + The static function + pre_relax + Whether to pre-relax the input atoms as is common static_job The static function. deform_kwargs @@ -50,15 +97,33 @@ def bulk_to_deformations_subflow( """ deform_kwargs = deform_kwargs or {} - deformations = make_deformations_from_bulk(atoms, **deform_kwargs) + if pre_relax: + undeformed_result = relax_job(atoms, relax_cell=True) + else: + undeformed_result = static_job(atoms) + + deformed_structure_set = make_deformations_from_bulk( + undeformed_result["atoms"], **deform_kwargs + ) results = [] - for deformed in deformations: - result = relax_job(deformed) + for deformed in deformed_structure_set: + result = relax_job(deformed.to_ase_atoms()) - if static_job is not None: + if run_static: result = static_job(result["atoms"]) results.append(result) - return results + elasticity_doc = deformations_to_elastic_tensor( + undeformed_result=undeformed_result, + deformed_structure_set=deformed_structure_set, + results=results, + ) + + return { + "deformed_structure_set": deformed_structure_set, + "deformed_results": results, + "undeformed_result": undeformed_result, + "elasticity_doc": elasticity_doc, + } diff --git a/src/quacc/recipes/emt/elastic.py b/src/quacc/recipes/emt/elastic.py index 3f2e9dbbb1..3d642b0792 100644 --- a/src/quacc/recipes/emt/elastic.py +++ b/src/quacc/recipes/emt/elastic.py @@ -15,17 +15,18 @@ from ase.atoms import Atoms - from quacc.types import OptSchema, RunSchema + from quacc.types import ElasticSchema @flow def bulk_to_deformations_flow( atoms: Atoms, - run_static: bool = True, + run_static: bool = False, + pre_relax: bool = True, deform_kwargs: dict[str, Any] | None = None, job_params: dict[str, dict[str, Any]] | None = None, job_decorators: dict[str, Callable | None] | None = None, -) -> list[RunSchema | OptSchema]: +) -> ElasticSchema: """ Workflow consisting of: @@ -66,11 +67,13 @@ def bulk_to_deformations_flow( [relax_job, static_job], param_swaps=job_params, decorators=job_decorators, - ) + ) # type: ignore return bulk_to_deformations_subflow( atoms, relax_job_, - static_job=static_job_ if run_static else None, + static_job=static_job_, + pre_relax=pre_relax, + run_static=run_static, deform_kwargs=deform_kwargs, ) diff --git a/src/quacc/recipes/mlp/elastic.py b/src/quacc/recipes/mlp/elastic.py new file mode 100644 index 0000000000..0599b15c1e --- /dev/null +++ b/src/quacc/recipes/mlp/elastic.py @@ -0,0 +1,81 @@ +"""Elastic constants recipes for MLPs.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from quacc import flow +from quacc.recipes.common.elastic import bulk_to_deformations_subflow +from quacc.recipes.mlp.core import relax_job, static_job +from quacc.wflow_tools.customizers import customize_funcs + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any + + from ase.atoms import Atoms + + from quacc.types import ElasticSchema + + +@flow +def bulk_to_deformations_flow( + atoms: Atoms, + run_static: bool = False, + pre_relax: bool = True, + deform_kwargs: dict[str, Any] | None = None, + job_params: dict[str, dict[str, Any]] | None = None, + job_decorators: dict[str, Callable | None] | None = None, +) -> ElasticSchema: + """ + Workflow consisting of: + + 1. Deformed structures generation + + 2. Deformed structures relaxations + - name: "relax_job" + - job: [quacc.recipes.mlp.core.relax_job][] + + 3. Deformed structures statics (optional) + - name: "static_job" + - job: [quacc.recipes.mlp.core.static_job][] + + Parameters + ---------- + atoms + Atoms object + run_static + Whether to run static calculations after the relaxations + pre_relax + Whether to pre-relax the input atoms as is common + deform_kwargs + Additional keyword arguments to pass to [quacc.atoms.deformation.make_deformations_from_bulk][] + job_params + Custom parameters to pass to each Job in the Flow. This is a dictionary where + the keys are the names of the jobs and the values are dictionaries of parameters. + job_decorators + Custom decorators to apply to each Job in the Flow. This is a dictionary where + the keys are the names of the jobs and the values are decorators. + + Returns + ------- + list[RunSchema | OptSchema] + [RunSchema][quacc.schemas.ase.Summarize.run] or + [OptSchema][quacc.schemas.ase.Summarize.opt] for each deformation. + See the return type-hint for the data structure. + """ + relax_job_, static_job_ = customize_funcs( + ["relax_job", "static_job"], + [relax_job, static_job], + param_swaps=job_params, + decorators=job_decorators, + ) # type: ignore + + return bulk_to_deformations_subflow( + atoms, + relax_job_, + static_job=static_job_, + pre_relax=pre_relax, + run_static=run_static, + deform_kwargs=deform_kwargs, + ) diff --git a/src/quacc/types.py b/src/quacc/types.py index e75dc01c53..dfec953aab 100644 --- a/src/quacc/types.py +++ b/src/quacc/types.py @@ -24,6 +24,7 @@ class DefaultSetting(BaseSettings): from ase.atoms import Atoms from ase.md.md import MolecularDynamics from ase.optimize.optimize import Dynamics + from emmet.core.elasticity import ElasticityDoc from emmet.core.math import ListMatrix3D, Matrix3D, Vector3D from emmet.core.symmetry import CrystalSystem from emmet.core.vasp.calc_types import CalcType @@ -32,6 +33,7 @@ class DefaultSetting(BaseSettings): from emmet.core.vasp.task_valid import TaskState from numpy.random import Generator from numpy.typing import ArrayLike, NDArray + from pymatgen.analysis.elasticity.strain import DeformedStructureSet from pymatgen.core.composition import Composition from pymatgen.core.lattice import Lattice from pymatgen.core.periodic_table import Element @@ -528,6 +530,12 @@ class ThermoSchema(AtomsSchema): parameters_thermo: ParametersThermo results: ThermoResults + class ElasticSchema(TypedDict): + deformed_structure_set: DeformedStructureSet + deformed_results: list[RunSchema | OptSchema] + undeformed_result: RunSchema | OptSchema + elasticity_doc: ElasticityDoc + class VibThermoSchema(VibSchema, ThermoSchema): """Combined Vibrations and Thermo schema""" diff --git a/tests/core/recipes/emt_recipes/test_emt_elastic.py b/tests/core/recipes/emt_recipes/test_emt_elastic.py index 43d7a76ae3..154769cd60 100644 --- a/tests/core/recipes/emt_recipes/test_emt_elastic.py +++ b/tests/core/recipes/emt_recipes/test_emt_elastic.py @@ -12,21 +12,29 @@ def test_elastic_jobs(tmp_path, monkeypatch): atoms = bulk("Cu") outputs = bulk_to_deformations_flow(atoms, run_static=False) - assert outputs[0]["atoms"].get_volume() != pytest.approx(atoms.get_volume()) - for output in outputs: + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + assert outputs["elasticity_doc"].bulk_modulus.voigt == pytest.approx(134.579) + for output in outputs["deformed_results"]: assert output["parameters"]["asap_cutoff"] is False assert output["name"] == "EMT Relax" assert output["nelements"] == 1 assert output["nsites"] == 1 - assert len(outputs) == 24 + assert len(outputs["deformed_results"]) == 24 outputs = bulk_to_deformations_flow( atoms, run_static=True, job_params={"static_job": {"asap_cutoff": True}} ) - assert outputs[0]["atoms"].get_volume() != pytest.approx(atoms.get_volume()) - for output in outputs: + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + for output in outputs["deformed_results"]: assert output["parameters"]["asap_cutoff"] is True assert output["name"] == "EMT Static" assert output["nelements"] == 1 assert output["nsites"] == 1 - assert len(outputs) == 24 + assert len(outputs["deformed_results"]) == 24 diff --git a/tests/core/recipes/mlp_recipes/test_mlp_elastic.py b/tests/core/recipes/mlp_recipes/test_mlp_elastic.py new file mode 100644 index 0000000000..9a1635ba36 --- /dev/null +++ b/tests/core/recipes/mlp_recipes/test_mlp_elastic.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import pytest +from ase.build import bulk + +from quacc.recipes.mlp.elastic import bulk_to_deformations_flow + + +def test_elastic_jobs(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + + atoms = bulk("Cu") + + outputs = bulk_to_deformations_flow( + atoms, + run_static=False, + pre_relax=True, + job_params={"all": {"method": "sevennet"}}, + ) + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + assert outputs["undeformed_result"]["results"]["stress"] == pytest.approx( + 0, abs=1e-2 + ) + assert outputs["elasticity_doc"].bulk_modulus.voigt == pytest.approx( + 143.771, abs=1e-2 + ) + for output in outputs["deformed_results"]: + assert output["nelements"] == 1 + assert output["nsites"] == 1 + assert len(outputs["deformed_results"]) == 24 + + outputs = bulk_to_deformations_flow( + atoms, + run_static=True, + pre_relax=True, + job_params={"all": {"method": "sevennet"}}, + ) + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + + for output in outputs["deformed_results"]: + assert output["nelements"] == 1 + assert output["nsites"] == 1 + assert len(outputs["deformed_results"]) == 24 From c1563b939331cd6c9245c872840d164446414bd4 Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 18:24:56 +0000 Subject: [PATCH 2/6] extend elastic tests to all mlps --- .../recipes/mlp_recipes/test_core_recipes.py | 12 +-- .../mlp_recipes/test_elastic_recipes.py | 101 ++++++++++++++++++ .../recipes/mlp_recipes/test_mlp_elastic.py | 47 -------- 3 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 tests/core/recipes/mlp_recipes/test_elastic_recipes.py delete mode 100644 tests/core/recipes/mlp_recipes/test_mlp_elastic.py diff --git a/tests/core/recipes/mlp_recipes/test_core_recipes.py b/tests/core/recipes/mlp_recipes/test_core_recipes.py index 7bc16157fc..60b146f6ca 100644 --- a/tests/core/recipes/mlp_recipes/test_core_recipes.py +++ b/tests/core/recipes/mlp_recipes/test_core_recipes.py @@ -32,6 +32,12 @@ methods.append("fairchem") +def _set_dtype(size, type_="float"): + globals()[f"{type_}_th"] = getattr(torch, f"{type_}{size}") + globals()[f"{type_}_np"] = getattr(np, f"{type_}{size}") + torch.set_default_dtype(getattr(torch, f"float{size}")) + + @pytest.mark.skipif(has_chgnet is None, reason="chgnet not installed") def test_bad_method(): atoms = bulk("Cu") @@ -39,12 +45,6 @@ def test_bad_method(): static_job(atoms, method="bad_method") -def _set_dtype(size, type_="float"): - globals()[f"{type_}_th"] = getattr(torch, f"{type_}{size}") - globals()[f"{type_}_np"] = getattr(np, f"{type_}{size}") - torch.set_default_dtype(getattr(torch, f"float{size}")) - - @pytest.mark.parametrize("method", methods) def test_static_job(tmp_path, monkeypatch, method): monkeypatch.chdir(tmp_path) diff --git a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py new file mode 100644 index 0000000000..567c9e0828 --- /dev/null +++ b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest +from ase.build import bulk + +from quacc.recipes.mlp.elastic import bulk_to_deformations_flow + +torch = pytest.importorskip("torch") + +from importlib.util import find_spec + +methods = [] +if has_mace := find_spec("mace"): + methods.append("mace-mp-0") + +if has_matgl := find_spec("matgl"): + methods.append("m3gnet") + +if has_chgnet := find_spec("chgnet"): + methods.append("chgnet") + +if has_sevennet := find_spec("sevenn"): + methods.append("sevennet") + +if has_orb := find_spec("orb_models"): + methods.append("orb") + +if has_fairchem := find_spec("fairchem"): + methods.append("fairchem") + + +def _set_dtype(size, type_="float"): + globals()[f"{type_}_th"] = getattr(torch, f"{type_}{size}") + globals()[f"{type_}_np"] = getattr(np, f"{type_}{size}") + torch.set_default_dtype(getattr(torch, f"float{size}")) + + +@pytest.mark.parametrize("method", methods) +def test_elastic_jobs(tmp_path, monkeypatch, method): + monkeypatch.chdir(tmp_path) + + if method == "mace-mp-0": + _set_dtype(64) + else: + _set_dtype(32) + + if method == "fairchem": + calc_kwargs = { + "checkpoint_path": Path(__file__).parent / "eqV2_31M_omat_mp_salex.pt" + } + else: + calc_kwargs = {} + + ref_elastic_modulus = { + "chgnet": 140, + "m3gnet": 140, + "mace-mp-0": 140, + "sevennet": 143.77, + "orb": 140, + "fairchem": 140, + } + + atoms = bulk("Cu") + + outputs = bulk_to_deformations_flow( + atoms, + run_static=False, + pre_relax=True, + job_params={"all": {"method": method, **calc_kwargs}}, + ) + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + assert outputs["undeformed_result"]["results"]["stress"] == pytest.approx( + 0, abs=1e-2 + ) + assert outputs["elasticity_doc"].bulk_modulus.voigt == pytest.approx( + ref_elastic_modulus[method], abs=1e-2 + ) + for output in outputs["deformed_results"]: + assert output["nelements"] == 1 + assert output["nsites"] == 1 + assert len(outputs["deformed_results"]) == 24 + + outputs = bulk_to_deformations_flow( + atoms, + run_static=True, + pre_relax=True, + job_params={"all": {"method": method, **calc_kwargs}}, + ) + assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( + atoms.get_volume() + ) + + for output in outputs["deformed_results"]: + assert output["nelements"] == 1 + assert output["nsites"] == 1 + assert len(outputs["deformed_results"]) == 24 diff --git a/tests/core/recipes/mlp_recipes/test_mlp_elastic.py b/tests/core/recipes/mlp_recipes/test_mlp_elastic.py deleted file mode 100644 index 9a1635ba36..0000000000 --- a/tests/core/recipes/mlp_recipes/test_mlp_elastic.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -import pytest -from ase.build import bulk - -from quacc.recipes.mlp.elastic import bulk_to_deformations_flow - - -def test_elastic_jobs(tmp_path, monkeypatch): - monkeypatch.chdir(tmp_path) - - atoms = bulk("Cu") - - outputs = bulk_to_deformations_flow( - atoms, - run_static=False, - pre_relax=True, - job_params={"all": {"method": "sevennet"}}, - ) - assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( - atoms.get_volume() - ) - assert outputs["undeformed_result"]["results"]["stress"] == pytest.approx( - 0, abs=1e-2 - ) - assert outputs["elasticity_doc"].bulk_modulus.voigt == pytest.approx( - 143.771, abs=1e-2 - ) - for output in outputs["deformed_results"]: - assert output["nelements"] == 1 - assert output["nsites"] == 1 - assert len(outputs["deformed_results"]) == 24 - - outputs = bulk_to_deformations_flow( - atoms, - run_static=True, - pre_relax=True, - job_params={"all": {"method": "sevennet"}}, - ) - assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( - atoms.get_volume() - ) - - for output in outputs["deformed_results"]: - assert output["nelements"] == 1 - assert output["nsites"] == 1 - assert len(outputs["deformed_results"]) == 24 From 184f65c66803fac5b1325946319ec1a082973ec8 Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 18:56:28 +0000 Subject: [PATCH 3/6] fix elastic flow logic --- src/quacc/atoms/deformation.py | 3 --- src/quacc/recipes/common/elastic.py | 18 +++++------------- src/quacc/recipes/emt/elastic.py | 10 +++++++--- src/quacc/recipes/mlp/elastic.py | 10 +++++++--- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/quacc/atoms/deformation.py b/src/quacc/atoms/deformation.py index ac44fb218c..5df0160b24 100644 --- a/src/quacc/atoms/deformation.py +++ b/src/quacc/atoms/deformation.py @@ -7,15 +7,12 @@ from pymatgen.analysis.elasticity.strain import DeformedStructureSet from pymatgen.io.ase import AseAtomsAdaptor -from quacc import job - if TYPE_CHECKING: from collections.abc import Sequence from ase.atoms import Atoms -@job def make_deformations_from_bulk( atoms: Atoms, norm_strains: Sequence[float] = (-0.01, -0.005, 0.005, 0.01), diff --git a/src/quacc/recipes/common/elastic.py b/src/quacc/recipes/common/elastic.py index 0b8fb50e60..c48dd03f07 100644 --- a/src/quacc/recipes/common/elastic.py +++ b/src/quacc/recipes/common/elastic.py @@ -11,13 +11,12 @@ from pymatgen.analysis.elasticity.stress import Stress from pymatgen.io.ase import AseAtomsAdaptor -from quacc import job +from quacc import job, subflow from quacc.atoms.deformation import make_deformations_from_bulk if TYPE_CHECKING: from typing import Any - from ase.atoms import Atoms from pymatgen.analysis.elasticity.strain import DeformedStructureSet from quacc import Job @@ -57,12 +56,12 @@ def deformations_to_elastic_tensor( ) +@subflow def bulk_to_deformations_subflow( - atoms: Atoms, + undeformed_result: OptSchema | RunSchema, relax_job: Job, static_job: Job, run_static: bool = False, - pre_relax: bool = True, deform_kwargs: dict[str, Any] | None = None, ) -> ElasticSchema: """ @@ -76,14 +75,12 @@ def bulk_to_deformations_subflow( Parameters ---------- - atoms - Atoms object + undeformed_result + Result of a static or optimization calculation relax_job The relaxation function. static_job The static function - pre_relax - Whether to pre-relax the input atoms as is common static_job The static function. deform_kwargs @@ -97,11 +94,6 @@ def bulk_to_deformations_subflow( """ deform_kwargs = deform_kwargs or {} - if pre_relax: - undeformed_result = relax_job(atoms, relax_cell=True) - else: - undeformed_result = static_job(atoms) - deformed_structure_set = make_deformations_from_bulk( undeformed_result["atoms"], **deform_kwargs ) diff --git a/src/quacc/recipes/emt/elastic.py b/src/quacc/recipes/emt/elastic.py index 3d642b0792..65721a6610 100644 --- a/src/quacc/recipes/emt/elastic.py +++ b/src/quacc/recipes/emt/elastic.py @@ -69,11 +69,15 @@ def bulk_to_deformations_flow( decorators=job_decorators, ) # type: ignore + if pre_relax: + undeformed_result = relax_job_(atoms, relax_cell=True) + else: + undeformed_result = static_job_(atoms) + return bulk_to_deformations_subflow( - atoms, - relax_job_, + undeformed_result=undeformed_result, + relax_job=relax_job_, static_job=static_job_, - pre_relax=pre_relax, run_static=run_static, deform_kwargs=deform_kwargs, ) diff --git a/src/quacc/recipes/mlp/elastic.py b/src/quacc/recipes/mlp/elastic.py index 0599b15c1e..c9290ec829 100644 --- a/src/quacc/recipes/mlp/elastic.py +++ b/src/quacc/recipes/mlp/elastic.py @@ -71,11 +71,15 @@ def bulk_to_deformations_flow( decorators=job_decorators, ) # type: ignore + if pre_relax: + undeformed_result = relax_job_(atoms, relax_cell=True) + else: + undeformed_result = static_job_(atoms) + return bulk_to_deformations_subflow( - atoms, - relax_job_, + undeformed_result=undeformed_result, + relax_job=relax_job_, static_job=static_job_, - pre_relax=pre_relax, run_static=run_static, deform_kwargs=deform_kwargs, ) From d50d1fa4706ef58ab1d98f71ea8373592b74e0cd Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 19:57:59 +0000 Subject: [PATCH 4/6] typo in elastic properties --- src/quacc/recipes/common/elastic.py | 4 ++-- .../mlp_recipes/test_elastic_recipes.py | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/quacc/recipes/common/elastic.py b/src/quacc/recipes/common/elastic.py index c48dd03f07..ebeb434106 100644 --- a/src/quacc/recipes/common/elastic.py +++ b/src/quacc/recipes/common/elastic.py @@ -46,8 +46,8 @@ def deformations_to_elastic_tensor( Stress( ( voigt_6_to_full_3x3_stress(relax_result["results"]["stress"]) - if len(undeformed_result["results"]["stress"]) == 6 - else undeformed_result["results"]["stress"] + if len(relax_result["results"]["stress"]) == 6 + else relax_result["results"]["stress"] ) / units.GPa ) diff --git a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py index 567c9e0828..2cdf1d94bf 100644 --- a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py +++ b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py @@ -49,18 +49,20 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): if method == "fairchem": calc_kwargs = { - "checkpoint_path": Path(__file__).parent / "eqV2_31M_omat_mp_salex.pt" + "checkpoint_path": Path(__file__).parent / "eqV2_31M_omat_mp_salex.pt", + "seed": 0, + "disable_amp": True, } else: calc_kwargs = {} ref_elastic_modulus = { - "chgnet": 140, + "chgnet": 199, "m3gnet": 140, "mace-mp-0": 140, - "sevennet": 143.77, + "sevennet": 142.296, "orb": 140, - "fairchem": 140, + "fairchem": 105, } atoms = bulk("Cu") @@ -69,7 +71,10 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): atoms, run_static=False, pre_relax=True, - job_params={"all": {"method": method, **calc_kwargs}}, + job_params={ + "all": {"method": method, **calc_kwargs}, + "relax_job": {"opt_params": {"fmax": 0.01}}, + }, ) assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( atoms.get_volume() @@ -78,7 +83,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): 0, abs=1e-2 ) assert outputs["elasticity_doc"].bulk_modulus.voigt == pytest.approx( - ref_elastic_modulus[method], abs=1e-2 + ref_elastic_modulus[method], abs=2 ) for output in outputs["deformed_results"]: assert output["nelements"] == 1 @@ -89,7 +94,10 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): atoms, run_static=True, pre_relax=True, - job_params={"all": {"method": method, **calc_kwargs}}, + job_params={ + "all": {"method": method, **calc_kwargs}, + "relax_job": {"opt_params": {"fmax": 0.01}}, + }, ) assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx( atoms.get_volume() From f61f4d31a3f2547cf38640649ca694676caa4c18 Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 20:03:26 +0000 Subject: [PATCH 5/6] fill in missing ref elastic modulus based on quacc public tests --- tests/core/recipes/mlp_recipes/test_elastic_recipes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py index 2cdf1d94bf..61344c2624 100644 --- a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py +++ b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py @@ -58,10 +58,10 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): ref_elastic_modulus = { "chgnet": 199, - "m3gnet": 140, - "mace-mp-0": 140, + "m3gnet": 109.369, + "mace-mp-0": 130.727, "sevennet": 142.296, - "orb": 140, + "orb": 187.107, "fairchem": 105, } From 5e0110689a240800acb9eb24b26843d3239e5f64 Mon Sep 17 00:00:00 2001 From: Zack Ulissi Date: Fri, 7 Mar 2025 21:14:29 +0000 Subject: [PATCH 6/6] fix tests for make_deformations_from_bulk --- tests/core/atoms/test_elastic.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/core/atoms/test_elastic.py b/tests/core/atoms/test_elastic.py index 8a04e78604..d522ca64ce 100644 --- a/tests/core/atoms/test_elastic.py +++ b/tests/core/atoms/test_elastic.py @@ -16,8 +16,13 @@ def test_make_deformations_from_bulk(): atoms.info["test"] = "hi" deformations = make_deformations_from_bulk(atoms) assert len(deformations) == 24 - assert deformations[0].get_volume() != pytest.approx(atoms.get_volume()) + assert deformations[0].to_ase_atoms().get_volume() != pytest.approx( + atoms.get_volume() + ) for deformation in deformations: - assert_equal(deformation.get_atomic_numbers(), [30, 30, 30, 30, 52, 52, 52, 52]) - assert_equal(deformation.get_chemical_formula(), "Te4Zn4") - assert deformation.info["test"] == "hi" + assert_equal( + deformation.to_ase_atoms().get_atomic_numbers(), + [30, 30, 30, 30, 52, 52, 52, 52], + ) + assert_equal(deformation.to_ase_atoms().get_chemical_formula(), "Te4Zn4") + assert deformation.to_ase_atoms().info["test"] == "hi"