@@ -229,8 +229,14 @@ class VaspFiles(BaseModel):
229
229
"""Define required and optional files for validation."""
230
230
231
231
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." )
234
240
235
241
@model_validator (mode = "before" )
236
242
@classmethod
@@ -246,6 +252,17 @@ def coerce_to_lightweight(cls, config: Any) -> Any:
246
252
config ["vasprun" ] = LightVasprun .from_vasprun (config ["vasprun" ])
247
253
return config
248
254
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
+
249
266
@property
250
267
def md5 (self ) -> str :
251
268
"""Get MD5 of VaspFiles for use in validation checks."""
@@ -308,8 +325,7 @@ def from_paths(
308
325
309
326
return cls (** config )
310
327
311
- @cached_property
312
- def run_type (self ) -> str :
328
+ def set_run_type (self ) -> str | None :
313
329
"""Get the run type of a calculation."""
314
330
315
331
ibrion = self .user_input .incar .get ("IBRION" , VASP_DEFAULTS_DICT ["IBRION" ].value )
@@ -322,30 +338,28 @@ def run_type(self) -> str:
322
338
** {k : "relax" for k in range (1 , 4 )},
323
339
** {k : "phonon" for k in range (5 , 9 )},
324
340
** {k : "ts" for k in (40 , 44 )},
325
- }.get (ibrion )
341
+ }.get (ibrion , None )
326
342
327
343
if self .user_input .incar .get ("ICHARG" , VASP_DEFAULTS_DICT ["ICHARG" ].value ) >= 10 :
328
344
run_type = "nonscf"
329
345
if self .user_input .incar .get ("LCHIMAG" , VASP_DEFAULTS_DICT ["LCHIMAG" ].value ):
330
346
run_type == "nmr"
331
347
332
348
if run_type is None :
333
- raise ValidationError (
349
+ self . validation_errors += [
334
350
"Could not determine a valid run type. We currently only validate "
335
351
"Geometry optimizations (relaxations), single-points (statics), "
336
352
"and non-self-consistent fixed charged density calculations. " ,
337
- )
353
+ ]
338
354
339
355
return run_type
340
356
341
- @cached_property
342
- def functional (self ) -> str :
357
+ def set_functional (self ) -> str | None :
343
358
"""Determine the functional used in the calculation.
344
359
345
360
Note that this is not a complete determination.
346
361
Only the functionals used by MP are detected here.
347
362
"""
348
-
349
363
func = None
350
364
func_from_potcar = None
351
365
if self .user_input .potcar :
@@ -364,11 +378,13 @@ def functional(self) -> str:
364
378
365
379
if (metagga := self .user_input .incar .get ("METAGGA" )) and metagga .lower () != "none" :
366
380
if gga :
367
- raise ValidationError (
381
+ self . validation_errors += [
368
382
"Both the GGA and METAGGA tags were set, which can lead to large errors. "
369
383
"For context, see:\n "
370
384
"https://github.com/materialsproject/atomate2/issues/453#issuecomment-1699605867"
371
- )
385
+ ]
386
+ return None
387
+
372
388
if metagga .lower () == "scan" :
373
389
func = "scan"
374
390
elif metagga .lower ().startswith ("r2sca" ):
@@ -384,12 +400,13 @@ def functional(self) -> str:
384
400
385
401
func = func or func_from_potcar
386
402
if func is None :
387
- raise ValidationError (
403
+ self . validation_errors += [
388
404
"Currently, we only validate calculations using the following functionals:\n "
389
405
"GGA : PBE, PBEsol\n "
390
406
"meta-GGA : SCAN, r2SCAN\n "
391
407
"Hybrids: HSE06"
392
- )
408
+ ]
409
+
393
410
return func
394
411
395
412
@property
@@ -399,16 +416,14 @@ def bandgap(self) -> float | None:
399
416
return self .vasprun .bandgap
400
417
return None
401
418
402
- @cached_property
403
- def valid_input_set (self ) -> VaspInputSafe :
419
+ def set_valid_input_set_name (self ) -> str | None :
404
420
"""
405
- Determine the MP-compliant input set for a calculation.
421
+ Determine the MP-compliant input set import string for a calculation.
406
422
407
423
We need only determine a rough input set here.
408
424
The precise details of the input set do not matter.
409
425
"""
410
426
411
- incar_updates : dict [str , Any ] = {}
412
427
set_name : str | None = None
413
428
if self .functional == "pbe" :
414
429
if self .run_type == "nonscf" :
@@ -420,26 +435,36 @@ def valid_input_set(self) -> VaspInputSafe:
420
435
elif self .run_type in ("relax" , "static" ):
421
436
set_name = f"MP{ self .run_type .capitalize ()} Set"
422
437
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
- )
433
438
set_name = f"MPScan{ self .run_type .capitalize ()} Set"
434
439
435
440
if set_name is None :
436
- raise ValidationError (
441
+ self . validation_errors += [
437
442
"Could not determine a valid input set from the specified "
438
443
f"functional = { self .functional } and calculation type { self .run_type } ."
439
- )
444
+ ]
445
+ return None
446
+ return set_name
440
447
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
+ )
441
466
# 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 )(
443
468
structure = self .user_input .structure ,
444
469
bandgap = None ,
445
470
user_incar_settings = incar_updates ,
0 commit comments