diff --git a/src/aiida_epw/calculations/epw.py b/src/aiida_epw/calculations/epw.py index 13cb48c6..1a7dd4f1 100644 --- a/src/aiida_epw/calculations/epw.py +++ b/src/aiida_epw/calculations/epw.py @@ -77,7 +77,6 @@ class EpwCalculation(NamelistsCalculation): ("INPUTEPW", "nkf1"), ("INPUTEPW", "nkf2"), ("INPUTEPW", "nkf3"), - ("INPUTEPW", "eliashberg"), ("INPUTEPW", "liso"), ("INPUTEPW", "laniso"), ("INPUTEPW", "fbw"), @@ -99,8 +98,10 @@ class EpwCalculation(NamelistsCalculation): _OUTPUT_SUBFOLDER = "./out/" _FOLDER_SAVE = "save" _FOLDER_DYNAMICAL_MATRIX = "DYN_MAT" - _kfpoints_input_file = "kfpoints.kpt" - _qfpoints_input_file = "qfpoints.kpt" + _FILE_EPMATWP = _PREFIX + ".epmatwp" + _FOLDER_EPHMAT = _PREFIX + ".ephmat" + _KFPOINTS_INPUT_FILE = "kfpoints.kpt" + _QFPOINTS_INPUT_FILE = "qfpoints.kpt" _OUTPUT_XML_TENSOR_FILE_NAME = "tensors.xml" _OUTPUT_DOS_FILE = _PREFIX + ".dos" _OUTPUT_PHDOS_FILE = _PREFIX + ".phdos" @@ -109,13 +110,13 @@ class EpwCalculation(NamelistsCalculation): _OUTPUT_A2F_PROJ_FILE = _PREFIX + ".a2f_proj" _OUTPUT_LAMBDA_FS_FILE = _PREFIX + ".lambda_FS" _OUTPUT_LAMBDA_K_PAIRS_FILE = _PREFIX + ".lambda_k_pairs" - _output_elbands_file = "band.eig" - _output_phbands_file = "phband.freq" + _OUTPUT_ELBANDS_FILE = "band.eig" + _OUTPUT_PHBANDS_FILE = "phband.freq" _MAX_NSTEMP = 1000 - # Not using symlink in pw to allow multiple nscf to run on top of the same scf - _default_symlink_usage = False + # Use symlink by default for parent calculations (NSCF, PH, CHK, EPW) + _default_symlink_usage = True _PARALLELIZATION_FLAGS = BasePwCpInputGenerator._PARALLELIZATION_FLAGS _ENABLED_PARALLELIZATION_FLAGS = ( "nimage", @@ -138,8 +139,8 @@ def define(cls, spec): required=False, serializer=serialize_restart_type, help=( - "EPW run/restart mode: none, ephwrite, ephread, " - "ephwrite_restart, ephread_restart, or epwread." + "EPW run/restart mode: from_scratch, from_epb, from_epmatwp, " + "ephwrite, or from_eph." ), ) spec.input( @@ -459,28 +460,32 @@ def validate_restart_inputs(cls, parameters, inputs): ) return + if "restart_type" not in inputs: + return + + restart_node = inputs["restart_type"] restart_type = ( - inputs["restart_type"].get_member() if "restart_type" in inputs else None + restart_node.get_member() + if hasattr(restart_node, "get_member") + else restart_node ) parent_restart_types = ( + RestartType.FROM_EPB, + RestartType.FROM_EPMATWP, RestartType.EPHWRITE, - RestartType.EPHREAD, - RestartType.EPHWRITE_RESTART, - RestartType.EPHREAD_RESTART, - RestartType.EPWREAD, + RestartType.FROM_EPH, ) - uses_parent = restart_type in parent_restart_types - has_parent = "parent_folder_epw" in inputs - if uses_parent != has_parent: - if uses_parent: - raise exceptions.InputValidationError( - f"`parent_folder_epw` must be specified when " - f"restart_type is '{restart_type.value}'." - ) + if restart_type in parent_restart_types and "parent_folder_epw" not in inputs: + raise exceptions.InputValidationError( + f"`parent_folder_epw` must be specified when " + f"restart_type is '{restart_type.value}'." + ) + + if restart_type is RestartType.FROM_SCRATCH and "parent_folder_epw" in inputs: raise exceptions.InputValidationError( - "`restart_type` must be set to a mode that reads an EPW parent " - "when `parent_folder_epw` is provided." + "`parent_folder_epw` cannot be specified when " + "restart_type is 'from_scratch'." ) @staticmethod @@ -740,6 +745,20 @@ def cap_nstemp(self, inputepw_parameters): ) inputepw_parameters["nstemp"] = self._MAX_NSTEMP + def set_restart_parameters(self, inputepw_parameters): + """Set restart parameters in INPUTEPW based on the restart_type input.""" + if "restart_type" in self.inputs: + from aiida_epw.common.types import RESTART_TYPE_DEFAULTS + + restart_node = self.inputs.restart_type + restart_type = ( + restart_node.get_member() + if hasattr(restart_node, "get_member") + else restart_node + ) + for key, value in RESTART_TYPE_DEFAULTS[restart_type].items(): + inputepw_parameters.setdefault(key, value) + def prepare_input_parameters(self, folder, parameters): """Populate plugin-managed EPW parameters before writing the input file.""" inputepw_parameters = parameters["INPUTEPW"] @@ -765,7 +784,10 @@ def prepare_input_parameters(self, folder, parameters): inputepw_parameters["liso"] = not momentum_dependence if "full_bandwidth" in self.inputs: - inputepw_parameters["fbw"] = self.inputs.full_bandwidth.value + fbw = self.inputs.full_bandwidth.value + inputepw_parameters["fbw"] = fbw + if fbw: + inputepw_parameters["tc_linear"] = False if "real_axis" in self.inputs: real_axis = self.inputs.real_axis.value @@ -788,57 +810,7 @@ def prepare_input_parameters(self, folder, parameters): inputepw_parameters["lpade"] = False inputepw_parameters["lacon"] = False - if "restart_type" in self.inputs: - from aiida_epw.common.types import RestartType - - restart_type = self.inputs.restart_type.get_member() - if restart_type is RestartType.NONE: - inputepw_parameters.update( - { - "epwread": False, - "epwwrite": True, - "restart": False, - "ep_coupling": True, - "elph": True, - "epbwrite": True, - "epbread": False, - } - ) - elif restart_type in (RestartType.EPHWRITE, RestartType.EPHWRITE_RESTART): - inputepw_parameters.update( - { - "epwread": True, - "epwwrite": False, - "restart": restart_type is RestartType.EPHWRITE_RESTART, - "ep_coupling": True, - "elph": True, - "ephwrite": True, - } - ) - elif restart_type in (RestartType.EPHREAD, RestartType.EPHREAD_RESTART): - inputepw_parameters.update( - { - "epwread": True, - "restart": restart_type is RestartType.EPHREAD_RESTART, - "ep_coupling": False, - "elph": False, - "ephwrite": False, - } - ) - if inputepw_parameters.get("scattering", False): - inputepw_parameters["epmatkqread"] = True - elif restart_type is RestartType.EPWREAD: - inputepw_parameters.update( - { - "epwread": True, - "epwwrite": False, - "epbwrite": False, - "epbread": False, - "ep_coupling": True, - "elph": True, - } - ) - inputepw_parameters.setdefault("restart", False) + self.set_restart_parameters(inputepw_parameters) inputepw_parameters["outdir"] = self._OUTPUT_SUBFOLDER inputepw_parameters["dvscf_dir"] = self._FOLDER_SAVE @@ -872,7 +844,7 @@ def prepare_input_parameters(self, folder, parameters): self.inputs.qfpoints, ("nqf1", "nqf2", "nqf3"), "filqf", - self._qfpoints_input_file, + self._QFPOINTS_INPUT_FILE, "Cannot get the fine q-point grid", ) @@ -892,7 +864,7 @@ def prepare_input_parameters(self, folder, parameters): self.inputs.kfpoints, ("nkf1", "nkf2", "nkf3"), "filkf", - self._kfpoints_input_file, + self._KFPOINTS_INPUT_FILE, "Cannot get the fine k-point grid", ) @@ -903,7 +875,7 @@ def get_additional_retrieve_list(self, parameters): retrieve_list = [] if parameters["INPUTEPW"].get("band_plot"): - retrieve_list += [self._output_elbands_file, self._output_phbands_file] + retrieve_list += [self._OUTPUT_ELBANDS_FILE, self._OUTPUT_PHBANDS_FILE] if parameters["INPUTEPW"].get("eliashberg", False): retrieve_list.append(self._OUTPUT_A2F_FILE) @@ -935,23 +907,53 @@ def get_parent_folder_path(parent_folder): return Path(parent_folder.get_remote_path()) - def stage_nscf_parent(self, remote_copy_list): - """Stage the NSCF output directory into the EPW working directory.""" + def stage_nscf_parent(self, folder, settings, remote_list): + """Stage the NSCF output directory files into the EPW local `out` directory.""" if "parent_folder_nscf" not in self.inputs: return + folder.get_subfolder(self._OUTPUT_SUBFOLDER, create=True) + parent_folder_nscf = self.inputs.parent_folder_nscf - remote_copy_list.append( + nscf_path = self.get_parent_folder_path(parent_folder_nscf) + nscf_out = Path(nscf_path, PwCalculation._OUTPUT_SUBFOLDER).as_posix() + comp_uuid = parent_folder_nscf.computer.uuid + + # Stage save folder and xml file + remote_list.append( ( - parent_folder_nscf.computer.uuid, - Path( - parent_folder_nscf.get_remote_path(), - PwCalculation._OUTPUT_SUBFOLDER, - ).as_posix(), + comp_uuid, + Path(nscf_out, f"{self._PREFIX}.save").as_posix(), + Path(self._OUTPUT_SUBFOLDER, f"{self._PREFIX}.save").as_posix(), + ) + ) + remote_list.append( + ( + comp_uuid, + Path(nscf_out, f"{self._PREFIX}.xml").as_posix(), + Path(self._OUTPUT_SUBFOLDER, f"{self._PREFIX}.xml").as_posix(), + ) + ) + + # Stage wavefunction files + remote_list.append( + ( + comp_uuid, + Path(nscf_out, f"{self._PREFIX}.wfc*").as_posix(), self._OUTPUT_SUBFOLDER, ) ) + # Stage DFT+U / Hubbard non-orthogonal orbitals if USE_HUBBARD_U is set + if settings.get("USE_HUBBARD_U", False): + remote_list.append( + ( + comp_uuid, + Path(nscf_out, f"{self._PREFIX}.hubnoS*").as_posix(), + self._OUTPUT_SUBFOLDER, + ) + ) + def stage_chk_parent(self, remote_list): """Stage Wannier checkpoint files required by EPW.""" if "parent_folder_chk" not in self.inputs: @@ -994,39 +996,44 @@ def stage_ph_parent(self, folder, settings, remote_list): else: nqpt = get_parent_ph_qpoint_ibz_count(parent_folder_ph) - prefix = self._PREFIX - outdir = PhCalculation._OUTPUT_SUBFOLDER - fildvscf = PhCalculation._DVSCF_PREFIX - fildyn = PhCalculation._OUTPUT_DYNAMICAL_MATRIX_PREFIX ph_path = self.get_parent_folder_path(parent_folder_ph) remote_list.append( ( parent_folder_ph.computer.uuid, - Path(ph_path, outdir, "_ph0", f"{prefix}.phsave").as_posix(), + Path( + ph_path, + PhCalculation._OUTPUT_SUBFOLDER, + "_ph0", + f"{self._PREFIX}.phsave", + ).as_posix(), self._FOLDER_SAVE, ) ) for iqpt in range(1, nqpt + 1): + q_dir = "" if iqpt == 1 else f"{self._PREFIX}.q_{iqpt}" remote_list.append( ( parent_folder_ph.computer.uuid, Path( ph_path, - outdir, + PhCalculation._OUTPUT_SUBFOLDER, "_ph0", - "" if iqpt == 1 else f"{prefix}.q_{iqpt}", - f"{prefix}.{fildvscf}1", + q_dir, + f"{self._PREFIX}.{PhCalculation._DVSCF_PREFIX}1", ).as_posix(), - Path(self._FOLDER_SAVE, f"{prefix}.dvscf_q{iqpt}").as_posix(), + Path(self._FOLDER_SAVE, f"{self._PREFIX}.dvscf_q{iqpt}").as_posix(), ) ) remote_list.append( ( parent_folder_ph.computer.uuid, - Path(ph_path, f"{fildyn}{iqpt}").as_posix(), - Path(self._FOLDER_SAVE, f"{prefix}.dyn_q{iqpt}").as_posix(), + Path( + ph_path, + f"{PhCalculation._OUTPUT_DYNAMICAL_MATRIX_PREFIX}{iqpt}", + ).as_posix(), + Path(self._FOLDER_SAVE, f"{self._PREFIX}.dyn_q{iqpt}").as_posix(), ) ) @@ -1035,86 +1042,84 @@ def stage_epw_parent(self, folder, parameters, remote_list, remote_symlink_list) if "parent_folder_epw" not in self.inputs: return + from aiida_epw.common.types import RestartType + + restart_node = self.inputs.get("restart_type", None) + restart_type = ( + restart_node.get_member() + if hasattr(restart_node, "get_member") + else restart_node + ) + + if restart_type is None or restart_type is RestartType.FROM_SCRATCH: + return + folder.get_subfolder(self._OUTPUT_SUBFOLDER, create=True) parent_folder_epw = self.inputs.parent_folder_epw epw_path = self.get_parent_folder_path(parent_folder_epw) - file_list = [ - "selecq.fmt", - "crystal.fmt", - "epwdata.fmt", - "dmedata.fmt", - "vmedata.fmt", - "wigner.fmt", - "quadrupole.fmt", - "decay.H", - "decay.v", - "decay.P", - "decay.dynmat", - "decay.epmate", - "decay.epmatp", - f"{self._PREFIX}.kgmap", - f"{self._PREFIX}.kmap", - f"{self._PREFIX}.ukk", - f"{self._PREFIX}.mmn", - f"{self._PREFIX}.bvec", - self._FOLDER_SAVE, - ] - if parameters["INPUTEPW"].get("restart", False): - file_list.append("restart.fmt") - - if parameters["INPUTEPW"].get("epwread", False) and parameters["INPUTEPW"].get( - "elph", False - ): - remote_symlink_list.append( - ( - parent_folder_epw.computer.uuid, - Path( - epw_path, - f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.epmatwp", - ).as_posix(), - Path(f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.epmatwp").as_posix(), + if restart_type is RestartType.FROM_EPB: + file_list = [ + (f"{self._PREFIX}.epb*", "."), + (self._FOLDER_SAVE, self._FOLDER_SAVE), + (f"{self._PREFIX}.ukk", f"{self._PREFIX}.ukk"), + ] + for src_name, dst_name in file_list: + remote_list.append( + ( + parent_folder_epw.computer.uuid, + Path(epw_path, src_name).as_posix(), + Path(dst_name).as_posix(), + ) ) - ) + return - if parameters["INPUTEPW"].get("eliashberg", False): - if parameters["INPUTEPW"].get("ephwrite", True): - if parameters["INPUTEPW"].get("restart", False): - remote_symlink_list.append( - ( - parent_folder_epw.computer.uuid, - Path( - epw_path, - f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.ephmat", - ).as_posix(), - Path( - f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.ephmat" - ).as_posix(), - ) - ) - else: - remote_symlink_list.append( + if restart_type in (RestartType.FROM_EPMATWP, RestartType.EPHWRITE): + epmatwp_rel = Path(self._OUTPUT_SUBFOLDER, self._FILE_EPMATWP).as_posix() + file_list = [ + f"{self._PREFIX}.bvec", + f"{self._PREFIX}.kgmap", + f"{self._PREFIX}.kmap", + f"{self._PREFIX}.mmn", + f"{self._PREFIX}.ukk", + "crystal.fmt", + "dmedata.fmt", + "epwdata.fmt", + epmatwp_rel, + self._FOLDER_SAVE, + "selecq.fmt", + "vmedata.fmt", + ] + for filename in file_list: + remote_list.append( ( parent_folder_epw.computer.uuid, - Path( - epw_path, - f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.ephmat", - ).as_posix(), - Path( - f"{self._OUTPUT_SUBFOLDER}/{self._PREFIX}.ephmat" - ).as_posix(), + Path(epw_path, filename).as_posix(), + Path(filename).as_posix(), ) ) + return - for filename in file_list: - remote_list.append( - ( - parent_folder_epw.computer.uuid, - Path(epw_path, filename).as_posix(), - Path(filename).as_posix(), + if restart_type is RestartType.FROM_EPH: + ephmat_rel = Path(self._OUTPUT_SUBFOLDER, self._FOLDER_EPHMAT).as_posix() + dos_rel = Path(self._OUTPUT_SUBFOLDER, self._OUTPUT_DOS_FILE).as_posix() + file_list = [ + "crystal.fmt", + ephmat_rel, + dos_rel, + self._OUTPUT_A2F_FILE, + "selecq.fmt", + ] + for filename in file_list: + remote_list.append( + ( + parent_folder_epw.computer.uuid, + Path(epw_path, filename).as_posix(), + Path(filename).as_posix(), + ) ) - ) + return def stage_quadrupole(self, local_copy_list, remote_list): """Stage quadrupole file/directory if provided as inputs.""" @@ -1152,10 +1157,11 @@ def stage_parent_folders( else remote_copy_list ) - self.stage_nscf_parent(remote_copy_list) + self.stage_nscf_parent(folder, settings, remote_list) self.stage_chk_parent(remote_list) self.stage_ph_parent(folder, settings, remote_list) self.stage_epw_parent(folder, parameters, remote_list, remote_symlink_list) + settings.pop("USE_HUBBARD_U", None) def _add_parallelization_flags_to_cmdline_params(self, cmdline_params): """Return cmdline parameters with validated parallelization flags appended.""" diff --git a/src/aiida_epw/common/types.py b/src/aiida_epw/common/types.py index ab12b65e..5dc4d65d 100644 --- a/src/aiida_epw/common/types.py +++ b/src/aiida_epw/common/types.py @@ -4,9 +4,43 @@ class RestartType(enum.Enum): """Enumeration of EPW run/restart modes.""" - NONE = "none" + FROM_SCRATCH = "from_scratch" + FROM_EPB = "from_epb" + FROM_EPMATWP = "from_epmatwp" EPHWRITE = "ephwrite" - EPHREAD = "ephread" - EPHWRITE_RESTART = "ephwrite_restart" - EPHREAD_RESTART = "ephread_restart" - EPWREAD = "epwread" + FROM_EPH = "from_eph" + + +RESTART_TYPE_DEFAULTS = { + RestartType.FROM_SCRATCH: { + "epwread": False, + "epwwrite": True, + "epbwrite": True, + "epbread": False, + }, + RestartType.FROM_EPB: { + "epbread": True, + "epbwrite": False, + "epwread": False, + "epwwrite": True, + }, + RestartType.FROM_EPMATWP: { + "epwread": True, + "epwwrite": False, + "epbwrite": False, + "epbread": False, + }, + RestartType.EPHWRITE: { + "epwread": True, + "ep_coupling": True, + "elph": True, + "ephwrite": True, + }, + RestartType.FROM_EPH: { + "epwread": True, + "ep_coupling": False, + "elph": False, + "ephwrite": False, + "restart": False, + }, +} diff --git a/src/aiida_epw/parsers/epw.py b/src/aiida_epw/parsers/epw.py index dd48419a..072c4f8c 100644 --- a/src/aiida_epw/parsers/epw.py +++ b/src/aiida_epw/parsers/epw.py @@ -89,7 +89,7 @@ def parse(self, **kwargs): parsed_data.update(parsed_epw) elbands_contents = self.get_retrieved_content( - EpwCalculation._output_elbands_file + EpwCalculation._OUTPUT_ELBANDS_FILE ) if elbands_contents is not None: self.out( @@ -100,7 +100,7 @@ def parse(self, **kwargs): ) phbands_contents = self.get_retrieved_content( - EpwCalculation._output_phbands_file + EpwCalculation._OUTPUT_PHBANDS_FILE ) if phbands_contents is not None: self.out( diff --git a/src/aiida_epw/workflows/base.py b/src/aiida_epw/workflows/base.py index ebd6376c..e18f3c74 100644 --- a/src/aiida_epw/workflows/base.py +++ b/src/aiida_epw/workflows/base.py @@ -518,9 +518,7 @@ def handle_cannot_bracket_ef(self, calculation): restart_type_node.get_member() if restart_type_node is not None else None ) next_restart_type = { - "NONE": "EPWREAD", - "EPHWRITE": "EPHREAD", - "EPHWRITE_RESTART": "EPHREAD", + "FROM_SCRATCH": "FROM_EPB", }.get(getattr(restart_type, "name", None)) if next_restart_type is not None: self.ctx.inputs.restart_type = restart_type.__class__[next_restart_type] @@ -645,12 +643,9 @@ def handle_pade_approximants(self, calculation): True, self.exit_codes.ERROR_KNOWN_UNRECOVERABLE_FAILURE ) - try: - from aiida_epw.common.types import RestartType + from aiida_epw.common.types import RestartType - self.ctx.inputs.restart_type = RestartType.EPHREAD - except ImportError: - parameters.setdefault("INPUTEPW", {})["epwread"] = True + self.ctx.inputs.restart_type = RestartType.FROM_EPH self.ctx.inputs.parameters = orm.Dict(parameters) self.ctx.inputs.parent_folder_epw = calculation.outputs.remote_folder diff --git a/src/aiida_epw/workflows/mobility.py b/src/aiida_epw/workflows/mobility.py index d598debe..59876c7d 100644 --- a/src/aiida_epw/workflows/mobility.py +++ b/src/aiida_epw/workflows/mobility.py @@ -5,7 +5,6 @@ from aiida_quantumespresso.workflows.protocols.utils import ProtocolMixin -from aiida_epw.calculations.epw import serialize_restart_type from aiida_epw.tools.band_analysis import extract_band_edges_from_epw_prep from aiida_epw.workflows.base import EpwBaseWorkChain @@ -448,7 +447,6 @@ def run_conv(self): "code": self.inputs.epw_mobility.code, "structure": self.inputs.structure, "parent_folder_epw": self.inputs.parent_folder_epw, - "restart_type": serialize_restart_type("ephread_restart"), "qfpoints_distance": self.ctx.interpolation_list.pop(0), "kfpoints_factor": self.inputs.kfpoints_factor, "parameters": orm.Dict(parameters), diff --git a/src/aiida_epw/workflows/prep.py b/src/aiida_epw/workflows/prep.py index bf54f109..a093f33d 100644 --- a/src/aiida_epw/workflows/prep.py +++ b/src/aiida_epw/workflows/prep.py @@ -1193,7 +1193,7 @@ def run_epw(self): from aiida_epw.common.types import RestartType - inputs.restart_type = RestartType.NONE + inputs.restart_type = RestartType.FROM_SCRATCH workchain_node = self.submit(EpwBaseWorkChain, **inputs) self.report( @@ -1274,7 +1274,7 @@ def run_epw_bands(self): from aiida_epw.common.types import RestartType - inputs.restart_type = RestartType.EPWREAD + inputs.restart_type = RestartType.FROM_EPMATWP workchain_node = self.submit(EpwBaseWorkChain, **inputs) self.report( diff --git a/src/aiida_epw/workflows/protocols/prep.yaml b/src/aiida_epw/workflows/protocols/prep.yaml index ed4e321f..6632bab2 100644 --- a/src/aiida_epw/workflows/protocols/prep.yaml +++ b/src/aiida_epw/workflows/protocols/prep.yaml @@ -61,7 +61,7 @@ default_inputs: temps: 300 vme: 'dipole' wannierize: False - restart_type: wannierize + restart_type: from_scratch epw_bands: options: resources: @@ -70,7 +70,7 @@ default_inputs: parameters: INPUTEPW: band_plot: True - restart_type: epwread + restart_type: from_epmatwp default_protocol: moderate protocols: moderate: diff --git a/src/aiida_epw/workflows/protocols/prep_sc.yaml b/src/aiida_epw/workflows/protocols/prep_sc.yaml index 0eb5c444..0b9bd065 100644 --- a/src/aiida_epw/workflows/protocols/prep_sc.yaml +++ b/src/aiida_epw/workflows/protocols/prep_sc.yaml @@ -57,7 +57,7 @@ default_inputs: fsthick: 100 phonselfen: False temps: 300 - restart_type: wannierize + restart_type: from_scratch epw_bands: options: resources: @@ -66,7 +66,7 @@ default_inputs: parameters: INPUTEPW: band_plot: True - restart_type: epwread + restart_type: from_epmatwp default_protocol: moderate protocols: moderate: diff --git a/src/aiida_epw/workflows/protocols/supercon.yaml b/src/aiida_epw/workflows/protocols/supercon.yaml index f230a8a4..1880ab45 100644 --- a/src/aiida_epw/workflows/protocols/supercon.yaml +++ b/src/aiida_epw/workflows/protocols/supercon.yaml @@ -18,7 +18,6 @@ default_inputs: num_machines: 1 max_wallclock_seconds: 43200 # Twelve hours withmpi: True - restart_type: ephwrite momentum_dependence: False analytical_continuation: pade parameters: @@ -46,12 +45,11 @@ default_inputs: num_machines: 1 max_wallclock_seconds: 43200 # Twelve hours withmpi: True - restart_type: ephread parameters: INPUTEPW: eliashberg: True - elph: True - ep_coupling: True + elph: False + ep_coupling: False conv_thr_iaxis: 0.01 elecselfen: False etf_mem: 1 @@ -60,9 +58,7 @@ default_inputs: muc: 0.13 nqstep: 500 nsiter: 500 - nstemp: 40 selecqread: False - temps: 1 40 vme: 'dipole' wscut: 0.5 wannierize: False @@ -72,12 +68,11 @@ default_inputs: num_machines: 1 max_wallclock_seconds: 43200 # Twelve hours withmpi: True - restart_type: ephread parameters: INPUTEPW: eliashberg: True - elph: True - ep_coupling: True + elph: False + ep_coupling: False conv_thr_iaxis: 0.01 elecselfen: False etf_mem: 1 @@ -86,9 +81,7 @@ default_inputs: muc: 0.13 nqstep: 500 nsiter: 500 - nstemp: 20 selecqread: False - temps: 5 43 vme: 'dipole' wscut: 0.5 wannierize: False diff --git a/src/aiida_epw/workflows/supercon.py b/src/aiida_epw/workflows/supercon.py index 6cd7edde..57d48c6f 100644 --- a/src/aiida_epw/workflows/supercon.py +++ b/src/aiida_epw/workflows/supercon.py @@ -121,7 +121,6 @@ def define(cls, spec): "parent_folder_chk", "qfpoints", "kfpoints", - "restart_type", ), namespace_options={ "help": ( @@ -140,7 +139,6 @@ def define(cls, spec): "parent_folder_chk", "qfpoints_distance", "kfpoints_factor", - "restart_type", ), namespace_options={ "help": ( @@ -159,7 +157,6 @@ def define(cls, spec): "parent_folder_chk", "qfpoints_distance", "kfpoints_factor", - "restart_type", ), namespace_options={ "help": ( @@ -286,7 +283,11 @@ def get_builder_from_protocol( real_axis = epw_inputs.pop("real_axis", False) analytical_continuation = epw_inputs.pop("analytical_continuation", None) epw_inputs.pop("calculation_type", None) - epw_inputs.pop("restart_type", None) + restart_type = epw_inputs.pop("restart_type", None) + if restart_type is None: + restart_type = ( + "ephwrite" if epw_namespace == "epw_interp" else "from_eph" + ) # Check which input ports are supported by EpwBaseWorkChain dynamically for cross-branch compatibility base_inputs = EpwBaseWorkChain.spec().inputs @@ -308,6 +309,9 @@ def get_builder_from_protocol( **kwargs, ) + if "restart_type" in base_inputs: + epw_builder.restart_type = serialize_restart_type(restart_type) + if epw_namespace == "epw_interp" and scon_epw_code is not None: epw_builder.code = scon_epw_code else: @@ -402,7 +406,8 @@ def run_conv(self): inputs.kfpoints_factor = self.inputs.epw_interp.kfpoints_factor inputs.qfpoints_distance = self.ctx.interpolation_list.pop() - inputs.restart_type = serialize_restart_type("ephwrite") + if "restart_type" not in inputs: + inputs.restart_type = serialize_restart_type("ephwrite") if self.ctx.degaussq: parameters = inputs.parameters.get_dict() @@ -469,7 +474,8 @@ def run_final_epw_iso(self): inputs.kfpoints = parent_folder_epw.creator.inputs.kfpoints inputs.qfpoints = parent_folder_epw.creator.inputs.qfpoints - inputs.restart_type = serialize_restart_type("ephread") + if "restart_type" not in inputs: + inputs.restart_type = serialize_restart_type("from_eph") if self.ctx.degaussq: parameters = inputs.parameters.get_dict() @@ -507,7 +513,8 @@ def run_final_epw_aniso(self): inputs.kfpoints = parent_folder_epw.creator.inputs.kfpoints inputs.qfpoints = parent_folder_epw.creator.inputs.qfpoints - inputs.restart_type = serialize_restart_type("ephread") + if "restart_type" not in inputs: + inputs.restart_type = serialize_restart_type("from_eph") inputs.metadata.call_link_label = "epw_final_aniso" workchain_node = self.submit(EpwBaseWorkChain, **inputs) diff --git a/tests/calculations/test_epw_calcjob.py b/tests/calculations/test_epw_calcjob.py index 42b54e8d..80681d17 100644 --- a/tests/calculations/test_epw_calcjob.py +++ b/tests/calculations/test_epw_calcjob.py @@ -390,21 +390,67 @@ def test_epw_stages_nscf_parent_output_folder( generate_inputs_epw, generate_remote_data, ): - """Test that the NSCF parent contributes the QE output directory by copy.""" + """Test that the NSCF parent stages save, xml, wfc*, and hubnoS* into out/.""" parent_folder = generate_remote_data( fixture_localhost, "/remote/nscf", "quantumespresso.pw" ) - inputs = generate_inputs_epw(parent_folder_nscf=parent_folder) + nscf_out = Path( + parent_folder.get_remote_path(), PwCalculation._OUTPUT_SUBFOLDER + ).as_posix() - calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) + expected_entries = { + ( + parent_folder.computer.uuid, + Path(nscf_out, "aiida.save").as_posix(), + Path(EpwCalculation._OUTPUT_SUBFOLDER, "aiida.save").as_posix(), + ), + ( + parent_folder.computer.uuid, + Path(nscf_out, "aiida.xml").as_posix(), + Path(EpwCalculation._OUTPUT_SUBFOLDER, "aiida.xml").as_posix(), + ), + ( + parent_folder.computer.uuid, + Path(nscf_out, "aiida.wfc*").as_posix(), + EpwCalculation._OUTPUT_SUBFOLDER, + ), + } + # 1. Default (symlink mode when PARENT_FOLDER_SYMLINK is not specified) + inputs_default = generate_inputs_epw(parent_folder_nscf=parent_folder) + calc_info_default = generate_calc_job(fixture_sandbox, "epw.epw", inputs_default) + + assert expected_entries.issubset(set(calc_info_default.remote_symlink_list)) assert ( parent_folder.computer.uuid, - Path( - parent_folder.get_remote_path(), PwCalculation._OUTPUT_SUBFOLDER - ).as_posix(), + nscf_out, EpwCalculation._OUTPUT_SUBFOLDER, - ) in calc_info.remote_copy_list + ) not in calc_info_default.remote_symlink_list + + # 2. Explicit copy mode + inputs_copy = generate_inputs_epw( + parent_folder_nscf=parent_folder, + settings=orm.Dict({"PARENT_FOLDER_SYMLINK": False}), + ) + calc_info_copy = generate_calc_job(fixture_sandbox, "epw.epw", inputs_copy) + assert expected_entries.issubset(set(calc_info_copy.remote_copy_list)) + + # 3. Symlink mode with USE_HUBBARD_U + inputs_hubbard = generate_inputs_epw( + parent_folder_nscf=parent_folder, + settings=orm.Dict({"USE_HUBBARD_U": True}), + ) + calc_info_hubbard = generate_calc_job(fixture_sandbox, "epw.epw", inputs_hubbard) + + expected_hubbard = expected_entries | { + ( + parent_folder.computer.uuid, + Path(nscf_out, "aiida.hubnoS*").as_posix(), + EpwCalculation._OUTPUT_SUBFOLDER, + ) + } + assert expected_hubbard.issubset(set(calc_info_hubbard.remote_symlink_list)) + assert not expected_hubbard.intersection(set(calc_info_hubbard.remote_copy_list)) def test_epw_stages_chk_parent_into_requested_transport_list( @@ -418,7 +464,7 @@ def test_epw_stages_chk_parent_into_requested_transport_list( parent_folder = generate_remote_data(fixture_localhost, "/remote/chk") inputs = generate_inputs_epw( parent_folder_chk=parent_folder, - settings=orm.Dict({"PARENT_FOLDER_SYMLINK": True}), + settings=orm.Dict({"PARENT_FOLDER_SYMLINK": False}), ) calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) @@ -441,53 +487,115 @@ def test_epw_stages_chk_parent_into_requested_transport_list( ), } - assert expected.issubset(set(calc_info.remote_symlink_list)) - assert not expected.intersection(set(calc_info.remote_copy_list)) + assert expected.issubset(set(calc_info.remote_copy_list)) + assert not expected.intersection(set(calc_info.remote_symlink_list)) -def test_epw_stages_epw_restart_files_without_copying_epmatwp( +def test_epw_stages_from_epb( fixture_sandbox, fixture_localhost, generate_calc_job, generate_inputs_epw, generate_remote_data, ): - """Test that EPW restart staging links the large `epmatwp` file and copies metadata files.""" + """Test that FROM_EPB stages prefix.epb*, save folder, and prefix.ukk.""" parent_folder = generate_remote_data(fixture_localhost, "/remote/epw") inputs = generate_inputs_epw( - restart_type=RestartType.EPWREAD, - parameters={"INPUTEPW": {}}, + restart_type=RestartType.FROM_EPB, parent_folder_epw=parent_folder, ) calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) + symlinked = [(entry[1], entry[2]) for entry in calc_info.remote_symlink_list] assert ( - parent_folder.computer.uuid, - Path( - parent_folder.get_remote_path(), - f"{EpwCalculation._OUTPUT_SUBFOLDER}/{EpwCalculation._PREFIX}.epmatwp", - ).as_posix(), - Path( - f"{EpwCalculation._OUTPUT_SUBFOLDER}/{EpwCalculation._PREFIX}.epmatwp" - ).as_posix(), - ) in calc_info.remote_symlink_list + Path(parent_folder.get_remote_path(), "aiida.epb*").as_posix(), + ".", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "save").as_posix(), + "save", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "aiida.ukk").as_posix(), + "aiida.ukk", + ) in symlinked - expected_copied = { - "crystal.fmt", - "epwdata.fmt", - "vmedata.fmt", - "dmedata.fmt", + +def test_epw_stages_from_epmatwp( + fixture_sandbox, + fixture_localhost, + generate_calc_job, + generate_inputs_epw, + generate_remote_data, +): + """Test that FROM_EPMATWP / EPHWRITE stages all required EPW metadata files, out, and save.""" + parent_folder = generate_remote_data(fixture_localhost, "/remote/epw") + inputs = generate_inputs_epw( + restart_type=RestartType.FROM_EPMATWP, + parameters={"INPUTEPW": {"eliashberg": True}}, + parent_folder_epw=parent_folder, + ) + + calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) + symlink_targets = {entry[2] for entry in calc_info.remote_symlink_list} + + expected_symlinked = { + "aiida.bvec", "aiida.kgmap", "aiida.kmap", - "aiida.ukk", "aiida.mmn", - "aiida.bvec", + "aiida.ukk", + "crystal.fmt", + "dmedata.fmt", + "epwdata.fmt", + "out/aiida.epmatwp", + "save", + "selecq.fmt", + "vmedata.fmt", } - copied_targets = {entry[2] for entry in calc_info.remote_copy_list} - assert expected_copied.issubset(copied_targets) - assert EpwCalculation._OUTPUT_SUBFOLDER not in copied_targets - assert Path(EpwCalculation._OUTPUT_SUBFOLDER).as_posix() not in copied_targets + assert expected_symlinked.issubset(symlink_targets) + assert "out" not in symlink_targets + + +def test_epw_stages_from_eph( + fixture_sandbox, + fixture_localhost, + generate_calc_job, + generate_inputs_epw, + generate_remote_data, +): + """Test that FROM_EPH stages out/prefix.ephmat and prefix.a2f.""" + parent_folder = generate_remote_data(fixture_localhost, "/remote/epw") + inputs = generate_inputs_epw( + restart_type=RestartType.FROM_EPH, + parent_folder_epw=parent_folder, + ) + + calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) + symlinked = [(entry[1], entry[2]) for entry in calc_info.remote_symlink_list] + + assert ( + Path(parent_folder.get_remote_path(), "crystal.fmt").as_posix(), + "crystal.fmt", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "out/aiida.ephmat").as_posix(), + "out/aiida.ephmat", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "out/aiida.dos").as_posix(), + "out/aiida.dos", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "aiida.a2f").as_posix(), + "aiida.a2f", + ) in symlinked + assert ( + Path(parent_folder.get_remote_path(), "selecq.fmt").as_posix(), + "selecq.fmt", + ) in symlinked + assert len(calc_info.remote_symlink_list) == 5 def test_epw_stages_ph_stash_folder_by_target_basepath( @@ -517,7 +625,7 @@ def test_epw_stages_ph_stash_folder_by_target_basepath( "/stash/ph", PhCalculation._OUTPUT_SUBFOLDER, "_ph0", "aiida.phsave" ).as_posix(), "save", - ) in calc_info.remote_copy_list + ) in calc_info.remote_symlink_list def test_epw_eliashberg_parameters( @@ -570,24 +678,34 @@ def test_epw_eliashberg_parameters_continuation_none( ("restart_type", "parameters", "expected_entries"), [ ( - RestartType.NONE, + RestartType.FROM_SCRATCH, {}, ("epwread = .false.", "epwwrite = .true.", "epbwrite = .true."), ), ( - RestartType.EPHWRITE_RESTART, + RestartType.FROM_EPB, {}, - ("epwread = .true.", "restart = .true.", "ephwrite = .true."), + ("epbread = .true.", "epbwrite = .false.", "epwwrite = .true."), ), ( - RestartType.EPHREAD, - {"scattering": True}, - ("epwread = .true.", "ep_coupling = .false.", "epmatkqread = .true."), + RestartType.FROM_EPMATWP, + {}, + ("epwread = .true.", "epwwrite = .false.", "epbwrite = .false."), ), ( - RestartType.EPWREAD, + RestartType.EPHWRITE, {}, - ("epwread = .true.", "epwwrite = .false.", "epbwrite = .false."), + ( + "epwread = .true.", + "ep_coupling = .true.", + "elph = .true.", + "ephwrite = .true.", + ), + ), + ( + RestartType.FROM_EPH, + {}, + ("epwread = .true.", "ep_coupling = .false.", "elph = .false."), ), ], ) @@ -607,7 +725,7 @@ def test_epw_restart_type_parameter( parameters={"INPUTEPW": parameters}, **( {} - if restart_type is RestartType.NONE + if restart_type is RestartType.FROM_SCRATCH else { "parent_folder_epw": generate_remote_data( fixture_localhost, "/remote/epw" @@ -621,3 +739,30 @@ def test_epw_restart_type_parameter( for entry in expected_entries: assert entry in input_contents + + +def test_epw_parent_folder_without_restart_type( + fixture_sandbox, + fixture_localhost, + generate_calc_job, + generate_inputs_epw, + generate_remote_data, +): + """Test that parent_folder_epw can be provided without specifying restart_type.""" + inputs = generate_inputs_epw( + parameters={ + "INPUTEPW": { + "epwread": True, + "elph": False, + "ep_coupling": False, + } + }, + parent_folder_epw=generate_remote_data(fixture_localhost, "/remote/epw"), + ) + + calc_info = generate_calc_job(fixture_sandbox, "epw.epw", inputs) + input_contents = Path(fixture_sandbox.abspath, "aiida.in").read_text() + + assert "epwread = .true." in input_contents + assert "elph = .false." in input_contents + assert not any("/remote/epw" in entry[1] for entry in calc_info.remote_copy_list) diff --git a/tests/workflows/test_base.py b/tests/workflows/test_base.py index 2b66485c..fdeb39c5 100644 --- a/tests/workflows/test_base.py +++ b/tests/workflows/test_base.py @@ -2,17 +2,11 @@ from unittest.mock import MagicMock -import enum import pytest from aiida import orm -try: - from aiida_epw.common.types import RestartType - - HAS_RESTART_TYPE = True -except ImportError: - HAS_RESTART_TYPE = False +from aiida_epw.common.types import RestartType from aiida_epw.workflows.base import EpwBaseWorkChain @@ -115,10 +109,7 @@ def report_error_handled(self, calculation, action): assert input_epw["npade"] == 25 # Restart settings should be set - if HAS_RESTART_TYPE: - assert workchain.ctx.inputs.restart_type == RestartType.EPHREAD - else: - assert input_epw.get("epwread") is True + assert workchain.ctx.inputs.restart_type == RestartType.FROM_EPH assert workchain.ctx.inputs.parent_folder_epw == calc.outputs.remote_folder @@ -522,27 +513,16 @@ def report_error_handled(self, calculation, action): @pytest.mark.parametrize( ("restart_type", "expected_restart_type"), - ( - ("NONE", "EPWREAD"), - ("EPHWRITE", "EPHREAD"), - ("EPHWRITE_RESTART", "EPHREAD"), - ), + (("FROM_SCRATCH", "FROM_EPB"),), ) 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] + return RestartType[restart_type] class MockWorkChain: exit_codes = EpwBaseWorkChain.exit_codes @@ -573,7 +553,7 @@ def report_error_handled(self, calculation, action): 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.restart_type == RestartType[expected_restart_type] assert workchain.ctx.inputs.parent_folder_epw is calculation.outputs.remote_folder assert expected_restart_type in workchain.report_messages[-1] diff --git a/tests/workflows/test_mobility.py b/tests/workflows/test_mobility.py index 57a171f0..a4091cce 100644 --- a/tests/workflows/test_mobility.py +++ b/tests/workflows/test_mobility.py @@ -2,7 +2,7 @@ def test_mobility_protocol_omits_plugin_managed_parameters(): - """Mobility protocols leave EPW run/restart flags to ``RestartType``.""" + """Mobility protocols omit plugin-managed EPW keywords.""" from aiida_epw.calculations.epw import EpwCalculation from aiida_epw.workflows.mobility import MobilityWorkChain diff --git a/tests/workflows/test_supercon.py b/tests/workflows/test_supercon.py index c9b1f3df..6ce581fb 100644 --- a/tests/workflows/test_supercon.py +++ b/tests/workflows/test_supercon.py @@ -223,8 +223,8 @@ def test_epw_base_restart_types(fixture_code, generate_structure): ) # We should be able to set and access restart_type on the builder - builder.restart_type = RestartType.EPHWRITE - assert builder.restart_type == RestartType.EPHWRITE + builder.restart_type = RestartType.FROM_EPMATWP + assert builder.restart_type == RestartType.FROM_EPMATWP def test_supercon_get_builder_from_protocol_default( @@ -286,7 +286,7 @@ def test_supercon_get_builder_from_protocol_default( if "restart_type" in builder.epw_final_iso: from aiida_epw.common import RestartType - assert builder.epw_final_iso.restart_type == RestartType.EPHREAD + assert builder.epw_final_iso.restart_type == RestartType.FROM_EPH assert ( builder.epw_final_iso.parameters.get_dict()["INPUTEPW"].get("tc_linear", False) is False @@ -307,4 +307,4 @@ def test_supercon_get_builder_from_protocol_default( if "restart_type" in builder.epw_final_aniso: from aiida_epw.common import RestartType - assert builder.epw_final_aniso.restart_type == RestartType.EPHREAD + assert builder.epw_final_aniso.restart_type == RestartType.FROM_EPH