From 819663fafec60c26375099f7810177a6dfbf4367 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Thu, 14 Nov 2024 15:52:39 -0800 Subject: [PATCH 1/6] fix LargeSigmaHandler in cases where OUTCAR not fully written, augment vasp_gam check --- src/custodian/vasp/handlers.py | 5 +++- src/custodian/vasp/jobs.py | 47 ++++++++++++++++++++++++++++------ tests/vasp/test_handlers.py | 26 ++++++++++++++++++- tests/vasp/test_jobs.py | 32 ++++++++++++++++++++++- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/custodian/vasp/handlers.py b/src/custodian/vasp/handlers.py index 2b152176..45c086ec 100644 --- a/src/custodian/vasp/handlers.py +++ b/src/custodian/vasp/handlers.py @@ -1404,7 +1404,10 @@ def check(self, directory="./") -> bool: e_step_idx = [step[0] for step in outcar.data.get("electronic_steps", [])] smearing_entropy = outcar.data.get("smearing_entropy", [0.0 for _ in e_step_idx]) for ie_step_idx, ie_step in enumerate(e_step_idx): - if ie_step <= completed_ionic_steps: + # Because this handler monitors OUTCAR dynamically, it sometimes tries + # to retrieve data in OUTCAR before that data is written. To avoid this, + # we have two checks for list length here + if ie_step <= completed_ionic_steps and ie_step_idx < len(smearing_entropy): entropies_per_atom[ie_step - 1] = smearing_entropy[ie_step_idx] if len(entropies_per_atom) > 0: diff --git a/src/custodian/vasp/jobs.py b/src/custodian/vasp/jobs.py index a94957c2..5611c685 100644 --- a/src/custodian/vasp/jobs.py +++ b/src/custodian/vasp/jobs.py @@ -260,8 +260,7 @@ def run(self, directory="./"): cmd = list(self.vasp_cmd) if self.auto_gamma: vi = VaspInput.from_directory(directory) - kpts = vi["KPOINTS"] - if kpts is not None and kpts.style == Kpoints.supported_modes.Gamma and tuple(kpts.kpts[0]) == (1, 1, 1): + if _gamma_point_only_check(vi): if self.gamma_vasp_cmd is not None and which(self.gamma_vasp_cmd[-1]): # pylint: disable=E1136 cmd = self.gamma_vasp_cmd elif which(cmd[-1] + ".gamma"): @@ -894,12 +893,8 @@ def run(self, directory="./"): """ cmd = list(self.vasp_cmd) if self.auto_gamma: - kpts = Kpoints.from_file(os.path.join(directory, "KPOINTS")) - if kpts.style == Kpoints.supported_modes.Gamma and tuple(kpts.kpts[0]) == ( - 1, - 1, - 1, - ): + vi = VaspInput.from_directory(directory) + if _gamma_point_only_check(vi): if self.gamma_vasp_cmd is not None and which(self.gamma_vasp_cmd[-1]): # pylint: disable=E1136 cmd = self.gamma_vasp_cmd elif which(cmd[-1] + ".gamma"): @@ -987,3 +982,39 @@ def run(self, directory="./") -> None: def postprocess(self, directory="./") -> None: """Dummy postprocess.""" + +def _gamma_point_only_check(vis: VaspInput) -> bool: + """ + Check if only a single k-point is used in this calculation + + Parameters + ----------- + vis: VaspInput, the VASP input set for the calculation + + Returns + ----------- + bool: True --> use vasp_gam, False --> use vasp_std + """ + kpts = vis["KPOINTS"] + if ( + kpts is not None + and kpts.style == Kpoints.supported_modes.Gamma + and tuple(kpts.kpts[0]) == (1, 1, 1) + and all(abs(ks) < 1.e-6 for ks in kpts.kpts_shift) + ): + return True + + if (kspacing := vis["INCAR"].get("KSPACING")) is not None and vis["INCAR"].get("KGAMMA",True): + # Get number of kpoints per axis according to the formula given by VASP: + # https://www.vasp.at/wiki/index.php/KSPACING + # Note that the VASP definition of the closure relation between reciprocal + # lattice vectors b_i and direct lattice vectors a_j is not the conventional + # b_i . a_j = 2 pi delta_ij, + # and instead places the 2 pi factor in the formula for getting the number + # of kpoints per axis. + nk = [ + int(max(1, np.ceil(vis["POSCAR"].structure.lattice.reciprocal_lattice.abc[ik] / kspacing))) for ik in range(3) + ] + return np.prod(nk) == 1 + + return False \ No newline at end of file diff --git a/tests/vasp/test_handlers.py b/tests/vasp/test_handlers.py index 6147862f..a0a8dcac 100644 --- a/tests/vasp/test_handlers.py +++ b/tests/vasp/test_handlers.py @@ -531,7 +531,6 @@ def test_too_large_kspacing(self) -> None: handler.check() dct = handler.correct() assert dct["errors"] == ["dentet"] - print(dct["actions"]) assert dct["actions"] == [{"action": {"_set": {"KSPACING": 1.333333, "KGAMMA": True}}, "dict": "INCAR"}] def test_nbands_not_sufficient(self) -> None: @@ -847,6 +846,31 @@ def test_check_correct_large_sigma(self) -> None: handler = LargeSigmaHandler(output_filename=zpath("OUTCAR_pass_sigma_check")) assert not handler.check() + def test_no_crash_on_partial_output(self) -> None: + from monty.io import zopen + from pathlib import Path + # ensure that the handler doesn't crash when the OUTCAR isn't completely written + # this prevents jobs from being killed when the handler itself crashes + + orig_outcar_path = Path(zpath("OUTCAR_pass_sigma_check")) + new_outcar_name = str(orig_outcar_path.parent.resolve() / f"temp_{orig_outcar_path.name}") + shutil.copy(orig_outcar_path, new_outcar_name) + + # simulate this behavior by manually removing one of the electronic + # entropy lines that the handler searches for + with zopen(new_outcar_name,"rt") as f: + data = f.read().splitlines() + + for rm_idx in range(len(data)-1,0,-1): + if "T*S" in data[rm_idx]: + data.pop(rm_idx) + break + + with zopen(new_outcar_name,"wt") as f: + f.write("\n".join(data)) + + handler = LargeSigmaHandler(output_filename=zpath("OUTCAR_partial_output")) + assert not handler.check() class ZpotrfErrorHandlerTest(PymatgenTest): def setUp(self) -> None: diff --git a/tests/vasp/test_jobs.py b/tests/vasp/test_jobs.py index bcbebce7..81894bca 100644 --- a/tests/vasp/test_jobs.py +++ b/tests/vasp/test_jobs.py @@ -9,7 +9,7 @@ from monty.tempfile import ScratchDir from pymatgen.io.vasp import Incar, Kpoints, Poscar -from custodian.vasp.jobs import GenerateVaspInputJob, VaspJob, VaspNEBJob +from custodian.vasp.jobs import GenerateVaspInputJob, VaspJob, VaspNEBJob, _gamma_point_only_check from tests.conftest import TEST_FILES pymatgen.core.SETTINGS["PMG_VASP_PSP_DIR"] = TEST_FILES @@ -182,3 +182,33 @@ def test_run(self) -> None: assert old_incar["ICHARG"] == 1 kpoints = Kpoints.from_file("KPOINTS") assert str(kpoints.style) == "Reciprocal" + +class TestAutoGamma: + """ + Test that a VASP job can automatically detect when only 1 k-point at GAMMA is used. + """ + from pymatgen.core import Structure + from pymatgen.io.vasp.sets import MPRelaxSet + + # Isolated atom in PBC + structure = Structure( + lattice = [[15 + 0.1*i if i == j else 0. for j in range(3)] for i in range(3)], + species = ["Na"], + coords = [[0.5 for _ in range(3)]] + ) + + vis = MPRelaxSet(structure=structure) + assert vis.kpoints.kpts == [(1, 1, 1)] + assert vis.kpoints.style.name == "Gamma" + assert _gamma_point_only_check(vis.get_input_set()) + + # no longer Gamma-centered + vis = MPRelaxSet(structure=structure,user_kpoints_settings=Kpoints(kpts_shift=(0.1,0.,0.))) + assert not _gamma_point_only_check(vis.get_input_set()) + + # have to increase KSPACING or this will result in a non 1 x 1 x 1 grid + vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5}) + assert _gamma_point_only_check(vis.get_input_set()) + + vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5, "KGAMMA": False}) + assert not _gamma_point_only_check(vis.get_input_set()) \ No newline at end of file From 49733ba1bb933a54c02f0d20a27bb67aad917973 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Thu, 14 Nov 2024 15:54:33 -0800 Subject: [PATCH 2/6] precommit --- src/custodian/vasp/jobs.py | 20 +++++++++++--------- tests/vasp/test_handlers.py | 14 ++++++++------ tests/vasp/test_jobs.py | 13 ++++++------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/custodian/vasp/jobs.py b/src/custodian/vasp/jobs.py index 5611c685..4907ed16 100644 --- a/src/custodian/vasp/jobs.py +++ b/src/custodian/vasp/jobs.py @@ -983,28 +983,29 @@ def run(self, directory="./") -> None: def postprocess(self, directory="./") -> None: """Dummy postprocess.""" + def _gamma_point_only_check(vis: VaspInput) -> bool: """ - Check if only a single k-point is used in this calculation + Check if only a single k-point is used in this calculation. Parameters ----------- vis: VaspInput, the VASP input set for the calculation - - Returns + + Returns: ----------- bool: True --> use vasp_gam, False --> use vasp_std """ kpts = vis["KPOINTS"] if ( kpts is not None - and kpts.style == Kpoints.supported_modes.Gamma + and kpts.style == Kpoints.supported_modes.Gamma and tuple(kpts.kpts[0]) == (1, 1, 1) - and all(abs(ks) < 1.e-6 for ks in kpts.kpts_shift) + and all(abs(ks) < 1.0e-6 for ks in kpts.kpts_shift) ): return True - if (kspacing := vis["INCAR"].get("KSPACING")) is not None and vis["INCAR"].get("KGAMMA",True): + if (kspacing := vis["INCAR"].get("KSPACING")) is not None and vis["INCAR"].get("KGAMMA", True): # Get number of kpoints per axis according to the formula given by VASP: # https://www.vasp.at/wiki/index.php/KSPACING # Note that the VASP definition of the closure relation between reciprocal @@ -1013,8 +1014,9 @@ def _gamma_point_only_check(vis: VaspInput) -> bool: # and instead places the 2 pi factor in the formula for getting the number # of kpoints per axis. nk = [ - int(max(1, np.ceil(vis["POSCAR"].structure.lattice.reciprocal_lattice.abc[ik] / kspacing))) for ik in range(3) + int(max(1, np.ceil(vis["POSCAR"].structure.lattice.reciprocal_lattice.abc[ik] / kspacing))) + for ik in range(3) ] return np.prod(nk) == 1 - - return False \ No newline at end of file + + return False diff --git a/tests/vasp/test_handlers.py b/tests/vasp/test_handlers.py index a0a8dcac..2904ddb8 100644 --- a/tests/vasp/test_handlers.py +++ b/tests/vasp/test_handlers.py @@ -847,8 +847,9 @@ def test_check_correct_large_sigma(self) -> None: assert not handler.check() def test_no_crash_on_partial_output(self) -> None: - from monty.io import zopen from pathlib import Path + + from monty.io import zopen # ensure that the handler doesn't crash when the OUTCAR isn't completely written # this prevents jobs from being killed when the handler itself crashes @@ -858,20 +859,21 @@ def test_no_crash_on_partial_output(self) -> None: # simulate this behavior by manually removing one of the electronic # entropy lines that the handler searches for - with zopen(new_outcar_name,"rt") as f: + with zopen(new_outcar_name, "rt") as f: data = f.read().splitlines() - for rm_idx in range(len(data)-1,0,-1): + for rm_idx in range(len(data) - 1, 0, -1): if "T*S" in data[rm_idx]: data.pop(rm_idx) break - - with zopen(new_outcar_name,"wt") as f: + + with zopen(new_outcar_name, "wt") as f: f.write("\n".join(data)) - + handler = LargeSigmaHandler(output_filename=zpath("OUTCAR_partial_output")) assert not handler.check() + class ZpotrfErrorHandlerTest(PymatgenTest): def setUp(self) -> None: copy_tmp_files( diff --git a/tests/vasp/test_jobs.py b/tests/vasp/test_jobs.py index 81894bca..2fb97162 100644 --- a/tests/vasp/test_jobs.py +++ b/tests/vasp/test_jobs.py @@ -183,18 +183,20 @@ def test_run(self) -> None: kpoints = Kpoints.from_file("KPOINTS") assert str(kpoints.style) == "Reciprocal" + class TestAutoGamma: """ Test that a VASP job can automatically detect when only 1 k-point at GAMMA is used. """ + from pymatgen.core import Structure from pymatgen.io.vasp.sets import MPRelaxSet # Isolated atom in PBC structure = Structure( - lattice = [[15 + 0.1*i if i == j else 0. for j in range(3)] for i in range(3)], - species = ["Na"], - coords = [[0.5 for _ in range(3)]] + lattice=[[15 + 0.1 * i if i == j else 0.0 for j in range(3)] for i in range(3)], + species=["Na"], + coords=[[0.5 for _ in range(3)]], ) vis = MPRelaxSet(structure=structure) @@ -203,12 +205,9 @@ class TestAutoGamma: assert _gamma_point_only_check(vis.get_input_set()) # no longer Gamma-centered - vis = MPRelaxSet(structure=structure,user_kpoints_settings=Kpoints(kpts_shift=(0.1,0.,0.))) assert not _gamma_point_only_check(vis.get_input_set()) # have to increase KSPACING or this will result in a non 1 x 1 x 1 grid - vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5}) assert _gamma_point_only_check(vis.get_input_set()) - vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5, "KGAMMA": False}) - assert not _gamma_point_only_check(vis.get_input_set()) \ No newline at end of file + assert not _gamma_point_only_check(vis.get_input_set()) From 1063547ae6ce6859f6edc4ed19a3e809ec8cb546 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Thu, 14 Nov 2024 16:10:46 -0800 Subject: [PATCH 3/6] why did ruff remove stuff --- tests/vasp/test_jobs.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/vasp/test_jobs.py b/tests/vasp/test_jobs.py index 2fb97162..81894bca 100644 --- a/tests/vasp/test_jobs.py +++ b/tests/vasp/test_jobs.py @@ -183,20 +183,18 @@ def test_run(self) -> None: kpoints = Kpoints.from_file("KPOINTS") assert str(kpoints.style) == "Reciprocal" - class TestAutoGamma: """ Test that a VASP job can automatically detect when only 1 k-point at GAMMA is used. """ - from pymatgen.core import Structure from pymatgen.io.vasp.sets import MPRelaxSet # Isolated atom in PBC structure = Structure( - lattice=[[15 + 0.1 * i if i == j else 0.0 for j in range(3)] for i in range(3)], - species=["Na"], - coords=[[0.5 for _ in range(3)]], + lattice = [[15 + 0.1*i if i == j else 0. for j in range(3)] for i in range(3)], + species = ["Na"], + coords = [[0.5 for _ in range(3)]] ) vis = MPRelaxSet(structure=structure) @@ -205,9 +203,12 @@ class TestAutoGamma: assert _gamma_point_only_check(vis.get_input_set()) # no longer Gamma-centered + vis = MPRelaxSet(structure=structure,user_kpoints_settings=Kpoints(kpts_shift=(0.1,0.,0.))) assert not _gamma_point_only_check(vis.get_input_set()) # have to increase KSPACING or this will result in a non 1 x 1 x 1 grid + vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5}) assert _gamma_point_only_check(vis.get_input_set()) - assert not _gamma_point_only_check(vis.get_input_set()) + vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5, "KGAMMA": False}) + assert not _gamma_point_only_check(vis.get_input_set()) \ No newline at end of file From f3ac76b4234641044cc5acb3adf573d205a1be54 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Thu, 14 Nov 2024 16:15:19 -0800 Subject: [PATCH 4/6] whoops thats why --- tests/vasp/test_jobs.py | 52 +++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/tests/vasp/test_jobs.py b/tests/vasp/test_jobs.py index 81894bca..7e80caa8 100644 --- a/tests/vasp/test_jobs.py +++ b/tests/vasp/test_jobs.py @@ -7,7 +7,9 @@ import pytest from monty.os import cd from monty.tempfile import ScratchDir +from pymatgen.core import Structure from pymatgen.io.vasp import Incar, Kpoints, Poscar +from pymatgen.io.vasp.sets import MPRelaxSet from custodian.vasp.jobs import GenerateVaspInputJob, VaspJob, VaspNEBJob, _gamma_point_only_check from tests.conftest import TEST_FILES @@ -183,32 +185,32 @@ def test_run(self) -> None: kpoints = Kpoints.from_file("KPOINTS") assert str(kpoints.style) == "Reciprocal" + class TestAutoGamma: """ Test that a VASP job can automatically detect when only 1 k-point at GAMMA is used. """ - from pymatgen.core import Structure - from pymatgen.io.vasp.sets import MPRelaxSet - - # Isolated atom in PBC - structure = Structure( - lattice = [[15 + 0.1*i if i == j else 0. for j in range(3)] for i in range(3)], - species = ["Na"], - coords = [[0.5 for _ in range(3)]] - ) - - vis = MPRelaxSet(structure=structure) - assert vis.kpoints.kpts == [(1, 1, 1)] - assert vis.kpoints.style.name == "Gamma" - assert _gamma_point_only_check(vis.get_input_set()) - - # no longer Gamma-centered - vis = MPRelaxSet(structure=structure,user_kpoints_settings=Kpoints(kpts_shift=(0.1,0.,0.))) - assert not _gamma_point_only_check(vis.get_input_set()) - - # have to increase KSPACING or this will result in a non 1 x 1 x 1 grid - vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5}) - assert _gamma_point_only_check(vis.get_input_set()) - - vis = MPRelaxSet(structure=structure,user_incar_settings={"KSPACING": 0.5, "KGAMMA": False}) - assert not _gamma_point_only_check(vis.get_input_set()) \ No newline at end of file + + def test_gamma_checks(self) -> None: + # Isolated atom in PBC + structure = Structure( + lattice=[[15 + 0.1 * i if i == j else 0.0 for j in range(3)] for i in range(3)], + species=["Na"], + coords=[[0.5 for _ in range(3)]], + ) + + vis = MPRelaxSet(structure=structure) + assert vis.kpoints.kpts == [(1, 1, 1)] + assert vis.kpoints.style.name == "Gamma" + assert _gamma_point_only_check(vis.get_input_set()) + + # no longer Gamma-centered + vis = MPRelaxSet(structure=structure, user_kpoints_settings=Kpoints(kpts_shift=(0.1, 0.0, 0.0))) + assert not _gamma_point_only_check(vis.get_input_set()) + + # have to increase KSPACING or this will result in a non 1 x 1 x 1 grid + vis = MPRelaxSet(structure=structure, user_incar_settings={"KSPACING": 0.5}) + assert _gamma_point_only_check(vis.get_input_set()) + + vis = MPRelaxSet(structure=structure, user_incar_settings={"KSPACING": 0.5, "KGAMMA": False}) + assert not _gamma_point_only_check(vis.get_input_set()) From ee739fc3192280a95c72bf3bcfc2d3ed9ba5a117 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Tue, 3 Dec 2024 11:35:52 -0800 Subject: [PATCH 5/6] rearrange large sigma handler logic to allow for data missing in outcar --- src/custodian/vasp/handlers.py | 50 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/custodian/vasp/handlers.py b/src/custodian/vasp/handlers.py index 45c086ec..96506216 100644 --- a/src/custodian/vasp/handlers.py +++ b/src/custodian/vasp/handlers.py @@ -1365,7 +1365,7 @@ class LargeSigmaHandler(ErrorHandler): is_monitor: bool = True - def __init__(self, e_entropy_tol: float = 1e-3, min_sigma: float = 0.01, output_filename: str = "OUTCAR") -> None: + def __init__(self, e_entropy_tol: float = 1e-3, min_sigma: float = 0.001, output_filename: str = "OUTCAR") -> None: """Initializes the handler with a buffer time.""" self.e_entropy_tol = e_entropy_tol self.min_sigma = min_sigma @@ -1374,13 +1374,13 @@ def __init__(self, e_entropy_tol: float = 1e-3, min_sigma: float = 0.01, output_ def check(self, directory="./") -> bool: """Check for error.""" incar = Incar.from_file(os.path.join(directory, "INCAR")) + if incar.get("ISMEAR", 1) < 0: + # skip check + return False + try: outcar = load_outcar(os.path.join(directory, self.output_filename)) - except Exception: - # Can't perform check if outcar not valid - return False - if incar.get("ISMEAR", 1) >= 0: # get entropy terms, ionic step counts, and number of completed ionic steps outcar.read_pattern( {"smearing_entropy": r"entropy T\*S.*= *(\D\d*\.\d*)"}, @@ -1396,25 +1396,29 @@ def check(self, directory="./") -> bool: ) outcar.read_pattern({"completed_ionic_steps": r"(aborting loop)"}, reverse=False, terminate_on_match=False) - completed_ionic_steps = len(outcar.data.get("completed_ionic_steps")) - entropies_per_atom = [0.0 for _ in range(completed_ionic_steps)] - n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) + except Exception: + # Can't perform check if outcar not valid, or data is missing + return False - # `Iteration (#ionic step # electronic step)` always written before entropy - e_step_idx = [step[0] for step in outcar.data.get("electronic_steps", [])] - smearing_entropy = outcar.data.get("smearing_entropy", [0.0 for _ in e_step_idx]) - for ie_step_idx, ie_step in enumerate(e_step_idx): - # Because this handler monitors OUTCAR dynamically, it sometimes tries - # to retrieve data in OUTCAR before that data is written. To avoid this, - # we have two checks for list length here - if ie_step <= completed_ionic_steps and ie_step_idx < len(smearing_entropy): - entropies_per_atom[ie_step - 1] = smearing_entropy[ie_step_idx] - - if len(entropies_per_atom) > 0: - n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) - self.entropy_per_atom = np.max(np.abs(entropies_per_atom)) / n_atoms - if self.entropy_per_atom > self.e_entropy_tol: - return True + completed_ionic_steps = len(outcar.data.get("completed_ionic_steps")) + entropies_per_atom = [0.0 for _ in range(completed_ionic_steps)] + n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) + + # `Iteration (#ionic step # electronic step)` always written before entropy + e_step_idx = [step[0] for step in outcar.data.get("electronic_steps", [])] + smearing_entropy = outcar.data.get("smearing_entropy", [0.0 for _ in e_step_idx]) + for ie_step_idx, ie_step in enumerate(e_step_idx): + # Because this handler monitors OUTCAR dynamically, it sometimes tries + # to retrieve data in OUTCAR before that data is written. To avoid this, + # we have two checks for list length here + if ie_step <= completed_ionic_steps and ie_step_idx < len(smearing_entropy): + entropies_per_atom[ie_step - 1] = smearing_entropy[ie_step_idx] + + if len(entropies_per_atom) > 0: + n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) + self.entropy_per_atom = np.max(np.abs(entropies_per_atom)) / n_atoms + if self.entropy_per_atom > self.e_entropy_tol: + return True return False From ee8a2ce0fbbf96b0609f82fce25313bbeeece50e Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Mon, 16 Dec 2024 08:48:59 -0800 Subject: [PATCH 6/6] wrap entirety of LargeSigmaHandler in try...except to accomodate partial file output --- src/custodian/vasp/handlers.py | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/custodian/vasp/handlers.py b/src/custodian/vasp/handlers.py index 96506216..4908fb1e 100644 --- a/src/custodian/vasp/handlers.py +++ b/src/custodian/vasp/handlers.py @@ -1396,32 +1396,32 @@ def check(self, directory="./") -> bool: ) outcar.read_pattern({"completed_ionic_steps": r"(aborting loop)"}, reverse=False, terminate_on_match=False) + completed_ionic_steps = len(outcar.data.get("completed_ionic_steps")) + entropies_per_atom = [0.0 for _ in range(completed_ionic_steps)] + n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) + + # `Iteration (#ionic step # electronic step)` always written before entropy + e_step_idx = [step[0] for step in outcar.data.get("electronic_steps", [])] + smearing_entropy = outcar.data.get("smearing_entropy", [0.0 for _ in e_step_idx]) + for ie_step_idx, ie_step in enumerate(e_step_idx): + # Because this handler monitors OUTCAR dynamically, it sometimes tries + # to retrieve data in OUTCAR before that data is written. To avoid this, + # we have two checks for list length here + if ie_step <= completed_ionic_steps and ie_step_idx < len(smearing_entropy): + entropies_per_atom[ie_step - 1] = smearing_entropy[ie_step_idx] + + if len(entropies_per_atom) > 0: + n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) + self.entropy_per_atom = np.max(np.abs(entropies_per_atom)) / n_atoms + if self.entropy_per_atom > self.e_entropy_tol: + return True + + return False + except Exception: # Can't perform check if outcar not valid, or data is missing return False - completed_ionic_steps = len(outcar.data.get("completed_ionic_steps")) - entropies_per_atom = [0.0 for _ in range(completed_ionic_steps)] - n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) - - # `Iteration (#ionic step # electronic step)` always written before entropy - e_step_idx = [step[0] for step in outcar.data.get("electronic_steps", [])] - smearing_entropy = outcar.data.get("smearing_entropy", [0.0 for _ in e_step_idx]) - for ie_step_idx, ie_step in enumerate(e_step_idx): - # Because this handler monitors OUTCAR dynamically, it sometimes tries - # to retrieve data in OUTCAR before that data is written. To avoid this, - # we have two checks for list length here - if ie_step <= completed_ionic_steps and ie_step_idx < len(smearing_entropy): - entropies_per_atom[ie_step - 1] = smearing_entropy[ie_step_idx] - - if len(entropies_per_atom) > 0: - n_atoms = len(Structure.from_file(os.path.join(directory, "POSCAR"))) - self.entropy_per_atom = np.max(np.abs(entropies_per_atom)) / n_atoms - if self.entropy_per_atom > self.e_entropy_tol: - return True - - return False - def correct(self, directory="./"): """Perform corrections.""" backup(VASP_BACKUP_FILES, directory=directory)