diff --git a/pymatgen/io/validation/check_incar.py b/pymatgen/io/validation/check_incar.py index 52d7380..caebae5 100644 --- a/pymatgen/io/validation/check_incar.py +++ b/pymatgen/io/validation/check_incar.py @@ -24,7 +24,7 @@ def _check_incar( # # Any cases where that is not done is just to make the code more readable. I didn't think that would be necessary here. _check_chemical_shift_params(reasons, parameters, valid_input_set) _check_dipol_correction_params(reasons, parameters, valid_input_set) - _check_electronic_params(reasons, parameters, valid_input_set, structure, potcar) + _check_electronic_params(reasons, parameters, valid_input_set, calcs_reversed, structure, potcar) _check_electronic_projector_params(reasons, parameters, incar, valid_input_set) _check_fft_params(reasons, parameters, incar, valid_input_set, structure, fft_grid_tolerance) _check_hybrid_functional_params(reasons, parameters, valid_input_set) @@ -84,21 +84,21 @@ def _get_default_nbands(structure, parameters, nelect): return int(default_nbands) -def _get_default_nelect(structure, valid_input_set, potcar=None): - # for parsing raw calculation files or for users without the VASP pseudopotentials set up in the pymatgen `psp_resources` directory - if potcar is not None: - zval_dict = { - p.symbol.split("_")[0]: p.zval for p in potcar - } # num of electrons each species should have according to the POTCAR - # change something like "Fe_pv" to just "Fe" for easier matching of species - default_nelect = 0 - for site in structure.sites: - default_nelect += zval_dict[site.species_string] - # else try using functions that require the `psp_resources` directory to be set up for pymatgen. - else: - default_nelect = valid_input_set.nelect +# def _get_default_nelect(structure, valid_input_set, potcar=None): +# # for parsing raw calculation files or for users without the VASP pseudopotentials set up in the pymatgen `psp_resources` directory +# if potcar is not None: +# zval_dict = { +# p.symbol.split("_")[0]: p.zval for p in potcar +# } # num of electrons each species should have according to the POTCAR +# # change something like "Fe_pv" to just "Fe" for easier matching of species +# default_nelect = 0 +# for site in structure.sites: +# default_nelect += zval_dict[site.species_string] +# # else try using functions that require the `psp_resources` directory to be set up for pymatgen. +# else: +# default_nelect = valid_input_set.nelect - return int(default_nelect) +# return int(default_nelect) def _get_valid_ismears_and_sigma(parameters, bandgap, nionic_steps): @@ -164,7 +164,7 @@ def _check_dipol_correction_params(reasons, parameters, valid_input_set): _check_required_params(reasons, parameters, "EFIELD", default_efield, valid_efield) -def _check_electronic_params(reasons, parameters, valid_input_set, structure, potcar=None): +def _check_electronic_params(reasons, parameters, valid_input_set, calcs_reversed, structure, potcar=None): # EDIFF. Should be the same or smaller than in valid_input_set valid_ediff = valid_input_set.incar.get("EDIFF", 1e-4) _check_relative_params(reasons, parameters, "EDIFF", 1e-4, valid_ediff, "less than or equal to") @@ -172,7 +172,6 @@ def _check_electronic_params(reasons, parameters, valid_input_set, structure, po # ENCUT. Should be the same or greater than in valid_input_set, as this can affect energies & other results. # *** Note: "ENCUT" is not actually detected by the `Vasprun.parameters` object from pymatgen.io.vasp.outputs. # Rather, the ENMAX tag in the `Vasprun.parameters` object contains the relevant value for ENCUT. - parameters.get("ENMAX", 0) valid_encut = valid_input_set.incar.get("ENCUT", np.inf) _check_relative_params(reasons, parameters, "ENMAX", 0, valid_encut, "greater than or equal to") @@ -197,12 +196,21 @@ def _check_electronic_params(reasons, parameters, valid_input_set, structure, po _check_allowed_params(reasons, parameters, "IALGO", 38, valid_ialgos) # NELECT. - default_nelect = _get_default_nelect(structure, valid_input_set, potcar=potcar) - _check_required_params(reasons, parameters, "NELECT", default_nelect, default_nelect) + cur_nelect = parameters.get("NELECT") + valid_charge = 0.0 + cur_charge = calcs_reversed[0]["output"]["structure"]._charge + if not np.isclose(valid_charge, cur_charge): + reasons.append( + f"INPUT SETTINGS --> NELECT: set to {cur_nelect}, but this causes the structure to have a charge of {cur_charge}. " + f"NELECT should be set to {cur_nelect + cur_charge} instead." + ) + + # default_nelect = _get_default_nelect(structure, valid_input_set, potcar=potcar) + # _check_required_params(reasons, parameters, "NELECT", default_nelect, default_nelect) # NBANDS. - min_nbands = int(np.ceil(default_nelect / 2) + 1) - default_nbands = _get_default_nbands(structure, parameters, default_nelect) + min_nbands = int(np.ceil(cur_nelect / 2) + 1) + default_nbands = _get_default_nbands(structure, parameters, cur_nelect) # check for too many bands (can lead to unphysical electronic structures, see https://github.com/materialsproject/custodian/issues/224 for more context too_many_bands_msg = "Too many bands can lead to unphysical electronic structure (see https://github.com/materialsproject/custodian/issues/224 for more context.)" _check_relative_params( @@ -219,9 +227,9 @@ def _check_electronic_params(reasons, parameters, valid_input_set, structure, po def _check_electronic_projector_params(reasons, parameters, incar, valid_input_set): - # LREAL. Should be Auto or False (consistent with MP input sets). - # Do NOT use the value for LREAL from the `Vasprun.parameters` object, as VASP changes these values automatically. - # Rather, check the LREAL value in the `Vasprun.incar` object. + # LREAL. + # Do NOT use the value for LREAL from the `Vasprun.parameters` object, as VASP changes these values + # relative to the INCAR. Rather, check the LREAL value in the `Vasprun.incar` object. if str(valid_input_set.incar.get("LREAL")).upper() in ["AUTO", "A"]: valid_lreals = ["FALSE", "AUTO", "A"] elif str(valid_input_set.incar.get("LREAL")).upper() in ["FALSE"]: @@ -397,7 +405,6 @@ def _check_ionic_params( # ISIF. default_isif = 2 - # TODO? valid_min_isif was highlighted before valid_min_isif = 2 _check_relative_params( reasons, @@ -497,7 +504,7 @@ def _check_ismear_and_sigma(reasons, warnings, parameters, task_doc, ionic_steps ) # SIGMA. - # TODO: improve logic SIGMA reasons given in the case where you have a material that should have been relaxed with ISMEAR in [-5, 0], but used ISMEAR in [1,2]. + # TODO: improve logic for SIGMA reasons given in the case where you have a material that should have been relaxed with ISMEAR in [-5, 0], but used ISMEAR in [1,2]. # Because in such cases, the user wouldn't need to update the SIGMA if they use tetrahedron smearing. cur_ismear = parameters.get("ISMEAR", 1) if cur_ismear not in [-5, -4, -2]: # SIGMA is not used by the tetrahedron method. @@ -573,6 +580,7 @@ def _check_lmaxmix_and_lmaxtau(reasons, warnings, parameters, incar, valid_input if incar.get("METAGGA", None) not in ["--", None, "None"]: # cannot check LMAXTAU in the `Vasprun.parameters` object, as LMAXTAU is not printed to the parameters. Rather, we must check the INCAR. cur_lmaxtau = incar.get("LMAXTAU", 6) + if (cur_lmaxtau < valid_lmaxtau) or (cur_lmaxtau > 6): if valid_lmaxtau < 6: lmaxtau_msg = f"INPUT SETTINGS --> LMAXTAU: value is set to {cur_lmaxtau}, but should be between {valid_lmaxtau} and 6." @@ -843,14 +851,16 @@ def _check_u_params(reasons, incar, parameters, valid_input_set): valid_ldaul = valid_input_set.incar.get("LDAUL", []) cur_ldaul = incar.get("LDAUL", []) if cur_ldaul != valid_ldaul: - reasons.append(f"INPUT SETTINGS --> LDAUL: set to {cur_ldaul}, but should be set to {valid_ldaul}") + reasons.append(f"INPUT SETTINGS --> LDAUL: set to {cur_ldaul}, but should be set to {valid_ldaul}.") valid_ldautype = valid_input_set.incar.get("LDAUTYPE", [2]) if isinstance(valid_ldautype, list): valid_ldautype = valid_ldautype[0] cur_ldautype = parameters.get("LDAUTYPE", [2])[0] if cur_ldautype != valid_ldautype: - reasons.append(f"INPUT SETTINGS --> LDAUTYPE: set to {cur_ldautype}, but should be set to {valid_ldautype}") + reasons.append( + f"INPUT SETTINGS --> LDAUTYPE: set to {cur_ldautype}, but should be set to {valid_ldautype}." + ) def _check_write_params(reasons, parameters, valid_input_set): diff --git a/pymatgen/io/validation/validation.py b/pymatgen/io/validation/validation.py index 0836a2c..7be924a 100644 --- a/pymatgen/io/validation/validation.py +++ b/pymatgen/io/validation/validation.py @@ -162,9 +162,11 @@ def from_task_doc( valid_potcar_summary_stats = {} for valid_potcar in valid_input_set.potcar: titel_no_spc = valid_potcar.TITEL.replace(" ", "") - valid_potcar_summary_stats[titel_no_spc] = potcar_summary_stats[ - valid_input_set._config_dict["POTCAR_FUNCTIONAL"] - ][titel_no_spc].copy() + valid_potcar_summary_stats[titel_no_spc] = ( + potcar_summary_stats.get(valid_input_set._config_dict["POTCAR_FUNCTIONAL"], {}) + .get(titel_no_spc, {}) + .copy() + ) if potcar_summary_stats: _check_potcars(reasons, warnings, potcars, valid_potcar_summary_stats) @@ -357,18 +359,22 @@ def _check_potcars( try: incorrect_potcars = [] for potcar in potcars: - reference_summary_stats = valid_potcar_summary_stats.get(potcar["TITEL"].replace(" ", ""), []) + reference_summary_stats = valid_potcar_summary_stats.get(potcar["titel"].replace(" ", ""), []) + + if len(reference_summary_stats) == 0: + incorrect_potcars.append(potcar["symbol"]) + continue key_match = False data_match = False for ref_psp in reference_summary_stats: key_match = all( - set(ref_psp["keywords"][key]) == set(potcar["_summary_stats"]["keywords"][key]) # type: ignore + set(ref_psp["keywords"][key]) == set(potcar["summary_stats"]["keywords"][key]) # type: ignore for key in ["header", "data"] ) data_diff = [ - abs(ref_psp["stats"][key][stat] - potcar["_summary_stats"]["stats"][key][stat]) # type: ignore + abs(ref_psp["stats"][key][stat] - potcar["summary_stats"]["stats"][key][stat]) # type: ignore for stat in ["MEAN", "ABSMEAN", "VAR", "MIN", "MAX"] for key in ["header", "data"] ] @@ -393,7 +399,8 @@ def _check_potcars( "believe the POTCARs used are correct." ) - except KeyError: + except KeyError as e: + print(e) # Assume it is an old calculation without potcar_spec data and treat it as failing the POTCAR check reasons.append( "Old version of Emmet --> potcar_spec is not saved in TaskDoc and cannot be validated. Hence, it is marked as invalid" diff --git a/requirements-dev.txt b/requirements-dev.txt index b5eb4e5..6a8e39a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ pytest==7.1.2 -pytest-cov==3.0.0 +pytest-cov==4.1.0 coverage==6.2 mypy==0.950 pydocstyle==6.1.1 diff --git a/tests/test_validation.py b/tests/test_validation.py index 7102ea0..b039689 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,49 +1,42 @@ import pytest import copy -from conftest import assert_schemas_equal, get_test_object +from conftest import get_test_object from pymatgen.io.validation import ValidationDoc from emmet.core.tasks import TaskDoc from pymatgen.core.structure import Structure -from pathlib import Path - -### TODO: add POTCAR checks to each of the `ValidationDoc.from_task_doc` calls ### TODO: add tests for many other MP input sets (e.g. MPNSCFSet, MPNMRSet, MPScanRelaxSet, Hybrid sets, etc.) - ### TODO: add check for an MP input set that uses an IBRION other than [-1, 1, 2] - ### TODO: add in energy-based EDIFFG check - ### TODO: add in check for MP set where LEFG = True - ### TODO: add in check for MP set where LOPTICS = True - +### TODO: add check for an MP input set that uses an IBRION other than [-1, 1, 2] +### TODO: add in energy-based EDIFFG check +### TODO: add in check for MP set where LEFG = True +### TODO: add in check for MP set where LOPTICS = True @pytest.mark.parametrize( "object_name", [ - pytest.param("SiStatic", id="SiStatic"), + pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"), ], ) def test_validation_doc_from_directory(test_dir, object_name): - test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder - test_validation_doc = ValidationDoc.from_directory(dir_name = dir_name) + test_validation_doc = ValidationDoc.from_directory(dir_name=dir_name) task_doc = TaskDoc.from_directory(dir_name) valid_validation_doc = ValidationDoc.from_task_doc(task_doc) - # These attributes will always be different because the objects are created at + # The attributes below will always be different because the objects are created at # different times. Hence, ignore before checking. - delattr(test_validation_doc.builder_meta, 'build_date') - delattr(test_validation_doc, 'last_updated') - delattr(valid_validation_doc.builder_meta, 'build_date') - delattr(valid_validation_doc, 'last_updated') + delattr(test_validation_doc.builder_meta, "build_date") + delattr(test_validation_doc, "last_updated") + delattr(valid_validation_doc.builder_meta, "build_date") + delattr(valid_validation_doc, "last_updated") assert test_validation_doc == valid_validation_doc - - @pytest.mark.parametrize( "object_name", [ @@ -52,10 +45,10 @@ def test_validation_doc_from_directory(test_dir, object_name): ], ) def test_scf_incar_checks(test_dir, object_name): - test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # LCHIMAG check temp_task_doc = copy.deepcopy(task_doc) @@ -115,7 +108,8 @@ def test_scf_incar_checks(test_dir, object_name): # NELECT check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["NELECT"] = 1 + # temp_task_doc.input.parameters["NELECT"] = 1 + temp_task_doc.calcs_reversed[0].output.structure._charge = 1.0 temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["NELECT" in reason for reason in temp_validation_doc.reasons]) @@ -133,13 +127,15 @@ def test_scf_incar_checks(test_dir, object_name): # LREAL check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.incar["LREAL"] = True # must change `incar` and not `parameters` for LREAL checks! + temp_task_doc.calcs_reversed[0].input.incar[ + "LREAL" + ] = True # must change `incar` and not `parameters` for LREAL checks! temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LREAL" in reason for reason in temp_validation_doc.reasons]) # LMAXPAW check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["LMAXPAW"] = 0 # should be -100 + temp_task_doc.input.parameters["LMAXPAW"] = 0 # should be -100 temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LMAXPAW" in reason for reason in temp_validation_doc.reasons]) @@ -186,43 +182,43 @@ def test_scf_incar_checks(test_dir, object_name): # AEXX check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["AEXX"] = 1 # should never be set to this + temp_task_doc.input.parameters["AEXX"] = 1 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["AEXX" in reason for reason in temp_validation_doc.reasons]) # AGGAC check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["AGGAC"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["AGGAC"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["AGGAC" in reason for reason in temp_validation_doc.reasons]) # AGGAX check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["AGGAX"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["AGGAX"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["AGGAX" in reason for reason in temp_validation_doc.reasons]) # ALDAX check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["ALDAX"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["ALDAX"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["ALDAX" in reason for reason in temp_validation_doc.reasons]) # AMGGAX check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["AMGGAX"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["AMGGAX"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["AMGGAX" in reason for reason in temp_validation_doc.reasons]) # ALDAC check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["ALDAC"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["ALDAC"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["ALDAC" in reason for reason in temp_validation_doc.reasons]) # AMGGAC check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.input.parameters["AMGGAC"] = 0.5 # should never be set to this + temp_task_doc.input.parameters["AMGGAC"] = 0.5 # should never be set to this temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["AMGGAC" in reason for reason in temp_validation_doc.reasons]) @@ -272,7 +268,7 @@ def test_scf_incar_checks(test_dir, object_name): # EDIFFG / force convergence check temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["EDIFFG"] = 0.01 - temp_task_doc.output.forces = [[10,10,10],[10,10,10]] + temp_task_doc.output.forces = [[10, 10, 10], [10, 10, 10]] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["CONVERGENCE" in reason for reason in temp_validation_doc.reasons]) @@ -308,7 +304,7 @@ def test_scf_incar_checks(test_dir, object_name): # SIGMA too high for nonmetal with ISMEAR = -5 check (should not error) temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["ISMEAR"] = -5 - temp_task_doc.input.parameters["SIGMA"] = 1000 # should not matter + temp_task_doc.input.parameters["SIGMA"] = 1000 # should not matter temp_task_doc.output.bandgap = 1 temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert not any(["SIGMA" in reason for reason in temp_validation_doc.reasons]) @@ -481,7 +477,7 @@ def test_scf_incar_checks(test_dir, object_name): assert not any(["WEIMIN" in reason for reason in temp_validation_doc.reasons]) # EFERMI check (does not matter for VASP versions before 6.4) - # must check EFERMI in the *incar*, as it is saved as a numerical value after VASP + # must check EFERMI in the *incar*, as it is saved as a numerical value after VASP # guesses it in the vasprun.xml `parameters` temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.calcs_reversed[0].vasp_version = "5.4.4" @@ -537,8 +533,8 @@ def test_scf_incar_checks(test_dir, object_name): temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["ISPIN"] = 2 temp_task_doc.calcs_reversed[0].output.outcar["magnetization"] = ( - {'s': -0.0, 'p': 0.0, 'd': 0.0, 'tot': 0.0}, - {'s': -0.0, 'p': 0.0, 'd': 0.0, 'tot': -0.0} + {"s": -0.0, "p": 0.0, "d": 0.0, "tot": 0.0}, + {"s": -0.0, "p": 0.0, "d": 0.0, "tot": -0.0}, ) temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert not any(["LORBIT" in reason for reason in temp_validation_doc.reasons]) @@ -609,21 +605,21 @@ def test_scf_incar_checks(test_dir, object_name): # LDAUU check temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["LDAU"] = True - temp_task_doc.calcs_reversed[0].input.incar["LDAUU"] = [5,5] + temp_task_doc.calcs_reversed[0].input.incar["LDAUU"] = [5, 5] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LDAUU" in reason for reason in temp_validation_doc.reasons]) # LDAUJ check temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["LDAU"] = True - temp_task_doc.calcs_reversed[0].input.incar["LDAUJ"] = [5,5] + temp_task_doc.calcs_reversed[0].input.incar["LDAUJ"] = [5, 5] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LDAUJ" in reason for reason in temp_validation_doc.reasons]) # LDAUL check temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["LDAU"] = True - temp_task_doc.calcs_reversed[0].input.incar["LDAUL"] = [5,5] + temp_task_doc.calcs_reversed[0].input.incar["LDAUL"] = [5, 5] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LDAUL" in reason for reason in temp_validation_doc.reasons]) @@ -652,24 +648,20 @@ def test_scf_incar_checks(test_dir, object_name): temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LOPTICS" in reason for reason in temp_validation_doc.reasons]) - # LMAXTAU check for METAGGA calcs (A value of 4 should fail for all chemsys, - # including the below-tested `La` chemsys (has f electrons)) + # LMAXTAU check for METAGGA calcs (A value of 4 should fail for the `La` chemsys (has f electrons)) temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.chemsys = "La" temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], - species=["La", "La"], - coords=[[0, 0, 0], [0.5, 0.5, 0.5]] + lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], species=["La", "La"], coords=[[0, 0, 0], [0.5, 0.5, 0.5]] ) temp_task_doc.calcs_reversed[0].input.incar["LMAXTAU"] = 4 temp_task_doc.calcs_reversed[0].input.incar["METAGGA"] = "R2SCAN" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LMAXTAU" in reason for reason in temp_validation_doc.reasons]) - # LMAXTAU check for METAGGA calcs (A value of 4 should fail for all chemsys, - # including the below-tested `Si` chemsys) + # LMAXTAU check for METAGGA calcs (A value of 2 should fail for the `Si` chemsys) temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.incar["LMAXTAU"] = 4 + temp_task_doc.calcs_reversed[0].input.incar["LMAXTAU"] = 2 temp_task_doc.calcs_reversed[0].input.incar["METAGGA"] = "R2SCAN" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["LMAXTAU" in reason for reason in temp_validation_doc.reasons]) @@ -686,9 +678,7 @@ def test_scf_incar_checks(test_dir, object_name): temp_task_doc.input.parameters["ENAUG"] = 1 temp_task_doc.calcs_reversed[0].input.incar["METAGGA"] = "R2SCAN" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) - assert any(["ENAUG" in reason for reason in temp_validation_doc.reasons]) - - + assert any(["ENAUG" in reason for reason in temp_validation_doc.reasons]) @pytest.mark.parametrize( @@ -701,6 +691,7 @@ def test_nscf_incar_checks(test_dir, object_name): test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # ICHARG check temp_task_doc = copy.deepcopy(task_doc) @@ -718,39 +709,37 @@ def test_nscf_incar_checks(test_dir, object_name): assert not any(["LMAXMIX" in warning for warning in temp_validation_doc.warnings]) - - @pytest.mark.parametrize( "object_name", - [pytest.param("SiNonSCFUniform", id="SiNonSCFUniform"),], + [ + pytest.param("SiNonSCFUniform", id="SiNonSCFUniform"), + ], ) def test_nscf_kpoints_checks(test_dir, object_name): test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # Explicit kpoints for NSCF calc check (this should not raise any flags for NSCF calcs) temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[0,0,0], [0,0,0.5]] + temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[0, 0, 0], [0, 0, 0.5]] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert not any(["INPUT SETTINGS --> KPOINTS: explicitly" in reason for reason in temp_validation_doc.reasons]) - - @pytest.mark.parametrize( "object_name", [ pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"), # pytest.param("SiStatic", id="SiStatic"), - ], ) def test_common_error_checks(test_dir, object_name): - test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # METAGGA and GGA tag check (should never be set together) temp_task_doc = copy.deepcopy(task_doc) @@ -774,7 +763,7 @@ def test_common_error_checks(test_dir, object_name): # Drift forces too high check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].output.outcar["drift"] = [[1,1,1]] + temp_task_doc.calcs_reversed[0].output.outcar["drift"] = [[1, 1, 1]] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["CONVERGENCE --> Excessive drift" in reason for reason in temp_validation_doc.reasons]) @@ -788,8 +777,8 @@ def test_common_error_checks(test_dir, object_name): temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["ISPIN"] = 2 temp_task_doc.calcs_reversed[0].output.outcar["magnetization"] = ( - {'s': 9.0, 'p': 0.0, 'd': 0.0, 'tot': 9.0}, - {'s': 9.0, 'p': 0.0, 'd': 0.0, 'tot': 9.0} + {"s": 9.0, "p": 0.0, "d": 0.0, "tot": 9.0}, + {"s": 9.0, "p": 0.0, "d": 0.0, "tot": 9.0}, ) temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["MAGNETISM" in reason for reason in temp_validation_doc.reasons]) @@ -799,13 +788,11 @@ def test_common_error_checks(test_dir, object_name): temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["ISPIN"] = 2 temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], - species=["Gd", "Eu"], - coords=[[0, 0, 0], [0.5, 0.5, 0.5]] + lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], species=["Gd", "Eu"], coords=[[0, 0, 0], [0.5, 0.5, 0.5]] ) temp_task_doc.calcs_reversed[0].output.outcar["magnetization"] = ( - {'s':9.0, 'p': 0.0, 'd': 0.0, 'tot': 9.0}, - {'s':9.0, 'p': 0.0, 'd': 0.0, 'tot': 9.0} + {"s": 9.0, "p": 0.0, "d": 0.0, "tot": 9.0}, + {"s": 9.0, "p": 0.0, "d": 0.0, "tot": 9.0}, ) temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert not any(["MAGNETISM" in reason for reason in temp_validation_doc.reasons]) @@ -815,18 +802,16 @@ def test_common_error_checks(test_dir, object_name): temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.input.parameters["ISPIN"] = 2 temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], - species=["Gd", "Eu"], - coords=[[0, 0, 0], [0.5, 0.5, 0.5]] + lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], species=["Gd", "Eu"], coords=[[0, 0, 0], [0.5, 0.5, 0.5]] ) temp_task_doc.calcs_reversed[0].output.outcar["magnetization"] = ( - {'s':11.0, 'p': 0.0, 'd': 0.0, 'tot': 11.0}, - {'s':11.0, 'p': 0.0, 'd': 0.0, 'tot': 11.0} + {"s": 11.0, "p": 0.0, "d": 0.0, "tot": 11.0}, + {"s": 11.0, "p": 0.0, "d": 0.0, "tot": 11.0}, ) temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["MAGNETISM" in reason for reason in temp_validation_doc.reasons]) - # Element Po present + # Element Po present temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.chemsys = "Po" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) @@ -839,88 +824,83 @@ def test_common_error_checks(test_dir, object_name): assert any(["COMPOSITION" in reason for reason in temp_validation_doc.reasons]) - - - @pytest.mark.parametrize( "object_name", [ pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"), - ], ) def test_kpoints_checks(test_dir, object_name): - test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # Valid mesh type check (should flag HCP structures) temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[0.5,-0.866025403784439,0],[0.5,0.866025403784439,0],[0,0,1.6329931618554521]], - coords=[[0,0,0],[0.333333333333333,-0.333333333333333, 0.5]], - species=["H", "H"] - ) # HCP structure + lattice=[[0.5, -0.866025403784439, 0], [0.5, 0.866025403784439, 0], [0, 0, 1.6329931618554521]], + coords=[[0, 0, 0], [0.333333333333333, -0.333333333333333, 0.5]], + species=["H", "H"], + ) # HCP structure temp_task_doc.calcs_reversed[0].input.kpoints["generation_style"] = "monkhorst" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) - assert any(["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons]) + assert any( + ["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons] + ) # Valid mesh type check (should flag FCC structures) temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[0.0,0.5,0.5],[0.5,0.0,0.5], [0.5,0.5,0.0]], - coords=[[0,0,0]], - species=["H"] - ) # FCC structure + lattice=[[0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]], coords=[[0, 0, 0]], species=["H"] + ) # FCC structure temp_task_doc.calcs_reversed[0].input.kpoints["generation_style"] = "monkhorst" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) - assert any(["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons]) + assert any( + ["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons] + ) # Valid mesh type check (should *not* flag BCC structures) temp_task_doc = copy.deepcopy(task_doc) temp_task_doc.calcs_reversed[0].input.structure = Structure( - lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], - species=["H", "H"], - coords=[[0, 0, 0], [0.5, 0.5, 0.5]] - ) # BCC structure + lattice=[[2.9, 0, 0], [0, 2.9, 0], [0, 0, 2.9]], species=["H", "H"], coords=[[0, 0, 0], [0.5, 0.5, 0.5]] + ) # BCC structure temp_task_doc.calcs_reversed[0].input.kpoints["generation_style"] = "monkhorst" temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) - assert not any(["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons]) + assert not any( + ["INPUT SETTINGS --> KPOINTS or KGAMMA: monkhorst-pack" in reason for reason in temp_validation_doc.reasons] + ) # Too few kpoints check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[3,3,3]] + temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[3, 3, 3]] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["INPUT SETTINGS --> KPOINTS or KSPACING:" in reason for reason in temp_validation_doc.reasons]) # Explicit kpoints for SCF calc check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[0,0,0], [0,0,0.5]] + temp_task_doc.calcs_reversed[0].input.kpoints["kpoints"] = [[0, 0, 0], [0, 0, 0.5]] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["INPUT SETTINGS --> KPOINTS: explicitly" in reason for reason in temp_validation_doc.reasons]) # Shifting kpoints for SCF calc check temp_task_doc = copy.deepcopy(task_doc) - temp_task_doc.calcs_reversed[0].input.kpoints["usershift"] = [0.5,0,0] + temp_task_doc.calcs_reversed[0].input.kpoints["usershift"] = [0.5, 0, 0] temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) assert any(["INPUT SETTINGS --> KPOINTS: shifting" in reason for reason in temp_validation_doc.reasons]) - - @pytest.mark.parametrize( "object_name", [ pytest.param("SiOptimizeDouble", id="SiOptimizeDouble"), - ], ) def test_vasp_version_check(test_dir, object_name): - test_object = get_test_object(object_name) dir_name = test_dir / "vasp" / test_object.folder task_doc = TaskDoc.from_directory(dir_name) + task_doc.calcs_reversed[0].output.structure._charge = 0.0 # patch for old test files # Check VASP versions < 5.4.4 temp_task_doc = copy.deepcopy(task_doc) @@ -963,11 +943,9 @@ def test_vasp_version_check(test_dir, object_name): # assert not any(["INPUT SETTINGS --> KPOINTS: explicitly" in reason for reason in temp_validation_doc.reasons]) - - - # # template - # temp_task_doc = copy.deepcopy(task_doc) - # temp_task_doc.input.parameters["LCHIMAG"] = True - # temp_task_doc.calcs_reversed[0].input.incar["IWAVPR"] = 1 - # temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) - # assert any(["LCHIMAG" in reason for reason in temp_validation_doc.reasons]) \ No newline at end of file +# # template +# temp_task_doc = copy.deepcopy(task_doc) +# temp_task_doc.input.parameters["LCHIMAG"] = True +# temp_task_doc.calcs_reversed[0].input.incar["IWAVPR"] = 1 +# temp_validation_doc = ValidationDoc.from_task_doc(temp_task_doc) +# assert any(["LCHIMAG" in reason for reason in temp_validation_doc.reasons])