Skip to content

Commit bfaaca7

Browse files
Add missing fast kwarg to CheckIncar, add tests for fast mode
1 parent 0106eff commit bfaaca7

File tree

3 files changed

+91
-56
lines changed

3 files changed

+91
-56
lines changed

pymatgen/io/validation/validation.py

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ def from_task_doc(cls, task_doc: TaskDoc | TaskDocument, **kwargs) -> Validation
104104
"""
105105
Determines if a calculation is valid based on expected input parameters from a pymatgen inputset
106106
107-
Kwargs:
107+
Args:
108108
task_doc: the task document to process
109+
Possible kwargs for `from_dict` method:
109110
input_sets: a dictionary of task_types -> pymatgen input set for validation
110111
potcar_summary_stats: Dictionary of potcar summary data. Mapping is calculation type -> potcar symbol -> summary data.
111112
kpts_tolerance: the tolerance to allow kpts to lag behind the input set settings
@@ -142,6 +143,7 @@ def from_dict(
142143
143144
Args:
144145
task_doc: the task document to process
146+
Kwargs:
145147
input_sets: a dictionary of task_types -> pymatgen input set for validation
146148
potcar_summary_stats: Dictionary of potcar summary data. Mapping is calculation type -> potcar symbol -> summary data.
147149
kpts_tolerance: the tolerance to allow kpts to lag behind the input set settings
@@ -265,6 +267,7 @@ def from_dict(
265267
task_type=cls_kwargs["task_type"],
266268
defaults=_vasp_defaults,
267269
fft_grid_tolerance=fft_grid_tolerance,
270+
fast = fast,
268271
).check()
269272

270273
return cls(valid=len(cls_kwargs["reasons"]) == 0, **cls_kwargs)
@@ -273,20 +276,14 @@ def from_dict(
273276
def from_directory(
274277
cls,
275278
dir_name: Path | str,
276-
input_sets: dict[str, ImportString] = SETTINGS.VASP_DEFAULT_INPUT_SETS,
277-
check_potcar: bool = True,
278-
kpts_tolerance: float = SETTINGS.VASP_KPTS_TOLERANCE,
279-
allow_kpoint_shifts: bool = SETTINGS.VASP_ALLOW_KPT_SHIFT,
280-
allow_explicit_kpoint_mesh: str | bool = SETTINGS.VASP_ALLOW_EXPLICIT_KPT_MESH,
281-
fft_grid_tolerance: float = SETTINGS.VASP_FFT_GRID_TOLERANCE,
282-
num_ionic_steps_to_avg_drift_over: int = SETTINGS.VASP_NUM_IONIC_STEPS_FOR_DRIFT,
283-
max_allowed_scf_gradient: float = SETTINGS.VASP_MAX_SCF_GRADIENT,
279+
**kwargs
284280
) -> ValidationDoc:
285281
"""
286282
Determines if a calculation is valid based on expected input parameters from a pymatgen inputset
287283
288284
Args:
289285
dir_name: the directory containing the calculation files to process
286+
Possible kwargs for `from_dict` method:
290287
input_sets: a dictionary of task_types -> pymatgen input set for validation
291288
check_potcar: Whether to check POTCARs against known libraries.
292289
kpts_tolerance: the tolerance to allow kpts to lag behind the input set settings
@@ -303,21 +300,9 @@ def from_directory(
303300
volumetric_files=(),
304301
)
305302

306-
validation_doc = ValidationDoc.from_task_doc(
307-
task_doc=task_doc,
308-
input_sets=input_sets,
309-
check_potcar=check_potcar,
310-
kpts_tolerance=kpts_tolerance,
311-
allow_kpoint_shifts=allow_kpoint_shifts,
312-
allow_explicit_kpoint_mesh=allow_explicit_kpoint_mesh,
313-
fft_grid_tolerance=fft_grid_tolerance,
314-
num_ionic_steps_to_avg_drift_over=num_ionic_steps_to_avg_drift_over,
315-
max_allowed_scf_gradient=max_allowed_scf_gradient,
316-
)
317-
318-
return validation_doc
303+
return cls.from_task_doc(task_doc=task_doc, **kwargs)
304+
319305
except Exception as e:
320-
print(e)
321306
if "no vasp files found" in str(e).lower():
322307
raise Exception(f"NO CALCULATION FOUND --> {dir_name} is not a VASP calculation directory.")
323308
else:

tests/conftest.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
from pathlib import Path
21

2+
from emmet.core.tasks import TaskDoc
3+
from pathlib import Path
34
import pytest
45

6+
_test_dir = Path(__file__).parent.joinpath("test_files").resolve()
57

68
@pytest.fixture(scope="session")
79
def test_dir():
8-
return Path(__file__).parent.joinpath("test_files").resolve()
10+
return _test_dir
911

1012

1113
def assert_schemas_equal(test_schema, valid_schema):
@@ -249,3 +251,8 @@ class SiStatic(SchemaTestData):
249251
def get_test_object(object_name):
250252
"""Get the schema test data object from the class name."""
251253
return objects[object_name]
254+
255+
test_data_task_docs = {
256+
k : TaskDoc.from_directory(dir_name = _test_dir / "vasp" / v.folder)
257+
for k, v in objects.items()
258+
}

tests/test_validation.py

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
import copy
3-
from conftest import get_test_object
3+
from conftest import get_test_object, test_data_task_docs
44
from pymatgen.io.validation import ValidationDoc
55
from emmet.core.tasks import TaskDoc
66
from monty.serialization import loadfn
@@ -19,15 +19,16 @@ def run_check(
1919
error_message_to_search_for: str,
2020
should_the_check_pass: bool,
2121
vasprun_parameters_to_change: dict = {}, # for changing the parameters read from vasprun.xml
22-
incar_settings_to_change: dict = {}, # for directly changing the INCAR file
22+
incar_settings_to_change: dict = {}, # for directly changing the INCAR file,
23+
validation_doc_kwargs : dict = {}, # any kwargs to pass to the ValidationDoc class
2324
):
2425
for key, value in vasprun_parameters_to_change.items():
2526
task_doc.input.parameters[key] = value
2627

2728
for key, value in incar_settings_to_change.items():
2829
task_doc.calcs_reversed[0].input.incar[key] = value
2930

30-
validation_doc = ValidationDoc.from_task_doc(task_doc)
31+
validation_doc = ValidationDoc.from_task_doc(task_doc,**validation_doc_kwargs)
3132
has_specified_error = any([error_message_to_search_for in reason for reason in validation_doc.reasons])
3233

3334
assert (not has_specified_error) if should_the_check_pass else has_specified_error
@@ -44,7 +45,7 @@ def test_validation_doc_from_directory(test_dir, object_name):
4445
dir_name = test_dir / "vasp" / test_object.folder
4546
test_validation_doc = ValidationDoc.from_directory(dir_name=dir_name)
4647

47-
task_doc = TaskDoc.from_directory(dir_name)
48+
task_doc = test_data_task_docs[object_name]
4849
valid_validation_doc = ValidationDoc.from_task_doc(task_doc)
4950

5051
# The attributes below will always be different because the objects are created at
@@ -64,9 +65,8 @@ def test_validation_doc_from_directory(test_dir, object_name):
6465
],
6566
)
6667
def test_potcar_validation(test_dir, object_name):
67-
test_object = get_test_object(object_name)
68-
dir_name = test_dir / "vasp" / test_object.folder
69-
task_doc = TaskDoc.from_directory(dir_name)
68+
69+
task_doc = test_data_task_docs[object_name]
7070

7171
correct_potcar_summary_stats = loadfn(test_dir / "vasp" / "Si_potcar_spec.json.gz")
7272

@@ -91,9 +91,7 @@ def test_potcar_validation(test_dir, object_name):
9191
],
9292
)
9393
def test_scf_incar_checks(test_dir, object_name):
94-
test_object = get_test_object(object_name)
95-
dir_name = test_dir / "vasp" / test_object.folder
96-
task_doc = TaskDoc.from_directory(dir_name)
94+
task_doc = test_data_task_docs[object_name]
9795
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
9896

9997
# Pay *very* close attention to whether a tag is modified in the incar or in the vasprun.xml's parameters!
@@ -311,10 +309,8 @@ def test_scf_incar_checks(test_dir, object_name):
311309
pytest.param("SiNonSCFUniform", id="SiNonSCFUniform"),
312310
],
313311
)
314-
def test_nscf_incar_checks(test_dir, object_name):
315-
test_object = get_test_object(object_name)
316-
dir_name = test_dir / "vasp" / test_object.folder
317-
task_doc = TaskDoc.from_directory(dir_name)
312+
def test_nscf_incar_checks(object_name):
313+
task_doc = test_data_task_docs[object_name]
318314
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
319315

320316
# ICHARG check
@@ -338,10 +334,8 @@ def test_nscf_incar_checks(test_dir, object_name):
338334
pytest.param("SiNonSCFUniform", id="SiNonSCFUniform"),
339335
],
340336
)
341-
def test_nscf_kpoints_checks(test_dir, object_name):
342-
test_object = get_test_object(object_name)
343-
dir_name = test_dir / "vasp" / test_object.folder
344-
task_doc = TaskDoc.from_directory(dir_name)
337+
def test_nscf_kpoints_checks(object_name):
338+
task_doc = test_data_task_docs[object_name]
345339
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
346340

347341
# Explicit kpoints for NSCF calc check (this should not raise any flags for NSCF calcs)
@@ -367,10 +361,8 @@ def test_nscf_kpoints_checks(test_dir, object_name):
367361
# pytest.param("SiStatic", id="SiStatic"),
368362
],
369363
)
370-
def test_common_error_checks(test_dir, object_name):
371-
test_object = get_test_object(object_name)
372-
dir_name = test_dir / "vasp" / test_object.folder
373-
task_doc = TaskDoc.from_directory(dir_name)
364+
def test_common_error_checks(object_name):
365+
task_doc = test_data_task_docs[object_name]
374366
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
375367

376368
# METAGGA and GGA tag check (should never be set together)
@@ -461,10 +453,8 @@ def _update_kpoints_for_test(task_doc: TaskDoc, kpoints_updates: dict):
461453
pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"),
462454
],
463455
)
464-
def test_kpoints_checks(test_dir, object_name):
465-
test_object = get_test_object(object_name)
466-
dir_name = test_dir / "vasp" / test_object.folder
467-
task_doc = TaskDoc.from_directory(dir_name)
456+
def test_kpoints_checks(object_name):
457+
task_doc = test_data_task_docs[object_name]
468458
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
469459

470460
# Valid mesh type check (should flag HCP structures)
@@ -524,10 +514,8 @@ def test_kpoints_checks(test_dir, object_name):
524514
pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"),
525515
],
526516
)
527-
def test_vasp_version_check(test_dir, object_name):
528-
test_object = get_test_object(object_name)
529-
dir_name = test_dir / "vasp" / test_object.folder
530-
task_doc = TaskDoc.from_directory(dir_name)
517+
def test_vasp_version_check(object_name):
518+
task_doc = test_data_task_docs[object_name]
531519
task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files
532520

533521
vasp_version_list = [
@@ -568,10 +556,65 @@ def test_task_document(test_dir):
568556
valid_docs = {}
569557
for calc in calcs:
570558
valid_docs[calc] = ValidationDoc.from_task_doc(TaskDocument(**calcs[calc]))
559+
# quickly check that `from_dict` and `from_task_doc` give same document
560+
assert set(ValidationDoc.from_dict(calcs[calc]).reasons) == set(valid_docs[calc].reasons)
571561

572562
assert valid_docs["compliant"].valid
573563
assert not valid_docs["non-compliant"].valid
574564

575565
expected_reasons = ["KPOINTS", "ENCUT", "ENAUG"]
576566
for expected_reason in expected_reasons:
577-
assert any(expected_reason in reason for reason in valid_docs["non-compliant"].reasons)
567+
assert any(expected_reason in reason for reason in valid_docs["non-compliant"].reasons)
568+
569+
def test_fast_mode():
570+
task_doc = test_data_task_docs["SiStatic"]
571+
valid_doc = ValidationDoc.from_task_doc(task_doc,check_potcar=False)
572+
573+
# Without POTCAR check, this doc is valid
574+
assert valid_doc.valid
575+
576+
# Now introduce sequence of changes to test how fast validation works
577+
# Check order:
578+
# 1. VASP version
579+
# 2. Common errors (known bugs, missing output, etc.)
580+
# 3. K-point density
581+
# 4. POTCAR check
582+
# 5. INCAR check
583+
584+
og_kpoints = task_doc.calcs_reversed[0].input.kpoints
585+
# Introduce series of errors, then ablate them
586+
# use unacceptable version and set METAGGA and GGA simultaneously ->
587+
# should only get version error in reasons
588+
task_doc.calcs_reversed[0].vasp_version = "4.0.0"
589+
task_doc.input.parameters["NBANDS"] = -5
590+
bad_incar_updates = {"METAGGA": "R2SCAN", "GGA": "PE",}
591+
task_doc.calcs_reversed[0].input.incar.update(bad_incar_updates)
592+
593+
_update_kpoints_for_test(task_doc, {"kpoints": [[1,1,2]]})
594+
595+
valid_doc = ValidationDoc.from_task_doc(task_doc, check_potcar = True, fast = True)
596+
assert len(valid_doc.reasons) == 1
597+
assert "VASP VERSION" in valid_doc.reasons[0]
598+
599+
# Now correct version, should just get METAGGA / GGA bug
600+
task_doc.calcs_reversed[0].vasp_version = "6.3.2"
601+
valid_doc = ValidationDoc.from_task_doc(task_doc, check_potcar = True, fast = True)
602+
assert len(valid_doc.reasons) == 1
603+
assert "KNOWN BUG" in valid_doc.reasons[0]
604+
605+
# Now remove GGA tag, get k-point density error
606+
task_doc.calcs_reversed[0].input.incar.pop("GGA")
607+
valid_doc = ValidationDoc.from_task_doc(task_doc, check_potcar = True, fast = True)
608+
assert len(valid_doc.reasons) == 1
609+
assert "INPUT SETTINGS --> KPOINTS or KSPACING:" in valid_doc.reasons[0]
610+
611+
# Now restore k-points and check POTCAR --> get error
612+
_update_kpoints_for_test(task_doc, og_kpoints)
613+
valid_doc = ValidationDoc.from_task_doc(task_doc, check_potcar = True, fast = True)
614+
assert len(valid_doc.reasons) == 1
615+
assert "PSEUDOPOTENTIALS" in valid_doc.reasons[0]
616+
617+
# Without POTCAR check, should get INCAR check error for NGX
618+
valid_doc = ValidationDoc.from_task_doc(task_doc, check_potcar = False, fast = True)
619+
assert len(valid_doc.reasons) == 1
620+
assert "NBANDS" in valid_doc.reasons[0]

0 commit comments

Comments
 (0)