diff --git a/src/pymatgen/io/vasp/MP24RelaxSet.yaml b/src/pymatgen/io/vasp/MP24RelaxSet.yaml new file mode 100644 index 00000000000..72de8bef925 --- /dev/null +++ b/src/pymatgen/io/vasp/MP24RelaxSet.yaml @@ -0,0 +1,31 @@ +# Default VASP settings for calculations in the Materials Project +# using the Regularized-Restored Strongly Constrained and Appropriately +# Normed functional (r2SCAN). +PARENT: PBE64Base +INCAR: + ALGO: Normal + EDIFF: 1.e-05 + EDIFFG: -0.02 + ENAUG: 1360 + ENCUT: 680 + GGA_COMPAT: False + IBRION: 2 + ISIF: 3 + ISMEAR: 0 # included to have some reasonable default + ISPIN: 2 + KSPACING: 0.22 # included to have some reasonable default + LAECHG: True + LASPH: True + LCHARG: True + LELF: False # LELF = True restricts calculation to KPAR = 1 + LMAXMIX: 6 # per benchmark + LMIXTAU: True + LORBIT: 11 + LREAL: False # per benchmark + LVTOT: True + LWAVE: False + METAGGA: R2SCAN + NELM: 200 + NSW: 99 + PREC: Accurate + SIGMA: 0.05 # included to have some reasonable default diff --git a/src/pymatgen/io/vasp/PBE64Base.yaml b/src/pymatgen/io/vasp/PBE64Base.yaml index af0236253ed..358944a2b26 100644 --- a/src/pymatgen/io/vasp/PBE64Base.yaml +++ b/src/pymatgen/io/vasp/PBE64Base.yaml @@ -11,7 +11,7 @@ POTCAR: At: At Au: Au B: B - Ba: Ba_sv + Ba: Ba_sv_GW Be: Be_sv Bi: Bi Br: Br @@ -26,8 +26,8 @@ POTCAR: Cr: Cr_pv Cs: Cs_sv Cu: Cu_pv - Dy: Dy_3 - Er: Er_3 + Dy: Dy_h + Er: Er_h Eu: Eu F: F Fe: Fe_pv @@ -39,7 +39,7 @@ POTCAR: He: He Hf: Hf_pv Hg: Hg - Ho: Ho_3 + Ho: Ho_h I: I In: In_d Ir: Ir @@ -54,7 +54,7 @@ POTCAR: N: N Na: Na_pv Nb: Nb_pv - Nd: Nd_3 + Nd: Nd_h Ne: Ne Ni: Ni_pv Np: Np @@ -64,9 +64,9 @@ POTCAR: Pa: Pa Pb: Pb_d Pd: Pd - Pm: Pm_3 + Pm: Pm_h Po: Po_d - Pr: Pr_3 + Pr: Pr_h Pt: Pt Pu: Pu Ra: Ra_sv @@ -80,22 +80,22 @@ POTCAR: Sc: Sc_sv Se: Se Si: Si - Sm: Sm_3 + Sm: Sm_h Sn: Sn_d Sr: Sr_sv Ta: Ta_pv - Tb: Tb_3 + Tb: Tb_h Tc: Tc_pv Te: Te Th: Th Ti: Ti_pv Tl: Tl_d - Tm: Tm_3 + Tm: Tm_h U: U V: V_pv W: W_sv - Xe: Xe + Xe: Xe_GW Y: Y_sv - Yb: Yb_3 + Yb: Yb_h Zn: Zn Zr: Zr_sv diff --git a/src/pymatgen/io/vasp/sets.py b/src/pymatgen/io/vasp/sets.py index 11e5e555816..d23a861ca38 100644 --- a/src/pymatgen/io/vasp/sets.py +++ b/src/pymatgen/io/vasp/sets.py @@ -84,7 +84,10 @@ def _load_yaml_config(fname): - config = loadfn(f"{MODULE_DIR}/{fname}.yaml") + fname = f"{MODULE_DIR}/{fname}" + if not fname.endswith(".yaml"): + fname += ".yaml" + config = loadfn(fname) if "PARENT" in config: parent_config = _load_yaml_config(config["PARENT"]) for k, v in parent_config.items(): @@ -623,7 +626,11 @@ def incar(self) -> Incar: elif key == "KSPACING" and self.auto_kspacing: # Default to metal if no prev calc available bandgap = 0 if self.bandgap is None else self.bandgap - incar[key] = auto_kspacing(bandgap, self.bandgap_tol) + if new_kspacing := getattr(self, "kspacing_update", None): + # allow custom KSPACING update + incar[key] = new_kspacing + else: + incar[key] = auto_kspacing(bandgap, self.bandgap_tol) else: incar[key] = setting @@ -1294,7 +1301,7 @@ class MPRelaxSet(VaspInputSet): @due.dcite( Doi("10.1021/acs.jpclett.0c02405"), - description="AccurAccurate and Numerically Efficient r2SCAN Meta-Generalized Gradient Approximation", + description="Accurate and Numerically Efficient r2SCAN Meta-Generalized Gradient Approximation", ) @due.dcite( Doi("10.1103/PhysRevLett.115.036402"), @@ -1352,7 +1359,7 @@ class MPScanRelaxSet(VaspInputSet): James W. Furness, Aaron D. Kaplan, Jinliang Ning, John P. Perdew, and Jianwei Sun. Accurate and Numerically Efficient r2SCAN Meta-Generalized Gradient Approximation. - The Journal of Physical Chemistry Letters 0, 11 DOI: 10.1021/acs.jpclett.0c02405 + The Journal of Physical Chemistry Letters 11, 8208-8215 (2022) DOI: 10.1021/acs.jpclett.0c02405 """ bandgap: float | None = None @@ -1375,6 +1382,149 @@ def __post_init__(self) -> None: self._config_dict["INCAR"].pop(k, None) +@dataclass +class MP24RelaxSet(VaspInputSet): + """ + Materials Project relax set after a 2023-2024 benchmarking effort. + + By default, this uses r2SCAN as the xc functional. + For discussion around this benchmarking effort, see: + https://github.com/materialsproject/foundation/pull/26 + + Citation info: + - r2SCAN: + James W. Furness, Aaron D. Kaplan, Jinliang Ning, John P. Perdew, and Jianwei Sun. + Accurate and Numerically Efficient r2SCAN Meta-Generalized Gradient Approximation. + J. Phys. Chem. Lett. 11, 8208-8215 (2022) DOI: 10.1021/acs.jpclett.0c02405 + - r2SCAN-rVV10: + Jinliang Ning, Manish Kothakonda, James W. Furness, Aaron D. Kaplan, Sebastian Ehlert, + Jan Gerit Brandenburg, John P. Perdew, and Jianwei Sun. + Workhorse minimally empirical dispersion-corrected density functional with tests for + weakly bound systems: r2SCAN+rVV10. + Phys. Rev. B 106, 075422 (2022) DOI: 10.1103/PhysRevB.106.075422 + - r2SCAN-D4: + Sebastian Ehlert, Uwe Huniar, Jinliang Ning, James W. Furness, Jianwei Sun, + Aaron D. Kaplan, John P. Perdew, and Jan Gerit Brandenburg. + r2SCAN-D4: Dispersion corrected meta-generalized gradient approximation for general chemical applications. + J. Chem. Phys. 154, 061101 (2021) DOI: 10.1063/5.0041008 + - PBE: + John P. Perdew, Kieron Burke, and Matthias Ernzerhof, + Generalized Gradient Approximation Made Simple, + Phys. Rev. Lett. 77, 3865 (1996) DOI: 10.1103/PhysRevLett.77.3865 + - PBEsol: + John P. Perdew, Adrienn Ruzsinszky, Gábor I. Csonka, Oleg A. Vydrov, + Gustavo E. Scuseria, Lucian A. Constantin, Xiaolan Zhou, and Kieron Burke. + Restoring the Density-Gradient Expansion for Exchange in Solids and Surfaces. + Phys. Rev. Lett. 100, 136406 (2009) DOI: 10.1103/PhysRevLett.100.136406 + - PBE-D4 and PBEsol-D4: + Eike Caldeweyher,Jan-Michael Mewes, Sebastian Ehlert, and Stefan Grimme. + Extension and evaluation of the D4 London-dispersion model for periodic systems. + Phys. Chem. Chem. Phys. 22, 8499-8512 (2020) DOI: 10.1039/D0CP00502A + """ + + xc_functional: Literal["r2SCAN", "PBE", "PBEsol"] = "r2SCAN" + dispersion: Literal["rVV10", "D4"] | None = None + CONFIG = _load_yaml_config("MP24RelaxSet") + auto_ismear: bool = False + auto_kspacing: bool = True + inherit_incar: bool = False + + def __post_init__(self) -> None: + super().__post_init__() + + to_func = { + "r2SCAN": "R2SCAN", + "PBE": "PE", + "PBEsol": "PS", + } + + config_updates: dict[str, Any] = {} + if self.xc_functional == "r2SCAN": + config_updates |= {"METAGGA": to_func[self.xc_functional]} + self._config_dict["INCAR"].pop("GGA", None) + elif self.xc_functional in ["PBE", "PBEsol"]: + config_updates |= {"GGA": to_func[self.xc_functional]} + self._config_dict["INCAR"].pop("METAGGA", None) + else: + raise ValueError(f"Unknown XC functional {self.xc_functional}!") + + if self.dispersion == "rVV10": + if self.xc_functional == "r2SCAN": + config_updates |= {"BPARAM": 11.95, "CPARAM": 0.0093, "LUSE_VDW": True} + else: + raise ValueError( + "Use of rVV10 with functionals other than r2 / SCAN is not currently supported in VASP." + ) + + elif self.dispersion == "D4": + d4_pars = { + "r2SCAN": { + "S6": 1.0, + "S8": 0.60187490, + "A1": 0.51559235, + "A2": 5.77342911, + }, + "PBE": { + "S6": 1.0, + "S8": 0.95948085, + "A1": 0.38574991, + "A2": 4.80688534, + }, + "PBEsol": { + "S6": 1.0, + "S8": 1.71885698, + "A1": 0.47901421, + "A2": 5.96771589, + }, + } + config_updates |= {"IVDW": 13, **{f"VDW_{k}": v for k, v in d4_pars[self.xc_functional].items()}} + + if len(config_updates) > 0: + self._config_dict["INCAR"].update(config_updates) + + @staticmethod + def _sigmoid_interp( + bg, min_dk: float = 0.22, max_dk: float = 0.5, shape: float = 0.43, center: float = 4.15, fac: float = 12.0 + ): + delta = shape * (bg - center) + sigmoid = delta / (1.0 + delta**fac) ** (1.0 / fac) + return 0.5 * (min_dk + max_dk + (max_dk - min_dk) * sigmoid) + + def _multi_sigmoid_interp( + self, + bandgap: float, + dks: tuple[float, ...] = (0.22, 0.44, 0.5), + shape: tuple[float, ...] = (1.1, 2.5), + center: tuple[float, ...] = (2.35, 6.1), + fac: tuple[float, ...] = (8, 8), + bg_cut: tuple[float, ...] = (4.5,), + ): + if bandgap < self.bandgap_tol: + return dks[0] + + min_bds = [self.bandgap_tol, *bg_cut] + max_bds = [*bg_cut, np.inf] + + for icut, min_bd in enumerate(min_bds): + if min_bd <= bandgap < max_bds[icut]: + return self._sigmoid_interp( + bandgap, + min_dk=dks[icut], + max_dk=dks[icut + 1], + shape=shape[icut], + center=center[icut], + fac=fac[icut], + ) + + return None + + @property + def kspacing_update(self): + if self.bandgap is None: + return 0.22 + return self._multi_sigmoid_interp(self.bandgap) + + @dataclass class MPMetalRelaxSet(VaspInputSet): """ @@ -1571,7 +1721,8 @@ def __post_init__(self) -> None: ) if self.xc_functional.upper() == "R2SCAN": - self._config_dict["INCAR"].update({"METAGGA": "R2SCAN", "ALGO": "ALL", "GGA": None}) + self._config_dict["INCAR"].update({"METAGGA": "R2SCAN", "ALGO": "ALL"}) + self._config_dict["INCAR"].pop("GGA", None) if self.xc_functional.upper().endswith("+U"): self._config_dict["INCAR"]["LDAU"] = True @@ -1601,6 +1752,7 @@ class MPScanStaticSet(MPScanRelaxSet): def incar_updates(self) -> dict[str, Any]: """Updates to the INCAR config for this calculation type.""" updates: dict[str, Any] = { + "ALGO": "Fast", "LREAL": False, "NSW": 0, "LORBIT": 11, @@ -1626,6 +1778,49 @@ def incar_updates(self) -> dict[str, Any]: return updates +@dataclass +class MP24StaticSet(MP24RelaxSet): + """Create input files for a static calculation using MP24 parameters. + + For class information, refer to `MP24RelaxSet`. + If you use this set, please consider citing the appropriate papers in `MP24RelaxSet`. + + Args: + structure (Structure): Structure from previous run. + bandgap (float): Bandgap of the structure in eV. The bandgap is used to + compute the appropriate k-point density and determine the smearing settings. + lepsilon (bool): Whether to add static dielectric calculation + lcalcpol (bool): Whether to turn on evaluation of the Berry phase approximations + for electronic polarization. + **kwargs: Keywords supported by MP24RelaxSet. + """ + + lepsilon: bool = False + lcalcpol: bool = False + inherit_incar: bool = False + auto_kspacing: bool = True + + @property + def incar_updates(self) -> dict[str, Any]: + """Updates to the INCAR config for this calculation type.""" + updates: dict[str, Any] = { + "NSW": 0, + "LORBIT": 11, + "ISMEAR": -5, + } + + if self.lepsilon: + # LPEAD=T: numerical evaluation of overlap integral prevents + # LRF_COMMUTATOR errors and can lead to better expt. agreement + # but produces slightly different results + updates |= {"IBRION": 8, "LEPSILON": True, "LPEAD": True, "NSW": 1, "NPAR": None} + + if self.lcalcpol: + updates["LCALCPOL"] = True + + return updates + + @dataclass class MPHSEBSSet(VaspInputSet): """Implementation of a VaspInputSet for HSE band structure computations. diff --git a/tests/io/vasp/test_sets.py b/tests/io/vasp/test_sets.py index 4509e1b95f5..cef111d7d7e 100644 --- a/tests/io/vasp/test_sets.py +++ b/tests/io/vasp/test_sets.py @@ -29,6 +29,8 @@ MITMDSet, MITNEBSet, MITRelaxSet, + MP24RelaxSet, + MP24StaticSet, MPAbsorptionSet, MPHSEBSSet, MPHSERelaxSet, @@ -116,13 +118,14 @@ def test_sets_changed(self): "MPHSERelaxSet.yaml": "1779cb6a6af43ad54a12aec22882b9b8aa3469b764e29ac4ab486960d067b811", "VASPIncarBase.yaml": "8c1ce90d6697e45b650e1881e2b3d82a733dba17fb1bd73747a38261ec65a4c4", "MPSCANRelaxSet.yaml": "ad652ea740d06f9edd979494f31e25074b82b9fffdaaf7eff2ae5541fb0e6288", - "PBE64Base.yaml": "3434c918c17706feae397d0852f2224e771db94d7e4c988039e8658e66d87494", + "PBE64Base.yaml": "40e7e42159f59543b17f512666916001045f7644f422ccc45b8466d6a1cf0c48", "MPRelaxSet.yaml": "c9b0a519588fb3709509a9f9964632692584905e2961a0fe2e5f657561913083", "MITRelaxSet.yaml": "0b4bec619fa860dac648584853c3b3d5407e4148a85d0e95024fbd1dc315669d", "vdW_parameters.yaml": "7d2599a855533865335a313c043b6f89e03fc2633c88b6bc721723d94cc862bd", "MatPESStaticSet.yaml": "4ec60ad4bbbb9a756f1b3fea8ca4eab8fc767d8f6a67332e7af3908c910fd7c5", "MPAbsorptionSet.yaml": "e49cd0ab87864f1c244e9b5ceb4703243116ec1fbb8958a374ddff07f7a5625c", "PBE54Base.yaml": "cdffe123eca8b19354554b60a7f8de9b8776caac9e1da2bd2a0516b7bfac8634", + "MP24RelaxSet.yaml": "35a5d4456f01d644cf41218725c5e0896c59e1658045ecd1544579cbb1ed7b85", } for input_set, hash_str in hashes.items(): @@ -2285,3 +2288,98 @@ def test_dict_set_alias(): ): DictSet() assert isinstance(DictSet(), VaspInputSet) + + +class TestMP24Sets(PymatgenTest): + def setUp(self): + self.relax_set = MP24RelaxSet + self.static_set = MP24StaticSet + + filepath = f"{VASP_IN_DIR}/POSCAR" + self.structure = Structure.from_file(filepath) + + @staticmethod + def matches_ref(test_val, ref_val) -> bool: + if isinstance(ref_val, float): + return test_val == approx(ref_val) + return test_val == ref_val + + def test_kspacing(self): + bandgaps = [0.0, 0.1, 0.5, 1.0, 2.0, 3, 5, 10, 1e4] + expected_kspacing = [ + 0.22, + 0.22000976174867864, + 0.2200466614246148, + 0.22056799311325073, + 0.2876525546497567, + 0.40800309817134106, + 0.44000114629141485, + 0.4999999999540808, + 0.5, + ] + for i, bandgap in enumerate(bandgaps): + assert self.relax_set()._multi_sigmoid_interp(bandgap) == approx(expected_kspacing[i]) + + def test_default(self): + vis = self.relax_set(structure=self.structure) + expected_incar_relax = { + "ALGO": "Normal", + "EDIFF": 1.0e-05, + "EDIFFG": -0.02, + "ENAUG": 1360, + "ENCUT": 680, + "GGA_COMPAT": False, + "KSPACING": 0.22, + "ISMEAR": 0, + "SIGMA": 0.05, + "METAGGA": "R2scan", + "LMAXMIX": 6, + "LREAL": False, + } + + assert all(self.matches_ref(vis.incar[k], v) for k, v in expected_incar_relax.items()) + + assert self.relax_set(self.structure, xc_functional="r2SCAN")._config_dict == vis._config_dict + assert vis.inherit_incar is False + assert vis.dispersion is None + assert vis.potcar_functional == "PBE_64" + assert vis.potcar_symbols == ["Fe_pv", "P", "O"] + assert vis.kpoints is None + + vis = self.static_set(structure=self.structure, dispersion="rVV10") + + expected_incar_static = { + "ISMEAR": -5, + "NSW": 0, + "LORBIT": 11, + "METAGGA": "R2scan", + "LUSE_VDW": True, + "BPARAM": 11.95, + "CPARAM": 0.0093, + } + + assert all(self.matches_ref(vis.incar[k], v) for k, v in expected_incar_static.items()) + assert ( + self.static_set(self.structure, xc_functional="r2SCAN", dispersion="rVV10")._config_dict == vis._config_dict + ) + + def test_non_default_xc_func(self): + for xc_functional, vasp_name in {"PBE": "Pe", "PBEsol": "Ps"}.items(): + vis = self.relax_set(structure=self.structure, xc_functional=xc_functional) + assert vis.incar.get("METAGGA") is None + assert vis.incar["GGA"] == vasp_name + + vis = self.static_set(structure=self.structure, xc_functional=xc_functional, dispersion="D4") + assert vis.incar.get("METAGGA") is None + print(self.relax_set(structure=self.structure, xc_functional=xc_functional).incar) + assert vis.incar["GGA"] == vasp_name + assert all( + isinstance(vis.incar[k], float | int) + for k in ( + "IVDW", + "VDW_A1", + "VDW_A2", + "VDW_S6", + "VDW_S8", + ) + )