diff --git a/src/pymatgen/io/vasp/incar_parameters.json b/src/pymatgen/io/vasp/incar_parameters.json index 9cf0319c2c5..a7a8ffc6122 100644 --- a/src/pymatgen/io/vasp/incar_parameters.json +++ b/src/pymatgen/io/vasp/incar_parameters.json @@ -191,24 +191,35 @@ "GGA": { "type": "str", "values": [ - "91", - "PE", - "AM", - "HL", "CA", - "MK", - "RE", - "VW", - "B3", "PZ", + "SL", + "CA_C", + "PZ_C", + "VW", + "HL", "WI", + "LIBXC", + "LI", + "91", + "PE", + "PBE_X", + "PBE_C", + "RE", "RP", - "B5", - "BF", - "CO", "PS", + "AM", + "B3", + "B5", "OR", "BO", + "MK", + "ML", + "CX", + "BF", + "RP", + "CO", + "RE", "03", "05", "10", @@ -970,6 +981,17 @@ "ML_FF_WTSIF": { "type": "float" }, + "ML_MODE": { + "type": "str", + "values": [ + "train", + "select", + "refit", + "refitbayesian", + "run", + "none" + ] + }, "NBANDS": { "type": "int" }, @@ -1444,5 +1466,8 @@ }, "MAGDIPOLOUT": { "type": "bool" + }, + "ZAB_VDW": { + "type": "float" } } diff --git a/src/pymatgen/io/vasp/inputs.py b/src/pymatgen/io/vasp/inputs.py index 5066cbdc333..91eab02b099 100644 --- a/src/pymatgen/io/vasp/inputs.py +++ b/src/pymatgen/io/vasp/inputs.py @@ -723,7 +723,7 @@ class Incar(UserDict, MSONable): - Keys are stored in uppercase to allow case-insensitive access (set, get, del, update, setdefault). - String values are capitalized by default, except for keys specified - in the `lower_str_keys` of the `proc_val` method. + in the `lower_str_keys/as_is_str_keys` of the `proc_val` method. """ def __init__(self, params: dict[str, Any] | None = None) -> None: @@ -971,6 +971,8 @@ def proc_val(key: str, val: str) -> list | bool | float | int | str: "IVDW", ) lower_str_keys = ("ML_MODE",) + # String keywords to read "as is" (no case transformation, only stripped) + as_is_str_keys = ("SYSTEM",) def smart_int_or_float(num_str: str) -> float: """Determine whether a string represents an integer or a float.""" @@ -1006,6 +1008,9 @@ def smart_int_or_float(num_str: str) -> float: if key in lower_str_keys: return val.strip().lower() + if key in as_is_str_keys: + return val.strip() + except ValueError: pass @@ -1087,9 +1092,9 @@ def check_params(self) -> None: # Only check value when it's not None, # meaning there is recording for corresponding value if allowed_values is not None: - # Note: param_type could be a Union type, e.g. "str | bool" - if "str" in param_type: - allowed_values = [item.capitalize() if isinstance(item, str) else item for item in allowed_values] + allowed_values = [ + self.proc_val(tag, item) if isinstance(item, str) else item for item in allowed_values + ] if val not in allowed_values: warnings.warn( diff --git a/src/pymatgen/io/vasp/outputs.py b/src/pymatgen/io/vasp/outputs.py index 14f5cba7b15..9ab6c51dd43 100644 --- a/src/pymatgen/io/vasp/outputs.py +++ b/src/pymatgen/io/vasp/outputs.py @@ -474,7 +474,7 @@ def _parse( elif tag == "dielectricfunction": label = elem.attrib.get("comment", None) if label is None: - if self.incar.get("ALGO", "Normal").upper() == "BSE": + if self.incar.get("ALGO", "Normal") == "Bse": label = "freq_dependent" elif "density" not in self.dielectric_data: label = "density" @@ -634,7 +634,7 @@ def converged_electronic(self) -> bool: while set(final_elec_steps[idx]) == to_check: idx += 1 return idx + 1 != self.parameters["NELM"] - if self.incar.get("ALGO", "").upper() == "EXACT" and self.incar.get("NELM") == 1: + if self.incar.get("ALGO", "") == "Exact" and self.incar.get("NELM") == 1: return True return len(final_elec_steps) < self.parameters["NELM"] @@ -793,10 +793,10 @@ def run_type(self) -> str: "--", "None", }: - incar_tag = self.incar.get("METAGGA", "").strip().upper() + incar_tag = self.incar.get("METAGGA", "").upper() run_type = METAGGA_TYPES.get(incar_tag, incar_tag) elif self.parameters.get("GGA"): - incar_tag = self.parameters.get("GGA", "").strip().upper() + incar_tag = self.parameters.get("GGA", "").upper() run_type = GGA_TYPES.get(incar_tag, incar_tag) elif self.potcar_symbols[0].split()[0] == "PAW": run_type = "LDA" diff --git a/tests/io/vasp/test_inputs.py b/tests/io/vasp/test_inputs.py index e02d5985518..3b415eda9fe 100644 --- a/tests/io/vasp/test_inputs.py +++ b/tests/io/vasp/test_inputs.py @@ -615,6 +615,14 @@ def test_key_case_insensitive(self): incar.setdefault("NPAR", 4) assert incar["npar"] == 4 + # Test pop + assert incar.pop(test_tag.lower()) == 520 + assert test_tag.upper() not in incar + assert test_tag.lower() not in incar + + # Test pop with default value + assert incar.pop("missing_key", 42) == 42 + def test_copy(self): incar2 = self.incar.copy() assert isinstance(incar2, Incar), f"Expected Incar, got {type(incar2).__name__}" @@ -678,9 +686,11 @@ def test_diff(self): "ISMEAR": {"INCAR1": 0, "INCAR2": -5}, "NPAR": {"INCAR1": 8, "INCAR2": 1}, "SYSTEM": { - "INCAR1": "Id=[0] dblock_code=[97763-icsd] formula=[li mn (p o4)] sg_name=[p n m a]", - "INCAR2": "Id=[91090] dblock_code=[20070929235612linio-59.53134651-vasp] formula=[li3 ni3 o6] " - "sg_name=[r-3m]", + "INCAR1": "id=[0] dblock_code=[97763-ICSD] formula=[Li Mn (P O4)] sg_name=[P n m a]", + "INCAR2": ( + "id=[91090] dblock_code=[20070929235612LiNiO-59.53134651-VASP] " + "formula=[Li3 Ni3 O6] sg_name=[R-3m]" + ), }, "ALGO": {"INCAR1": "Damped", "INCAR2": "Fast"}, "LHFCALC": {"INCAR1": True, "INCAR2": None}, @@ -715,7 +725,7 @@ def test_from_file_and_from_dict(self): float/int, and might yield values in wrong type. """ # Init from dict - incar_dict = {"ENCUT": 500, "GGA": "PS", "NELM": 60.0} + incar_dict = {"ENCUT": 500, "GGA": "PS", "NELM": 60.0, "SYSTEM": "This should NOT BE capitalized"} incar_from_dict = Incar(incar_dict) # Init from file (from string) @@ -723,6 +733,7 @@ def test_from_file_and_from_dict(self): ENCUT = 500 GGA = PS NELM = 60.0 + SYSTEM = This should NOT BE capitalized """ with ScratchDir("."): @@ -739,6 +750,7 @@ def test_from_file_and_from_dict(self): assert incar_from_dict == incar_from_file for key in incar_from_dict: assert type(incar_from_dict[key]) is type(incar_from_file[key]) + assert incar_from_file["SYSTEM"] == "This should NOT BE capitalized" def test_write(self): tmp_file = f"{self.tmp_path}/INCAR.testing" @@ -774,7 +786,7 @@ def test_get_str(self): NUPDOWN = 0 PREC = Accurate SIGMA = 0.05 -SYSTEM = Id=[0] dblock_code=[97763-icsd] formula=[li mn (p o4)] sg_name=[p n m a] +SYSTEM = id=[0] dblock_code=[97763-ICSD] formula=[Li Mn (P O4)] sg_name=[P n m a] TIME = 0.4""" assert incar_str == expected @@ -876,13 +888,15 @@ def test_check_params(self): "AMIN": 0.01, "ICHARG": 1, "MAGMOM": [1, 2, 4, 5], + "ML_MODE": "RUN", # lower case string + "SYSTEM": "Hello world", # as is string "ENCUT": 500, # make sure float key is casted "GGA": "PS", # test string case insensitivity "LREAL": True, # special case: Union type "NBAND": 250, # typo in tag "METAGGA": "SCAM", # typo in value - "EDIFF": 5 + 1j, # value should be a float - "ISIF": 9, # value out of range + "EDIFF": 5 + 1j, # value should be float + "ISIF": 9, # value not unknown "LASPH": 5, # value should be bool "PHON_TLIST": "is_a_str", # value should be a list }