@@ -795,6 +795,10 @@ class Incar(UserDict, MSONable):
795795 in the `lower_str_keys/as_is_str_keys` of the `proc_val` method.
796796 """
797797
798+ # INCAR tag/value recording
799+ with open (os .path .join (MODULE_DIR , "incar_parameters.json" ), encoding = "utf-8" ) as json_file :
800+ INCAR_PARAMS : ClassVar [dict [Literal ["type" , "values" ], Any ]] = orjson .loads (json_file .read ())
801+
798802 def __init__ (self , params : Mapping [str , Any ] | None = None ) -> None :
799803 """
800804 Clean up params and create an Incar object.
@@ -970,88 +974,46 @@ def from_str(cls, string: str) -> Self:
970974 params [key ] = cls .proc_val (key , val )
971975 return cls (params )
972976
973- @staticmethod
974- def proc_val (key : str , val : str ) -> list | bool | float | int | str :
977+ @classmethod
978+ def proc_val (cls , key : str , val : str ) -> list | bool | float | int | str :
975979 """Helper method to convert INCAR parameters to proper types
976980 like ints, floats, lists, etc.
977981
978982 Args:
979983 key (str): INCAR parameter key.
980984 val (str): Value of INCAR parameter.
981985 """
982- list_keys = (
983- "LDAUU" ,
984- "LDAUL" ,
985- "LDAUJ" ,
986- "MAGMOM" ,
987- "DIPOL" ,
988- "LANGEVIN_GAMMA" ,
989- "QUAD_EFG" ,
990- "EINT" ,
991- "LATTICE_CONSTRAINTS" ,
992- )
993- bool_keys = (
994- "LDAU" ,
995- "LWAVE" ,
996- "LSCALU" ,
997- "LCHARG" ,
998- "LPLANE" ,
999- "LUSE_VDW" ,
1000- "LHFCALC" ,
1001- "ADDGRID" ,
1002- "LSORBIT" ,
1003- "LNONCOLLINEAR" ,
1004- )
1005- float_keys = (
1006- "EDIFF" ,
1007- "SIGMA" ,
1008- "TIME" ,
1009- "ENCUTFOCK" ,
1010- "HFSCREEN" ,
1011- "POTIM" ,
1012- "EDIFFG" ,
1013- "AGGAC" ,
1014- "PARAM1" ,
1015- "PARAM2" ,
1016- "ENCUT" ,
1017- "NUPDOWN" ,
1018- )
1019- int_keys = (
1020- "NSW" ,
1021- "NBANDS" ,
1022- "NELMIN" ,
1023- "ISIF" ,
1024- "IBRION" ,
1025- "ISPIN" ,
1026- "ISTART" ,
1027- "ICHARG" ,
1028- "NELM" ,
1029- "ISMEAR" ,
1030- "NPAR" ,
1031- "LDAUPRINT" ,
1032- "LMAXMIX" ,
1033- "NSIM" ,
1034- "NKRED" ,
1035- "ISPIND" ,
1036- "LDAUTYPE" ,
1037- "IVDW" ,
1038- )
986+ # Handle union type like "bool | str" for LREAL
987+ if incar_type := cls .INCAR_PARAMS .get (key , {}).get ("type" ):
988+ incar_types : list [str ] = [t .strip () for t in incar_type .split ("|" )]
989+ else :
990+ incar_types = []
991+
992+ # Special cases
993+ # Always lower case
1039994 lower_str_keys = ("ML_MODE" ,)
1040995 # String keywords to read "as is" (no case transformation, only stripped)
1041996 as_is_str_keys = ("SYSTEM" ,)
1042997
1043- def smart_int_or_float_bool (str_ : str ) -> float | int | bool :
1044- """Determine whether a string represents an integer or a float."""
1045- if str_ .lower ().startswith (".t" ) or str_ .lower ().startswith ("t" ):
1046- return True
1047- if str_ .lower ().startswith (".f" ) or str_ .lower ().startswith ("f" ):
1048- return False
1049- if "." in str_ or "e" in str_ .lower ():
1050- return float (str_ )
1051- return int (str_ )
1052-
1053998 try :
1054- if key in list_keys :
999+ if key in lower_str_keys :
1000+ return val .strip ().lower ()
1001+
1002+ if key in as_is_str_keys :
1003+ return val .strip ()
1004+
1005+ if "list" in incar_types :
1006+
1007+ def smart_int_or_float_bool (str_ : str ) -> float | int | bool :
1008+ """Determine whether a string represents an integer or a float."""
1009+ if str_ .lower ().startswith (".t" ) or str_ .lower ().startswith ("t" ):
1010+ return True
1011+ if str_ .lower ().startswith (".f" ) or str_ .lower ().startswith ("f" ):
1012+ return False
1013+ if "." in str_ or "e" in str_ .lower ():
1014+ return float (str_ )
1015+ return int (str_ )
1016+
10551017 output = []
10561018 tokens = re .findall (r"(-?\d+\.?\d*|[\.A-Z]+)\*?(-?\d+\.?\d*|[\.A-Z]+)?\*?(-?\d+\.?\d*|[\.A-Z]+)?" , val )
10571019 for tok in tokens :
@@ -1061,26 +1023,22 @@ def smart_int_or_float_bool(str_: str) -> float | int | bool:
10611023 output .extend ([smart_int_or_float_bool (tok [1 ])] * int (tok [0 ]))
10621024 else :
10631025 output .append (smart_int_or_float_bool (tok [0 ]))
1064- return output
10651026
1066- if key in bool_keys :
1027+ if output : # pass when fail to parse (val is not list)
1028+ return output
1029+
1030+ if "bool" in incar_types :
10671031 if match := re .match (r"^\.?([T|F|t|f])[A-Za-z]*\.?" , val ):
10681032 return match [1 ].lower () == "t"
10691033
10701034 raise ValueError (f"{ key } should be a boolean type!" )
10711035
1072- if key in float_keys :
1036+ if "float" in incar_types :
10731037 return float (re .search (r"^-?\d*\.?\d*[e|E]?-?\d*" , val )[0 ]) # type: ignore[index]
10741038
1075- if key in int_keys :
1039+ if "int" in incar_types :
10761040 return int (re .match (r"^-?[0-9]+" , val )[0 ]) # type: ignore[index]
10771041
1078- if key in lower_str_keys :
1079- return val .strip ().lower ()
1080-
1081- if key in as_is_str_keys :
1082- return val .strip ()
1083-
10841042 except ValueError :
10851043 pass
10861044
@@ -1138,13 +1096,9 @@ def check_params(self) -> None:
11381096 If a tag doesn't exist, calculation will still run, however VASP
11391097 will ignore the tag and set it as default without letting you know.
11401098 """
1141- # Load INCAR tag/value check reference file
1142- with open (os .path .join (MODULE_DIR , "incar_parameters.json" ), encoding = "utf-8" ) as json_file :
1143- incar_params = orjson .loads (json_file .read ())
1144-
11451099 for tag , val in self .items ():
11461100 # Check if the tag exists
1147- if tag not in incar_params :
1101+ if tag not in self . INCAR_PARAMS :
11481102 warnings .warn (
11491103 f"Cannot find { tag } in the list of INCAR tags" ,
11501104 BadIncarWarning ,
@@ -1153,8 +1107,8 @@ def check_params(self) -> None:
11531107 continue
11541108
11551109 # Check value type
1156- param_type : str = incar_params [tag ].get ("type" )
1157- allowed_values : list [Any ] = incar_params [tag ].get ("values" )
1110+ param_type : str = self . INCAR_PARAMS [tag ].get ("type" )
1111+ allowed_values : list [Any ] = self . INCAR_PARAMS [tag ].get ("values" )
11581112
11591113 if param_type is not None and not isinstance (val , eval (param_type )): # noqa: S307
11601114 warnings .warn (f"{ tag } : { val } is not a { param_type } " , BadIncarWarning , stacklevel = 2 )
0 commit comments