From b4cf6ec7f07aaaace5696cac3ddd0a4faab619ec Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Tue, 2 Jul 2024 14:07:51 +0000 Subject: [PATCH 01/21] adding ProjwfcBandWorkchain --- setup.cfg | 1 + src/aiidalab_qe/app/configuration/workflow.py | 9 --- src/aiidalab_qe/plugins/bands/__init__.py | 8 ++- src/aiidalab_qe/plugins/bands/setting.py | 42 +++++++++-- src/aiidalab_qe/plugins/bands/workchain.py | 71 +++++++++++++++---- 5 files changed, 99 insertions(+), 32 deletions(-) diff --git a/setup.cfg b/setup.cfg index f1fe1979f..452f2ff83 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ install_requires = aiida-pseudo~=1.4 filelock~=3.8 importlib-resources~=5.2 + aiida-wannier90-workflows~=2.2.0 python_requires = >=3.9 [options.packages.find] diff --git a/src/aiidalab_qe/app/configuration/workflow.py b/src/aiidalab_qe/app/configuration/workflow.py index 9fc2f0159..62c1fdc88 100644 --- a/src/aiidalab_qe/app/configuration/workflow.py +++ b/src/aiidalab_qe/app/configuration/workflow.py @@ -39,14 +39,6 @@ class WorkChainSettings(Panel): """

Properties

""" ) - properties_help = ipw.HTML( - """
- The band structure workflow will - automatically detect the default path in reciprocal space using the - - SeeK-path tool.
""" - ) - protocol_title = ipw.HTML( """

Protocol

""" @@ -117,7 +109,6 @@ def update_reminder_info(change, name=name): if name in setting_entries: self.properties[name].run.observe(update_reminder_info, "value") - self.property_children.append(self.properties_help) self.children = [ self.structure_title, self.structure_help, diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 3d67f0a7b..246de540c 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -1,6 +1,6 @@ # from aiidalab_qe.bands.result import Result from aiidalab_qe.common.panel import OutlinePanel - +from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget from .result import Result from .setting import Setting from .workchain import workchain_and_builder @@ -14,9 +14,15 @@ class BandsOutline(OutlinePanel): SeeK-path tool. """ +projwfc_bands = QEAppComputationalResourcesWidget( + description="projwfc.x for bands", + default_calc_job_plugin="quantumespresso.projwfc", +) + bands = { "outline": BandsOutline, + "code": {"projwfc_bands": projwfc_bands}, "setting": Setting, "result": Result, "workchain": workchain_and_builder, diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 5d3b06da8..84a4c057d 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -2,13 +2,14 @@ """Panel for Bands plugin.""" import ipywidgets as ipw - +import traitlets as tl from aiidalab_qe.common.panel import Panel - +from aiida import orm class Setting(Panel): title = "Bands Structure" identifier = "bands" + input_structure = tl.Instance(orm.StructureData, allow_none=True) def __init__(self, **kwargs): self.settings_title = ipw.HTML( @@ -19,10 +20,16 @@ def __init__(self, **kwargs): options=["fast", "moderate", "precise"], value="moderate", ) - self.kpath_2d_help = ipw.HTML( - """
- If your system has periodicity xy. Please select one of the five 2D Bravais lattices corresponding to your system. -
""" + self.properties_help = ipw.HTML( + """
+ The band structure workflow will + automatically detect the default path in reciprocal space using the + + SeeK-path tool.
""" + ) + self.projwfc_bands = ipw.Checkbox( + description="Flat bands calculation", + value=False, ) self.kpath_2d = ipw.Dropdown( description="Lattice:", @@ -35,23 +42,44 @@ def __init__(self, **kwargs): ], value="hexagonal", ) + self.kpath_2d.layout.visibility = "hidden" self.children = [ self.settings_title, - self.kpath_2d_help, + self.properties_help, + self.projwfc_bands, self.kpath_2d, ] super().__init__(**kwargs) + + @tl.observe("input_structure") + def _update_structure(self, _=None): + if self.input_structure.pbc == (True, True, False): + self.properties_help.value = """
+ Please select one of the five 2D Bravais lattices corresponding to your system. +
""" + self.kpath_2d.visibility = "visible" + elif self.input_structure.pbc == (True, False, False): + self.properties_help.value = """
+ The band structure path for systems with periodicity x is from Gamma to X. +
""" + self.kpath_2d.visibility = "hidden" + else: + self.kpath_2d.visibility = "hidden" + def get_panel_value(self): """Return a dictionary with the input parameters for the plugin.""" return { "kpath_2d": self.kpath_2d.value, + "projwfc_bands": self.projwfc_bands.value, } def set_panel_value(self, input_dict): """Load a dictionary with the input parameters for the plugin.""" self.kpath_2d.value = input_dict.get("kpath_2d", "hexagonal") + self.projwfc_bands.value = input_dict.get("projwfc_bands", False) def reset(self): """Reset the panel to its default values.""" self.kpath_2d.value = "hexagonal" + self.projwfc_bands.value = False diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index d3226a484..370a96096 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -2,13 +2,39 @@ from aiida.plugins import DataFactory, WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType from aiidalab_qe.plugins.utils import set_component_resources +from aiida import orm GAMMA = "\u0393" PwBandsWorkChain = WorkflowFactory("quantumespresso.pw.bands") +ProjwfcBandsWorkChain = WorkflowFactory("wannier90_workflows.projwfcbands") KpointsData = DataFactory("core.array.kpoints") +def check_codes(pw_code, projwfc_code): + """Check that the codes are installed on the same computer.""" + if ( + not any( + [ + pw_code is None, + projwfc_code is None, + ] + ) + and len( + set( + ( + pw_code.computer.pk, + projwfc_code.computer.pk, + ) + ) + ) + != 1 + ): + raise ValueError( + "All selected codes must be installed on the same computer. This is because the " + "PDOS calculations rely on large files that are not retrieved by AiiDA." + ) + def points_per_branch(vector_a, vector_b, reciprocal_cell, bands_kpoints_distance): """function to calculate the number of points per branch depending on the kpoints_distance and the reciprocal cell""" scaled_vector_a = np.array(vector_a) @@ -167,9 +193,11 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): return kpoints -def update_resources(builder, codes): +def update_resources(builder, codes, projwfc_bands=False): set_component_resources(builder.scf.pw, codes.get("pw")) set_component_resources(builder.bands.pw, codes.get("pw")) + if projwfc_bands: + set_component_resources(builder.projwfc, codes.get("projwfc_bands")) def get_builder(codes, structure, parameters, **kwargs): @@ -192,16 +220,32 @@ def get_builder(codes, structure, parameters, **kwargs): "bands": bands_overrides, "relax": relax_overrides, } - bands = PwBandsWorkChain.get_builder_from_protocol( - code=pw_code, - structure=structure, - protocol=protocol, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - overrides=overrides, - **kwargs, - ) + + if parameters["bands"]["projwfc_bands"]: + check_codes(pw_code, codes.get("projwfc_bands")["code"]) + overrides["scf"]["kpoints_distance"] = orm.Float(overrides["scf"]["kpoints_distance"]) #To be removed once the issue is fixed + bands = ProjwfcBandsWorkChain.get_builder_from_protocol( + pw_code=pw_code, + projwfc_code=codes.get("projwfc_bands")["code"], + structure=structure, + protocol=protocol, + electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), + spin_type=SpinType(parameters["workchain"]["spin_type"]), + initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], + overrides=overrides, + **kwargs, + ) + else: # Use the PwBandsWorkChain + bands = PwBandsWorkChain.get_builder_from_protocol( + code=pw_code, + structure=structure, + protocol=protocol, + electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), + spin_type=SpinType(parameters["workchain"]["spin_type"]), + initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], + overrides=overrides, + **kwargs, + ) if structure.pbc != (True, True, True): kpoints_distance = parameters["advanced"]["kpoints_distance"] @@ -219,11 +263,8 @@ def get_builder(codes, structure, parameters, **kwargs): bands.pop("structure", None) bands.pop("clean_workdir", None) # update resources - update_resources(bands, codes) + update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) - if scf_overrides["pw"]["parameters"]["SYSTEM"].get("tot_magnetization") is not None: - bands.scf["pw"]["parameters"]["SYSTEM"].pop("starting_magnetization", None) - bands.bands["pw"]["parameters"]["SYSTEM"].pop("starting_magnetization", None) return bands From 8061d8172f537b05b5ca0e8ac6ce7a0cc2575d94 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 07:30:01 +0000 Subject: [PATCH 02/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/aiidalab_qe/plugins/bands/__init__.py | 1 + src/aiidalab_qe/plugins/bands/setting.py | 4 ++-- src/aiidalab_qe/plugins/bands/workchain.py | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 246de540c..263d29297 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -14,6 +14,7 @@ class BandsOutline(OutlinePanel): SeeK-path tool. """ + projwfc_bands = QEAppComputationalResourcesWidget( description="projwfc.x for bands", default_calc_job_plugin="quantumespresso.projwfc", diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 84a4c057d..f562a8665 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -6,6 +6,7 @@ from aiidalab_qe.common.panel import Panel from aiida import orm + class Setting(Panel): title = "Bands Structure" identifier = "bands" @@ -21,7 +22,7 @@ def __init__(self, **kwargs): value="moderate", ) self.properties_help = ipw.HTML( - """
+ """
The band structure workflow will automatically detect the default path in reciprocal space using the @@ -51,7 +52,6 @@ def __init__(self, **kwargs): ] super().__init__(**kwargs) - @tl.observe("input_structure") def _update_structure(self, _=None): if self.input_structure.pbc == (True, True, False): diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 370a96096..071af7295 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -35,6 +35,7 @@ def check_codes(pw_code, projwfc_code): "PDOS calculations rely on large files that are not retrieved by AiiDA." ) + def points_per_branch(vector_a, vector_b, reciprocal_cell, bands_kpoints_distance): """function to calculate the number of points per branch depending on the kpoints_distance and the reciprocal cell""" scaled_vector_a = np.array(vector_a) @@ -223,7 +224,9 @@ def get_builder(codes, structure, parameters, **kwargs): if parameters["bands"]["projwfc_bands"]: check_codes(pw_code, codes.get("projwfc_bands")["code"]) - overrides["scf"]["kpoints_distance"] = orm.Float(overrides["scf"]["kpoints_distance"]) #To be removed once the issue is fixed + overrides["scf"]["kpoints_distance"] = orm.Float( + overrides["scf"]["kpoints_distance"] + ) # To be removed once the issue is fixed bands = ProjwfcBandsWorkChain.get_builder_from_protocol( pw_code=pw_code, projwfc_code=codes.get("projwfc_bands")["code"], @@ -235,7 +238,7 @@ def get_builder(codes, structure, parameters, **kwargs): overrides=overrides, **kwargs, ) - else: # Use the PwBandsWorkChain + else: # Use the PwBandsWorkChain bands = PwBandsWorkChain.get_builder_from_protocol( code=pw_code, structure=structure, @@ -265,7 +268,6 @@ def get_builder(codes, structure, parameters, **kwargs): # update resources update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) - return bands From 8bde640bc757fa5f5f9a3af32871abca21fbe27a Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Fri, 9 Aug 2024 08:58:28 +0000 Subject: [PATCH 03/21] changes temp --- .../plugins/bands/bands_workchain.py | 316 ++++++++++++++++++ src/aiidalab_qe/plugins/bands/workchain.py | 146 +++++--- 2 files changed, 415 insertions(+), 47 deletions(-) create mode 100644 src/aiidalab_qe/plugins/bands/bands_workchain.py diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py new file mode 100644 index 000000000..7e3f5521b --- /dev/null +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -0,0 +1,316 @@ +import numpy as np +from aiida.plugins import DataFactory, WorkflowFactory +from aiida_quantumespresso.common.types import ElectronicType, SpinType +from aiidalab_qe.plugins.utils import set_component_resources +from aiida import orm +from aiida.engine import ToContext, WorkChain, calcfunction + +GAMMA = "\u0393" + +PwBandsWorkChain = WorkflowFactory("quantumespresso.pw.bands") +ProjwfcBandsWorkChain = WorkflowFactory("wannier90_workflows.projwfcbands") + + +def points_per_branch(vector_a, vector_b, reciprocal_cell, bands_kpoints_distance): + """function to calculate the number of points per branch depending on the kpoints_distance and the reciprocal cell""" + scaled_vector_a = np.array(vector_a) + scaled_vector_b = np.array(vector_b) + reciprocal_vector_a = scaled_vector_a.dot(reciprocal_cell) + reciprocal_vector_b = scaled_vector_b.dot(reciprocal_cell) + distance = np.linalg.norm(reciprocal_vector_a - reciprocal_vector_b) + return max( + 2, int(np.round(distance / bands_kpoints_distance)) + ) # at least two points for each segment, including both endpoints explicitly + + +def calculate_bands_kpoints_distance(kpoints_distance): + """function to calculate the bands_kpoints_distance depending on the kpoints_distance""" + if kpoints_distance >= 0.5: + return 0.1 + elif 0.15 < kpoints_distance < 0.5: + return 0.025 + else: + return 0.015 + + +def generate_kpath_1d(structure, kpoints_distance): + """Return a kpoints object for one dimensional systems (from Gamma to X) + The number of kpoints is calculated based on the kpoints_distance (as in the PwBandsWorkChain protocol) + """ + kpoints = KpointsData() + kpoints.set_cell_from_structure(structure) + reciprocal_cell = kpoints.reciprocal_cell + bands_kpoints_distance = calculate_bands_kpoints_distance(kpoints_distance) + + # Number of points per branch + num_points_per_branch = points_per_branch( + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + reciprocal_cell, + bands_kpoints_distance, + ) + # Generate the kpoints + points = np.linspace( + start=[0.0, 0.0, 0.0], + stop=[0.5, 0.0, 0.0], + endpoint=True, + num=num_points_per_branch, + ) + kpoints.set_kpoints(points.tolist()) + kpoints.labels = [[0, GAMMA], [len(points) - 1, "X"]] + return kpoints + + +def generate_kpath_2d(structure, kpoints_distance, kpath_2d): + """Return a kpoints object for two dimensional systems based on the selected 2D symmetry path + The number of kpoints is calculated based on the kpoints_distance (as in the PwBandsWorkChain protocol) + The 2D symmetry paths are defined as in The Journal of Physical Chemistry Letters 2022 13 (50), 11581-11594 (https://pubs.acs.org/doi/10.1021/acs.jpclett.2c02972) + """ + kpoints = KpointsData() + kpoints.set_cell_from_structure(structure) + reciprocal_cell = kpoints.reciprocal_cell + bands_kpoints_distance = calculate_bands_kpoints_distance(kpoints_distance) + + # dictionary with the 2D symmetry paths + selected_paths = { + "hexagonal": { + "path": [ + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + [0.33333, 0.33333, 0.0], + [1.0, 0.0, 0.0], + ], + "labels": [GAMMA, "M", "K", GAMMA], + }, + "square": { + "path": [ + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + [0.5, 0.5, 0.0], + [1.0, 0.0, 0.0], + ], + "labels": [GAMMA, "X", "M", GAMMA], + }, + "rectangular": { + "path": [ + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + [0.5, 0.5, 0.0], + [0.0, 0.5, 0.0], + [1.0, 0.0, 0.0], + ], + "labels": [GAMMA, "X", "S", "Y", GAMMA], + }, + } + # if the selected path is centered_rectangular or oblique, the path is calculated based on the reciprocal cell + if kpath_2d in ["centered_rectangular", "oblique"]: + a1 = reciprocal_cell[0] + a2 = reciprocal_cell[1] + norm_a1 = np.linalg.norm(a1) + norm_a2 = np.linalg.norm(a2) + cos_gamma = ( + a1.dot(a2) / (norm_a1 * norm_a2) + ) # Angle between a1 and a2 # like in https://pubs.acs.org/doi/10.1021/acs.jpclett.2c02972 + gamma = np.arccos(cos_gamma) + eta = (1 - (norm_a1 / norm_a2) * cos_gamma) / (2 * np.power(np.sin(gamma), 2)) + nu = 0.5 - (eta * norm_a2 * cos_gamma) / norm_a1 + selected_paths["centered_rectangular"] = { + "path": [ + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + [1 - eta, nu, 0], + [0.5, 0.5, 0.0], + [eta, 1 - nu, 0.0], + [1.0, 0.0, 0.0], + ], + "labels": [GAMMA, "X", "H_1", "C", "H", GAMMA], + } + selected_paths["oblique"] = { + "path": [ + [0.0, 0.0, 0.0], + [0.5, 0.0, 0.0], + [1 - eta, nu, 0], + [0.5, 0.5, 0.0], + [eta, 1 - nu, 0.0], + [0.0, 0.5, 0.0], + [1.0, 0.0, 0.0], + ], + "labels": [GAMMA, "X", "H_1", "C", "H", "Y", GAMMA], + } + path = selected_paths[kpath_2d]["path"] + labels = selected_paths[kpath_2d]["labels"] + branches = zip(path[:-1], path[1:]) + + all_kpoints = [] # List to hold all k-points + label_map = [] # List to hold labels and their corresponding k-point indices + + # Calculate the number of points per branch and generate the kpoints + index_offset = 0 # Start index for each segment + for (start, end), label_start, label_end in zip(branches, labels[:-1], labels[1:]): + num_points_per_branch = points_per_branch( + start, end, reciprocal_cell, bands_kpoints_distance + ) + # Exclude endpoint except for the last segment to prevent duplication + points = np.linspace(start, end, num=num_points_per_branch, endpoint=False) + all_kpoints.extend(points) + label_map.append( + (index_offset, label_start) + ) # Label for the start of the segment + index_offset += len(points) + + # Include the last point and its label + all_kpoints.append(path[-1]) + label_map.append((index_offset, labels[-1])) # Label for the last point + + # Set the kpoints and their labels in KpointsData + kpoints.set_kpoints(all_kpoints) + kpoints.labels = label_map + + return kpoints + +def determine_symmetry_path(structure): + # Tolerance for checking equality + cell_lengths = structure.cell_lengths + cell_angles = structure.cell_angles + tolerance = 1e-3 + + # Define symmetry conditions and their corresponding types in a dictionary + symmetry_conditions = { + ( + math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) + and math.isclose(cell_angles[2], 120.0, abs_tol=tolerance) + ): "hexagonal", + ( + math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) + and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) + ): "square", + ( + math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False + and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) + ): "rectangular", + ( + math.isclose( + cell_lengths[1] * math.cos(math.radians(cell_angles[2])), + cell_lengths[0] / 2, + abs_tol=tolerance, + ) + ): "rectangular_centered", + ( + math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False + and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) == False + ): "oblique", + } + + # Check for symmetry type based on conditions + for condition, symmetry_type in symmetry_conditions.items(): + if condition: + if symmetry_type == "rectangular_centered" or "oblique": + cos_gamma = np.array(structure.cell[0]).dot(structure.cell[1]) / ( + cell_lengths[0] * cell_lengths[1] + ) + gamma = np.arccos(cos_gamma) + eta = (1 - (cell_lengths[0] / cell_lengths[1]) * cos_gamma) / ( + 2 * np.power(np.sin(gamma), 2) + ) + nu = 0.5 - (eta * cell_lengths[1] * cos_gamma) / cell_lengths[0] + return generate_2d_path(symmetry_type, eta, nu) + + return generate_2d_path(symmetry_type) + else: + raise ValueError("Invalid symmetry type") + +class BandsWorkChain(WorkChain): + "Workchain to compute the electronic band structure" + label = "bands" + + @classmethod + def define(cls, spec): + super().define(spec) + spec.expose_inputs(PwBandsWorkChain, namespace="bands") + spec.expose_inputs(ProjwfcBandsWorkChain, namespace="bands_projwfc") + + spec.expose_outputs(PwBandsWorkChain) + spec.expose_outputs(ProjwfcBandsWorkChain) + + spec.outline(cls.setup, cls.run_bands, cls.results) + + spec.exit_code(400, "ERROR_WORKCHAIN_FAILED", message="The workchain bands failed.") + + + @classmethod + def get_builder_from_protocol( + cls, + pw_code, + structure, + simulation_mode, + protocol=None, + projwfc_code=None, + overrides=None, + **kwargs, + ): + """ Return a BandsWorkChain builder prepopulated with inputs following the specified protocol + + :param structure: the ``StructureData`` instance to use. + :param pw_code: the ``Code`` instance configured for the ``quantumespresso.pw`` plugin. + :param protocol: protocol to use, if not specified, the default will be used. + :param projwfc_code: the ``Code`` instance configured for the ``quantumespresso.projwfc`` plugin. + :param simulation_mode: hat type of simulation to run normal band or fat bands. + + """ + + builder = cls.get_builder() + + if "simulation_mode" == "normal": + + args = (pw_code, structure, protocol) + builder = PwBandsWorkChain.get_builder_from_protocol( + *args, overrides=overrides, **kwargs + ) + elif "simulation_mode" == "fat_bands": + args = (pw_code, projwfc_code, structure, protocol, projwfc_code) + builder = ProjwfcBandsWorkChain.get_builder_from_protocol( + *args, overrides=overrides, **kwargs + ) + + if structure.pbc != (True, True, True): + kpoints_distance = overrides["scf"]["kpoints_distance"] + if structure.pbc == (True, False, False): + kpoints = generate_kpath_1d(structure, kpoints_distance) + elif structure.pbc == (True, True, False): + kpoints = generate_kpath_2d( + structure, kpoints_distance, determine_symmetry_path(structure) + ) + builder.pop("bands_kpoints_distance") + builder.update({"bands_kpoints": kpoints}) + + return builder + + + def setup(self): + """Define the current workchain""" + if "bands" in self.inputs: + self.ctx.workchain = PwBandsWorkChain + elif "bands_projwfc" in self.inputs: + self.ctx.workchain = ProjwfcBandsWorkChain + else: + self.report("No bands workchain specified") + return self.exit_codes.ERROR_WORKCHAIN_FAILED + + def run_bands(self): + """Run the bands workchain""" + inputs = self.exposed_inputs(self.ctx.workchain) + inputs.update(self.inputs[self.ctx.workchain.get_link_label("bands")]) + return ToContext( + bands=self.ctx.workchain.run.get_submission_node().get_outgoing().one().node + ) + + def results(self): + """Attach the bands results""" + workchain = self.ctx.workchain + + if not workchain.is_finished_ok: + self.report("Bands workchain failed") + return self.exit_codes.ERROR_WORKCHAIN_FAILED + else: + self.out_many(self.ctx.bands.outputs) + self.report("Bands workchain completed successfully") diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 071af7295..888617eab 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -4,6 +4,8 @@ from aiidalab_qe.plugins.utils import set_component_resources from aiida import orm +from .bands_workchain import BandsWorkChain + GAMMA = "\u0393" PwBandsWorkChain = WorkflowFactory("quantumespresso.pw.bands") @@ -195,10 +197,85 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): def update_resources(builder, codes, projwfc_bands=False): - set_component_resources(builder.scf.pw, codes.get("pw")) - set_component_resources(builder.bands.pw, codes.get("pw")) + + if "bands_projwfc" in builder: + label = "bands_projwfc" + else: + label = "bands" + set_component_resources(builder[label]["scf"]["pw"], codes.get("pw")) + set_component_resources(builder[label]["bands"]["pw"], codes.get("pw")) if projwfc_bands: - set_component_resources(builder.projwfc, codes.get("projwfc_bands")) + set_component_resources(builder.bands_projwfc.projwfc.projwfc , codes.get("projwfc_bands")) + + +# def get_builder(codes, structure, parameters, **kwargs): +# """Get a builder for the PwBandsWorkChain.""" +# from copy import deepcopy + +# pw_code = codes.get("pw")["code"] +# protocol = parameters["workchain"]["protocol"] +# scf_overrides = deepcopy(parameters["advanced"]) +# relax_overrides = { +# "base": deepcopy(parameters["advanced"]), +# "base_final_scf": deepcopy(parameters["advanced"]), +# } +# bands_overrides = deepcopy(parameters["advanced"]) +# bands_overrides.pop("kpoints_distance", None) +# bands_overrides["pw"]["parameters"]["SYSTEM"].pop("smearing", None) +# bands_overrides["pw"]["parameters"]["SYSTEM"].pop("degauss", None) +# overrides = { +# "scf": scf_overrides, +# "bands": bands_overrides, +# "relax": relax_overrides, +# } + +# if parameters["bands"]["projwfc_bands"]: +# check_codes(pw_code, codes.get("projwfc_bands")["code"]) +# overrides["scf"]["kpoints_distance"] = orm.Float( +# overrides["scf"]["kpoints_distance"] +# ) # To be removed once the issue is fixed +# bands = ProjwfcBandsWorkChain.get_builder_from_protocol( +# pw_code=pw_code, +# projwfc_code=codes.get("projwfc_bands")["code"], +# structure=structure, +# protocol=protocol, +# electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), +# spin_type=SpinType(parameters["workchain"]["spin_type"]), +# initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], +# overrides=overrides, +# **kwargs, +# ) +# else: # Use the PwBandsWorkChain +# bands = PwBandsWorkChain.get_builder_from_protocol( +# code=pw_code, +# structure=structure, +# protocol=protocol, +# electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), +# spin_type=SpinType(parameters["workchain"]["spin_type"]), +# initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], +# overrides=overrides, +# **kwargs, +# ) + +# if structure.pbc != (True, True, True): +# kpoints_distance = parameters["advanced"]["kpoints_distance"] +# if structure.pbc == (True, False, False): +# kpoints = generate_kpath_1d(structure, kpoints_distance) +# elif structure.pbc == (True, True, False): +# kpoints = generate_kpath_2d( +# structure, kpoints_distance, parameters["bands"]["kpath_2d"] +# ) +# bands.pop("bands_kpoints_distance") +# bands.update({"bands_kpoints": kpoints}) + +# # pop the inputs that are excluded from the expose_inputs +# bands.pop("relax") +# bands.pop("structure", None) +# bands.pop("clean_workdir", None) +# # update resources +# update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) + +# return bands def get_builder(codes, structure, parameters, **kwargs): @@ -222,55 +299,30 @@ def get_builder(codes, structure, parameters, **kwargs): "relax": relax_overrides, } - if parameters["bands"]["projwfc_bands"]: - check_codes(pw_code, codes.get("projwfc_bands")["code"]) - overrides["scf"]["kpoints_distance"] = orm.Float( - overrides["scf"]["kpoints_distance"] - ) # To be removed once the issue is fixed - bands = ProjwfcBandsWorkChain.get_builder_from_protocol( - pw_code=pw_code, - projwfc_code=codes.get("projwfc_bands")["code"], - structure=structure, - protocol=protocol, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - overrides=overrides, - **kwargs, - ) - else: # Use the PwBandsWorkChain - bands = PwBandsWorkChain.get_builder_from_protocol( - code=pw_code, - structure=structure, - protocol=protocol, - electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), - spin_type=SpinType(parameters["workchain"]["spin_type"]), - initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], - overrides=overrides, - **kwargs, - ) - - if structure.pbc != (True, True, True): - kpoints_distance = parameters["advanced"]["kpoints_distance"] - if structure.pbc == (True, False, False): - kpoints = generate_kpath_1d(structure, kpoints_distance) - elif structure.pbc == (True, True, False): - kpoints = generate_kpath_2d( - structure, kpoints_distance, parameters["bands"]["kpath_2d"] - ) - bands.pop("bands_kpoints_distance") - bands.update({"bands_kpoints": kpoints}) + check_codes(pw_code, codes.get("projwfc_bands")["code"]) - # pop the inputs that are excluded from the expose_inputs - bands.pop("relax") - bands.pop("structure", None) - bands.pop("clean_workdir", None) + if parameters["bands"]["projwfc_bands"]: + simulation_mode = "fat_bands" + else: + simulation_mode = "normal" + + bands = BandsWorkChain.get_builder_from_protocol( + pw_code=pw_code, + projwfc_code=codes.get("projwfc_bands")["code"], + structure=structure, + simulation_mode=simulation_mode, + protocol=protocol, + electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), + spin_type=SpinType(parameters["workchain"]["spin_type"]), + initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], + overrides=overrides, + **kwargs, + ) # update resources update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) return bands - def update_inputs(inputs, ctx): """Update the inputs using context.""" inputs.structure = ctx.current_structure @@ -282,7 +334,7 @@ def update_inputs(inputs, ctx): workchain_and_builder = { - "workchain": PwBandsWorkChain, + "workchain": BandsWorkChain, "exclude": ("structure", "relax"), "get_builder": get_builder, "update_inputs": update_inputs, From 16021b23ec680846053c0d3a76564c5d50e7c472 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Thu, 19 Sep 2024 06:57:50 +0000 Subject: [PATCH 04/21] Defining new WorkChain BandsWorkChain --- .../plugins/bands/bands_workchain.py | 158 ++++++++++++++---- src/aiidalab_qe/plugins/bands/result.py | 5 +- src/aiidalab_qe/plugins/bands/workchain.py | 155 +++++++---------- 3 files changed, 187 insertions(+), 131 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index 7e3f5521b..12ff117c5 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -1,14 +1,17 @@ import numpy as np from aiida.plugins import DataFactory, WorkflowFactory -from aiida_quantumespresso.common.types import ElectronicType, SpinType -from aiidalab_qe.plugins.utils import set_component_resources from aiida import orm -from aiida.engine import ToContext, WorkChain, calcfunction +from aiida.engine import WorkChain +import math + +from aiida.common import AttributeDict + GAMMA = "\u0393" PwBandsWorkChain = WorkflowFactory("quantumespresso.pw.bands") ProjwfcBandsWorkChain = WorkflowFactory("wannier90_workflows.projwfcbands") +KpointsData = DataFactory("core.array.kpoints") def points_per_branch(vector_a, vector_b, reciprocal_cell, bands_kpoints_distance): @@ -168,6 +171,7 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): return kpoints + def determine_symmetry_path(structure): # Tolerance for checking equality cell_lengths = structure.cell_lengths @@ -185,7 +189,7 @@ def determine_symmetry_path(structure): and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "square", ( - math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False + not math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "rectangular", ( @@ -196,8 +200,8 @@ def determine_symmetry_path(structure): ) ): "rectangular_centered", ( - math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) == False - and math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) == False + not math.isclose(cell_lengths[0], cell_lengths[1], abs_tol=tolerance) + and not math.isclose(cell_angles[2], 90.0, abs_tol=tolerance) ): "oblique", } @@ -213,29 +217,66 @@ def determine_symmetry_path(structure): 2 * np.power(np.sin(gamma), 2) ) nu = 0.5 - (eta * cell_lengths[1] * cos_gamma) / cell_lengths[0] - return generate_2d_path(symmetry_type, eta, nu) + return generate_kpath_2d(symmetry_type, eta, nu) - return generate_2d_path(symmetry_type) + return generate_kpath_2d(symmetry_type) else: raise ValueError("Invalid symmetry type") + class BandsWorkChain(WorkChain): "Workchain to compute the electronic band structure" + label = "bands" @classmethod def define(cls, spec): super().define(spec) - spec.expose_inputs(PwBandsWorkChain, namespace="bands") - spec.expose_inputs(ProjwfcBandsWorkChain, namespace="bands_projwfc") - spec.expose_outputs(PwBandsWorkChain) - spec.expose_outputs(ProjwfcBandsWorkChain) + spec.input("structure", valid_type=orm.StructureData) + spec.expose_inputs( + PwBandsWorkChain, + namespace="bands", + exclude=["structure", "relax"], + namespace_options={ + "required": False, + "populate_defaults": False, + "help": "Inputs for the `PwBandsWorkChain`, simulation mode normal.", + }, + ) + spec.expose_inputs( + ProjwfcBandsWorkChain, + namespace="bands_projwfc", + exclude=["structure", "relax"], + namespace_options={ + "required": False, + "populate_defaults": False, + "help": "Inputs for the `ProjwfcBandsWorkChain`, simulation mode fat_bands.", + }, + ) + + spec.expose_outputs( + PwBandsWorkChain, + namespace="bands", + namespace_options={ + "required": False, + "help": "Outputs of the `PwBandsWorkChain`.", + }, + ) + spec.expose_outputs( + ProjwfcBandsWorkChain, + namespace="bands_projwfc", + namespace_options={ + "required": False, + "help": "Outputs of the `PwBandsWorkChain`.", + }, + ) spec.outline(cls.setup, cls.run_bands, cls.results) - spec.exit_code(400, "ERROR_WORKCHAIN_FAILED", message="The workchain bands failed.") - + spec.exit_code( + 400, "ERROR_WORKCHAIN_FAILED", message="The workchain bands failed." + ) @classmethod def get_builder_from_protocol( @@ -248,69 +289,114 @@ def get_builder_from_protocol( overrides=None, **kwargs, ): - """ Return a BandsWorkChain builder prepopulated with inputs following the specified protocol - + """Return a BandsWorkChain builder prepopulated with inputs following the specified protocol + :param structure: the ``StructureData`` instance to use. :param pw_code: the ``Code`` instance configured for the ``quantumespresso.pw`` plugin. :param protocol: protocol to use, if not specified, the default will be used. :param projwfc_code: the ``Code`` instance configured for the ``quantumespresso.projwfc`` plugin. :param simulation_mode: hat type of simulation to run normal band or fat bands. - + """ builder = cls.get_builder() - if "simulation_mode" == "normal": - + if simulation_mode == "normal": args = (pw_code, structure, protocol) - builder = PwBandsWorkChain.get_builder_from_protocol( + builder_bands = PwBandsWorkChain.get_builder_from_protocol( *args, overrides=overrides, **kwargs ) - elif "simulation_mode" == "fat_bands": - args = (pw_code, projwfc_code, structure, protocol, projwfc_code) - builder = ProjwfcBandsWorkChain.get_builder_from_protocol( - *args, overrides=overrides, **kwargs + + builder_bands.bands.pw.metadata.options["resources"] = { + "num_machines": 1, + "num_mpiprocs_per_machine": 1, + } + builder_bands.scf.pw.metadata.options["resources"] = { + "num_machines": 1, + "num_mpiprocs_per_machine": 1, + } + + if structure.pbc != (True, True, True): + kpoints_distance = overrides["scf"]["kpoints_distance"] + if structure.pbc == (True, False, False): + kpoints = generate_kpath_1d(structure, kpoints_distance) + elif structure.pbc == (True, True, False): + kpoints = generate_kpath_2d( + structure, kpoints_distance, determine_symmetry_path(structure) + ) + builder_bands.pop("bands_kpoints_distance") + builder_bands.update({"bands_kpoints": kpoints}) + + builder.pop("bands_projwfc") + builder_bands.pop("relax", None) + builder_bands.pop("structure", None) + builder.bands = builder_bands + builder.structure = structure + + elif simulation_mode == "fat_bands": + args = ( + pw_code, + projwfc_code, + structure, + ) + builder_bands_projwfc = ProjwfcBandsWorkChain.get_builder_from_protocol( + *args, protocol=protocol, overrides=overrides, **kwargs ) - if structure.pbc != (True, True, True): - kpoints_distance = overrides["scf"]["kpoints_distance"] + if structure.pbc != (True, True, True): + kpoints_distance = overrides["scf"]["kpoints_distance"] if structure.pbc == (True, False, False): kpoints = generate_kpath_1d(structure, kpoints_distance) elif structure.pbc == (True, True, False): kpoints = generate_kpath_2d( structure, kpoints_distance, determine_symmetry_path(structure) ) - builder.pop("bands_kpoints_distance") - builder.update({"bands_kpoints": kpoints}) + builder_bands_projwfc.bands.pop("bands_kpoints_distance") + builder_bands_projwfc.bands.update({"bands_kpoints": kpoints}) + + builder.pop("bands") + builder_bands_projwfc.pop("relax", None) + builder_bands_projwfc.pop("structure", None) + builder.bands_projwfc = builder_bands_projwfc + builder.structure = structure return builder - def setup(self): """Define the current workchain""" + self.ctx.current_structure = self.inputs.structure if "bands" in self.inputs: + self.ctx.key = "bands" self.ctx.workchain = PwBandsWorkChain elif "bands_projwfc" in self.inputs: + self.ctx.key = "bands_projwfc" self.ctx.workchain = ProjwfcBandsWorkChain else: self.report("No bands workchain specified") return self.exit_codes.ERROR_WORKCHAIN_FAILED - + def run_bands(self): """Run the bands workchain""" - inputs = self.exposed_inputs(self.ctx.workchain) - inputs.update(self.inputs[self.ctx.workchain.get_link_label("bands")]) - return ToContext( - bands=self.ctx.workchain.run.get_submission_node().get_outgoing().one().node + inputs = AttributeDict( + self.exposed_inputs(self.ctx.workchain, namespace=self.ctx.key) ) + inputs.metadata.call_link_label = self.ctx.key + inputs.structure = self.ctx.current_structure + future = self.submit(self.ctx.workchain, **inputs) + self.report(f"submitting `WorkChain` ") + self.to_context(**{self.ctx.key: future}) def results(self): """Attach the bands results""" - workchain = self.ctx.workchain + workchain = self.ctx[self.ctx.key] if not workchain.is_finished_ok: self.report("Bands workchain failed") return self.exit_codes.ERROR_WORKCHAIN_FAILED else: - self.out_many(self.ctx.bands.outputs) + self.out_many( + self.exposed_outputs( + self.ctx[self.ctx.key], self.ctx.workchain, namespace=self.ctx.key + ) + ) self.report("Bands workchain completed successfully") diff --git a/src/aiidalab_qe/plugins/bands/result.py b/src/aiidalab_qe/plugins/bands/result.py index 03b7630cd..3327400e2 100644 --- a/src/aiidalab_qe/plugins/bands/result.py +++ b/src/aiidalab_qe/plugins/bands/result.py @@ -16,7 +16,10 @@ def __init__(self, node=None, **kwargs): def _update_view(self): # Check if the workchain has the outputs try: - bands_node = self.node.outputs.bands + if "bands" in self.node.outputs.bands: + bands_node = self.node.outputs.bands.bands + elif "bands_projwfc" in self.node.outputs.bands: + bands_node = self.node.outputs.bands.bands_projwfc except AttributeError: bands_node = None diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 888617eab..fc5f34ea9 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -2,8 +2,6 @@ from aiida.plugins import DataFactory, WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType from aiidalab_qe.plugins.utils import set_component_resources -from aiida import orm - from .bands_workchain import BandsWorkChain GAMMA = "\u0393" @@ -196,86 +194,33 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): return kpoints -def update_resources(builder, codes, projwfc_bands=False): - - if "bands_projwfc" in builder: - label = "bands_projwfc" - else: - label = "bands" - set_component_resources(builder[label]["scf"]["pw"], codes.get("pw")) - set_component_resources(builder[label]["bands"]["pw"], codes.get("pw")) - if projwfc_bands: - set_component_resources(builder.bands_projwfc.projwfc.projwfc , codes.get("projwfc_bands")) - - -# def get_builder(codes, structure, parameters, **kwargs): -# """Get a builder for the PwBandsWorkChain.""" -# from copy import deepcopy - -# pw_code = codes.get("pw")["code"] -# protocol = parameters["workchain"]["protocol"] -# scf_overrides = deepcopy(parameters["advanced"]) -# relax_overrides = { -# "base": deepcopy(parameters["advanced"]), -# "base_final_scf": deepcopy(parameters["advanced"]), -# } -# bands_overrides = deepcopy(parameters["advanced"]) -# bands_overrides.pop("kpoints_distance", None) -# bands_overrides["pw"]["parameters"]["SYSTEM"].pop("smearing", None) -# bands_overrides["pw"]["parameters"]["SYSTEM"].pop("degauss", None) -# overrides = { -# "scf": scf_overrides, -# "bands": bands_overrides, -# "relax": relax_overrides, -# } - -# if parameters["bands"]["projwfc_bands"]: -# check_codes(pw_code, codes.get("projwfc_bands")["code"]) -# overrides["scf"]["kpoints_distance"] = orm.Float( -# overrides["scf"]["kpoints_distance"] -# ) # To be removed once the issue is fixed -# bands = ProjwfcBandsWorkChain.get_builder_from_protocol( -# pw_code=pw_code, -# projwfc_code=codes.get("projwfc_bands")["code"], -# structure=structure, -# protocol=protocol, -# electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), -# spin_type=SpinType(parameters["workchain"]["spin_type"]), -# initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], -# overrides=overrides, -# **kwargs, -# ) -# else: # Use the PwBandsWorkChain -# bands = PwBandsWorkChain.get_builder_from_protocol( -# code=pw_code, -# structure=structure, -# protocol=protocol, -# electronic_type=ElectronicType(parameters["workchain"]["electronic_type"]), -# spin_type=SpinType(parameters["workchain"]["spin_type"]), -# initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], -# overrides=overrides, -# **kwargs, -# ) - -# if structure.pbc != (True, True, True): -# kpoints_distance = parameters["advanced"]["kpoints_distance"] -# if structure.pbc == (True, False, False): -# kpoints = generate_kpath_1d(structure, kpoints_distance) -# elif structure.pbc == (True, True, False): -# kpoints = generate_kpath_2d( -# structure, kpoints_distance, parameters["bands"]["kpath_2d"] -# ) -# bands.pop("bands_kpoints_distance") -# bands.update({"bands_kpoints": kpoints}) - -# # pop the inputs that are excluded from the expose_inputs -# bands.pop("relax") -# bands.pop("structure", None) -# bands.pop("clean_workdir", None) -# # update resources -# update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) - -# return bands +# def set_component_resources(component, code_info): +# """Set the resources for a given component based on the code info and return the updated metadata.""" +# if code_info: +# # Directly modify the existing 'resources' in 'metadata' +# component["metadata"]["options"]["resources"] = { +# "num_machines": code_info["nodes"], +# "num_mpiprocs_per_machine": code_info["ntasks_per_node"], +# "num_cores_per_mpiproc": code_info["cpus_per_task"], +# } +# # Optionally set 'max_wallclock_seconds' if it exists +# if "max_wallclock_seconds" in code_info: +# component["metadata"]["options"]["max_wallclock_seconds"] = code_info["max_wallclock_seconds"] + +# # Return the updated 'metadata' field from the component +# return component + + +def update_resources(builder, codes): + if "bands" in builder: + set_component_resources(builder.bands.scf.pw, codes.get("pw")) + set_component_resources(builder.bands.bands.pw, codes.get("pw")) + elif "bands_projwfc" in builder: + set_component_resources(builder.bands_projwfc.scf.pw, codes.get("pw")) + set_component_resources(builder.bands_projwfc.bands.pw, codes.get("pw")) + set_component_resources( + builder.bands_projwfc.projwfc.projwfc, codes.get("projwfc_bands") + ) def get_builder(codes, structure, parameters, **kwargs): @@ -293,20 +238,28 @@ def get_builder(codes, structure, parameters, **kwargs): bands_overrides.pop("kpoints_distance", None) bands_overrides["pw"]["parameters"]["SYSTEM"].pop("smearing", None) bands_overrides["pw"]["parameters"]["SYSTEM"].pop("degauss", None) + + check_codes(pw_code, codes.get("projwfc_bands")["code"]) + + # scf_overrides["pw"].update(set_component_resources(component, codes.get("pw"))) + # relax_overrides["base"]["pw"].update(set_component_resources(component, codes.get("pw"))) + # relax_overrides["base_final_scf"]["pw"].update(set_component_resources(component, codes.get("pw"))) + # bands_overrides["pw"].update(set_component_resources(component, codes.get("pw"))) + # projwfc_overrides = set_component_resources(component, codes.get("projwfc_bands")) + overrides = { "scf": scf_overrides, "bands": bands_overrides, "relax": relax_overrides, + # "projwfc": projwfc_overrides, } - check_codes(pw_code, codes.get("projwfc_bands")["code"]) - if parameters["bands"]["projwfc_bands"]: simulation_mode = "fat_bands" else: simulation_mode = "normal" - bands = BandsWorkChain.get_builder_from_protocol( + bands_builder = BandsWorkChain.get_builder_from_protocol( pw_code=pw_code, projwfc_code=codes.get("projwfc_bands")["code"], structure=structure, @@ -317,20 +270,34 @@ def get_builder(codes, structure, parameters, **kwargs): initial_magnetic_moments=parameters["advanced"]["initial_magnetic_moments"], overrides=overrides, **kwargs, - ) - # update resources - update_resources(bands, codes, parameters["bands"]["projwfc_bands"]) + ) + update_resources(bands_builder, codes) + + return bands_builder - return bands def update_inputs(inputs, ctx): """Update the inputs using context.""" inputs.structure = ctx.current_structure - inputs.scf.pw.parameters = inputs.scf.pw.parameters.get_dict() - if ctx.current_number_of_bands: - inputs.scf.pw.parameters.setdefault("SYSTEM", {}).setdefault( - "nbnd", ctx.current_number_of_bands - ) + + # inputs.bands.pw.parameters = inputs.bands.pw.parameters.get_dict() + # if ctx.bands.bands.current_number_of_bands: + # inputs.bands.bands.scf.pw.parameters.setdefault("SYSTEM", {}).setdefault( + # "nbnd", ctx.bands.bands.current_number_of_bands + # ) + + # labels_namespace = ["bands", "bands_projwfc"] + + # for label in labels_namespace: + # if label in ctx.bands: + # # Update parameters + # inputs[label].scf.pw.parameters = ctx[label].scf.pw.parameters.get_dict() + + # # Set the number of bands if available + # if ctx[label].current_number_of_bands: + # inputs[label].scf.pw.parameters.setdefault("SYSTEM", {}).setdefault( + # "nbnd", ctx[label].current_number_of_bands + # ) workchain_and_builder = { From 508b2bd82c5beead251587c975548e38af0c4615 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Tue, 24 Sep 2024 13:32:55 +0000 Subject: [PATCH 05/21] modification of workchain and projwfc --- src/aiidalab_qe/common/bandpdoswidget.py | 14 +++++- .../plugins/bands/bands_workchain.py | 46 ++++++++++--------- src/aiidalab_qe/plugins/bands/setting.py | 19 -------- src/aiidalab_qe/plugins/bands/workchain.py | 13 ------ .../plugins/electronic_structure/result.py | 5 +- tests/test_plugins_bands.py | 12 ++--- 6 files changed, 46 insertions(+), 63 deletions(-) diff --git a/src/aiidalab_qe/common/bandpdoswidget.py b/src/aiidalab_qe/common/bandpdoswidget.py index 0550c93a1..f5e6f79cb 100644 --- a/src/aiidalab_qe/common/bandpdoswidget.py +++ b/src/aiidalab_qe/common/bandpdoswidget.py @@ -420,7 +420,7 @@ class BandPdosWidget(ipw.VBox): Select the style of plotting the projected density of states.
""" ) - projected_bands_width = 0.5 + projected_bands_width = 0.05 def __init__(self, bands=None, pdos=None, **kwargs): if bands is None and pdos is None: @@ -472,6 +472,15 @@ def __init__(self, bands=None, pdos=None, **kwargs): description="Add `fat bands` projections", ) + self.project_bands_width = ipw.FloatSlider( + description="Width", + min=0.01, + max=1.0, + step=0.01, + value=0.5, + style={"description_width": "initial"}, + ) + # Information for the plot self.pdos_data = self._get_pdos_data() self.bands_data = self._get_bands_data() @@ -494,6 +503,7 @@ def __init__(self, bands=None, pdos=None, **kwargs): # If projections are available in the bands data, include the box to plot fat-bands if self.bands_data and "projected_bands" in self.bands_data: pdos_options_list.insert(4, self.project_bands_box) + pdos_options_list.insert(5, self.project_bands_width) self.pdos_options = ipw.VBox(pdos_options_list) @@ -583,7 +593,7 @@ def _get_bands_data(self): group_tag=self.dos_atoms_group.value, plot_tag=self.dos_plot_group.value, selected_atoms=expanded_selection, - bands_width=self.projected_bands_width, + bands_width=self.project_bands_width.value, ) return bands return None diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index 12ff117c5..a525aaedd 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -316,21 +316,22 @@ def get_builder_from_protocol( "num_mpiprocs_per_machine": 1, } - if structure.pbc != (True, True, True): - kpoints_distance = overrides["scf"]["kpoints_distance"] - if structure.pbc == (True, False, False): - kpoints = generate_kpath_1d(structure, kpoints_distance) - elif structure.pbc == (True, True, False): - kpoints = generate_kpath_2d( - structure, kpoints_distance, determine_symmetry_path(structure) - ) - builder_bands.pop("bands_kpoints_distance") - builder_bands.update({"bands_kpoints": kpoints}) - builder.pop("bands_projwfc") builder_bands.pop("relax", None) builder_bands.pop("structure", None) builder.bands = builder_bands + + if structure.pbc != (True, True, True): + kpoints_distance = overrides["scf"]["kpoints_distance"] + if structure.pbc == (True, False, False): + kpoints = generate_kpath_1d(structure, kpoints_distance) + elif structure.pbc == (True, True, False): + kpoints = generate_kpath_2d( + structure, kpoints_distance, determine_symmetry_path(structure) + ) + + builder.bands.pop("bands_kpoints_distance") + builder.bands.update({"bands_kpoints": kpoints}) builder.structure = structure elif simulation_mode == "fat_bands": @@ -343,21 +344,22 @@ def get_builder_from_protocol( *args, protocol=protocol, overrides=overrides, **kwargs ) - if structure.pbc != (True, True, True): - kpoints_distance = overrides["scf"]["kpoints_distance"] - if structure.pbc == (True, False, False): - kpoints = generate_kpath_1d(structure, kpoints_distance) - elif structure.pbc == (True, True, False): - kpoints = generate_kpath_2d( - structure, kpoints_distance, determine_symmetry_path(structure) - ) - builder_bands_projwfc.bands.pop("bands_kpoints_distance") - builder_bands_projwfc.bands.update({"bands_kpoints": kpoints}) - builder.pop("bands") builder_bands_projwfc.pop("relax", None) builder_bands_projwfc.pop("structure", None) builder.bands_projwfc = builder_bands_projwfc + + if structure.pbc != (True, True, True): + kpoints_distance = overrides["scf"]["kpoints_distance"] + if structure.pbc == (True, False, False): + kpoints = generate_kpath_1d(structure, kpoints_distance) + elif structure.pbc == (True, True, False): + kpoints = generate_kpath_2d( + structure, kpoints_distance, determine_symmetry_path(structure) + ) + builder.bands_projwfc.pop("bands_kpoints_distance") + builder.bands_projwfc.update({"bands_kpoints": kpoints}) + builder.structure = structure return builder diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index f562a8665..932580638 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -52,34 +52,15 @@ def __init__(self, **kwargs): ] super().__init__(**kwargs) - @tl.observe("input_structure") - def _update_structure(self, _=None): - if self.input_structure.pbc == (True, True, False): - self.properties_help.value = """
- Please select one of the five 2D Bravais lattices corresponding to your system. -
""" - self.kpath_2d.visibility = "visible" - elif self.input_structure.pbc == (True, False, False): - self.properties_help.value = """
- The band structure path for systems with periodicity x is from Gamma to X. -
""" - self.kpath_2d.visibility = "hidden" - else: - self.kpath_2d.visibility = "hidden" - def get_panel_value(self): """Return a dictionary with the input parameters for the plugin.""" return { - "kpath_2d": self.kpath_2d.value, "projwfc_bands": self.projwfc_bands.value, } def set_panel_value(self, input_dict): - """Load a dictionary with the input parameters for the plugin.""" - self.kpath_2d.value = input_dict.get("kpath_2d", "hexagonal") self.projwfc_bands.value = input_dict.get("projwfc_bands", False) def reset(self): """Reset the panel to its default values.""" - self.kpath_2d.value = "hexagonal" self.projwfc_bands.value = False diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index fc5f34ea9..cd86babe9 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -286,19 +286,6 @@ def update_inputs(inputs, ctx): # "nbnd", ctx.bands.bands.current_number_of_bands # ) - # labels_namespace = ["bands", "bands_projwfc"] - - # for label in labels_namespace: - # if label in ctx.bands: - # # Update parameters - # inputs[label].scf.pw.parameters = ctx[label].scf.pw.parameters.get_dict() - - # # Set the number of bands if available - # if ctx[label].current_number_of_bands: - # inputs[label].scf.pw.parameters.setdefault("SYSTEM", {}).setdefault( - # "nbnd", ctx[label].current_number_of_bands - # ) - workchain_and_builder = { "workchain": BandsWorkChain, diff --git a/src/aiidalab_qe/plugins/electronic_structure/result.py b/src/aiidalab_qe/plugins/electronic_structure/result.py index 1142ae450..d75b8d363 100644 --- a/src/aiidalab_qe/plugins/electronic_structure/result.py +++ b/src/aiidalab_qe/plugins/electronic_structure/result.py @@ -20,7 +20,10 @@ def _update_view(self): pdos_node = None try: - bands_node = self.node.outputs.bands + if "bands" in self.node.outputs.bands: + bands_node = self.node.outputs.bands.bands + elif "bands_projwfc" in self.node.outputs.bands: + bands_node = self.node.outputs.bands.bands_projwfc except AttributeError: bands_node = None _bands_dos_widget = BandPdosWidget(bands=bands_node, pdos=pdos_node) diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index 27533eec0..6288738de 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -38,15 +38,15 @@ def test_result(generate_qeapp_workchain): def test_structure_1d(generate_qeapp_workchain, generate_structure_data): structure = generate_structure_data("silicon", pbc=(True, False, False)) wkchain = generate_qeapp_workchain(structure=structure) - assert "bands_kpoints_distance" not in wkchain.inputs.bands - assert "bands_kpoints" in wkchain.inputs.bands - assert len(wkchain.inputs.bands.bands_kpoints.labels) == 2 + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands + assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 2 @pytest.mark.usefixtures("sssp") def test_structure_2d(generate_qeapp_workchain, generate_structure_data): structure = generate_structure_data("silicon", pbc=(True, True, False)) wkchain = generate_qeapp_workchain(structure=structure) - assert "bands_kpoints_distance" not in wkchain.inputs.bands - assert "bands_kpoints" in wkchain.inputs.bands - assert len(wkchain.inputs.bands.bands_kpoints.labels) == 4 + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands + assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 4 From 88e06893a217f98367b9f1dfb111de940cb01f30 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 25 Sep 2024 12:37:02 +0000 Subject: [PATCH 06/21] updating tests --- docs/source/development/architecture.rst | 2 +- docs/source/development/plugin.rst | 2 +- src/aiidalab_qe/common/bandpdoswidget.py | 4 +- .../plugins/bands/bands_workchain.py | 50 +++++-------- src/aiidalab_qe/plugins/bands/setting.py | 13 ---- src/aiidalab_qe/plugins/bands/workchain.py | 24 ------ tests/conftest.py | 75 +++++++++++++++---- tests/test_plugins_bands.py | 16 ++-- tests/test_submit_qe_workchain.py | 9 ++- .../test_create_builder_default.yml | 1 - 10 files changed, 97 insertions(+), 99 deletions(-) diff --git a/docs/source/development/architecture.rst b/docs/source/development/architecture.rst index 4daf5c908..5627b6c78 100644 --- a/docs/source/development/architecture.rst +++ b/docs/source/development/architecture.rst @@ -49,7 +49,7 @@ The dictionary has the following structure: "pseudo_family": "SSSP/1.3/PBEsol/efficiency", "kpoints_distance": 0.5, }, - "bands": {"kpath_2d": "hexagonal"}, + "bands": {}, "pdos": {...}, "plugin_1": {...}, "plugin_2": {...}, diff --git a/docs/source/development/plugin.rst b/docs/source/development/plugin.rst index 9176e69fd..5199e70cf 100644 --- a/docs/source/development/plugin.rst +++ b/docs/source/development/plugin.rst @@ -210,7 +210,7 @@ The `parameters` passed to the `get_builder` function has the following structur "pseudo_family": "SSSP/1.3/PBEsol/efficiency", "kpoints_distance": 0.5, }, - "bands": {"kpath_2d": "hexagonal"}, + "bands": {}, "pdos": {...}, "eos": {...}, "plugin_1": {...}, diff --git a/src/aiidalab_qe/common/bandpdoswidget.py b/src/aiidalab_qe/common/bandpdoswidget.py index f5e6f79cb..265164eaa 100644 --- a/src/aiidalab_qe/common/bandpdoswidget.py +++ b/src/aiidalab_qe/common/bandpdoswidget.py @@ -674,8 +674,8 @@ def _prepare_projections_to_plot(bands_data, projections, bands_width): for proj in projections[spin]: # Create the upper and lower boundary of the fat bands based on the orbital projections - y_bands_proj_upper = y_bands + bands_width * proj["projections"].T - y_bands_proj_lower = y_bands - bands_width * proj["projections"].T + y_bands_proj_upper = y_bands + bands_width / 2 * proj["projections"].T + y_bands_proj_lower = y_bands - bands_width / 2 * proj["projections"].T # As mentioned above, the bands need to be concatenated with their mirror image # to create the filled areas properly y_bands_mirror = np.hstack( diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index a525aaedd..5f872f923 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -208,20 +208,9 @@ def determine_symmetry_path(structure): # Check for symmetry type based on conditions for condition, symmetry_type in symmetry_conditions.items(): if condition: - if symmetry_type == "rectangular_centered" or "oblique": - cos_gamma = np.array(structure.cell[0]).dot(structure.cell[1]) / ( - cell_lengths[0] * cell_lengths[1] - ) - gamma = np.arccos(cos_gamma) - eta = (1 - (cell_lengths[0] / cell_lengths[1]) * cos_gamma) / ( - 2 * np.power(np.sin(gamma), 2) - ) - nu = 0.5 - (eta * cell_lengths[1] * cos_gamma) / cell_lengths[0] - return generate_kpath_2d(symmetry_type, eta, nu) + return symmetry_type - return generate_kpath_2d(symmetry_type) - else: - raise ValueError("Invalid symmetry type") + raise ValueError("Invalid symmetry type") class BandsWorkChain(WorkChain): @@ -283,7 +272,7 @@ def get_builder_from_protocol( cls, pw_code, structure, - simulation_mode, + simulation_mode="normal", protocol=None, projwfc_code=None, overrides=None, @@ -307,33 +296,30 @@ def get_builder_from_protocol( *args, overrides=overrides, **kwargs ) - builder_bands.bands.pw.metadata.options["resources"] = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } - builder_bands.scf.pw.metadata.options["resources"] = { - "num_machines": 1, - "num_mpiprocs_per_machine": 1, - } - builder.pop("bands_projwfc") builder_bands.pop("relax", None) builder_bands.pop("structure", None) builder.bands = builder_bands if structure.pbc != (True, True, True): - kpoints_distance = overrides["scf"]["kpoints_distance"] + kpoints_distance = overrides.get("scf", {}).get( + "kpoints_distance", builder.bands.scf.kpoints_distance + ) if structure.pbc == (True, False, False): kpoints = generate_kpath_1d(structure, kpoints_distance) elif structure.pbc == (True, True, False): kpoints = generate_kpath_2d( - structure, kpoints_distance, determine_symmetry_path(structure) + structure=structure, + kpoints_distance=kpoints_distance, + kpath_2d=determine_symmetry_path(structure), ) - builder.bands.pop("bands_kpoints_distance") - builder.bands.update({"bands_kpoints": kpoints}) + builder.bands.pop("bands_kpoints_distance") + builder.bands.update({"bands_kpoints": kpoints}) builder.structure = structure + return builder + elif simulation_mode == "fat_bands": args = ( pw_code, @@ -355,14 +341,16 @@ def get_builder_from_protocol( kpoints = generate_kpath_1d(structure, kpoints_distance) elif structure.pbc == (True, True, False): kpoints = generate_kpath_2d( - structure, kpoints_distance, determine_symmetry_path(structure) + structure=structure, + kpoints_distance=kpoints_distance, + kpath_2d=determine_symmetry_path(structure), ) - builder.bands_projwfc.pop("bands_kpoints_distance") - builder.bands_projwfc.update({"bands_kpoints": kpoints}) + builder.bands_projwfc.pop("bands_kpoints_distance") + builder.bands_projwfc.update({"bands_kpoints": kpoints}) builder.structure = structure - return builder + return builder def setup(self): """Define the current workchain""" diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 932580638..d81eacc4f 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -32,23 +32,10 @@ def __init__(self, **kwargs): description="Flat bands calculation", value=False, ) - self.kpath_2d = ipw.Dropdown( - description="Lattice:", - options=[ - ("Hexagonal", "hexagonal"), - ("Square", "square"), - ("Rectangular", "rectangular"), - ("Centered Rectangular", "centered_rectangular"), - ("Oblique", "oblique"), - ], - value="hexagonal", - ) - self.kpath_2d.layout.visibility = "hidden" self.children = [ self.settings_title, self.properties_help, self.projwfc_bands, - self.kpath_2d, ] super().__init__(**kwargs) diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index cd86babe9..8b3f0c865 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -194,23 +194,6 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): return kpoints -# def set_component_resources(component, code_info): -# """Set the resources for a given component based on the code info and return the updated metadata.""" -# if code_info: -# # Directly modify the existing 'resources' in 'metadata' -# component["metadata"]["options"]["resources"] = { -# "num_machines": code_info["nodes"], -# "num_mpiprocs_per_machine": code_info["ntasks_per_node"], -# "num_cores_per_mpiproc": code_info["cpus_per_task"], -# } -# # Optionally set 'max_wallclock_seconds' if it exists -# if "max_wallclock_seconds" in code_info: -# component["metadata"]["options"]["max_wallclock_seconds"] = code_info["max_wallclock_seconds"] - -# # Return the updated 'metadata' field from the component -# return component - - def update_resources(builder, codes): if "bands" in builder: set_component_resources(builder.bands.scf.pw, codes.get("pw")) @@ -241,17 +224,10 @@ def get_builder(codes, structure, parameters, **kwargs): check_codes(pw_code, codes.get("projwfc_bands")["code"]) - # scf_overrides["pw"].update(set_component_resources(component, codes.get("pw"))) - # relax_overrides["base"]["pw"].update(set_component_resources(component, codes.get("pw"))) - # relax_overrides["base_final_scf"]["pw"].update(set_component_resources(component, codes.get("pw"))) - # bands_overrides["pw"].update(set_component_resources(component, codes.get("pw"))) - # projwfc_overrides = set_component_resources(component, codes.get("projwfc_bands")) - overrides = { "scf": scf_overrides, "bands": bands_overrides, "relax": relax_overrides, - # "projwfc": projwfc_overrides, } if parameters["bands"]["projwfc_bands"]: diff --git a/tests/conftest.py b/tests/conftest.py index 100ebdcfd..01fa32a3c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,6 +90,14 @@ def _generate_structure_data(name="silicon", pbc=(True, True, True)): for site in sites: structure.append_atom(position=site[2], symbols=site[0], name=site[1]) + + elif name == "MoS2": + cell = [[3.1922, 0, 0], [-1.5961, 2.7646, 0], [0, 0, 13.3783]] + structure = orm.StructureData(cell=cell) + structure.append_atom(position=(-0.0, 1.84, 10.03), symbols="Mo") + structure.append_atom(position=(1.6, 0.92, 8.47), symbols="S") + structure.append_atom(position=(1.6, 0.92, 11.6), symbols="S") + structure.pbc = pbc return structure @@ -556,17 +564,16 @@ def generate_bands_workchain( """Generate an instance of a the WorkChain.""" def _generate_bands_workchain(structure): - from copy import deepcopy - from aiida import engine from aiida.orm import Dict - from aiida_quantumespresso.workflows.pw.bands import PwBandsWorkChain + from aiidalab_qe.plugins.bands.bands_workchain import BandsWorkChain pseudo_family = f"SSSP/{SSSP_VERSION}/PBEsol/efficiency" inputs = { - "code": fixture_code("quantumespresso.pw"), + "pw_code": fixture_code("quantumespresso.pw"), "structure": structure, + "simulation_mode": "normal", "overrides": { "scf": { "pseudo_family": pseudo_family, @@ -584,20 +591,34 @@ def _generate_bands_workchain(structure): }, }, } - builder = PwBandsWorkChain.get_builder_from_protocol(**inputs) + builder = BandsWorkChain.get_builder_from_protocol(**inputs) inputs = builder._inputs() - inputs["relax"]["base_final_scf"] = deepcopy(inputs["relax"]["base"]) - wkchain = generate_workchain(PwBandsWorkChain, inputs) + wkchain = generate_workchain(BandsWorkChain, inputs) wkchain.setup() # run bands and return the process - output_parameters = Dict(dict={"fermi_energy": 2.0}) - output_parameters.store() - wkchain.out("scf_parameters", output_parameters) - wkchain.out("band_parameters", output_parameters) + fermi_dict = Dict(dict={"fermi_energy": 2.0}) + fermi_dict.store() + + output_parameters = Dict( + dict={ + "bands": { + "scf_parameters": fermi_dict, + "band_parameters": fermi_dict, + } + } + ) + + wkchain.out( + "bands.scf_parameters", output_parameters["bands"]["scf_parameters"] + ) + wkchain.out( + "bands.band_parameters", output_parameters["bands"]["band_parameters"] + ) + # band_structure = generate_bands_data() band_structure.store() - wkchain.out("band_structure", band_structure) + wkchain.out("bands.band_structure", band_structure) wkchain.update_outputs() # bands_node = wkchain.node @@ -679,12 +700,34 @@ def _generate_qeapp_workchain( builder = s3._create_builder() inputs = builder._inputs() inputs["relax"]["base_final_scf"] = deepcopy(inputs["relax"]["base"]) + + inputs["bands"]["bands_projwfc"]["scf"]["pw"] = deepcopy( + inputs["bands"]["bands"]["scf"]["pw"] + ) + inputs["bands"]["bands_projwfc"]["bands"]["pw"] = deepcopy( + inputs["bands"]["bands"]["bands"]["pw"] + ) + inputs["bands"]["bands_projwfc"]["bands"]["pw"]["code"] = inputs["bands"][ + "bands" + ]["bands"]["pw"]["code"] + inputs["bands"]["bands_projwfc"]["scf"]["pw"]["code"] = inputs["bands"][ + "bands" + ]["scf"]["pw"]["code"] + + inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["code"] = inputs["pdos"][ + "projwfc" + ]["code"] + inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["parameters"] = deepcopy( + inputs["pdos"]["projwfc"]["parameters"] + ) + + wkchain = generate_workchain(QeAppWorkChain, inputs) + wkchain.setup() + print(inputs["properties"]) if run_bands: inputs["properties"].append("bands") if run_pdos: inputs["properties"].append("pdos") - wkchain = generate_workchain(QeAppWorkChain, inputs) - wkchain.setup() # mock output if relax_type != "none": wkchain.out("structure", s1.confirmed_structure) @@ -696,11 +739,11 @@ def _generate_qeapp_workchain( wkchain.exposed_outputs(pdos.node, PdosWorkChain, namespace="pdos") ) if run_bands: - from aiida_quantumespresso.workflows.pw.bands import PwBandsWorkChain + from aiidalab_qe.plugins.bands.bands_workchain import BandsWorkChain bands = generate_bands_workchain(structure) wkchain.out_many( - wkchain.exposed_outputs(bands.node, PwBandsWorkChain, namespace="bands") + wkchain.exposed_outputs(bands.node, BandsWorkChain, namespace="bands") ) wkchain.update_outputs() # set ui_parameters diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index 6288738de..50a95d4c7 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -9,6 +9,7 @@ def test_result(generate_qeapp_workchain): wkchain = generate_qeapp_workchain() # generate structure for scf calculation + print(wkchain.outputs) result = Result(wkchain.node) result._update_view() assert isinstance(result.children[0], BandPdosWidget) @@ -38,15 +39,16 @@ def test_result(generate_qeapp_workchain): def test_structure_1d(generate_qeapp_workchain, generate_structure_data): structure = generate_structure_data("silicon", pbc=(True, False, False)) wkchain = generate_qeapp_workchain(structure=structure) - assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands - assert "bands_kpoints" in wkchain.inputs.bands.bands - assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 2 + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands.bands + assert len(wkchain.inputs.bands.bands.bands.bands_kpoints.labels) == 2 @pytest.mark.usefixtures("sssp") def test_structure_2d(generate_qeapp_workchain, generate_structure_data): - structure = generate_structure_data("silicon", pbc=(True, True, False)) + structure = generate_structure_data("MoS2", pbc=(True, True, False)) wkchain = generate_qeapp_workchain(structure=structure) - assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands - assert "bands_kpoints" in wkchain.inputs.bands.bands - assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 4 + print(wkchain.inputs.bands.bands.bands) + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands.bands + assert len(wkchain.inputs.bands.bands.bands.bands_kpoints.labels) == 4 diff --git a/tests/test_submit_qe_workchain.py b/tests/test_submit_qe_workchain.py index 140d6a3b7..913ad2f8c 100644 --- a/tests/test_submit_qe_workchain.py +++ b/tests/test_submit_qe_workchain.py @@ -44,8 +44,11 @@ def test_create_builder_insulator( # check and validate the builder got = builder_to_readable_dict(builder) - assert got["bands"]["scf"]["pw"]["parameters"]["SYSTEM"]["occupations"] == "fixed" - assert "smearing" not in got["bands"]["scf"]["pw"]["parameters"]["SYSTEM"] + assert ( + got["bands"]["bands"]["scf"]["pw"]["parameters"]["SYSTEM"]["occupations"] + == "fixed" + ) + assert "smearing" not in got["bands"]["bands"]["scf"]["pw"]["parameters"]["SYSTEM"] @pytest.mark.usefixtures("sssp") @@ -78,7 +81,7 @@ def test_create_builder_advanced_settings( # test tot_charge is updated in the three steps for parameters in [ got["relax"]["base"], - got["bands"]["scf"], + got["bands"]["bands"]["scf"], got["pdos"]["scf"], got["pdos"]["nscf"], ]: diff --git a/tests/test_submit_qe_workchain/test_create_builder_default.yml b/tests/test_submit_qe_workchain/test_create_builder_default.yml index d16d0e9f7..da6c50dd7 100644 --- a/tests/test_submit_qe_workchain/test_create_builder_default.yml +++ b/tests/test_submit_qe_workchain/test_create_builder_default.yml @@ -19,7 +19,6 @@ advanced: vdw_corr: none pseudos: {} bands: - kpath_2d: hexagonal codes: dos: cpus: 1 From 22fd3a871f66a64f13663dbc6f713004bb65e08d Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 25 Sep 2024 12:40:55 +0000 Subject: [PATCH 07/21] adapting bandspdoswidget --- src/aiidalab_qe/common/bandpdoswidget.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aiidalab_qe/common/bandpdoswidget.py b/src/aiidalab_qe/common/bandpdoswidget.py index 265164eaa..6c48fecf8 100644 --- a/src/aiidalab_qe/common/bandpdoswidget.py +++ b/src/aiidalab_qe/common/bandpdoswidget.py @@ -420,7 +420,6 @@ class BandPdosWidget(ipw.VBox): Select the style of plotting the projected density of states.
""" ) - projected_bands_width = 0.05 def __init__(self, bands=None, pdos=None, **kwargs): if bands is None and pdos is None: @@ -472,7 +471,7 @@ def __init__(self, bands=None, pdos=None, **kwargs): description="Add `fat bands` projections", ) - self.project_bands_width = ipw.FloatSlider( + self.proj_bands_width_slider = ipw.FloatSlider( description="Width", min=0.01, max=1.0, @@ -503,7 +502,7 @@ def __init__(self, bands=None, pdos=None, **kwargs): # If projections are available in the bands data, include the box to plot fat-bands if self.bands_data and "projected_bands" in self.bands_data: pdos_options_list.insert(4, self.project_bands_box) - pdos_options_list.insert(5, self.project_bands_width) + pdos_options_list.insert(5, self.proj_bands_width_slider) self.pdos_options = ipw.VBox(pdos_options_list) @@ -593,7 +592,7 @@ def _get_bands_data(self): group_tag=self.dos_atoms_group.value, plot_tag=self.dos_plot_group.value, selected_atoms=expanded_selection, - bands_width=self.project_bands_width.value, + bands_width=self.proj_bands_width_slider.value, ) return bands return None From f8ad5f4bc423ccb275daab527d175d78448b0c60 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:42:10 +0000 Subject: [PATCH 08/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/aiidalab_qe/plugins/bands/__init__.py | 1 + src/aiidalab_qe/plugins/bands/bands_workchain.py | 10 +++++----- src/aiidalab_qe/plugins/bands/setting.py | 3 ++- src/aiidalab_qe/plugins/bands/workchain.py | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 263d29297..1bd97509b 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -1,6 +1,7 @@ # from aiidalab_qe.bands.result import Result from aiidalab_qe.common.panel import OutlinePanel from aiidalab_qe.common.widgets import QEAppComputationalResourcesWidget + from .result import Result from .setting import Setting from .workchain import workchain_and_builder diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index 5f872f923..a99fb79e7 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -1,11 +1,11 @@ -import numpy as np -from aiida.plugins import DataFactory, WorkflowFactory -from aiida import orm -from aiida.engine import WorkChain import math -from aiida.common import AttributeDict +import numpy as np +from aiida import orm +from aiida.common import AttributeDict +from aiida.engine import WorkChain +from aiida.plugins import DataFactory, WorkflowFactory GAMMA = "\u0393" diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index d81eacc4f..53e99bb92 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -3,8 +3,9 @@ import ipywidgets as ipw import traitlets as tl -from aiidalab_qe.common.panel import Panel + from aiida import orm +from aiidalab_qe.common.panel import Panel class Setting(Panel): diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 8b3f0c865..04ce0ade0 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -2,6 +2,7 @@ from aiida.plugins import DataFactory, WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType from aiidalab_qe.plugins.utils import set_component_resources + from .bands_workchain import BandsWorkChain GAMMA = "\u0393" From 965f3ad132698953c6fed4acd081a3835067b8c1 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 25 Sep 2024 12:50:39 +0000 Subject: [PATCH 09/21] fix ruff --- src/aiidalab_qe/plugins/bands/bands_workchain.py | 2 +- src/aiidalab_qe/plugins/bands/workchain.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index a99fb79e7..4059cee57 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -149,7 +149,7 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): # Calculate the number of points per branch and generate the kpoints index_offset = 0 # Start index for each segment - for (start, end), label_start, label_end in zip(branches, labels[:-1], labels[1:]): + for (start, end), label_start, _ in zip(branches, labels[:-1], labels[1:]): num_points_per_branch = points_per_branch( start, end, reciprocal_cell, bands_kpoints_distance ) diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index ad418a183..7b13a20b3 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -23,12 +23,10 @@ def check_codes(pw_code, projwfc_code): ] ) and len( - set( - ( - pw_code.computer.pk, - projwfc_code.computer.pk, - ) - ) + { + pw_code.computer.pk, + projwfc_code.computer.pk, + } ) != 1 ): @@ -173,7 +171,7 @@ def generate_kpath_2d(structure, kpoints_distance, kpath_2d): # Calculate the number of points per branch and generate the kpoints index_offset = 0 # Start index for each segment - for (start, end), label_start, _label_end in zip(branches, labels[:-1], labels[1:]): + for (start, end), label_start, _ in zip(branches, labels[:-1], labels[1:]): num_points_per_branch = points_per_branch( start, end, reciprocal_cell, bands_kpoints_distance ) From 849b0640c3363fac590b74dcfd8546b58dc312b9 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 25 Sep 2024 14:57:27 +0000 Subject: [PATCH 10/21] adapting tests --- src/aiidalab_qe/plugins/bands/__init__.py | 6 +- .../plugins/bands/bands_workchain.py | 78 ++++---- src/aiidalab_qe/plugins/bands/setting.py | 2 +- src/aiidalab_qe/plugins/bands/workchain.py | 175 +----------------- tests/conftest.py | 22 +-- .../test_create_builder_default.yml | 1 + 6 files changed, 54 insertions(+), 230 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 1bd97509b..6911e2c55 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -16,15 +16,15 @@ class BandsOutline(OutlinePanel): """ -projwfc_bands = QEAppComputationalResourcesWidget( - description="projwfc.x for bands", +projwfc_code = QEAppComputationalResourcesWidget( + description="projwfc.x", default_calc_job_plugin="quantumespresso.projwfc", ) bands = { "outline": BandsOutline, - "code": {"projwfc_bands": projwfc_bands}, + "code": {"projwfc": projwfc_code}, "setting": Setting, "result": Result, "workchain": workchain_and_builder, diff --git a/src/aiidalab_qe/plugins/bands/bands_workchain.py b/src/aiidalab_qe/plugins/bands/bands_workchain.py index 4059cee57..d5401341d 100644 --- a/src/aiidalab_qe/plugins/bands/bands_workchain.py +++ b/src/aiidalab_qe/plugins/bands/bands_workchain.py @@ -271,10 +271,10 @@ def define(cls, spec): def get_builder_from_protocol( cls, pw_code, + projwfc_code, structure, simulation_mode="normal", protocol=None, - projwfc_code=None, overrides=None, **kwargs, ): @@ -291,66 +291,56 @@ def get_builder_from_protocol( builder = cls.get_builder() if simulation_mode == "normal": - args = (pw_code, structure, protocol) builder_bands = PwBandsWorkChain.get_builder_from_protocol( - *args, overrides=overrides, **kwargs + pw_code, structure, protocol, overrides=overrides, **kwargs ) - - builder.pop("bands_projwfc") + builder.pop("bands_projwfc", None) builder_bands.pop("relax", None) builder_bands.pop("structure", None) builder.bands = builder_bands - if structure.pbc != (True, True, True): - kpoints_distance = overrides.get("scf", {}).get( - "kpoints_distance", builder.bands.scf.kpoints_distance - ) - if structure.pbc == (True, False, False): - kpoints = generate_kpath_1d(structure, kpoints_distance) - elif structure.pbc == (True, True, False): - kpoints = generate_kpath_2d( - structure=structure, - kpoints_distance=kpoints_distance, - kpath_2d=determine_symmetry_path(structure), - ) - - builder.bands.pop("bands_kpoints_distance") - builder.bands.update({"bands_kpoints": kpoints}) - builder.structure = structure - - return builder - elif simulation_mode == "fat_bands": - args = ( + builder_bands_projwfc = ProjwfcBandsWorkChain.get_builder_from_protocol( pw_code, projwfc_code, structure, + protocol=protocol, + overrides=overrides, + **kwargs, ) - builder_bands_projwfc = ProjwfcBandsWorkChain.get_builder_from_protocol( - *args, protocol=protocol, overrides=overrides, **kwargs - ) - - builder.pop("bands") + builder.pop("bands", None) builder_bands_projwfc.pop("relax", None) builder_bands_projwfc.pop("structure", None) builder.bands_projwfc = builder_bands_projwfc - if structure.pbc != (True, True, True): - kpoints_distance = overrides["scf"]["kpoints_distance"] - if structure.pbc == (True, False, False): - kpoints = generate_kpath_1d(structure, kpoints_distance) - elif structure.pbc == (True, True, False): - kpoints = generate_kpath_2d( - structure=structure, - kpoints_distance=kpoints_distance, - kpath_2d=determine_symmetry_path(structure), - ) - builder.bands_projwfc.pop("bands_kpoints_distance") - builder.bands_projwfc.update({"bands_kpoints": kpoints}) + else: + raise ValueError(f"Unknown simulation_mode: {simulation_mode}") + + # Handle periodic boundary conditions (PBC) + if structure.pbc != (True, True, True): + kpoints_distance = overrides.get("scf", {}).get( + "kpoints_distance", + builder.get("bands", {}).get("scf", {}).get("kpoints_distance"), + ) + kpoints = None + if structure.pbc == (True, False, False): + kpoints = generate_kpath_1d(structure, kpoints_distance) + elif structure.pbc == (True, True, False): + kpoints = generate_kpath_2d( + structure=structure, + kpoints_distance=kpoints_distance, + kpath_2d=determine_symmetry_path(structure), + ) - builder.structure = structure + if simulation_mode == "normal": + builder.bands.pop("bands_kpoints_distance", None) + builder.bands.update({"bands_kpoints": kpoints}) + elif simulation_mode == "fat_bands": + builder.bands_projwfc.pop("bands_kpoints_distance", None) + builder.bands_projwfc.update({"bands_kpoints": kpoints}) - return builder + builder.structure = structure + return builder def setup(self): """Define the current workchain""" diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 99cb389ad..169bf07bb 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -29,7 +29,7 @@ def __init__(self, **kwargs): SeeK-path tool.""" ) self.projwfc_bands = ipw.Checkbox( - description="Flat bands calculation", + description="Fat bands calculation", value=False, ) self.children = [ diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 7b13a20b3..87c258183 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -1,17 +1,8 @@ -import numpy as np - -from aiida.plugins import DataFactory, WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType from aiidalab_qe.plugins.utils import set_component_resources from .bands_workchain import BandsWorkChain -GAMMA = "\u0393" - -PwBandsWorkChain = WorkflowFactory("quantumespresso.pw.bands") -ProjwfcBandsWorkChain = WorkflowFactory("wannier90_workflows.projwfcbands") -KpointsData = DataFactory("core.array.kpoints") - def check_codes(pw_code, projwfc_code): """Check that the codes are installed on the same computer.""" @@ -36,164 +27,6 @@ def check_codes(pw_code, projwfc_code): ) -def points_per_branch(vector_a, vector_b, reciprocal_cell, bands_kpoints_distance): - """function to calculate the number of points per branch depending on the kpoints_distance and the reciprocal cell""" - scaled_vector_a = np.array(vector_a) - scaled_vector_b = np.array(vector_b) - reciprocal_vector_a = scaled_vector_a.dot(reciprocal_cell) - reciprocal_vector_b = scaled_vector_b.dot(reciprocal_cell) - distance = np.linalg.norm(reciprocal_vector_a - reciprocal_vector_b) - return max( - 2, int(np.round(distance / bands_kpoints_distance)) - ) # at least two points for each segment, including both endpoints explicitly - - -def calculate_bands_kpoints_distance(kpoints_distance): - """function to calculate the bands_kpoints_distance depending on the kpoints_distance""" - if kpoints_distance >= 0.5: - return 0.1 - elif 0.15 < kpoints_distance < 0.5: - return 0.025 - else: - return 0.015 - - -def generate_kpath_1d(structure, kpoints_distance): - """Return a kpoints object for one dimensional systems (from Gamma to X) - The number of kpoints is calculated based on the kpoints_distance (as in the PwBandsWorkChain protocol) - """ - kpoints = KpointsData() - kpoints.set_cell_from_structure(structure) - reciprocal_cell = kpoints.reciprocal_cell - bands_kpoints_distance = calculate_bands_kpoints_distance(kpoints_distance) - - # Number of points per branch - num_points_per_branch = points_per_branch( - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - reciprocal_cell, - bands_kpoints_distance, - ) - # Generate the kpoints - points = np.linspace( - start=[0.0, 0.0, 0.0], - stop=[0.5, 0.0, 0.0], - endpoint=True, - num=num_points_per_branch, - ) - kpoints.set_kpoints(points.tolist()) - kpoints.labels = [[0, GAMMA], [len(points) - 1, "X"]] - return kpoints - - -def generate_kpath_2d(structure, kpoints_distance, kpath_2d): - """Return a kpoints object for two dimensional systems based on the selected 2D symmetry path - The number of kpoints is calculated based on the kpoints_distance (as in the PwBandsWorkChain protocol) - The 2D symmetry paths are defined as in The Journal of Physical Chemistry Letters 2022 13 (50), 11581-11594 (https://pubs.acs.org/doi/10.1021/acs.jpclett.2c02972) - """ - kpoints = KpointsData() - kpoints.set_cell_from_structure(structure) - reciprocal_cell = kpoints.reciprocal_cell - bands_kpoints_distance = calculate_bands_kpoints_distance(kpoints_distance) - - # dictionary with the 2D symmetry paths - selected_paths = { - "hexagonal": { - "path": [ - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - [0.33333, 0.33333, 0.0], - [1.0, 0.0, 0.0], - ], - "labels": [GAMMA, "M", "K", GAMMA], - }, - "square": { - "path": [ - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - [0.5, 0.5, 0.0], - [1.0, 0.0, 0.0], - ], - "labels": [GAMMA, "X", "M", GAMMA], - }, - "rectangular": { - "path": [ - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - [0.5, 0.5, 0.0], - [0.0, 0.5, 0.0], - [1.0, 0.0, 0.0], - ], - "labels": [GAMMA, "X", "S", "Y", GAMMA], - }, - } - # if the selected path is centered_rectangular or oblique, the path is calculated based on the reciprocal cell - if kpath_2d in ["centered_rectangular", "oblique"]: - a1 = reciprocal_cell[0] - a2 = reciprocal_cell[1] - norm_a1 = np.linalg.norm(a1) - norm_a2 = np.linalg.norm(a2) - cos_gamma = ( - a1.dot(a2) / (norm_a1 * norm_a2) - ) # Angle between a1 and a2 # like in https://pubs.acs.org/doi/10.1021/acs.jpclett.2c02972 - gamma = np.arccos(cos_gamma) - eta = (1 - (norm_a1 / norm_a2) * cos_gamma) / (2 * np.power(np.sin(gamma), 2)) - nu = 0.5 - (eta * norm_a2 * cos_gamma) / norm_a1 - selected_paths["centered_rectangular"] = { - "path": [ - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - [1 - eta, nu, 0], - [0.5, 0.5, 0.0], - [eta, 1 - nu, 0.0], - [1.0, 0.0, 0.0], - ], - "labels": [GAMMA, "X", "H_1", "C", "H", GAMMA], - } - selected_paths["oblique"] = { - "path": [ - [0.0, 0.0, 0.0], - [0.5, 0.0, 0.0], - [1 - eta, nu, 0], - [0.5, 0.5, 0.0], - [eta, 1 - nu, 0.0], - [0.0, 0.5, 0.0], - [1.0, 0.0, 0.0], - ], - "labels": [GAMMA, "X", "H_1", "C", "H", "Y", GAMMA], - } - path = selected_paths[kpath_2d]["path"] - labels = selected_paths[kpath_2d]["labels"] - branches = zip(path[:-1], path[1:]) - - all_kpoints = [] # List to hold all k-points - label_map = [] # List to hold labels and their corresponding k-point indices - - # Calculate the number of points per branch and generate the kpoints - index_offset = 0 # Start index for each segment - for (start, end), label_start, _ in zip(branches, labels[:-1], labels[1:]): - num_points_per_branch = points_per_branch( - start, end, reciprocal_cell, bands_kpoints_distance - ) - # Exclude endpoint except for the last segment to prevent duplication - points = np.linspace(start, end, num=num_points_per_branch, endpoint=False) - all_kpoints.extend(points) - label_map.append( - (index_offset, label_start) - ) # Label for the start of the segment - index_offset += len(points) - - # Include the last point and its label - all_kpoints.append(path[-1]) - label_map.append((index_offset, labels[-1])) # Label for the last point - - # Set the kpoints and their labels in KpointsData - kpoints.set_kpoints(all_kpoints) - kpoints.labels = label_map - - return kpoints - - def update_resources(builder, codes): if "bands" in builder: set_component_resources(builder.bands.scf.pw, codes.get("pw")) @@ -202,12 +35,12 @@ def update_resources(builder, codes): set_component_resources(builder.bands_projwfc.scf.pw, codes.get("pw")) set_component_resources(builder.bands_projwfc.bands.pw, codes.get("pw")) set_component_resources( - builder.bands_projwfc.projwfc.projwfc, codes.get("projwfc_bands") + builder.bands_projwfc.projwfc.projwfc, codes.get("projwfc") ) def get_builder(codes, structure, parameters, **kwargs): - """Get a builder for the PwBandsWorkChain.""" + """Get a builder for the BandsWorkChain.""" from copy import deepcopy pw_code = codes.get("pw")["code"] @@ -222,7 +55,7 @@ def get_builder(codes, structure, parameters, **kwargs): bands_overrides["pw"]["parameters"]["SYSTEM"].pop("smearing", None) bands_overrides["pw"]["parameters"]["SYSTEM"].pop("degauss", None) - check_codes(pw_code, codes.get("projwfc_bands")["code"]) + check_codes(pw_code, codes.get("projwfc")["code"]) overrides = { "scf": scf_overrides, @@ -237,7 +70,7 @@ def get_builder(codes, structure, parameters, **kwargs): bands_builder = BandsWorkChain.get_builder_from_protocol( pw_code=pw_code, - projwfc_code=codes.get("projwfc_bands")["code"], + projwfc_code=codes.get("projwfc")["code"], structure=structure, simulation_mode=simulation_mode, protocol=protocol, diff --git a/tests/conftest.py b/tests/conftest.py index e80ccf54f..71234fce9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -574,6 +574,7 @@ def _generate_bands_workchain(structure): inputs = { "pw_code": fixture_code("quantumespresso.pw"), + "projwfc_code": fixture_code("quantumespresso.projwfc"), "structure": structure, "simulation_mode": "normal", "overrides": { @@ -600,15 +601,12 @@ def _generate_bands_workchain(structure): # run bands and return the process fermi_dict = Dict(dict={"fermi_energy": 2.0}) fermi_dict.store() - - output_parameters = Dict( - dict={ - "bands": { - "scf_parameters": fermi_dict, - "band_parameters": fermi_dict, - } + output_parameters = { + "bands": { + "scf_parameters": fermi_dict, + "band_parameters": fermi_dict, } - ) + } wkchain.out( "bands.scf_parameters", output_parameters["bands"]["scf_parameters"] @@ -702,6 +700,8 @@ def _generate_qeapp_workchain( inputs = builder._inputs() inputs["relax"]["base_final_scf"] = deepcopy(inputs["relax"]["base"]) + # Setting up inputs for bands_projwfc + inputs["bands"]["bands_projwfc"]["scf"]["pw"] = deepcopy( inputs["bands"]["bands"]["scf"]["pw"] ) @@ -722,13 +722,13 @@ def _generate_qeapp_workchain( inputs["pdos"]["projwfc"]["parameters"] ) - wkchain = generate_workchain(QeAppWorkChain, inputs) - wkchain.setup() - print(inputs["properties"]) if run_bands: inputs["properties"].append("bands") if run_pdos: inputs["properties"].append("pdos") + + wkchain = generate_workchain(QeAppWorkChain, inputs) + wkchain.setup() # mock output if relax_type != "none": wkchain.out("structure", s1.confirmed_structure) diff --git a/tests/test_submit_qe_workchain/test_create_builder_default.yml b/tests/test_submit_qe_workchain/test_create_builder_default.yml index d9c346983..41289c7f9 100644 --- a/tests/test_submit_qe_workchain/test_create_builder_default.yml +++ b/tests/test_submit_qe_workchain/test_create_builder_default.yml @@ -20,6 +20,7 @@ advanced: vdw_corr: none pseudos: {} bands: + projwfc_bands: false codes: dos: cpus: 1 From 55ae94c7f85bc1f97ce1fb158f00eafa0de86dbc Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 25 Sep 2024 20:16:37 +0000 Subject: [PATCH 11/21] fixing test_bands_plugins --- setup.cfg | 3 +++ src/aiidalab_qe/plugins/bands/workchain.py | 6 ++++-- tests/test_plugins_bands.py | 13 ++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2bdc935af..3da97dad7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,9 @@ aiidalab_qe.properties = electronic_structure = aiidalab_qe.plugins.electronic_structure:electronic_structure xas = aiidalab_qe.plugins.xas:xas +aiida.workflows = + aiidalab_qe.bands_workchain = aiidalab_qe.plugins.bands.bands_workchain:BandsWorkChain + [aiidalab] title = Quantum ESPRESSO description = Perform Quantum ESPRESSO calculations diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 87c258183..0cbfe5103 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -1,7 +1,9 @@ +from aiida.plugins import WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType from aiidalab_qe.plugins.utils import set_component_resources -from .bands_workchain import BandsWorkChain +BandsWorkChain = WorkflowFactory("aiidalab_qe.bands_workchain") +# from .bands_workchain import BandsWorkChain def check_codes(pw_code, projwfc_code): @@ -23,7 +25,7 @@ def check_codes(pw_code, projwfc_code): ): raise ValueError( "All selected codes must be installed on the same computer. This is because the " - "PDOS calculations rely on large files that are not retrieved by AiiDA." + "BandsWorkChain calculations rely on large files that are not retrieved by AiiDA." ) diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index b0fb50218..9a815cc0b 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -40,16 +40,15 @@ def test_result(generate_qeapp_workchain): def test_structure_1d(generate_qeapp_workchain, generate_structure_data): structure = generate_structure_data("silicon", pbc=(True, False, False)) wkchain = generate_qeapp_workchain(structure=structure) - assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands.bands - assert "bands_kpoints" in wkchain.inputs.bands.bands.bands - assert len(wkchain.inputs.bands.bands.bands.bands_kpoints.labels) == 2 + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands + assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 2 @pytest.mark.usefixtures("sssp") def test_structure_2d(generate_qeapp_workchain, generate_structure_data): structure = generate_structure_data("MoS2", pbc=(True, True, False)) wkchain = generate_qeapp_workchain(structure=structure) - print(wkchain.inputs.bands.bands.bands) - assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands.bands - assert "bands_kpoints" in wkchain.inputs.bands.bands.bands - assert len(wkchain.inputs.bands.bands.bands.bands_kpoints.labels) == 4 + assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands + assert "bands_kpoints" in wkchain.inputs.bands.bands + assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 4 From c810d93e7518fa30291f808df3dacafe1d4b38ce Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Thu, 26 Sep 2024 09:08:53 +0000 Subject: [PATCH 12/21] updating conftest for new workchain --- src/aiidalab_qe/app/parameters/qeapp.yaml | 2 ++ src/aiidalab_qe/plugins/bands/__init__.py | 2 +- src/aiidalab_qe/plugins/bands/workchain.py | 6 ++-- tests/conftest.py | 28 ++++++++++++++----- .../test_create_builder_default.yml | 6 ++++ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/aiidalab_qe/app/parameters/qeapp.yaml b/src/aiidalab_qe/app/parameters/qeapp.yaml index c00c0f1c1..1fba8c032 100644 --- a/src/aiidalab_qe/app/parameters/qeapp.yaml +++ b/src/aiidalab_qe/app/parameters/qeapp.yaml @@ -29,6 +29,8 @@ codes: code: dos-7.2@localhost projwfc: code: projwfc-7.2@localhost + projwfc_bands: + code: projwfc-7.2@localhost pw: code: pw-7.2@localhost pp: diff --git a/src/aiidalab_qe/plugins/bands/__init__.py b/src/aiidalab_qe/plugins/bands/__init__.py index 6911e2c55..b38797141 100644 --- a/src/aiidalab_qe/plugins/bands/__init__.py +++ b/src/aiidalab_qe/plugins/bands/__init__.py @@ -24,7 +24,7 @@ class BandsOutline(OutlinePanel): bands = { "outline": BandsOutline, - "code": {"projwfc": projwfc_code}, + "code": {"projwfc_bands": projwfc_code}, "setting": Setting, "result": Result, "workchain": workchain_and_builder, diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 0cbfe5103..8bfc2839a 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -37,7 +37,7 @@ def update_resources(builder, codes): set_component_resources(builder.bands_projwfc.scf.pw, codes.get("pw")) set_component_resources(builder.bands_projwfc.bands.pw, codes.get("pw")) set_component_resources( - builder.bands_projwfc.projwfc.projwfc, codes.get("projwfc") + builder.bands_projwfc.projwfc.projwfc, codes.get("projwfc_bands") ) @@ -57,7 +57,7 @@ def get_builder(codes, structure, parameters, **kwargs): bands_overrides["pw"]["parameters"]["SYSTEM"].pop("smearing", None) bands_overrides["pw"]["parameters"]["SYSTEM"].pop("degauss", None) - check_codes(pw_code, codes.get("projwfc")["code"]) + check_codes(pw_code, codes.get("projwfc_bands")["code"]) overrides = { "scf": scf_overrides, @@ -72,7 +72,7 @@ def get_builder(codes, structure, parameters, **kwargs): bands_builder = BandsWorkChain.get_builder_from_protocol( pw_code=pw_code, - projwfc_code=codes.get("projwfc")["code"], + projwfc_code=codes.get("projwfc_bands")["code"], structure=structure, simulation_mode=simulation_mode, protocol=protocol, diff --git a/tests/conftest.py b/tests/conftest.py index 71234fce9..20d9bae4e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -297,6 +297,16 @@ def projwfc_code(aiida_local_code_factory): ) +@pytest.fixture +def projwfc_bands_code(aiida_local_code_factory): + """Return a `Code` configured for the projwfc.x executable.""" + return aiida_local_code_factory( + label="projwfc_bands", + executable="bash", + entry_point="quantumespresso.projwfc", + ) + + @pytest.fixture() def workchain_settings_generator(): """Return a function that generates a workchain settings dictionary.""" @@ -324,7 +334,7 @@ def _smearing_settings_generator(**kwargs): @pytest.fixture -def app(pw_code, dos_code, projwfc_code): +def app(pw_code, dos_code, projwfc_code, projwfc_bands_code): from aiidalab_qe.app.main import App # Since we use `qe_auto_setup=False`, which will skip the pseudo library installation @@ -337,10 +347,12 @@ def app(pw_code, dos_code, projwfc_code): app.submit_step.pw_code.code_selection.refresh() app.submit_step.codes["dos"].code_selection.refresh() app.submit_step.codes["projwfc"].code_selection.refresh() + app.submit_step.codes["projwfc_bands"].code_selection.refresh() app.submit_step.pw_code.value = pw_code.uuid app.submit_step.codes["dos"].value = dos_code.uuid app.submit_step.codes["projwfc"].value = projwfc_code.uuid + app.submit_step.codes["projwfc_bands"].value = projwfc_bands_code.uuid yield app @@ -635,6 +647,7 @@ def generate_qeapp_workchain( generate_workchain, generate_pdos_workchain, generate_bands_workchain, + fixture_code, ): """Generate an instance of the WorkChain.""" @@ -651,6 +664,7 @@ def _generate_qeapp_workchain( ): from copy import deepcopy + from aiida.orm import Dict from aiida.orm.utils.serialize import serialize from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep @@ -696,12 +710,12 @@ def _generate_qeapp_workchain( # step 3 setup code and resources s3: SubmitQeAppWorkChainStep = app.submit_step s3.pw_code.num_cpus.value = 4 + builder = s3._create_builder() inputs = builder._inputs() inputs["relax"]["base_final_scf"] = deepcopy(inputs["relax"]["base"]) # Setting up inputs for bands_projwfc - inputs["bands"]["bands_projwfc"]["scf"]["pw"] = deepcopy( inputs["bands"]["bands"]["scf"]["pw"] ) @@ -715,12 +729,12 @@ def _generate_qeapp_workchain( "bands" ]["scf"]["pw"]["code"] - inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["code"] = inputs["pdos"][ - "projwfc" - ]["code"] - inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["parameters"] = deepcopy( - inputs["pdos"]["projwfc"]["parameters"] + inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["code"] = fixture_code( + "quantumespresso.projwfc" ) + inputs["bands"]["bands_projwfc"]["projwfc"]["projwfc"]["parameters"] = Dict( + {"PROJWFC": {"DeltaE": 0.01}} + ).store() if run_bands: inputs["properties"].append("bands") diff --git a/tests/test_submit_qe_workchain/test_create_builder_default.yml b/tests/test_submit_qe_workchain/test_create_builder_default.yml index 41289c7f9..25aa7f06d 100644 --- a/tests/test_submit_qe_workchain/test_create_builder_default.yml +++ b/tests/test_submit_qe_workchain/test_create_builder_default.yml @@ -34,6 +34,12 @@ codes: max_wallclock_seconds: 43200 nodes: 1 ntasks_per_node: 1 + projwfc_bands: + cpus: 1 + cpus_per_task: 1 + max_wallclock_seconds: 43200 + nodes: 1 + ntasks_per_node: 1 pw: cpus: 2 cpus_per_task: 1 From bbab942c5cafbf4286481437b26890d52138f889 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Thu, 26 Sep 2024 09:17:15 +0000 Subject: [PATCH 13/21] fix conflict --- src/aiidalab_qe/common/bandpdoswidget.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/aiidalab_qe/common/bandpdoswidget.py b/src/aiidalab_qe/common/bandpdoswidget.py index baebbf4ec..c777f7ca8 100644 --- a/src/aiidalab_qe/common/bandpdoswidget.py +++ b/src/aiidalab_qe/common/bandpdoswidget.py @@ -494,15 +494,6 @@ def __init__(self, bands=None, pdos=None, **kwargs): layout=ipw.Layout(width="380px", visibility="hidden"), ) - self.proj_bands_width_slider = ipw.FloatSlider( - description="Width", - min=0.01, - max=1.0, - step=0.01, - value=0.5, - style={"description_width": "initial"}, - ) - # Information for the plot self.pdos_data = self._get_pdos_data() self.bands_data = self._get_bands_data() From 27b01fe0a44bf5af19306fba2af77dd9e44abf66 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Tue, 1 Oct 2024 14:14:25 +0000 Subject: [PATCH 14/21] updating new aiida-wannier90-workflow --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3da97dad7..85ee798f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ install_requires = aiida-pseudo~=1.4 filelock~=3.8 importlib-resources~=5.2 - aiida-wannier90-workflows~=2.2.0 + aiida-wannier90-workflows~=2.3.0 python_requires = >=3.9 [options.packages.find] From 574b0f6abfa12ac6162414a2438ac6926a3a1876 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Tue, 1 Oct 2024 14:17:06 +0000 Subject: [PATCH 15/21] updating new aiida-wannier90-workflow --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 85ee798f8..ae279ba08 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ install_requires = aiida-pseudo~=1.4 filelock~=3.8 importlib-resources~=5.2 - aiida-wannier90-workflows~=2.3.0 + aiida-wannier90-workflows==2.3.0 python_requires = >=3.9 [options.packages.find] From 5f40f82919a2ba2f82626a2e0f7cd8319eb785f1 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 2 Oct 2024 07:43:02 +0000 Subject: [PATCH 16/21] remove input_structure traitlet --- src/aiidalab_qe/plugins/bands/setting.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index 169bf07bb..ae0486d86 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -1,16 +1,13 @@ """Panel for Bands plugin.""" import ipywidgets as ipw -import traitlets as tl -from aiida import orm from aiidalab_qe.common.panel import Panel class Setting(Panel): title = "Bands Structure" identifier = "bands" - input_structure = tl.Instance(orm.StructureData, allow_none=True) def __init__(self, **kwargs): self.settings_title = ipw.HTML( From cd0de9479a3255d5277e7d00a6ad2fe1dc8a20df Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 2 Oct 2024 09:07:08 +0000 Subject: [PATCH 17/21] removing print statement from test --- src/aiidalab_qe/plugins/bands/workchain.py | 6 ------ tests/test_plugins_bands.py | 1 - 2 files changed, 7 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/workchain.py b/src/aiidalab_qe/plugins/bands/workchain.py index 8bfc2839a..863a7dc0b 100644 --- a/src/aiidalab_qe/plugins/bands/workchain.py +++ b/src/aiidalab_qe/plugins/bands/workchain.py @@ -91,12 +91,6 @@ def update_inputs(inputs, ctx): """Update the inputs using context.""" inputs.structure = ctx.current_structure - # inputs.bands.pw.parameters = inputs.bands.pw.parameters.get_dict() - # if ctx.bands.bands.current_number_of_bands: - # inputs.bands.bands.scf.pw.parameters.setdefault("SYSTEM", {}).setdefault( - # "nbnd", ctx.bands.bands.current_number_of_bands - # ) - workchain_and_builder = { "workchain": BandsWorkChain, diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index 9a815cc0b..42fbd2284 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -10,7 +10,6 @@ def test_result(generate_qeapp_workchain): wkchain = generate_qeapp_workchain() # generate structure for scf calculation - print(wkchain.outputs) result = Result(wkchain.node) result._update_view() assert isinstance(result.children[0], BandPdosWidget) From 103474a17edfa616c39e95d2f3f2dd7d72464bba Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 2 Oct 2024 16:13:48 +0000 Subject: [PATCH 18/21] including description for fat bands calculation --- src/aiidalab_qe/plugins/bands/setting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index ae0486d86..efa05fd58 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -23,7 +23,10 @@ def __init__(self, **kwargs): The band structure workflow will automatically detect the default path in reciprocal space using the - SeeK-path tool.""" + SeeK-path tool. +

+ Fat Bands is a band structure plot that includes the angular momentum contributions from specific atoms or orbitals to each energy band. The thickness of the bands represents the strength of these contributions, providing insight into the electronic structure. + """ ) self.projwfc_bands = ipw.Checkbox( description="Fat bands calculation", From a3a0bce28e477660489a76f2c752a7281abed41f Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Wed, 2 Oct 2024 16:17:45 +0000 Subject: [PATCH 19/21] modify style of checkbox --- src/aiidalab_qe/plugins/bands/setting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aiidalab_qe/plugins/bands/setting.py b/src/aiidalab_qe/plugins/bands/setting.py index efa05fd58..05ff99e4e 100644 --- a/src/aiidalab_qe/plugins/bands/setting.py +++ b/src/aiidalab_qe/plugins/bands/setting.py @@ -31,6 +31,7 @@ def __init__(self, **kwargs): self.projwfc_bands = ipw.Checkbox( description="Fat bands calculation", value=False, + style={"description_width": "initial"}, ) self.children = [ self.settings_title, From e60bef0fdf7eded948725c79dbdfa8949b56ffb5 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Thu, 3 Oct 2024 07:53:15 +0000 Subject: [PATCH 20/21] backwards compatibility for bands plugin --- src/aiidalab_qe/plugins/bands/result.py | 24 ++++++++++++------- .../plugins/electronic_structure/result.py | 24 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/aiidalab_qe/plugins/bands/result.py b/src/aiidalab_qe/plugins/bands/result.py index 3327400e2..7ca4b206c 100644 --- a/src/aiidalab_qe/plugins/bands/result.py +++ b/src/aiidalab_qe/plugins/bands/result.py @@ -14,14 +14,22 @@ def __init__(self, node=None, **kwargs): super().__init__(node=node, **kwargs) def _update_view(self): - # Check if the workchain has the outputs - try: - if "bands" in self.node.outputs.bands: - bands_node = self.node.outputs.bands.bands - elif "bands_projwfc" in self.node.outputs.bands: - bands_node = self.node.outputs.bands.bands_projwfc - except AttributeError: - bands_node = None + # Initialize bands_node to None by default + bands_node = None + + # Check if the workchain has the 'bands' output + if hasattr(self.node.outputs, "bands"): + bands_output = self.node.outputs.bands + + # Check for 'bands' or 'bands_projwfc' attributes within 'bands' output + if hasattr(bands_output, "bands"): + bands_node = bands_output.bands + elif hasattr(bands_output, "bands_projwfc"): + bands_node = bands_output.bands_projwfc + else: + # If neither 'bands' nor 'bands_projwfc' exist, use 'bands_output' itself + # This is the case for compatibility with older versions of the plugin + bands_node = bands_output _bands_plot_view = BandPdosWidget(bands=bands_node) self.children = [ diff --git a/src/aiidalab_qe/plugins/electronic_structure/result.py b/src/aiidalab_qe/plugins/electronic_structure/result.py index d75b8d363..890e92c3a 100644 --- a/src/aiidalab_qe/plugins/electronic_structure/result.py +++ b/src/aiidalab_qe/plugins/electronic_structure/result.py @@ -19,13 +19,23 @@ def _update_view(self): except AttributeError: pdos_node = None - try: - if "bands" in self.node.outputs.bands: - bands_node = self.node.outputs.bands.bands - elif "bands_projwfc" in self.node.outputs.bands: - bands_node = self.node.outputs.bands.bands_projwfc - except AttributeError: - bands_node = None + # Initialize bands_node to None by default + bands_node = None + + # Check if the workchain has the 'bands' output + if hasattr(self.node.outputs, "bands"): + bands_output = self.node.outputs.bands + + # Check for 'bands' or 'bands_projwfc' attributes within 'bands' output + if hasattr(bands_output, "bands"): + bands_node = bands_output.bands + elif hasattr(bands_output, "bands_projwfc"): + bands_node = bands_output.bands_projwfc + else: + # If neither 'bands' nor 'bands_projwfc' exist, use 'bands_output' itself + # This is the case for compatibility with older versions of the plugin + bands_node = bands_output + _bands_dos_widget = BandPdosWidget(bands=bands_node, pdos=pdos_node) # update the electronic structure tab self.children = [_bands_dos_widget] From 8ab667e1298eef896ca87cf2b8dbccf008af609f Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero Date: Thu, 3 Oct 2024 09:01:55 +0000 Subject: [PATCH 21/21] updating test bands --- tests/test_plugins_bands.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index 42fbd2284..00e7a75c8 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -42,6 +42,7 @@ def test_structure_1d(generate_qeapp_workchain, generate_structure_data): assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands assert "bands_kpoints" in wkchain.inputs.bands.bands assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 2 + assert wkchain.inputs.bands.bands.bands_kpoints.labels == [(0, "Γ"), (9, "X")] @pytest.mark.usefixtures("sssp") @@ -51,3 +52,9 @@ def test_structure_2d(generate_qeapp_workchain, generate_structure_data): assert "bands_kpoints_distance" not in wkchain.inputs.bands.bands assert "bands_kpoints" in wkchain.inputs.bands.bands assert len(wkchain.inputs.bands.bands.bands_kpoints.labels) == 4 + assert wkchain.inputs.bands.bands.bands_kpoints.labels == [ + (0, "Γ"), + (11, "M"), + (18, "K"), + (31, "Γ"), + ]