Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/aiida_epw/parsers/epw.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class EpwParser(BaseParser):
"""``Parser`` implementation for the ``EpwCalculation`` calculation job."""

success_string = "EPW.bib"
class_error_map = {
r"internal error,\s*cannot bracket Ef": "ERROR_CANNOT_BRACKET_EF",
}

@staticmethod
def get_parser_settings_key():
Expand Down
3 changes: 2 additions & 1 deletion src/aiida_epw/tools/error_handling/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Error-recovery helpers used by EPW work chains."""

from . import epw
from . import supercon
from . import transport

__all__ = ("supercon", "transport")
__all__ = ("epw", "supercon", "transport")
24 changes: 24 additions & 0 deletions src/aiida_epw/tools/error_handling/epw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Generic EPW error-recovery helpers."""

from copy import deepcopy


def prepare_bracket_ef_recovery(parameters, fermi_energy_coarse):
"""Return updated parameters and recovery action, or ``(None, None)``."""
if fermi_energy_coarse is None:
return None, None
parameters = deepcopy(parameters)
inputepw = parameters.setdefault("INPUTEPW", {})
updated = inputepw.get("efermi_read") is not True
inputepw["efermi_read"] = True
if (
inputepw.get("fermi_energy") is None
or abs(float(inputepw["fermi_energy"]) - float(fermi_energy_coarse)) > 1.0e-6
):
inputepw["fermi_energy"] = fermi_energy_coarse
updated = True
if not updated:
return None, None
return parameters, (
f"set `efermi_read = True` and `fermi_energy = {fermi_energy_coarse}`"
)
44 changes: 44 additions & 0 deletions src/aiida_epw/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
from aiida.orm.nodes.data.base import to_aiida_type

from aiida_epw.tools.kpoints import check_kpoints_qpoints_compatibility
from aiida_epw.tools.error_handling import epw as epw_error_handling
from aiida_epw.tools.error_handling import supercon as supercon_error_handling
from aiida_epw.tools.error_handling import transport as transport_error_handling


EpwCalculation = CalculationFactory("epw.epw")


Expand Down Expand Up @@ -489,6 +491,48 @@ def report_error_handled(self, calculation, action):
self.report("{}<{}> failed with exit status {}: {}".format(*arguments))
self.report(f"Action taken: {action}")

@process_handler(
priority=700,
exit_codes=[EpwCalculation.exit_codes.ERROR_CANNOT_BRACKET_EF],
)
def handle_cannot_bracket_ef(self, calculation):
"""Retry with the coarse-grid Fermi energy explicitly fixed."""
parameters, action_taken = epw_error_handling.prepare_bracket_ef_recovery(
self.ctx.inputs.parameters.get_dict(),
calculation.outputs.output_parameters.get_dict().get("fermi_energy_coarse"),
)
if action_taken is None:
self.report_error_handled(
calculation, "Fermi energy could not be recovered"
)
return ProcessHandlerReport(
True, self.exit_codes.ERROR_KNOWN_UNRECOVERABLE_FAILURE
)
self.ctx.inputs.parameters = orm.Dict(parameters)

# A recovered Fermi level must be read from the failed calculation on
# the next attempt. Preserve the available coarse-grid data by
# changing writer restarts to their corresponding reader modes.
restart_type_node = getattr(self.ctx.inputs, "restart_type", None)
restart_type = (
restart_type_node.get_member() if restart_type_node is not None else None
)
next_restart_type = {
"NONE": "EPWREAD",
"EPHWRITE": "EPHREAD",
"EPHWRITE_RESTART": "EPHREAD",
}.get(getattr(restart_type, "name", None))
if next_restart_type is not None:
self.ctx.inputs.restart_type = restart_type.__class__[next_restart_type]
self.ctx.inputs.parent_folder_epw = calculation.outputs.remote_folder
action_taken = (
f"{action_taken} Switched restart type from "
f"{restart_type.name} to {next_restart_type}."
)

self.report_error_handled(calculation, action_taken)
return ProcessHandlerReport(True)

@process_handler(priority=10)
def handle_unrecoverable_failure(self, calculation):
"""Handle calculations with an exit status below 400 which are unrecoverable, so abort the work chain."""
Expand Down
13 changes: 13 additions & 0 deletions tests/files/parsers/epw/failed_cannot_bracket_ef/aiida.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Program EPW v.6.0 starts

Fermi energy coarse grid = 14.853630 eV
The Fermi level will be determined with 17.00000 electrons

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Error in routine efermig (1):
internal error, cannot bracket Ef
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

stopping ...
[CRAYBLAS_WARNING] Application linked against multiple cray-libsci libraries
[CRAYBLAS_WARNING] Application linked against multiple cray-libsci libraries
14 changes: 14 additions & 0 deletions tests/parsers/test_epw.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ def test_epw_iteration_nan_without_pade_is_not_pade_failure(aiida_localhost):
)


def test_epw_cannot_bracket_ef(parse_from_files):
"""Test parsing the Fermi-level bracketing failure and coarse Fermi energy."""
results, calcfunction = parse_from_files(EpwParser, "failed_cannot_bracket_ef")

assert calcfunction.is_failed
assert (
calcfunction.exit_status
== EpwCalculation.exit_codes.ERROR_CANNOT_BRACKET_EF.status
)
assert results["output_parameters"].get_dict()[
"fermi_energy_coarse"
] == pytest.approx(14.85363)


def test_epw_reads_dos_from_output_subfolder(aiida_localhost, files_path):
"""Test that DOS data is parsed even when retrieved inside the EPW output folder."""
parser_entry_point = get_entry_point_string_from_class(
Expand Down
137 changes: 137 additions & 0 deletions tests/workflows/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from unittest.mock import MagicMock

import enum

import pytest
from aiida import orm

Expand Down Expand Up @@ -477,3 +479,138 @@ def test_validate_real_axis_without_continuation():
}

assert EpwBaseWorkChain.spec().inputs.validator(inputs) is None


def test_handle_cannot_bracket_ef_updates_fermi_energy():
"""Test retrying with the Fermi energy parsed from the coarse grid."""

class MockWorkChain:
exit_codes = EpwBaseWorkChain.exit_codes

def __init__(self):
self.ctx = type("Context", (), {})()
self.ctx.inputs = type("Inputs", (), {})()
self.report_messages = []

def report(self, message):
self.report_messages.append(message)

def report_error_handled(self, calculation, action):
self.report(action)

handle_cannot_bracket_ef = EpwBaseWorkChain.handle_cannot_bracket_ef

workchain = MockWorkChain()
workchain.ctx.inputs.parameters = orm.Dict(
dict={"INPUTEPW": {"fermi_energy": 14.8}}
)
calculation = type("Calculation", (), {})()
calculation.outputs = type("Outputs", (), {})()
calculation.outputs.output_parameters = orm.Dict(
dict={"fermi_energy_coarse": 14.85363}
)

report = workchain.handle_cannot_bracket_ef.__wrapped__(calculation)

assert report.do_break is True
assert report.exit_code.status == 0
assert workchain.ctx.inputs.parameters.get_dict()["INPUTEPW"] == {
"efermi_read": True,
"fermi_energy": 14.85363,
}


@pytest.mark.parametrize(
("restart_type", "expected_restart_type"),
(
("NONE", "EPWREAD"),
("EPHWRITE", "EPHREAD"),
("EPHWRITE_RESTART", "EPHREAD"),
),
)
def test_handle_cannot_bracket_ef_switches_writer_restart_to_reader(
restart_type, expected_restart_type
):
"""Test that Fermi-level recovery reuses files from the failed calculation."""

class MockRestartType(enum.Enum):
NONE = "none"
EPHWRITE = "ephwrite"
EPHREAD = "ephread"
EPHWRITE_RESTART = "ephwrite_restart"
EPWREAD = "epwread"

class RestartTypeNode:
def get_member(self):
return MockRestartType[restart_type]

class MockWorkChain:
exit_codes = EpwBaseWorkChain.exit_codes

def __init__(self):
self.ctx = type("Context", (), {})()
self.ctx.inputs = type("Inputs", (), {})()
self.report_messages = []

def report(self, message):
self.report_messages.append(message)

def report_error_handled(self, calculation, action):
self.report(action)

handle_cannot_bracket_ef = EpwBaseWorkChain.handle_cannot_bracket_ef

workchain = MockWorkChain()
workchain.ctx.inputs.parameters = orm.Dict(dict={"INPUTEPW": {}})
workchain.ctx.inputs.restart_type = RestartTypeNode()
calculation = type("Calculation", (), {})()
calculation.outputs = type("Outputs", (), {})()
calculation.outputs.output_parameters = orm.Dict(
dict={"fermi_energy_coarse": 14.85363}
)
calculation.outputs.remote_folder = object()

report = workchain.handle_cannot_bracket_ef.__wrapped__(calculation)

assert report.exit_code.status == 0
assert workchain.ctx.inputs.restart_type == MockRestartType[expected_restart_type]
assert workchain.ctx.inputs.parent_folder_epw is calculation.outputs.remote_folder
assert expected_restart_type in workchain.report_messages[-1]


def test_handle_cannot_bracket_ef_does_not_repeat_unchanged_input():
"""Test that an already-correct Fermi energy does not cause an endless retry."""

class MockWorkChain:
exit_codes = EpwBaseWorkChain.exit_codes

def __init__(self):
self.ctx = type("Context", (), {})()
self.ctx.inputs = type("Inputs", (), {})()
self.report_messages = []

def report(self, message):
self.report_messages.append(message)

def report_error_handled(self, calculation, action):
self.report(action)

handle_cannot_bracket_ef = EpwBaseWorkChain.handle_cannot_bracket_ef

workchain = MockWorkChain()
workchain.ctx.inputs.parameters = orm.Dict(
dict={"INPUTEPW": {"efermi_read": True, "fermi_energy": 14.85363}}
)
calculation = type("Calculation", (), {})()
calculation.outputs = type("Outputs", (), {})()
calculation.outputs.output_parameters = orm.Dict(
dict={"fermi_energy_coarse": 14.85363}
)

report = workchain.handle_cannot_bracket_ef.__wrapped__(calculation)

assert report.do_break is True
assert (
report.exit_code.status
== EpwBaseWorkChain.exit_codes.ERROR_KNOWN_UNRECOVERABLE_FAILURE.status
)
Loading