diff --git a/CHANGELOG.md b/CHANGELOG.md index df18c6e6a4..234d4cdc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added a `store_intermediate_files` keyword option to `quacc.runners.ase.run_opt()` to allow for storing of the logfiles in intermediate geometry optimization steps. - Added support for Pymatgen-based input sets in VASP jobs - Added an MP meta-GGA VASP static job +- Added MP GGA relax job, MP GGA static job, and MP GGA relax flow - Added a validity checker on CLI parameters ### Changed - Changed the default ASE optimizer from `FIRE` to `BFGS` for most recipes +- Changed the VASP `DoubleRelaxSchema` to be consistent between flows ### Fixed diff --git a/docs/user/recipes/recipes_list.md b/docs/user/recipes/recipes_list.md index 39f251094c..ff2ca1e942 100644 --- a/docs/user/recipes/recipes_list.md +++ b/docs/user/recipes/recipes_list.md @@ -238,6 +238,9 @@ The list of available quacc recipes is shown below. The "Req'd Extras" column sp | VASP Slab Relax | `#!Python @job` | [quacc.recipes.vasp.slabs.relax_job][] | | | VASP Bulk to Slabs | `#!Python @flow` | [quacc.recipes.vasp.slabs.bulk_to_slabs_flow][] | | | VASP Slab to Adsorbates | `#!Python @flow` | [quacc.recipes.vasp.slabs.slab_to_ads_flow][] | | +| VASP MP GGA Relax | `#!Python @job` | [quacc.recipes.vasp.mp.mp_gga_relax_job][] | | +| VASP MP GGA Static | `#!Python @job` | [quacc.recipes.vasp.mp.mp_gga_static_job][] | | +| VASP MP GGA Relax Workflow | `#!Python @flow` | [quacc.recipes.vasp.mp.mp_gga_relax_flow][] | | | VASP MP Meta-GGA Prerelax | `#!Python @job` | [quacc.recipes.vasp.mp.mp_metagga_relax_job][] | | | VASP MP Meta-GGA Relax | `#!Python @job` | [quacc.recipes.vasp.mp.mp_metagga_relax_job][] | | | VASP MP Meta-GGA Static | `#!Python @job` | [quacc.recipes.vasp.mp.mp_metagga_static_job][] | | diff --git a/src/quacc/calculators/vasp/params.py b/src/quacc/calculators/vasp/params.py index 362a120dd3..55fda349eb 100644 --- a/src/quacc/calculators/vasp/params.py +++ b/src/quacc/calculators/vasp/params.py @@ -286,6 +286,26 @@ def remove_unused_flags(user_calc_params: dict[str, Any]) -> dict[str, Any]: return user_calc_params +def normalize_params(user_calc_params: dict[str, Any]) -> dict[str, Any]: + """ + Normalizes the user-provided calculator parameters. + + Parameters + ------- + user_calc_params + The user-provided calculator parameters. + + Returns + ------- + dict + The updated user-provided calculator parameters. + """ + for k, v in user_calc_params.items(): + if isinstance(v, str): + user_calc_params[k] = v.lower() + return user_calc_params + + def set_auto_dipole( user_calc_params: dict[str, Any], input_atoms: Atoms ) -> dict[str, Any]: diff --git a/src/quacc/calculators/vasp/vasp.py b/src/quacc/calculators/vasp/vasp.py index 6861fe67e8..90962dc11b 100644 --- a/src/quacc/calculators/vasp/vasp.py +++ b/src/quacc/calculators/vasp/vasp.py @@ -18,6 +18,7 @@ from quacc.calculators.vasp.params import ( get_param_swaps, get_pmg_input_set_params, + normalize_params, remove_unused_flags, set_auto_dipole, set_pmg_kpts, @@ -286,5 +287,7 @@ def _cleanup_params(self) -> None: self.user_calc_params, self.pmg_kpts, self.input_atoms, self.incar_copilot ) - # Remove unused INCAR flags - self.user_calc_params = sort_dict(remove_unused_flags(self.user_calc_params)) + # Clean up the user calc parameters + self.user_calc_params = sort_dict( + normalize_params(remove_unused_flags(self.user_calc_params)) + ) diff --git a/src/quacc/recipes/vasp/core.py b/src/quacc/recipes/vasp/core.py index a22a3798c2..b962942361 100644 --- a/src/quacc/recipes/vasp/core.py +++ b/src/quacc/recipes/vasp/core.py @@ -173,6 +173,5 @@ def double_relax_job( copy_files=[Path(summary1["dir_name"]) / "WAVECAR"], **relax2_kwargs, ) - summary2["relax1"] = summary1 - return summary2 + return {"relax1": summary1, "relax2": summary2} diff --git a/src/quacc/recipes/vasp/mp.py b/src/quacc/recipes/vasp/mp.py index 0178ef419a..44ba18b079 100644 --- a/src/quacc/recipes/vasp/mp.py +++ b/src/quacc/recipes/vasp/mp.py @@ -19,7 +19,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from pymatgen.io.vasp.sets import MPScanRelaxSet +from pymatgen.io.vasp.sets import MPRelaxSet, MPScanRelaxSet, MPStaticSet from quacc import flow, job from quacc.recipes.vasp._base import base_fn @@ -30,18 +30,121 @@ from ase.atoms import Atoms - from quacc.schemas._aliases.vasp import MPMetaGGARelaxFlowSchema, VaspSchema + from quacc.schemas._aliases.vasp import ( + DoubleRelaxSchema, + MPGGARelaxFlowSchema, + MPMetaGGARelaxFlowSchema, + VaspSchema, + ) + + +@job +def mp_gga_relax_job( + atoms: Atoms, copy_files: str | Path | list[str | Path] | None = None, **calc_kwargs +) -> DoubleRelaxSchema: + """ + Function to (double) relax a structure with the original Materials Project GGA(+U) settings. + + Parameters + ---------- + atoms + Atoms object + copy_files + File(s) to copy to the runtime directory. If a directory is provided, it will be recursively unpacked. + **calc_kwargs + Custom kwargs for the Vasp calculator. Set a value to + `None` to remove a pre-existing key entirely. For a list of available + keys, refer to [ase.calculators.vasp.vasp.Vasp][]. + + Returns + ------- + DoubleRelaxSchema + Dictionary of results. + """ + + def _relax( + atoms: Atoms, + copy_files: str | Path | list[str | Path] | None = None, + calc_kwargs: dict[str, Any] | None = None, + ) -> VaspSchema: + """A helper function to run a relaxation with the MP GGA settings.""" + calc_defaults = {"pmg_input_set": MPRelaxSet} + return base_fn( + atoms, + calc_defaults=calc_defaults, + calc_swaps=calc_kwargs, + additional_fields={"name": "MP GGA Relax"}, + copy_files=copy_files, + ) + + summary1 = _relax(atoms, copy_files=copy_files, **calc_kwargs) + summary2 = _relax( + atoms, + copy_files=[ + Path(summary1["dir_name"]) / "CHGCAR", + Path(summary1["dir_name"]) / "WAVECAR", + ], + **calc_kwargs, + ) + + return {"relax1": summary1, "relax2": summary2} + + +@job +def mp_gga_static_job( + atoms: Atoms, + bandgap: float | None = None, + copy_files: str | Path | list[str | Path] | None = None, + **calc_kwargs, +) -> VaspSchema: + """ + Function to run a static calculation on a structure with the original Materials Project GGA(+U) settings. + + Parameters + ---------- + atoms + Atoms object + bandgap + The bandgap in eV, if known from a prior calculation. + copy_files + File(s) to copy to the runtime directory. If a directory is provided, it will be recursively unpacked. + **calc_kwargs + Custom kwargs for the Vasp calculator. Set a value to + `None` to remove a pre-existing key entirely. For a list of available + keys, refer to [ase.calculators.vasp.vasp.Vasp][]. + + Returns + ------- + VaspSchema + Dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][]. + """ + + calc_defaults = { + "pmg_input_set": partial( + MPStaticSet, bandgap=bandgap, small_gap_multiply=[1e-4, 3.125] + ), + "algo": "fast", + "lwave": True, # Deviation from MP (but logical) + "lreal": False, + } + return base_fn( + atoms, + calc_defaults=calc_defaults, + calc_swaps=calc_kwargs, + additional_fields={"name": "MP GGA Static"}, + copy_files=copy_files, + ) @job def mp_metagga_prerelax_job( atoms: Atoms, - bandgap: float = 0.0, + bandgap: float | None = None, copy_files: str | Path | list[str | Path] | None = None, **calc_kwargs, ) -> VaspSchema: """ - Function to pre-relax a structure with Materials Project settings. By default, this + Function to pre-relax a structure with Materials Project r2SCAN workflow settings. By default, this uses a PBEsol pre-relax step. Reference: https://doi.org/10.1103/PhysRevMaterials.6.013801 @@ -67,7 +170,9 @@ def mp_metagga_prerelax_job( """ calc_defaults = { - "pmg_input_set": partial(MPScanRelaxSet, bandgap=bandgap, auto_ismear=False), + "pmg_input_set": partial( + MPScanRelaxSet, bandgap=bandgap or 0.0, auto_ismear=False + ), "ediffg": -0.05, "gga": "PS", "laechg": False, # Deviation from MP (but logical) @@ -87,12 +192,12 @@ def mp_metagga_prerelax_job( @job def mp_metagga_relax_job( atoms: Atoms, - bandgap: float = 0.0, + bandgap: float | None = None, copy_files: str | Path | list[str | Path] | None = None, **calc_kwargs, -) -> VaspSchema: +) -> DoubleRelaxSchema: """ - Function to relax a structure with Materials Project settings. By default, this uses + Function to (double) relax a structure with Materials Project r2SCAN workflow settings. By default, this uses an r2SCAN relax step. Reference: https://doi.org/10.1103/PhysRevMaterials.6.013801 @@ -112,36 +217,57 @@ def mp_metagga_relax_job( Returns ------- - VaspSchema - Dictionary of results from [quacc.schemas.vasp.vasp_summarize_run][]. - See the type-hint for the data structure. + DoubleRelaxSchema + Dictionary of results. """ - calc_defaults = { - "pmg_input_set": partial(MPScanRelaxSet, bandgap=bandgap, auto_ismear=False), - "laechg": False, # Deviation from MP (but logical) - "lvtot": False, # Deviation from MP (but logical) - "lwave": True, - } - return base_fn( + def _relax( + atoms: Atoms, + copy_files: str | Path | list[str | Path] | None = None, + bandgap: float | None = None, + calc_kwargs: dict[str, Any] | None = None, + ) -> VaspSchema: + """A helper function to run a relaxation with the MP r2SCAN settings.""" + calc_defaults = { + "pmg_input_set": partial( + MPScanRelaxSet, bandgap=bandgap or 0.0, auto_ismear=False + ), + "laechg": False, # Deviation from MP (but logical) + "lvtot": False, # Deviation from MP (but logical) + "lwave": True, + } + return base_fn( + atoms, + calc_defaults=calc_defaults, + calc_swaps=calc_kwargs, + additional_fields={"name": "MP Meta-GGA Relax"}, + copy_files=copy_files, + ) + + summary1 = _relax(atoms, copy_files=copy_files, bandgap=bandgap, **calc_kwargs) + summary2 = _relax( atoms, - calc_defaults=calc_defaults, - calc_swaps=calc_kwargs, - additional_fields={"name": "MP Meta-GGA Relax"}, - copy_files=copy_files, + copy_files=[ + Path(summary1["dir_name"]) / "CHGCAR", + Path(summary1["dir_name"]) / "WAVECAR", + ], + bandgap=bandgap, + **calc_kwargs, ) + return {"relax1": summary1, "relax2": summary2} + @job def mp_metagga_static_job( atoms: Atoms, - bandgap: float = 0.0, + bandgap: float | None = None, copy_files: str | Path | list[str | Path] | None = None, **calc_kwargs, ) -> VaspSchema: """ - Function to run a static calculation on a structure with Materials Project settings. By default, this uses - an r2SCAN static step. + Function to run a static calculation on a structure with r2SCAN workflow Materials Project settings. + By default, this uses an r2SCAN static step. Parameters ---------- @@ -164,7 +290,9 @@ def mp_metagga_static_job( """ calc_defaults = { - "pmg_input_set": partial(MPScanRelaxSet, bandgap=bandgap, auto_ismear=False), + "pmg_input_set": partial( + MPScanRelaxSet, bandgap=bandgap or 0.0, auto_ismear=False + ), "algo": "fast", "ismear": -5, "lreal": False, @@ -180,6 +308,62 @@ def mp_metagga_static_job( ) +@flow +def mp_gga_relax_flow( + atoms: Atoms, + job_params: dict[str, dict[str, Any]] | None = None, + job_decorators: dict[str, Callable | None] | None = None, +) -> MPGGARelaxFlowSchema: + """ + Materials Project GGA workflow consisting of: + + 1. MP-compatible (double) relax + - name: "mp_gga_relax_job" + - job: [quacc.recipes.vasp.mp.mp_gga_relax_job][] + + 2. MP-compatible static + - name: "mp_gga_static_job" + - job: [quacc.recipes.vasp.mp.mp_gga_static_job][] + + Parameters + ---------- + atoms + Atoms object for the structure. + job_params + Custom parameters to pass to each Job in the Flow. This is a dictinoary 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 + ------- + MPGGARelaxFlowSchema + Dictionary of results. See the type-hint for the data structure. + """ + (mp_gga_relax_job_, mp_gga_static_job_) = customize_funcs( + ["mp_gga_relax_job", "mp_gga_static_job"], + [mp_gga_relax_job, mp_gga_static_job], + parameters=job_params, + decorators=job_decorators, + ) + + # Run the relax + relax_results = mp_gga_relax_job_(atoms) + + # Run the static + static_results = mp_gga_static_job_( + relax_results["relax2"]["atoms"], + bandgap=relax_results["relax2"]["output"]["bandgap"], + copy_files=[ + Path(relax_results["relax2"]["dir_name"]) / "CHGCAR", + Path(relax_results["relax2"]["dir_name"]) / "WAVECAR", + ], + ) + + return {"relax": relax_results, "static": static_results} + + @flow def mp_metagga_relax_flow( atoms: Atoms, @@ -187,13 +371,13 @@ def mp_metagga_relax_flow( job_decorators: dict[str, Callable | None] | None = None, ) -> MPMetaGGARelaxFlowSchema: """ - Workflow consisting of: + Materials Project r2SCAN workflow consisting of: 1. MP-compatible pre-relax - name: "mp_metagga_prerelax_job" - job: [quacc.recipes.vasp.mp.mp_metagga_prerelax_job][] - 2. MP-compatible relax + 2. MP-compatible (double) relax - name: "mp_metagga_relax_job" - job: [quacc.recipes.vasp.mp.mp_metagga_relax_job][] @@ -242,15 +426,14 @@ def mp_metagga_relax_flow( Path(prerelax_results["dir_name"]) / "WAVECAR", ], ) - relax_results["prerelax"] = prerelax_results # Run the static static_results = mp_metagga_static_job_( - relax_results["atoms"], - bandgap=relax_results["output"]["bandgap"], + relax_results["relax2"]["atoms"], + bandgap=relax_results["relax2"]["output"]["bandgap"], copy_files=[ - Path(relax_results["dir_name"]) / "CHGCAR", - Path(relax_results["dir_name"]) / "WAVECAR", + Path(relax_results["relax2"]["dir_name"]) / "CHGCAR", + Path(relax_results["relax2"]["dir_name"]) / "WAVECAR", ], ) diff --git a/src/quacc/schemas/_aliases/vasp.py b/src/quacc/schemas/_aliases/vasp.py index 00634f2d44..aa7ca882b0 100644 --- a/src/quacc/schemas/_aliases/vasp.py +++ b/src/quacc/schemas/_aliases/vasp.py @@ -56,6 +56,7 @@ class DoubleRelaxSchema(VaspSchema): """Type hint associated with double relaxation jobs.""" relax1: VaspSchema + relax2: VaspSchema class MPMetaGGARelaxFlowSchema(VaspSchema): @@ -66,6 +67,13 @@ class MPMetaGGARelaxFlowSchema(VaspSchema): static: VaspSchema +class MPGGARelaxFlowSchema(VaspSchema): + """Type hint associated with the MP GGA relaxation flows.""" + + relax: VaspSchema + static: VaspSchema + + class QMOFRelaxSchema(VaspSchema): """Type hint associated with the QMOF relaxation jobs.""" diff --git a/tests/core/calculators/vasp/test_vasp.py b/tests/core/calculators/vasp/test_vasp.py index 1069e6e019..6effb8cb86 100644 --- a/tests/core/calculators/vasp/test_vasp.py +++ b/tests/core/calculators/vasp/test_vasp.py @@ -835,7 +835,7 @@ def test_pmg_input_set(): atoms = bulk("Cu") calc = Vasp(atoms, pmg_input_set=MPRelaxSet, incar_copilot="off") assert calc.parameters == { - "algo": "Fast", + "algo": "fast", "ediff": 5e-05, "encut": 520, "ibrion": 2, @@ -844,12 +844,12 @@ def test_pmg_input_set(): "ispin": 2, "lasph": True, "lorbit": 11, - "lreal": "Auto", + "lreal": "auto", "lwave": False, "nelm": 100, "nsw": 99, - "pp": "PBE", - "prec": "Accurate", + "pp": "pbe", + "prec": "accurate", "sigma": 0.05, "magmom": [0.6], "lmaxmix": 4, @@ -864,7 +864,7 @@ def test_pmg_input_set2(): atoms[0].symbol = "O" calc = Vasp(atoms, pmg_input_set=MPRelaxSet, incar_copilot="off") assert calc.parameters == { - "algo": "Fast", + "algo": "fast", "ediff": 0.0001, "encut": 520, "ibrion": 2, @@ -879,12 +879,12 @@ def test_pmg_input_set2(): "ldauu": [0, 5.3], "ldauprint": 1, "lorbit": 11, - "lreal": "Auto", + "lreal": "auto", "lwave": False, "nelm": 100, "nsw": 99, - "pp": "PBE", - "prec": "Accurate", + "pp": "pbe", + "prec": "accurate", "sigma": 0.05, "magmom": [2.3, 2.3], "lmaxmix": 4, diff --git a/tests/core/recipes/vasp_recipes/mocked/fake_pseudos/potpaw_PBE/Ni_pv/POTCAR b/tests/core/recipes/vasp_recipes/mocked/fake_pseudos/potpaw_PBE/Ni_pv/POTCAR new file mode 100644 index 0000000000..9b8f3aa012 --- /dev/null +++ b/tests/core/recipes/vasp_recipes/mocked/fake_pseudos/potpaw_PBE/Ni_pv/POTCAR @@ -0,0 +1 @@ +<3 \ No newline at end of file diff --git a/tests/core/recipes/vasp_recipes/mocked/test_vasp_recipes.py b/tests/core/recipes/vasp_recipes/mocked/test_vasp_recipes.py index 65e433fadc..db1784a481 100644 --- a/tests/core/recipes/vasp_recipes/mocked/test_vasp_recipes.py +++ b/tests/core/recipes/vasp_recipes/mocked/test_vasp_recipes.py @@ -4,6 +4,9 @@ from quacc import SETTINGS from quacc.recipes.vasp.core import double_relax_job, relax_job, static_job from quacc.recipes.vasp.mp import ( + mp_gga_relax_flow, + mp_gga_relax_job, + mp_gga_static_job, mp_metagga_prerelax_job, mp_metagga_relax_flow, mp_metagga_relax_job, @@ -106,13 +109,13 @@ def test_doublerelax_job(tmp_path, monkeypatch): assert output["relax1"]["parameters"]["nsw"] > 0 assert output["relax1"]["parameters"]["isif"] == 3 assert output["relax1"]["parameters"]["lwave"] is False - assert output["nsites"] == len(atoms) - assert output["parameters"]["isym"] == 0 - assert output["parameters"]["nsw"] > 0 - assert output["parameters"]["isif"] == 3 - assert output["parameters"]["lwave"] is False assert output["relax1"]["parameters"]["encut"] == 520 - assert output["parameters"]["encut"] == 520 + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"]["isym"] == 0 + assert output["relax2"]["parameters"]["nsw"] > 0 + assert output["relax2"]["parameters"]["isif"] == 3 + assert output["relax2"]["parameters"]["lwave"] is False + assert output["relax2"]["parameters"]["encut"] == 520 output = double_relax_job(atoms, relax2_kwargs={"nelmin": 6}) assert output["relax1"]["nsites"] == len(atoms) @@ -120,30 +123,30 @@ def test_doublerelax_job(tmp_path, monkeypatch): assert output["relax1"]["parameters"]["nsw"] > 0 assert output["relax1"]["parameters"]["isif"] == 3 assert output["relax1"]["parameters"]["lwave"] is False - assert output["nsites"] == len(atoms) - assert output["parameters"]["isym"] == 0 - assert output["parameters"]["nsw"] > 0 - assert output["parameters"]["isif"] == 3 - assert output["parameters"]["lwave"] is False assert output["relax1"]["parameters"]["encut"] == 520 - assert output["parameters"]["encut"] == 520 - assert output["parameters"]["nelmin"] == 6 + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"]["isym"] == 0 + assert output["relax2"]["parameters"]["nsw"] > 0 + assert output["relax2"]["parameters"]["isif"] == 3 + assert output["relax2"]["parameters"]["lwave"] is False + assert output["relax2"]["parameters"]["encut"] == 520 + assert output["relax2"]["parameters"]["nelmin"] == 6 output = double_relax_job(atoms, relax_cell=False) assert output["relax1"]["nsites"] == len(atoms) assert output["relax1"]["parameters"]["isym"] == 0 assert output["relax1"]["parameters"]["nsw"] > 0 assert output["relax1"]["parameters"]["lwave"] is False - assert output["nsites"] == len(atoms) - assert output["parameters"]["isym"] == 0 - assert output["parameters"]["nsw"] > 0 - assert output["parameters"]["lwave"] is False - assert output["relax1"]["parameters"]["encut"] == 520 - assert output["parameters"]["encut"] == 520 assert output["relax1"]["parameters"]["isif"] == 2 - assert output["parameters"]["isif"] == 2 + assert output["relax1"]["parameters"]["encut"] == 520 + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"]["isym"] == 0 + assert output["relax2"]["parameters"]["nsw"] > 0 + assert output["relax2"]["parameters"]["lwave"] is False + assert output["relax2"]["parameters"]["encut"] == 520 + assert output["relax2"]["parameters"]["isif"] == 2 - double_relax_job(atoms, relax1_kwargs={"kpts": [1, 1, 1]}) + assert double_relax_job(atoms, relax1_kwargs={"kpts": [1, 1, 1]}) def test_slab_static_job(tmp_path, monkeypatch): @@ -363,13 +366,13 @@ def test_mp_metagga_prerelax_job(tmp_path, monkeypatch): output = mp_metagga_prerelax_job(atoms) assert output["nsites"] == len(atoms) assert output["parameters"] == { - "algo": "All", + "algo": "all", "ediff": 1e-5, "ediffg": -0.05, "efermi": "midgap", # added by copilot "enaug": 1360, "encut": 680, - "gga": "PS", + "gga": "ps", "ibrion": 2, "isif": 3, "ismear": 0, @@ -381,38 +384,38 @@ def test_mp_metagga_prerelax_job(tmp_path, monkeypatch): "lelf": False, "lmixtau": True, "lorbit": 11, - "lreal": "Auto", + "lreal": "auto", "lvtot": False, # disabled by us "lwave": True, "magmom": [0.6], "nelm": 200, "nsw": 99, - "prec": "Accurate", + "prec": "accurate", "setups": {"Al": ""}, "sigma": 0.05, - "pp": "PBE", + "pp": "pbe", } output = mp_metagga_prerelax_job(atoms, bandgap=0) assert output["nsites"] == len(atoms) - assert output["parameters"]["gga"] == "PS" + assert output["parameters"]["gga"] == "ps" assert output["parameters"]["ediffg"] == -0.05 assert output["parameters"]["encut"] == 680 assert output["parameters"]["kspacing"] == 0.22 assert output["parameters"]["ismear"] == 0 assert output["parameters"]["sigma"] == 0.05 - assert output["parameters"]["pp"] == "PBE" + assert output["parameters"]["pp"] == "pbe" assert "metagga" not in output["parameters"] output = mp_metagga_prerelax_job(atoms, bandgap=100) assert output["nsites"] == len(atoms) - assert output["parameters"]["gga"] == "PS" + assert output["parameters"]["gga"] == "ps" assert output["parameters"]["ediffg"] == -0.05 assert output["parameters"]["encut"] == 680 assert output["parameters"]["kspacing"] == 0.44 assert output["parameters"]["ismear"] == 0 assert output["parameters"]["sigma"] == 0.05 - assert output["parameters"]["pp"] == "PBE" + assert output["parameters"]["pp"] == "pbe" assert "metagga" not in output["parameters"] @@ -422,9 +425,9 @@ def test_mp_metagga_relax_job(tmp_path, monkeypatch): atoms = bulk("Al") output = mp_metagga_relax_job(atoms) - assert output["nsites"] == len(atoms) - assert output["parameters"] == { - "algo": "All", + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"] == { + "algo": "all", "ediff": 1e-5, "ediffg": -0.02, "efermi": "midgap", # added by copilot @@ -441,38 +444,38 @@ def test_mp_metagga_relax_job(tmp_path, monkeypatch): "lelf": False, "lmixtau": True, "lorbit": 11, - "lreal": "Auto", + "lreal": "auto", "lvtot": False, # disabled by us "lwave": True, "magmom": [0.6], - "metagga": "R2scan", + "metagga": "r2scan", "nelm": 200, "nsw": 99, - "prec": "Accurate", + "prec": "accurate", "sigma": 0.05, - "pp": "PBE", + "pp": "pbe", "setups": {"Al": ""}, } output = mp_metagga_relax_job(atoms, bandgap=0) - assert output["nsites"] == len(atoms) - assert output["parameters"]["metagga"].lower() == "r2scan" - assert output["parameters"]["ediffg"] == -0.02 - assert output["parameters"]["encut"] == 680 - assert output["parameters"]["kspacing"] == 0.22 - assert output["parameters"]["ismear"] == 0 - assert output["parameters"]["sigma"] == 0.05 - assert output["parameters"]["pp"] == "PBE" + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"]["metagga"].lower() == "r2scan" + assert output["relax2"]["parameters"]["ediffg"] == -0.02 + assert output["relax2"]["parameters"]["encut"] == 680 + assert output["relax2"]["parameters"]["kspacing"] == 0.22 + assert output["relax2"]["parameters"]["ismear"] == 0 + assert output["relax2"]["parameters"]["sigma"] == 0.05 + assert output["relax2"]["parameters"]["pp"] == "pbe" output = mp_metagga_relax_job(atoms, bandgap=100) - assert output["nsites"] == len(atoms) - assert output["parameters"]["metagga"].lower() == "r2scan" - assert output["parameters"]["ediffg"] == -0.02 - assert output["parameters"]["encut"] == 680 - assert output["parameters"]["kspacing"] == 0.44 - assert output["parameters"]["ismear"] == 0 - assert output["parameters"]["sigma"] == 0.05 - assert output["parameters"]["pp"] == "PBE" + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax2"]["parameters"]["metagga"].lower() == "r2scan" + assert output["relax2"]["parameters"]["ediffg"] == -0.02 + assert output["relax2"]["parameters"]["encut"] == 680 + assert output["relax2"]["parameters"]["kspacing"] == 0.44 + assert output["relax2"]["parameters"]["ismear"] == 0 + assert output["relax2"]["parameters"]["sigma"] == 0.05 + assert output["relax2"]["parameters"]["pp"] == "pbe" def test_mp_metagga_static_job(tmp_path, monkeypatch): @@ -501,12 +504,12 @@ def test_mp_metagga_static_job(tmp_path, monkeypatch): "lvtot": True, "lwave": True, # enabled by us "magmom": [0.6], - "metagga": "R2scan", + "metagga": "r2scan", "nelm": 200, "nsw": 0, - "prec": "Accurate", + "prec": "accurate", "sigma": 0.05, - "pp": "PBE", + "pp": "pbe", "setups": {"Al": ""}, } @@ -518,30 +521,30 @@ def test_mp_metagga_relax_flow(tmp_path, monkeypatch): output = mp_metagga_relax_flow(atoms) assert output["static"]["nsites"] == len(atoms) - assert output["relax"]["parameters"]["metagga"].lower() == "r2scan" - assert output["relax"]["parameters"]["ediffg"] == -0.02 - assert output["relax"]["parameters"]["encut"] == 680 - assert output["relax"]["parameters"]["ismear"] == 0 - assert output["relax"]["parameters"]["sigma"] == 0.05 - assert output["relax"]["parameters"]["kspacing"] == 0.22 - assert output["relax"]["parameters"]["pp"] == "PBE" - assert output["prerelax"]["parameters"]["gga"] == "PS" + assert output["relax"]["relax2"]["parameters"]["metagga"].lower() == "r2scan" + assert output["relax"]["relax2"]["parameters"]["ediffg"] == -0.02 + assert output["relax"]["relax2"]["parameters"]["encut"] == 680 + assert output["relax"]["relax2"]["parameters"]["ismear"] == 0 + assert output["relax"]["relax2"]["parameters"]["sigma"] == 0.05 + assert output["relax"]["relax2"]["parameters"]["kspacing"] == 0.22 + assert output["relax"]["relax2"]["parameters"]["pp"] == "pbe" + assert output["prerelax"]["parameters"]["gga"] == "ps" assert output["prerelax"]["parameters"]["ismear"] == 0 - assert output["prerelax"]["parameters"]["pp"] == "PBE" + assert output["prerelax"]["parameters"]["pp"] == "pbe" atoms = bulk("C") output = mp_metagga_relax_flow(atoms) assert output["static"]["nsites"] == len(atoms) - assert output["relax"]["parameters"]["metagga"].lower() == "r2scan" - assert output["relax"]["parameters"]["ediffg"] == -0.02 - assert output["relax"]["parameters"]["encut"] == 680 - assert output["relax"]["parameters"]["ismear"] == 0 - assert output["relax"]["parameters"]["kspacing"] == pytest.approx( + assert output["relax"]["relax2"]["parameters"]["metagga"].lower() == "r2scan" + assert output["relax"]["relax2"]["parameters"]["ediffg"] == -0.02 + assert output["relax"]["relax2"]["parameters"]["encut"] == 680 + assert output["relax"]["relax2"]["parameters"]["ismear"] == 0 + assert output["relax"]["relax2"]["parameters"]["kspacing"] == pytest.approx( 0.28329488761304206 ) - assert output["relax"]["parameters"]["pp"] == "PBE" + assert output["relax"]["relax2"]["parameters"]["pp"] == "pbe" assert output["prerelax"]["parameters"]["ismear"] == 0 - assert output["prerelax"]["parameters"]["pp"] == "PBE" + assert output["prerelax"]["parameters"]["pp"] == "pbe" atoms = molecule("O2") atoms.center(vacuum=10) @@ -551,13 +554,156 @@ def test_mp_metagga_relax_flow(tmp_path, monkeypatch): assert output["static"]["parameters"]["ismear"] == -5 assert output["static"]["parameters"]["nsw"] == 0 assert output["static"]["parameters"]["algo"] == "fast" - assert output["relax"]["parameters"]["metagga"].lower() == "r2scan" - assert output["relax"]["parameters"]["ediffg"] == -0.02 - assert output["relax"]["parameters"]["encut"] == 680 - assert output["relax"]["parameters"]["ismear"] == 0 - assert output["relax"]["parameters"]["kspacing"] == pytest.approx( + assert output["relax"]["relax2"]["parameters"]["metagga"].lower() == "r2scan" + assert output["relax"]["relax2"]["parameters"]["ediffg"] == -0.02 + assert output["relax"]["relax2"]["parameters"]["encut"] == 680 + assert output["relax"]["relax2"]["parameters"]["ismear"] == 0 + assert output["relax"]["relax2"]["parameters"]["kspacing"] == pytest.approx( 0.28329488761304206 ) - assert output["relax"]["parameters"]["pp"] == "PBE" + assert output["relax"]["relax2"]["parameters"]["pp"] == "pbe" assert output["prerelax"]["parameters"]["ismear"] == 0 - assert output["prerelax"]["parameters"]["pp"] == "PBE" + assert output["prerelax"]["parameters"]["pp"] == "pbe" + + +def test_mp_gga_relax_job(): + atoms = bulk("Ni") * (2, 1, 1) + atoms[0].symbol = "O" + output = mp_gga_relax_job(atoms) + assert output["relax2"]["nsites"] == len(atoms) + assert output["relax1"]["atoms"].get_chemical_symbols() == ["O", "Ni"] + assert output["relax2"]["atoms"].get_chemical_symbols() == ["O", "Ni"] + assert output["relax2"]["parameters"] == { + "algo": "fast", + "ediff": 0.0001, + "efermi": "midgap", # added by copilot + "encut": 520, + "gamma": True, + "ibrion": 2, + "isif": 3, + "ismear": -5, + "ispin": 2, + "kpts": [5, 11, 11], + "lasph": True, + "ldau": True, + "ldauj": [0, 0], + "ldaul": [0, 2], + "ldauprint": 1, # added by us (sensible) + "ldautype": 2, # added by us (sensible) + "ldauu": [0, 6.2], + "lmaxmix": 4, + "lorbit": 11, + "lreal": "auto", + "lwave": False, + "magmom": [0.6, 0.6], + "nelm": 100, + "nsw": 99, + "prec": "accurate", + "sigma": 0.05, + "pp": "pbe", + "setups": {"O": "", "Ni": "_pv"}, + } + + +def test_mp_gga_static_job(): + atoms = bulk("Ni") * (2, 1, 1) + atoms[0].symbol = "O" + output = mp_gga_static_job(atoms) + assert output["nsites"] == len(atoms) + assert output["parameters"] == { + "algo": "fast", + "ediff": 0.0001, + "efermi": "midgap", # added by copilot + "encut": 520, + "gamma": True, + "ismear": -5, + "ispin": 2, + "kpts": [6, 13, 13], + "lasph": True, + "lcharg": True, # modified by us (sensible) + "ldau": True, + "ldauj": [0, 0], + "ldaul": [0, 2], + "ldauprint": 1, # added by us (sensible) + "ldautype": 2, # added by us (sensible) + "ldauu": [0, 6.2], + "lmaxmix": 4, + "lorbit": 11, + "lreal": False, + "lwave": True, # modified by us (sensible) + "magmom": [0.6, 0.6], + "nelm": 100, + "nsw": 0, + "prec": "accurate", + "sigma": 0.05, + "pp": "pbe", + "setups": {"Ni": "_pv", "O": ""}, + } + + +def test_mp_gga_relax_flow(): + atoms = bulk("Ni") * (2, 1, 1) + atoms[0].symbol = "O" + output = mp_gga_relax_flow(atoms) + assert output["static"]["nsites"] == len(atoms) + relax_params = { + "algo": "fast", + "ediff": 0.0001, + "efermi": "midgap", # added by copilot + "encut": 520, + "gamma": True, + "ibrion": 2, + "isif": 3, + "ismear": -5, + "ispin": 2, + "kpts": [5, 11, 11], + "lasph": True, + "ldau": True, + "ldauj": [0, 0], + "ldaul": [0, 2], + "ldauprint": 1, # added by us (sensible) + "ldautype": 2, # added by us (sensible) + "ldauu": [0, 6.2], + "lmaxmix": 4, + "lorbit": 11, + "lreal": "auto", + "lwave": False, + "magmom": [0.6, 0.6], + "nelm": 100, + "nsw": 99, + "prec": "accurate", + "sigma": 0.05, + "pp": "pbe", + "setups": {"O": "", "Ni": "_pv"}, + } + assert output["relax"]["relax1"]["parameters"] == relax_params + assert output["relax"]["relax2"]["parameters"] == relax_params + assert output["static"]["parameters"] == { + "algo": "fast", + "ediff": 0.0001, + "efermi": "midgap", # added by copilot + "encut": 520, + "gamma": True, + "ismear": -5, + "ispin": 2, + "kpts": [6, 13, 13], + "lasph": True, + "lcharg": True, # modified by us (sensible) + "ldau": True, + "ldauj": [0, 0], + "ldaul": [0, 2], + "ldauprint": 1, # added by us (sensible) + "ldautype": 2, # added by us (sensible) + "ldauu": [0, 6.2], + "lmaxmix": 4, + "lorbit": 11, + "lreal": False, + "lwave": True, # modified by us (sensible) + "magmom": [0.6, 0.6], + "nelm": 100, + "nsw": 0, + "prec": "accurate", + "sigma": 0.05, + "pp": "pbe", + "setups": {"Ni": "_pv", "O": ""}, + }