Repository: Quantum-Accelerators/quacc
Pull request #2720 (2025-03-17 04:26:43)

Refactor elastic tensor flows
CC @zulissimeta 

- Removed code duplication between EMT and MLP elastic workflows
- BREAKING: Renamed `bulk_to_elastic_tensor_flow` to `elastic_tensor_flow`. The "bulk" prefix is somewhat irrelevant here. Eventually, I should fix the `bulk_to_defects_flow` for the same reason. This was spawned from a contributor who was copying `bulk_to_slabs_flow`.
- Fixed some incorrect docstrings.


Patch:
diff --git a/docs/user/recipes/recipes_list.md b/docs/user/recipes/recipes_list.md
index f70044a9..25d86761 100644
--- a/docs/user/recipes/recipes_list.md
+++ b/docs/user/recipes/recipes_list.md
@@ -25,15 +25,15 @@ The list of available quacc recipes is shown below. The "Req'd Extras" column sp
 
 <center>
 
-| Name                     | Decorator        | Documentation                                             | Req'd Extras     |
-| ------------------------ | ---------------- | --------------------------------------------------------- | ---------------- |
-| EMT Static               | `#!Python @job`  | [quacc.recipes.emt.core.static_job][]                     |                  |
-| EMT Relax                | `#!Python @job`  | [quacc.recipes.emt.core.relax_job][]                      |                  |
-| EMT MD                   | `#!Python @job`  | [quacc.recipes.emt.md.md_job][]                           |                  |
-| EMT Bulk to Defects      | `#!Python @flow` | [quacc.recipes.emt.defects.bulk_to_defects_flow][]        | `quacc[defects]` |
-| EMT Bulk to Slabs        | `#!Python @flow` | [quacc.recipes.emt.slabs.bulk_to_slabs_flow][]            |                  |
-| EMT Phonons              | `#!Python @flow` | [quacc.recipes.emt.phonons.phonon_flow][]                 | `quacc[phonons]` |
-| EMT Bulk to Deformations | `#!Python @flow` | [quacc.recipes.emt.elastic.bulk_to_elastic_tensor_flow][] |                  |
+| Name                     | Decorator        | Documentation                                      | Req'd Extras     |
+| ------------------------ | ---------------- | -------------------------------------------------- | ---------------- |
+| EMT Static               | `#!Python @job`  | [quacc.recipes.emt.core.static_job][]              |                  |
+| EMT Relax                | `#!Python @job`  | [quacc.recipes.emt.core.relax_job][]               |                  |
+| EMT MD                   | `#!Python @job`  | [quacc.recipes.emt.md.md_job][]                    |                  |
+| EMT Bulk to Defects      | `#!Python @flow` | [quacc.recipes.emt.defects.bulk_to_defects_flow][] | `quacc[defects]` |
+| EMT Bulk to Slabs        | `#!Python @flow` | [quacc.recipes.emt.slabs.bulk_to_slabs_flow][]     |                  |
+| EMT Phonons              | `#!Python @flow` | [quacc.recipes.emt.phonons.phonon_flow][]          | `quacc[phonons]` |
+| EMT Bulk to Deformations | `#!Python @flow` | [quacc.recipes.emt.elastic.elastic_tensor_flow][]  |                  |
 
 </center>
 
@@ -91,12 +91,12 @@ The list of available quacc recipes is shown below. The "Req'd Extras" column sp
 
 <center>
 
-| Name           | Decorator        | Documentation                                                | Req'd Extras         |
-| -------------- | ---------------- | ------------------------------------------------------------ | -------------------- |
-| MLP Static     | `#!Python @job`  | [quacc.recipes.mlp.core.static_job][]                        | `quacc[mlp]`         |
-| MLP Relax      | `#!Python @job`  | [quacc.recipes.mlp.core.relax_job][]                         | `quacc[mlp]`         |
-| MLP Phonons    | `#!Python @flow` | [quacc.recipes.mlp.phonons.phonon_flow][]                    | `quacc[mlp,phonons]` |
-| MLP Elasticity | `#!Python @flow` | [quacc.recipes.mlp.elastic.bulk_to_elastic_tensor_flow][] | `quacc[mlp]` |
+| Name           | Decorator        | Documentation                                     | Req'd Extras         |
+| -------------- | ---------------- | ------------------------------------------------- | -------------------- |
+| MLP Static     | `#!Python @job`  | [quacc.recipes.mlp.core.static_job][]             | `quacc[mlp]`         |
+| MLP Relax      | `#!Python @job`  | [quacc.recipes.mlp.core.relax_job][]              | `quacc[mlp]`         |
+| MLP Phonons    | `#!Python @flow` | [quacc.recipes.mlp.phonons.phonon_flow][]         | `quacc[mlp,phonons]` |
+| MLP Elasticity | `#!Python @flow` | [quacc.recipes.mlp.elastic.elastic_tensor_flow][] | `quacc[mlp]`         |
 
 </center>
 
@@ -108,16 +108,16 @@ The list of available quacc recipes is shown below. The "Req'd Extras" column sp
 
 <center>
 
-| Name                | Decorator       | Documentation                                 | Req'd Extras   |
-|---------------------| --------------- |-----------------------------------------------|                |
-| NewtonNet Static    | `#!Python @job` | [quacc.recipes.newtonnet.core.static_job][]   |                |
-| NewtonNet Relax     | `#!Python @job` | [quacc.recipes.newtonnet.core.relax_job][]    |                |
-| NewtonNet Frequency | `#!Python @job` | [quacc.recipes.newtonnet.core.freq_job][]     |                |
-| NewtonNet TS        | `#!Python @job` | [quacc.recipes.newtonnet.ts.ts_job][]         | `quacc[sella]` |
-| NewtonNet IRC       | `#!Python @job` | [quacc.recipes.newtonnet.ts.irc_job][]        | `quacc[sella]` |
-| NewtonNet Quasi IRC | `#!Python @job` | [quacc.recipes.newtonnet.ts.quasi_irc_job][]  | `quacc[sella]` |
-| NewtonNet neb       | `#!Python @job` | [quacc.recipes.newtonnet.ts.neb_job][]        |                |
-| NewtonNet geodesic  | `#!Python @job` | [quacc.recipes.newtonnet.ts.geodesic_job][]   |                |
+| Name | Decorator | Documentation | Req'd Extras |
+|---------------------| --------------- |-----------------------------------------------| |
+| NewtonNet Static | `#!Python @job` | [quacc.recipes.newtonnet.core.static_job][] | |
+| NewtonNet Relax | `#!Python @job` | [quacc.recipes.newtonnet.core.relax_job][] | |
+| NewtonNet Frequency | `#!Python @job` | [quacc.recipes.newtonnet.core.freq_job][] | |
+| NewtonNet TS | `#!Python @job` | [quacc.recipes.newtonnet.ts.ts_job][] | `quacc[sella]` |
+| NewtonNet IRC | `#!Python @job` | [quacc.recipes.newtonnet.ts.irc_job][] | `quacc[sella]` |
+| NewtonNet Quasi IRC | `#!Python @job` | [quacc.recipes.newtonnet.ts.quasi_irc_job][] | `quacc[sella]` |
+| NewtonNet neb | `#!Python @job` | [quacc.recipes.newtonnet.ts.neb_job][] | |
+| NewtonNet geodesic | `#!Python @job` | [quacc.recipes.newtonnet.ts.geodesic_job][] | |
 
 </center>
 
diff --git a/src/quacc/recipes/common/elastic.py b/src/quacc/recipes/common/elastic.py
index 388b4a6a..fa2c98f9 100644
--- a/src/quacc/recipes/common/elastic.py
+++ b/src/quacc/recipes/common/elastic.py
@@ -11,71 +11,69 @@ from emmet.core.mpid import MPID
 from pymatgen.analysis.elasticity.stress import Stress
 from pymatgen.io.ase import AseAtomsAdaptor
 
-from quacc import job, subflow
+from quacc import flow, 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
     from quacc.types import ElasticSchema, RunSchema
 
 
-@job
-def deformations_to_elastic_tensor(
-    undeformed_result: RunSchema,
-    deformed_structure_set: DeformedStructureSet,
-    results: list[RunSchema],
-) -> ElasticityDoc:
+@flow
+def elastic_tensor_flow(
+    atoms: Atoms,
+    relax_job: Job,
+    static_job: Job,
+    pre_relax: bool = True,
+    run_static: bool = False,
+    deform_kwargs: dict[str, Any] | None = None,
+) -> ElasticSchema:
     """
-    Function to fit a DeformedStructureSet and result documents to an elastic tensor
+    Common workflow for calculating elastic tensors.
 
     Parameters
     ----------
-    undeformed_result
-        Single point or relaxation of the undeformed result (for the relaxed stress, if it's not quite 0)
-    deformed_structure_set
-        The pymatgen DeformedStructureSet with information on the strains of each structure
-    results
-        A list of results (one per deformed structure) corresponding to single points or relaxations
-        on the deformed structures.
+    atoms
+        Atoms object
+    relax_job
+        The relaxation function.
+    static_job
+        The static function
+    pre_relax
+        Whether to run a relaxation on the bulk structure before deformation (true) or run a static
+        calculation (false)
+    run_static
+        Whether to run static calculations after any relaxations on the undeformed or deformed structures
+    deform_kwargs
+        Additional keyword arguments to pass to [quacc.atoms.deformation.make_deformations_from_bulk][]
 
     Returns
     -------
-    ElasticityDoc
-        An emmet (i.e. MaterialsProject) ElasticityDoc with information about the final elastic tensor fit
+    ElasticSchema
+        See the return type-hint for the data structure.
     """
-    structure = AseAtomsAdaptor.get_structure(undeformed_result["atoms"])  # type: ignore
-    return ElasticityDoc.from_deformations_and_stresses(
-        structure,
-        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(relax_result["results"]["stress"]) == 6
-                    else relax_result["results"]["stress"]
-                )
-                / units.GPa
-            )
-            for relax_result in results
-        ],
+    if pre_relax:
+        undeformed_result = relax_job(atoms, relax_cell=True)
+        if run_static:
+            undeformed_result = static_job(undeformed_result["atoms"])
+    else:
+        undeformed_result = static_job(atoms)
+
+    return _elastic_tensor_subflow(
+        undeformed_result=undeformed_result,
+        relax_job=relax_job,
+        static_job=static_job if run_static else None,
+        deform_kwargs=deform_kwargs,
     )
 
 
 @subflow
-def bulk_to_elastic_tensor_subflow(
+def _elastic_tensor_subflow(
     undeformed_result: RunSchema,
     relax_job: Job,
     static_job: Job | None = None,
@@ -124,7 +122,7 @@ def bulk_to_elastic_tensor_subflow(
 
         results.append(result)
 
-    elasticity_doc = deformations_to_elastic_tensor(
+    elasticity_doc = _deformations_to_elastic_tensor(
         undeformed_result=undeformed_result,
         deformed_structure_set=deformed_structure_set,
         results=results,
@@ -136,3 +134,56 @@ def bulk_to_elastic_tensor_subflow(
         "undeformed_result": undeformed_result,
         "elasticity_doc": elasticity_doc,
     }
+
+
+@job
+def _deformations_to_elastic_tensor(
+    undeformed_result: RunSchema,
+    deformed_structure_set: DeformedStructureSet,
+    results: list[RunSchema],
+) -> ElasticityDoc:
+    """
+    Function to fit a DeformedStructureSet and result documents to an elastic tensor
+
+    Parameters
+    ----------
+    undeformed_result
+        Single point or relaxation of the undeformed result (for the relaxed stress, if it's not quite 0)
+    deformed_structure_set
+        The pymatgen DeformedStructureSet with information on the strains of each structure
+    results
+        A list of results (one per deformed structure) corresponding to single points or relaxations
+        on the deformed structures.
+
+    Returns
+    -------
+    ElasticityDoc
+        An emmet (i.e. MaterialsProject) ElasticityDoc with information about the final elastic tensor fit
+    """
+    structure = AseAtomsAdaptor.get_structure(undeformed_result["atoms"])
+    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(relax_result["results"]["stress"]) == 6
+                else relax_result["results"]["stress"]
+            )
+            / units.GPa
+        )
+        for relax_result in results
+    ]
+    return ElasticityDoc.from_deformations_and_stresses(
+        structure,
+        MPID("quacc-00"),
+        deformations=deformed_structure_set.deformations,
+        equilibrium_stress=equilibrium_stress,
+        stresses=stresses,
+    )
diff --git a/src/quacc/recipes/emt/elastic.py b/src/quacc/recipes/emt/elastic.py
index 220d732b..420dc385 100644
--- a/src/quacc/recipes/emt/elastic.py
+++ b/src/quacc/recipes/emt/elastic.py
@@ -5,7 +5,7 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 from quacc import flow
-from quacc.recipes.common.elastic import bulk_to_elastic_tensor_subflow
+from quacc.recipes.common.elastic import elastic_tensor_flow as elastic_tensor_flow_
 from quacc.recipes.emt.core import relax_job, static_job
 from quacc.wflow_tools.customizers import customize_funcs
 
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
 
 
 @flow
-def bulk_to_elastic_tensor_flow(
+def elastic_tensor_flow(
     atoms: Atoms,
     pre_relax: bool = True,
     run_static: bool = False,
@@ -30,25 +30,32 @@ def bulk_to_elastic_tensor_flow(
     """
     Workflow consisting of:
 
-    1. Deformed structures generation
+    1. Bulk structure relaxation (if pre_relax is True)
+        - name: "relax_job"
+        - job: [quacc.recipes.emt.core.relax_job][]
 
-    2. Deformed structures relaxations
+    2. Bulk structure static calculation (if run_static is True)
+        - name: "static_job"
+        - job: [quacc.recipes.emt.core.static_job][]
+
+    3. Deformed structures generation
+
+    4. Deformed structures relaxations
         - name: "relax_job"
         - job: [quacc.recipes.emt.core.relax_job][]
 
-    3. Deformed structures statics (optional)
+    5. Deformed structures statics (if run_static is True)
         - name: "static_job"
         - job: [quacc.recipes.emt.core.static_job][]
 
-    4. Elastic tensor calculation
+    6. Elastic tensor calculation
 
     Parameters
     ----------
     atoms
         Atoms object
     pre_relax
-        Whether to run a relaxation on the bulk structure before deformation (true) or run a static
-        calculation (false)
+        Whether to run a relaxation on the structure before deformation (true)
     run_static
         Whether to run static calculations after any relaxations on the undeformed or deformed structures
     deform_kwargs
@@ -72,16 +79,11 @@ def bulk_to_elastic_tensor_flow(
         decorators=job_decorators,
     )  # type: ignore
 
-    if pre_relax:
-        undeformed_result = relax_job_(atoms, relax_cell=True)
-        if run_static:
-            undeformed_result = static_job_(undeformed_result["atoms"])
-    else:
-        undeformed_result = static_job_(atoms)
-
-    return bulk_to_elastic_tensor_subflow(
-        undeformed_result=undeformed_result,
+    return elastic_tensor_flow_(
+        atoms=atoms,
         relax_job=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
index 1363939b..f413827e 100644
--- a/src/quacc/recipes/mlp/elastic.py
+++ b/src/quacc/recipes/mlp/elastic.py
@@ -5,7 +5,7 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 from quacc import flow
-from quacc.recipes.common.elastic import bulk_to_elastic_tensor_subflow
+from quacc.recipes.common.elastic import elastic_tensor_flow as elastic_tensor_flow_
 from quacc.recipes.mlp.core import relax_job, static_job
 from quacc.wflow_tools.customizers import customize_funcs
 
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
 
 
 @flow
-def bulk_to_elastic_tensor_flow(
+def elastic_tensor_flow(
     atoms: Atoms,
     pre_relax: bool = True,
     run_static: bool = False,
@@ -30,26 +30,32 @@ def bulk_to_elastic_tensor_flow(
     """
     Workflow consisting of:
 
-    1. Deformed structures generation
-
-    2. Deformed structures relaxations
+    1. Bulk structure relaxation (if pre_relax is True)
         - name: "relax_job"
         - job: [quacc.recipes.emt.core.relax_job][]
 
-    3. Deformed structures statics (optional)
+    2. Bulk structure static calculation (if run_static is True)
         - name: "static_job"
         - job: [quacc.recipes.emt.core.static_job][]
 
-    4. Elastic tensor calculation
+    3. Deformed structures generation
+
+    4. Deformed structures relaxations
+        - name: "relax_job"
+        - job: [quacc.recipes.emt.core.relax_job][]
+
+    5. Deformed structures statics (if run_static is True)
+        - name: "static_job"
+        - job: [quacc.recipes.emt.core.static_job][]
 
+    6. Elastic tensor calculation
 
     Parameters
     ----------
     atoms
         Atoms object
     pre_relax
-        Whether to run a relaxation on the bulk structure before deformation (true) or run a static
-        calculation (false)
+        Whether to run a relaxation on the structure before deformation (true)
     run_static
         Whether to run static calculations after any relaxations on the undeformed or deformed structures
     deform_kwargs
@@ -73,16 +79,11 @@ def bulk_to_elastic_tensor_flow(
         decorators=job_decorators,
     )  # type: ignore
 
-    if pre_relax:
-        undeformed_result = relax_job_(atoms, relax_cell=True)
-        if run_static:
-            undeformed_result = static_job_(undeformed_result["atoms"])
-    else:
-        undeformed_result = static_job_(atoms)
-
-    return bulk_to_elastic_tensor_subflow(
-        undeformed_result=undeformed_result,
+    return elastic_tensor_flow_(
+        atoms=atoms,
         relax_job=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/wflow_tools/db.py b/src/quacc/wflow_tools/db.py
index 53b7a6a7..f937aeee 100644
--- a/src/quacc/wflow_tools/db.py
+++ b/src/quacc/wflow_tools/db.py
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
     from maggma.core import Store
 
 
-def results_to_db(store: Store, results: dict[str, Any] | list[dict]) -> None:
+def results_to_db(store: Store, result: dict[str, Any]) -> None:
     """
     Store the results of a quacc recipe in a user-specified Maggma Store. A UUID will be
     generated for each entry.
@@ -22,24 +22,16 @@ def results_to_db(store: Store, results: dict[str, Any] | list[dict]) -> None:
     ----------
     store
         The Maggma Store object to store the results in
-    results
-        The output summary dictionary or list of dictionaries from a quacc
-        recipe
+    result
+        The output summary dictionary from a quacc recipe
 
     Returns
     -------
     None
     """
-    if not isinstance(results, list):
-        results = [results]
 
-    sanitized_results = [
-        jsanitize(result, enum_values=True, recursive_msonable=True)
-        for result in results
-    ]
-
-    for result in sanitized_results:
-        result["uuid"] = str(uuid.uuid4())
+    sanitized_result = jsanitize(result, enum_values=True, recursive_msonable=True)
+    sanitized_result["uuid"] = str(uuid.uuid4())
 
     with store:
-        store.update(sanitized_results, key="uuid")
+        store.update(sanitized_result, key="uuid")


Test patch:
diff --git a/tests/core/recipes/emt_recipes/test_emt_elastic.py b/tests/core/recipes/emt_recipes/test_emt_elastic.py
index 370a46dd..fbd75745 100644
--- a/tests/core/recipes/emt_recipes/test_emt_elastic.py
+++ b/tests/core/recipes/emt_recipes/test_emt_elastic.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import pytest
 from ase.build import bulk
 
-from quacc.recipes.emt.elastic import bulk_to_elastic_tensor_flow
+from quacc.recipes.emt.elastic import elastic_tensor_flow
 
 
 def test_elastic_jobs(tmp_path, monkeypatch):
@@ -11,7 +11,7 @@ def test_elastic_jobs(tmp_path, monkeypatch):
 
     atoms = bulk("Cu")
 
-    outputs = bulk_to_elastic_tensor_flow(atoms, run_static=False)
+    outputs = elastic_tensor_flow(atoms, run_static=False)
     assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx(
         atoms.get_volume()
     )
@@ -23,7 +23,7 @@ def test_elastic_jobs(tmp_path, monkeypatch):
         assert output["structure_metadata"]["nsites"] == 1
     assert len(outputs["deformed_results"]) == 24
 
-    outputs = bulk_to_elastic_tensor_flow(
+    outputs = elastic_tensor_flow(
         atoms, run_static=True, job_params={"static_job": {"asap_cutoff": True}}
     )
     assert outputs["deformed_results"][0]["atoms"].get_volume() != pytest.approx(
diff --git a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py
index 04a23849..a1fb3c32 100644
--- a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py
+++ b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py
@@ -4,7 +4,7 @@ import numpy as np
 import pytest
 from ase.build import bulk
 
-from quacc.recipes.mlp.elastic import bulk_to_elastic_tensor_flow
+from quacc.recipes.mlp.elastic import elastic_tensor_flow
 
 torch = pytest.importorskip("torch")
 
@@ -67,7 +67,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method):
 
     atoms = bulk("Cu")
 
-    outputs = bulk_to_elastic_tensor_flow(
+    outputs = elastic_tensor_flow(
         atoms,
         run_static=False,
         pre_relax=True,
@@ -90,7 +90,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method):
         assert output["structure_metadata"]["nsites"] == 1
     assert len(outputs["deformed_results"]) == 24
 
-    outputs = bulk_to_elastic_tensor_flow(
+    outputs = elastic_tensor_flow(
         atoms,
         run_static=True,
         pre_relax=True,
@@ -108,7 +108,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method):
         assert output["structure_metadata"]["nsites"] == 1
     assert len(outputs["deformed_results"]) == 24
 
-    outputs = bulk_to_elastic_tensor_flow(
+    outputs = elastic_tensor_flow(
         atoms,
         run_static=True,
         pre_relax=False,
@@ -126,7 +126,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method):
         assert output["structure_metadata"]["nsites"] == 1
     assert len(outputs["deformed_results"]) == 24
 
-    outputs = bulk_to_elastic_tensor_flow(
+    outputs = elastic_tensor_flow(
         atoms,
         run_static=False,
         pre_relax=False,
diff --git a/tests/requirements-mlp2.txt b/tests/requirements-mlp2.txt
index 369c71c6..aa311438 100644
--- a/tests/requirements-mlp2.txt
+++ b/tests/requirements-mlp2.txt
@@ -1,2 +1,2 @@
-mace-torch==0.3.10
+mace-torch==0.3.12
 matgl @ git+https://github.com/materialsvirtuallab/matgl.git