From 860f59bd5b0a033a5bd5f28a5e6dd3732b384d86 Mon Sep 17 00:00:00 2001 From: AndresOrtegaGuerrero <34098967+AndresOrtegaGuerrero@users.noreply.github.com> Date: Sat, 5 Oct 2024 08:48:21 +0200 Subject: [PATCH] Feature/handling molecules (#834) - Include the option molecule for StructureData editor - Band structure calculation should be disabled - Cell Optimizatio not available if structure is molecule - kpoint_distance should be disable and force [1,1,1] - Realoading an old workflow should restore normal behavior of app --- src/aiidalab_qe/app/configuration/__init__.py | 5 +++ src/aiidalab_qe/app/configuration/advanced.py | 45 ++++++++++++++++--- src/aiidalab_qe/app/configuration/workflow.py | 36 +++++++++++++++ src/aiidalab_qe/app/main.py | 1 + src/aiidalab_qe/app/result/summary_viewer.py | 1 + src/aiidalab_qe/common/widgets.py | 7 +-- src/aiidalab_qe/plugins/pdos/setting.py | 8 ++++ tests/configuration/test_advanced.py | 37 +++++++++++++++ tests/conftest.py | 7 +++ 9 files changed, 136 insertions(+), 11 deletions(-) diff --git a/src/aiidalab_qe/app/configuration/__init__.py b/src/aiidalab_qe/app/configuration/__init__.py index adebd3898..39f5f07ae 100644 --- a/src/aiidalab_qe/app/configuration/__init__.py +++ b/src/aiidalab_qe/app/configuration/__init__.py @@ -45,6 +45,11 @@ def __init__(self, **kwargs): (self.advanced_settings, "input_structure"), ) # + ipw.dlink( + (self, "input_structure"), + (self.workchain_settings, "input_structure"), + ) + # self.built_in_settings = [ self.workchain_settings, self.advanced_settings, diff --git a/src/aiidalab_qe/app/configuration/advanced.py b/src/aiidalab_qe/app/configuration/advanced.py index 2793d08e2..5df0e76cd 100644 --- a/src/aiidalab_qe/app/configuration/advanced.py +++ b/src/aiidalab_qe/app/configuration/advanced.py @@ -107,11 +107,7 @@ def __init__(self, default_protocol=None, **kwargs): style={"description_width": "initial"}, ) self.mesh_grid = ipw.HTML() - ipw.dlink( - (self.override, "value"), - (self.kpoints_distance, "disabled"), - lambda override: not override, - ) + self.create_kpoints_distance_link() self.kpoints_distance.observe(self._callback_value_set, "value") # Hubbard setting widget @@ -285,6 +281,20 @@ def __init__(self, default_protocol=None, **kwargs): # Default settings to trigger the callback self.reset() + def create_kpoints_distance_link(self): + """Create the dlink for override and kpoints_distance.""" + self.kpoints_distance_link = ipw.dlink( + (self.override, "value"), + (self.kpoints_distance, "disabled"), + lambda override: not override, + ) + + def remove_kpoints_distance_link(self): + """Remove the kpoints_distance_link.""" + if hasattr(self, "kpoints_distance_link"): + self.kpoints_distance_link.unlink() + del self.kpoints_distance_link + def _create_electron_maxstep_widgets(self): self.electron_maxstep = ipw.BoundedIntText( min=20, @@ -332,10 +342,22 @@ def _update_input_structure(self, change): self.hubbard_widget.update_widgets(change["new"]) if isinstance(self.input_structure, HubbardStructureData): self.override.value = True + if self.input_structure.pbc == (False, False, False): + self.kpoints_distance.value = 100.0 + self.kpoints_distance.disabled = True + if hasattr(self, "kpoints_distance_link"): + self.remove_kpoints_distance_link() + else: + # self.kpoints_distance.disabled = False + if not hasattr(self, "kpoints_distance_link"): + self.create_kpoints_distance_link() else: self.magnetization.input_structure = None self.pseudo_setter.structure = None self.hubbard_widget.update_widgets(None) + self.kpoints_distance.disabled = False + if not hasattr(self, "kpoints_distance_link"): + self.create_kpoints_distance_link() @tl.observe("electronic_type") def _electronic_type_changed(self, change): @@ -356,7 +378,14 @@ def _update_settings_from_protocol(self, protocol): parameters = PwBaseWorkChain.get_protocol_inputs(protocol) - self.kpoints_distance.value = parameters["kpoints_distance"] + if self.input_structure: + if self.input_structure.pbc == (False, False, False): + self.kpoints_distance.value = 100.0 + self.kpoints_distance.disabled = True + else: + self.kpoints_distance.value = parameters["kpoints_distance"] + else: + self.kpoints_distance.value = parameters["kpoints_distance"] num_atoms = len(self.input_structure.sites) if self.input_structure else 1 @@ -630,6 +659,10 @@ def reset(self): self.pseudo_setter._reset() else: self.pseudo_setter._reset() + if self.input_structure.pbc == (False, False, False): + self.kpoints_distance.value = 100.0 + self.kpoints_distance.disabled = True + # reset the magnetization self.magnetization.reset() # reset the hubbard widget diff --git a/src/aiidalab_qe/app/configuration/workflow.py b/src/aiidalab_qe/app/configuration/workflow.py index 408906ee5..cba0df521 100644 --- a/src/aiidalab_qe/app/configuration/workflow.py +++ b/src/aiidalab_qe/app/configuration/workflow.py @@ -4,7 +4,9 @@ """ import ipywidgets as ipw +import traitlets as tl +from aiida import orm from aiida_quantumespresso.common.types import RelaxType from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS from aiidalab_qe.app.utils import get_entry_items @@ -49,6 +51,8 @@ class WorkChainSettings(Panel): with less precision and the "precise" protocol to aim at best accuracy (at the price of longer/costlier calculations).""" ) + input_structure = tl.Instance(orm.StructureData, allow_none=True) + def __init__(self, **kwargs): # RelaxType: degrees of freedom in geometry optimization self.relax_type = ipw.ToggleButtons( @@ -141,6 +145,37 @@ def update_reminder_info(change, name=name): **kwargs, ) + @tl.observe("input_structure") + def _on_input_structure_change(self, change): + """Update the relax type options based on the input structure.""" + structure = change["new"] + if structure is None or structure.pbc != (False, False, False): + self.relax_type.options = [ + ("Structure as is", "none"), + ("Atomic positions", "positions"), + ("Full geometry", "positions_cell"), + ] + # Ensure the value is in the options + if self.relax_type.value not in [ + option[1] for option in self.relax_type.options + ]: + self.relax_type.value = "positions_cell" + + self.properties["bands"].run.disabled = False + elif structure.pbc == (False, False, False): + self.relax_type.options = [ + ("Structure as is", "none"), + ("Atomic positions", "positions"), + ] + # Ensure the value is in the options + if self.relax_type.value not in [ + option[1] for option in self.relax_type.options + ]: + self.relax_type.value = "positions" + + self.properties["bands"].run.value = False + self.properties["bands"].run.disabled = True + def get_panel_value(self): # Work chain settings relax_type = self.relax_type.value @@ -192,6 +227,7 @@ def set_panel_value(self, parameters): def reset(self): """Reset the panel to the default value.""" + self.input_structure = None for key in ["relax_type", "spin_type", "electronic_type"]: getattr(self, key).value = DEFAULT_PARAMETERS["workchain"][key] self.workchain_protocol.value = DEFAULT_PARAMETERS["workchain"]["protocol"] diff --git a/src/aiidalab_qe/app/main.py b/src/aiidalab_qe/app/main.py index b02d4feb3..ab3d3d875 100644 --- a/src/aiidalab_qe/app/main.py +++ b/src/aiidalab_qe/app/main.py @@ -139,6 +139,7 @@ def _observe_process_selection(self, change): self.structure_step.structure = process.inputs.structure self.structure_step.confirm() self.submit_step.process = process + # set ui_parameters # print out error message if yaml format ui_parameters is not reachable ui_parameters = process.base.extras.get("ui_parameters", {}) diff --git a/src/aiidalab_qe/app/result/summary_viewer.py b/src/aiidalab_qe/app/result/summary_viewer.py index fca5db6a3..56fc24505 100644 --- a/src/aiidalab_qe/app/result/summary_viewer.py +++ b/src/aiidalab_qe/app/result/summary_viewer.py @@ -23,6 +23,7 @@ (True, True, True): "xyz", (True, True, False): "xy", (True, False, False): "x", + (False, False, False): "molecule", } VDW_CORRECTION_VERSION = { diff --git a/src/aiidalab_qe/common/widgets.py b/src/aiidalab_qe/common/widgets.py index 0d07650a2..5aaad1a7c 100644 --- a/src/aiidalab_qe/common/widgets.py +++ b/src/aiidalab_qe/common/widgets.py @@ -465,11 +465,7 @@ def __init__(self, title=""): layout={"width": "initial"}, ) self.periodicity = ipw.RadioButtons( - options=[ - "xyz", - "xy", - "x", - ], + options=["xyz", "xy", "x", "molecule"], value="xyz", description="Periodicty: ", layout={"width": "initial"}, @@ -613,6 +609,7 @@ def _select_periodicity(self, _=None): "xyz": (True, True, True), "xy": (True, True, False), "x": (True, False, False), + "molecule": (False, False, False), } new_structure = deepcopy(self.structure) new_structure.set_pbc(periodicity_options[self.periodicity.value]) diff --git a/src/aiidalab_qe/plugins/pdos/setting.py b/src/aiidalab_qe/plugins/pdos/setting.py index 60159d1b0..5ddc4c255 100644 --- a/src/aiidalab_qe/plugins/pdos/setting.py +++ b/src/aiidalab_qe/plugins/pdos/setting.py @@ -27,6 +27,8 @@ def __init__(self, **kwargs): self.description = ipw.HTML( """