@@ -229,8 +229,14 @@ class VaspFiles(BaseModel):
229229 """Define required and optional files for validation."""
230230
231231 user_input : VaspInputSafe = Field (description = "The VASP input set used in the calculation." )
232- outcar : Optional [LightOutcar ] = None
233- vasprun : Optional [LightVasprun ] = None
232+ outcar : LightOutcar | None = None
233+ vasprun : LightVasprun | None = None
234+ run_type : str | None = Field (None , description = "The type of VASP calculation performed." )
235+ functional : str | None = Field (None , description = "The density functional used in the calculation." )
236+ valid_input_set_name : str | None = Field (
237+ None , description = "The import string of the reference MP-compatible input set."
238+ )
239+ validation_errors : list [str ] = Field ([], description = "Errors arising when attempting to validate the input set." )
234240
235241 @model_validator (mode = "before" )
236242 @classmethod
@@ -246,6 +252,17 @@ def coerce_to_lightweight(cls, config: Any) -> Any:
246252 config ["vasprun" ] = LightVasprun .from_vasprun (config ["vasprun" ])
247253 return config
248254
255+ @model_validator (mode = "after" )
256+ def validate_inputs (self ) -> Self :
257+ """Check that the input set could be referenced against known input sets."""
258+ self .validation_errors = []
259+ self .run_type = self .set_run_type ()
260+ self .functional = self .set_functional ()
261+ self .valid_input_set_name = None # Ensure this gets reset / checked
262+ if self .run_type and self .functional :
263+ self .valid_input_set_name = self .set_valid_input_set_name ()
264+ return self
265+
249266 @property
250267 def md5 (self ) -> str :
251268 """Get MD5 of VaspFiles for use in validation checks."""
@@ -308,8 +325,7 @@ def from_paths(
308325
309326 return cls (** config )
310327
311- @cached_property
312- def run_type (self ) -> str :
328+ def set_run_type (self ) -> str | None :
313329 """Get the run type of a calculation."""
314330
315331 ibrion = self .user_input .incar .get ("IBRION" , VASP_DEFAULTS_DICT ["IBRION" ].value )
@@ -322,30 +338,28 @@ def run_type(self) -> str:
322338 ** {k : "relax" for k in range (1 , 4 )},
323339 ** {k : "phonon" for k in range (5 , 9 )},
324340 ** {k : "ts" for k in (40 , 44 )},
325- }.get (ibrion )
341+ }.get (ibrion , None )
326342
327343 if self .user_input .incar .get ("ICHARG" , VASP_DEFAULTS_DICT ["ICHARG" ].value ) >= 10 :
328344 run_type = "nonscf"
329345 if self .user_input .incar .get ("LCHIMAG" , VASP_DEFAULTS_DICT ["LCHIMAG" ].value ):
330346 run_type == "nmr"
331347
332348 if run_type is None :
333- raise ValidationError (
349+ self . validation_errors += [
334350 "Could not determine a valid run type. We currently only validate "
335351 "Geometry optimizations (relaxations), single-points (statics), "
336352 "and non-self-consistent fixed charged density calculations. " ,
337- )
353+ ]
338354
339355 return run_type
340356
341- @cached_property
342- def functional (self ) -> str :
357+ def set_functional (self ) -> str | None :
343358 """Determine the functional used in the calculation.
344359
345360 Note that this is not a complete determination.
346361 Only the functionals used by MP are detected here.
347362 """
348-
349363 func = None
350364 func_from_potcar = None
351365 if self .user_input .potcar :
@@ -364,11 +378,13 @@ def functional(self) -> str:
364378
365379 if (metagga := self .user_input .incar .get ("METAGGA" )) and metagga .lower () != "none" :
366380 if gga :
367- raise ValidationError (
381+ self . validation_errors += [
368382 "Both the GGA and METAGGA tags were set, which can lead to large errors. "
369383 "For context, see:\n "
370384 "https://github.com/materialsproject/atomate2/issues/453#issuecomment-1699605867"
371- )
385+ ]
386+ return None
387+
372388 if metagga .lower () == "scan" :
373389 func = "scan"
374390 elif metagga .lower ().startswith ("r2sca" ):
@@ -384,12 +400,13 @@ def functional(self) -> str:
384400
385401 func = func or func_from_potcar
386402 if func is None :
387- raise ValidationError (
403+ self . validation_errors += [
388404 "Currently, we only validate calculations using the following functionals:\n "
389405 "GGA : PBE, PBEsol\n "
390406 "meta-GGA : SCAN, r2SCAN\n "
391407 "Hybrids: HSE06"
392- )
408+ ]
409+
393410 return func
394411
395412 @property
@@ -399,16 +416,14 @@ def bandgap(self) -> float | None:
399416 return self .vasprun .bandgap
400417 return None
401418
402- @cached_property
403- def valid_input_set (self ) -> VaspInputSafe :
419+ def set_valid_input_set_name (self ) -> str | None :
404420 """
405- Determine the MP-compliant input set for a calculation.
421+ Determine the MP-compliant input set import string for a calculation.
406422
407423 We need only determine a rough input set here.
408424 The precise details of the input set do not matter.
409425 """
410426
411- incar_updates : dict [str , Any ] = {}
412427 set_name : str | None = None
413428 if self .functional == "pbe" :
414429 if self .run_type == "nonscf" :
@@ -420,26 +435,36 @@ def valid_input_set(self) -> VaspInputSafe:
420435 elif self .run_type in ("relax" , "static" ):
421436 set_name = f"MP{ self .run_type .capitalize ()} Set"
422437 elif self .functional in ("pbesol" , "scan" , "r2scan" , "hse06" ):
423- if self .functional == "pbesol" :
424- incar_updates ["GGA" ] = "PS"
425- elif self .functional == "scan" :
426- incar_updates ["METAGGA" ] = "SCAN"
427- elif self .functional == "hse06" :
428- incar_updates .update (
429- LHFCALC = True ,
430- HFSCREEN = 0.2 ,
431- GGA = "PE" ,
432- )
433438 set_name = f"MPScan{ self .run_type .capitalize ()} Set"
434439
435440 if set_name is None :
436- raise ValidationError (
441+ self . validation_errors += [
437442 "Could not determine a valid input set from the specified "
438443 f"functional = { self .functional } and calculation type { self .run_type } ."
439- )
444+ ]
445+ return None
446+ return set_name
440447
448+ @cached_property
449+ def valid_input_set (self ) -> VaspInputSafe :
450+ """Determine the MP-compliant input set for a calculation."""
451+
452+ if self .valid_input_set_name is None :
453+ raise ValidationError ("Cannot determine a valid input set, see `validation_errors` for more details." )
454+
455+ incar_updates : dict [str , Any ] = {}
456+ if self .functional == "pbesol" :
457+ incar_updates ["GGA" ] = "PS"
458+ elif self .functional == "scan" :
459+ incar_updates ["METAGGA" ] = "SCAN"
460+ elif self .functional == "hse06" :
461+ incar_updates .update (
462+ LHFCALC = True ,
463+ HFSCREEN = 0.2 ,
464+ GGA = "PE" ,
465+ )
441466 # Note that only the *previous* bandgap informs the k-point density
442- vis = getattr (import_module ("pymatgen.io.vasp.sets" ), set_name )(
467+ vis = getattr (import_module ("pymatgen.io.vasp.sets" ), self . valid_input_set_name )(
443468 structure = self .user_input .structure ,
444469 bandgap = None ,
445470 user_incar_settings = incar_updates ,
0 commit comments