diff --git a/src/quacc/recipes/mlp/_base.py b/src/quacc/recipes/mlp/_base.py index c7a2e1674a..990d95cc11 100644 --- a/src/quacc/recipes/mlp/_base.py +++ b/src/quacc/recipes/mlp/_base.py @@ -5,6 +5,7 @@ from functools import lru_cache, wraps from importlib.util import find_spec from logging import getLogger +from pathlib import Path from typing import TYPE_CHECKING from ase.units import GPa as _GPa_to_eV_per_A3 @@ -51,12 +52,100 @@ def wrapped(*args, **kwargs): return wrapped +@lru_cache +def _get_omat24_references() -> dict[str, float]: + """ + Fetch formation energy references for OMAT24-trained models from HuggingFace. + + These references come from https://huggingface.co/facebook/UMA/blob/main/references/form_elem_refs.yaml + + Returns + ------- + dict[str, float] + Dictionary mapping element symbols to reference energies (eV/atom). + """ + import yaml + from huggingface_hub import hf_hub_download + + LOGGER.info("Downloading OMAT24 formation energy references from HuggingFace...") + + # Download the form_elem_refs.yaml file from HuggingFace + refs_file = hf_hub_download( + repo_id="facebook/UMA", + filename="references/form_elem_refs.yaml", + repo_type="model", + ) + + # Load and extract the omat references + with Path.open(refs_file) as f: + refs_data = yaml.safe_load(f) + + omat_refs = refs_data.get("refs", {}).get("omat", {}) + + if not omat_refs: + raise ValueError("Could not find 'refs.omat' in the downloaded reference file.") + + LOGGER.info(f"Loaded OMAT24 references for {len(omat_refs)} elements.") + return omat_refs + + +@lru_cache +def _get_mp20_references() -> dict[str, float]: + """ + Load formation energy references for MP-20 compatible models. + + These references come from matbench-discovery repository: + https://github.com/janosh/matbench-discovery + + Returns + ------- + dict[str, float] + Dictionary mapping element symbols to reference energies (eV/atom). + """ + import gzip + import json + from pathlib import Path + + LOGGER.info("Loading MP-20 formation energy references from local file...") + + # Load from local gzipped JSON file + refs_file = ( + Path(__file__).parent + / "references" + / "2023-02-07-mp-elemental-reference-entries.json.gz" + ) + + if not refs_file.exists(): + raise FileNotFoundError( + f"MP-20 reference file not found at {refs_file}. " + "Please ensure the file is in src/quacc/recipes/mlp/references/" + ) + + # Load the gzipped JSON file + with gzip.open(refs_file, "rt") as f: + refs_data = json.load(f) + + # Extract element references based on the expected structure + # The file should contain element references + if isinstance(refs_data, dict): + mp20_refs = refs_data + else: + raise ValueError( + f"Unexpected format in MP-20 reference file: {type(refs_data)}" + ) + + LOGGER.info(f"Loaded MP-20 references for {len(mp20_refs)} elements.") + return mp20_refs + + @freezeargs @lru_cache def pick_calculator( method: Literal[ "mace-mp", "m3gnet", "chgnet", "tensornet", "sevennet", "orb", "fairchem" ], + use_formation_energy: bool = False, + references: Literal["MP20", "OMAT24"] | None = None, **calc_kwargs, ) -> BaseCalculator: """ @@ -71,6 +160,17 @@ def pick_calculator( ---------- method Name of the calculator to use. + use_formation_energy + If True, wrap the calculator with FormationEnergyCalculator to compute + formation energies. Requires fairchem-core package to be installed. + Supported for all calculator types. Default is False. + references + Formation energy references to use. Only used if use_formation_energy=True. + Options: + - None: Use built-in references from FormationEnergyCalculator (FAIRChem models only) + - "OMAT24": Use OMAT24 references from https://huggingface.co/facebook/UMA + - "MP20": Use MP-20 references from matbench-discovery + Default is None. **calc_kwargs Custom kwargs for the underlying calculator. Set a value to `quacc.Remove` to remove a pre-existing key entirely. @@ -78,7 +178,7 @@ def pick_calculator( Returns ------- BaseCalculator - The instantiated calculator + The instantiated calculator (optionally wrapped with FormationEnergyCalculator) """ import torch @@ -125,7 +225,7 @@ def pick_calculator( from orb_models.forcefield import pretrained from orb_models.forcefield.calculator import ORBCalculator - orb_model = calc_kwargs.get("model", "orb_v2") + orb_model = calc_kwargs.get("model", "orb_v3_conservative_inf_omat") orbff = getattr(pretrained, orb_model)() calc = ORBCalculator(model=orbff, **calc_kwargs) @@ -139,4 +239,23 @@ def pick_calculator( calc.parameters["version"] = __version__ + # Wrap with FormationEnergyCalculator if requested + if use_formation_energy: + from fairchem.core.calculate.ase_calculator import FormationEnergyCalculator + + # Determine which reference energies to use + fe_kwargs = {} + + if references == "OMAT24": + # Use OMAT24 references from HuggingFace + fe_kwargs["references"] = _get_omat24_references() + elif references == "MP20": + # Use MP-20 references from local file + fe_kwargs["references"] = _get_mp20_references() + # If references is None, use built-in references from FormationEnergyCalculator + # (works for FAIRChem models with task_name specified) + + # Wrap with FormationEnergyCalculator using provided kwargs + calc = FormationEnergyCalculator(calculator=calc, **fe_kwargs) + return calc diff --git a/src/quacc/recipes/mlp/core.py b/src/quacc/recipes/mlp/core.py index 1adfe07de8..53c57da543 100644 --- a/src/quacc/recipes/mlp/core.py +++ b/src/quacc/recipes/mlp/core.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from quacc import job from quacc.recipes.mlp._base import pick_calculator @@ -11,7 +11,7 @@ from quacc.utils.dicts import recursive_dict_merge if TYPE_CHECKING: - from typing import Any, Literal + from typing import Literal from ase.atoms import Atoms @@ -23,6 +23,8 @@ def static_job( atoms: Atoms, method: Literal["mace-mp-0", "m3gnet", "chgnet", "sevennet", "orb", "fairchem"], additional_fields: dict[str, Any] | None = None, + use_formation_energy: bool = False, + references: Literal["MP20", "OMAT24"] | None = None, **calc_kwargs, ) -> RunSchema: """ @@ -36,11 +38,24 @@ def static_job( Universal ML interatomic potential method to use additional_fields Additional fields to add to the results dictionary. + use_formation_energy + If True, wrap the calculator with FormationEnergyCalculator to compute + formation energies. Requires fairchem-core package to be installed. + Supported for all methods. Default is False. The formation energy is + returned in eV per formula unit (not eV/atom). + references + Formation energy references to use. Only used if use_formation_energy=True. + Options: + - None: Use built-in references from FormationEnergyCalculator (FAIRChem models only) + - "OMAT24": Use OMAT24 references from https://huggingface.co/facebook/UMA + - "MP20": Use MP-20 references from matbench-discovery + Default is None. **calc_kwargs Custom kwargs for the underlying calculator. Set a value to - `quacc.Remove` to remove a pre-existing key entirely. For a list of available - keys, refer to the `mace.calculators.mace_mp`, `chgnet.model.dynamics.CHGNetCalculator`, - `matgl.ext.ase.M3GNetCalculator`, `sevenn.sevennet_calculator.SevenNetCalculator`, + `quacc.Remove` to remove a pre-existing key entirely. For a list of + available keys, refer to the `mace.calculators.mace_mp`, + `chgnet.model.dynamics.CHGNetCalculator`, `matgl.ext.ase.M3GNetCalculator`, + `sevenn.sevennet_calculator.SevenNetCalculator`, `orb_models.forcefield.calculator.ORBCalculator`, `fairchem.core.FAIRChemCalculator` calculators. @@ -50,7 +65,12 @@ def static_job( Dictionary of results from [quacc.schemas.ase.Summarize.run][]. See the type-hint for the data structure. """ - calc = pick_calculator(method, **calc_kwargs) + calc = pick_calculator( + method, + use_formation_energy=use_formation_energy, + references=references, + **calc_kwargs, + ) final_atoms = Runner(atoms, calc).run_calc() return Summarize( additional_fields={"name": f"{method} Static"} | (additional_fields or {}) @@ -64,6 +84,8 @@ def relax_job( relax_cell: bool = False, opt_params: OptParams | None = None, additional_fields: dict[str, Any] | None = None, + use_formation_energy: bool = False, + references: Literal["MP20", "OMAT24"] | None = None, **calc_kwargs, ) -> OptSchema: """ @@ -82,11 +104,24 @@ def relax_job( of available keys, refer to [quacc.runners.ase.Runner.run_opt][]. additional_fields Additional fields to add to the results dictionary. + use_formation_energy + If True, wrap the calculator with FormationEnergyCalculator to compute + formation energies. Requires fairchem-core package to be installed. + Supported for all methods. Default is False. The formation energy is + returned in eV per formula unit (not eV/atom). + references + Formation energy references to use. Only used if use_formation_energy=True. + Options: + - None: Use built-in references from FormationEnergyCalculator (FAIRChem models only) + - "OMAT24": Use OMAT24 references from https://huggingface.co/facebook/UMA + - "MP20": Use MP-20 references from matbench-discovery + Default is None. **calc_kwargs Custom kwargs for the underlying calculator. Set a value to - `quacc.Remove` to remove a pre-existing key entirely. For a list of available - keys, refer to the `mace.calculators.mace_mp`, `chgnet.model.dynamics.CHGNetCalculator`, - `matgl.ext.ase.M3GNetCalculator`, `sevenn.sevennet_calculator.SevenNetCalculator`, + `quacc.Remove` to remove a pre-existing key entirely. For a list of + available keys, refer to the `mace.calculators.mace_mp`, + `chgnet.model.dynamics.CHGNetCalculator`, `matgl.ext.ase.M3GNetCalculator`, + `sevenn.sevennet_calculator.SevenNetCalculator`, `orb_models.forcefield.calculator.ORBCalculator`, `fairchem.core.FAIRChemCalculator` calculators. @@ -99,7 +134,12 @@ def relax_job( opt_defaults = {"fmax": 0.05} opt_flags = recursive_dict_merge(opt_defaults, opt_params) - calc = pick_calculator(method, **calc_kwargs) + calc = pick_calculator( + method, + use_formation_energy=use_formation_energy, + references=references, + **calc_kwargs, + ) dyn = Runner(atoms, calc).run_opt(relax_cell=relax_cell, **opt_flags) diff --git a/src/quacc/recipes/mlp/references/2023-02-07-mp-elemental-reference-entries.json.gz b/src/quacc/recipes/mlp/references/2023-02-07-mp-elemental-reference-entries.json.gz new file mode 100644 index 0000000000..d5e66456ad Binary files /dev/null and b/src/quacc/recipes/mlp/references/2023-02-07-mp-elemental-reference-entries.json.gz differ diff --git a/tests/core/recipes/mlp_recipes/test_core_recipes.py b/tests/core/recipes/mlp_recipes/test_core_recipes.py index 46d7392d50..f02295b545 100644 --- a/tests/core/recipes/mlp_recipes/test_core_recipes.py +++ b/tests/core/recipes/mlp_recipes/test_core_recipes.py @@ -56,7 +56,7 @@ def test_static_job(tmp_path, monkeypatch, method): "tensornet": -3.7593491077423096, "mace-mp": -4.097862720291976, "sevennet": -4.096191883087158, - "orb": -4.093477725982666, + "orb": -3.7420763969421387, "fairchem": -3.7579006783217954, } atoms = bulk("Cu") @@ -95,7 +95,7 @@ def test_relax_job(tmp_path, monkeypatch, method): "mace-mp": -32.78264569638644, "tensornet": -30.074462890625, "sevennet": -32.76924133300781, - "orb": -32.7361946105957, + "orb": -29.93630599975586, "fairchem": -30.004380887389797, } @@ -143,7 +143,7 @@ def test_relax_cell_job(tmp_path, monkeypatch, method): "mace-mp": -32.8069374165035, "tensornet": -30.079431533813477, "sevennet": -32.76963806152344, - "orb": -32.73428726196289, + "orb": -29.93630599975586, "fairchem": -30.005004590392726, } @@ -154,3 +154,387 @@ def test_relax_cell_job(tmp_path, monkeypatch, method): assert np.shape(output["results"]["forces"]) == (8, 3) assert output["atoms"] != atoms assert output["atoms"].get_volume() != pytest.approx(atoms.get_volume()) + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_static_job_formation_energy_fairchem(tmp_path, monkeypatch): + """Test formation energy calculation with FAIRChem UMA omat.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + calc_kwargs = {"name_or_path": "uma-s-1", "task_name": "omat"} + + # Test Cu (elemental system - formation energy should be ~0) + atoms_cu = bulk("Cu") + output_cu = static_job( + atoms_cu, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # For pure elements, total formation energy should be close to zero (eV) + assert abs(output_cu["results"]["energy"]) < 0.1 + assert np.shape(output_cu["results"]["forces"]) == (1, 3) + + # Test MgO (binary compound - formation energy should be negative) + atoms_mgo = bulk("MgO", crystalstructure="rocksalt", a=4.2) + output_mgo = static_job( + atoms_mgo, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # MgO has a substantial negative formation energy (eV per formula unit) + assert output_mgo["results"]["energy"] < -2.0 + assert np.shape(output_mgo["results"]["forces"]) == (2, 3) + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_relax_job_formation_energy_fairchem(tmp_path, monkeypatch): + """Test formation energy calculation during relaxation with FAIRChem UMA omat.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + calc_kwargs = {"name_or_path": "uma-s-1", "task_name": "omat"} + + # Test Cu supercell + atoms = bulk("Cu") * (2, 2, 2) + atoms[0].position += 0.1 + output = relax_job( + atoms, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # 8 Cu atoms, total formation energy should be near zero (eV) + assert abs(output["results"]["energy"]) < 0.8 + assert np.shape(output["results"]["forces"]) == (8, 3) + assert output["atoms"] != atoms + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_formation_energy_multiple_compounds_fairchem(tmp_path, monkeypatch): + """Test formation energy for multiple compound types with FAIRChem UMA omat.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + calc_kwargs = {"name_or_path": "uma-s-1", "task_name": "omat"} + + # Test NaCl + atoms_nacl = bulk("NaCl", crystalstructure="rocksalt", a=5.64) + output_nacl = static_job( + atoms_nacl, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # NaCl has negative formation energy (eV per formula unit) + assert output_nacl["results"]["energy"] < -1.0 + + # Test Si + atoms_si = bulk("Si") + output_si = static_job( + atoms_si, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # Pure Si should have near-zero formation energy (eV) + assert abs(output_si["results"]["energy"]) < 0.1 + + # Test Al + atoms_al = bulk("Al") + output_al = static_job( + atoms_al, method="fairchem", use_formation_energy=True, **calc_kwargs + ) + # Pure Al should have near-zero formation energy (eV) + assert abs(output_al["results"]["energy"]) < 0.1 + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_relax_job_formation_energy_cell_fairchem(tmp_path, monkeypatch): + """Test formation energy calculation with cell relaxation using FAIRChem UMA omat.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + calc_kwargs = {"name_or_path": "uma-s-1", "task_name": "omat"} + + # Test MgO with cell relaxation + atoms_mgo = bulk("MgO", crystalstructure="rocksalt", a=4.2) * (2, 2, 2) + atoms_mgo[0].position += 0.05 + output = relax_job( + atoms_mgo, + method="fairchem", + relax_cell=True, + use_formation_energy=True, + **calc_kwargs, + ) + # Should have relaxed and computed formation energy + assert ( + output["results"]["energy"] < -8.0 + ) # 8 formula units * ~-1 eV per formula unit + assert output["atoms"] != atoms_mgo + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_formation_energy_non_omat_task(tmp_path, monkeypatch): + """Test that formation energy works with non-omat tasks (uses default references).""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + # Formation energy should work with non-omat tasks, using default references + atoms = bulk("Cu") + # This should not raise an error - it will use default references + output = static_job( + atoms, + method="fairchem", + name_or_path="uma-s-1", + task_name="omol", # Non-omat task + use_formation_energy=True, + ) + # Should complete successfully + assert "energy" in output["results"] + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_formation_energy_with_mace(tmp_path, monkeypatch): + """Test that formation energy works with non-FAIRChem models when references provided.""" + monkeypatch.chdir(tmp_path) + + if "mace-mp" not in methods: + pytest.skip("mace-mp not available") + + _set_dtype(64) + + # Formation energy should work with mace-mp when references are provided + atoms = bulk("Cu") + # Use OMAT24 references for testing + + output = static_job( + atoms, method="mace-mp", use_formation_energy=True, references="OMAT24" + ) + # Should complete successfully + assert "energy" in output["results"] + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_builtin_vs_omat24_references(tmp_path, monkeypatch): + """Test that built-in and OMAT24 references give identical results for UMA-s-1p1 with task_name='omat'.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + calc_kwargs = {"name_or_path": "uma-s-1p1", "task_name": "omat"} + atoms = bulk("Cu") + + # Test with built-in references (None) + output_builtin = static_job( + atoms, + method="fairchem", + use_formation_energy=True, + references=None, # Use built-in + **calc_kwargs, + ) + energy_builtin = output_builtin["results"]["energy"] + + # Test with OMAT24 references (explicit) + output_omat24 = static_job( + atoms, + method="fairchem", + use_formation_energy=True, + references="OMAT24", + **calc_kwargs, + ) + energy_omat24 = output_omat24["results"]["energy"] + + # Results should be identical (or very close due to numerical precision) + assert abs(energy_builtin - energy_omat24) < 1e-6, ( + f"Built-in references ({energy_builtin:.6f} eV) differ from OMAT24 " + f"references ({energy_omat24:.6f} eV) by {abs(energy_builtin - energy_omat24):.6e} eV" + ) + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_static_job_formation_energy_with_references(tmp_path, monkeypatch): + """Test that references parameter is properly passed through.""" + monkeypatch.chdir(tmp_path) + _set_dtype(32) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + atoms = bulk("Cu") + output = static_job( + atoms, + method="fairchem", + name_or_path="uma-s-1", + task_name="omat", + use_formation_energy=True, + references=None, # Use built-in references + ) + # Should succeed and compute formation energy + assert abs(output["results"]["energy"]) < 0.1 + assert np.shape(output["results"]["forces"]) == (1, 3) + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_formation_energy_consistency_across_models(tmp_path, monkeypatch): + """Test that formation energies are consistent across different models. + + This test ensures that when different ML potentials calculate formation + energies with appropriate references, they yield similar results + (within ~0.1 eV/atom). This helps debug issues with formation energy + references and MP corrections. + """ + monkeypatch.chdir(tmp_path) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + # Get OMAT24 references for non-FAIRChem models + # (FAIRChem will use built-in references with None) + + # Test elemental system (Cu) - formation energy should be near zero + test_structures = [ + ("Cu", bulk("Cu"), 0.0, 0.2), # (name, structure, expected_per_atom, tolerance) + ("MgO", bulk("MgO", crystalstructure="rocksalt", a=4.2), -3.0, 0.5), + ] + + for struct_name, atoms, expected_per_atom, tolerance in test_structures: + results = {} + + # Test with FAIRChem (uses built-in references with None) + if "fairchem" in methods: + _set_dtype(32) + output = static_job( + atoms, + method="fairchem", + name_or_path="uma-s-1", + task_name="omat", + use_formation_energy=True, + references=None, # Use built-in + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + results["fairchem"] = energy_per_atom + + # Test with MACE (using OMAT24 references) + if "mace-mp" in methods: + _set_dtype(64) + output = static_job( + atoms, method="mace-mp", use_formation_energy=True, references="OMAT24" + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + results["mace-mp"] = energy_per_atom + + # Test with TensorNet (using OMAT24 references) + if "tensornet" in methods: + _set_dtype(32) + output = static_job( + atoms, + method="tensornet", + use_formation_energy=True, + references="OMAT24", + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + results["tensornet"] = energy_per_atom + + # Test with SevenNet (using OMAT24 references) + if "sevennet" in methods: + _set_dtype(32) + output = static_job( + atoms, method="sevennet", use_formation_energy=True, references="OMAT24" + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + results["sevennet"] = energy_per_atom + + # Test with ORB (using OMAT24 references) + if "orb" in methods: + _set_dtype(32) + output = static_job( + atoms, method="orb", use_formation_energy=True, references="OMAT24" + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + results["orb"] = energy_per_atom + + # Verify we have at least 2 results to compare + if len(results) < 2: + pytest.skip(f"Not enough models available to compare for {struct_name}") + + # Check that all results are within expected range + for method_name, energy in results.items(): + assert abs(energy - expected_per_atom) < tolerance, ( + f"{struct_name} - {method_name}: {energy:.4f} eV/atom is outside " + f"expected range {expected_per_atom} +/- {tolerance} eV/atom" + ) + + # Check consistency across models (within 0.1 eV/atom) + energies_list = list(results.values()) + max_energy = max(energies_list) + min_energy = min(energies_list) + spread = max_energy - min_energy + + # Allow slightly larger spread for compounds than elements + max_spread = 0.15 if "Cu" in struct_name else 0.2 + assert spread < max_spread, ( + f"{struct_name} - Spread of {spread:.4f} eV/atom exceeds maximum " + f"allowed {max_spread} eV/atom. Results: {results}" + ) + + +@pytest.mark.skipif(find_spec("fairchem") is None, reason="fairchem not installed") +def test_formation_energy_mp20_references(tmp_path, monkeypatch): + """Test that MP-20 references can be loaded and used.""" + monkeypatch.chdir(tmp_path) + + from huggingface_hub.utils._auth import get_token + + if not get_token(): + pytest.skip("HuggingFace token not available for FAIRChem") + + # Test loading MP-20 references + from quacc.recipes.mlp._base import _get_mp20_references + + try: + mp20_refs = _get_mp20_references() + assert isinstance(mp20_refs, dict) + assert len(mp20_refs) > 0 + + # Test with FAIRChem using MP20 references (explicit) + if "fairchem" in methods: + _set_dtype(32) + atoms = bulk("Cu") + + # Test with MP-20 references + output = static_job( + atoms, + method="fairchem", + name_or_path="uma-s-1", + task_name="omat", + use_formation_energy=True, + references="MP20", + ) + energy_per_atom = output["results"]["energy"] / len(atoms) + + # Should be close to zero for elemental system + assert abs(energy_per_atom) < 0.2 + + except Exception as e: + pytest.skip(f"Could not load MP-20 references: {e}") diff --git a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py index 18ba46be05..d85b5639a0 100644 --- a/tests/core/recipes/mlp_recipes/test_elastic_recipes.py +++ b/tests/core/recipes/mlp_recipes/test_elastic_recipes.py @@ -56,7 +56,7 @@ def test_elastic_jobs(tmp_path, monkeypatch, method): "tensornet": 138.172, "mace-mp": 130.727, "sevennet": 142.296, - "orb": 190.195, + "orb": 135.25, "fairchem": 151.367, }