From 89ae413d05611a93b17ca2ae5a33554972404972 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Sun, 22 Dec 2024 09:06:48 +0000 Subject: [PATCH] Define structure results panel based on relaxation property --- .../components/viewer/structure/__init__.py | 4 ++-- .../components/viewer/structure/model.py | 18 ++++++++++++--- .../components/viewer/structure/structure.py | 5 +++-- .../app/result/components/viewer/viewer.py | 12 +++++----- src/aiidalab_qe/common/panel.py | 14 ++++++++---- tests/test_plugins_electronic_structure.py | 2 +- tests/test_result.py | 22 ++++++++++++++++++- 7 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/aiidalab_qe/app/result/components/viewer/structure/__init__.py b/src/aiidalab_qe/app/result/components/viewer/structure/__init__.py index 35c30d920..b9c268acf 100644 --- a/src/aiidalab_qe/app/result/components/viewer/structure/__init__.py +++ b/src/aiidalab_qe/app/result/components/viewer/structure/__init__.py @@ -1,7 +1,7 @@ from .model import StructureResultsModel -from .structure import StructureResults +from .structure import StructureResultsPanel __all__ = [ "StructureResultsModel", - "StructureResults", + "StructureResultsPanel", ] diff --git a/src/aiidalab_qe/app/result/components/viewer/structure/model.py b/src/aiidalab_qe/app/result/components/viewer/structure/model.py index 6b8c21998..3d56ab48a 100644 --- a/src/aiidalab_qe/app/result/components/viewer/structure/model.py +++ b/src/aiidalab_qe/app/result/components/viewer/structure/model.py @@ -7,6 +7,18 @@ class StructureResultsModel(ResultsModel): _this_process_label = "PwRelaxWorkChain" - @property - def include(self): - return "relax" in self.properties + source = None + + def update(self): + super().update() + is_relaxed = "relax" in self.properties + self.title = "Relaxed structure" if is_relaxed else "Initial structure" + self.source = self.outputs if is_relaxed else self.inputs + self.auto_render = not is_relaxed # auto-render initial structure + + def get_structure(self): + try: + return self.source.structure if self.source else None + except AttributeError: + # If source is outputs but job failed, there may not be a structure + return None diff --git a/src/aiidalab_qe/app/result/components/viewer/structure/structure.py b/src/aiidalab_qe/app/result/components/viewer/structure/structure.py index 0ab6487c6..93a95e5c4 100644 --- a/src/aiidalab_qe/app/result/components/viewer/structure/structure.py +++ b/src/aiidalab_qe/app/result/components/viewer/structure/structure.py @@ -4,10 +4,11 @@ from .model import StructureResultsModel -class StructureResults(ResultsPanel[StructureResultsModel]): +class StructureResultsPanel(ResultsPanel[StructureResultsModel]): def _render(self): if not hasattr(self, "widget"): - self.widget = StructureDataViewer(structure=self._model.outputs.structure) + structure = self._model.get_structure() + self.widget = StructureDataViewer(structure=structure) self.children = [self.widget] # HACK to resize the NGL viewer in cases where it auto-rendered when its diff --git a/src/aiidalab_qe/app/result/components/viewer/viewer.py b/src/aiidalab_qe/app/result/components/viewer/viewer.py index fe5cf11e6..3357aa32d 100644 --- a/src/aiidalab_qe/app/result/components/viewer/viewer.py +++ b/src/aiidalab_qe/app/result/components/viewer/viewer.py @@ -7,13 +7,14 @@ from aiidalab_qe.common.panel import ResultsPanel from .model import WorkChainResultsViewerModel -from .structure import StructureResults, StructureResultsModel +from .structure import StructureResultsModel, StructureResultsPanel class WorkChainResultsViewer(ResultsComponent[WorkChainResultsViewerModel]): def __init__(self, model: WorkChainResultsViewerModel, **kwargs): super().__init__(model=model, **kwargs) self.panels: dict[str, ResultsPanel] = {} + self._add_structure_panel() # TODO consider refactoring structure panel as a plugin self._fetch_plugin_results() def _on_process_change(self, _): @@ -42,10 +43,6 @@ def _render(self): "selected_index", ) - # TODO consider refactoring structure relaxation panel as a plugin - if "relax" in self._model.properties: - self._add_structure_panel() - self.children = [ self.title, self.tabs, @@ -60,7 +57,8 @@ def _update_panels(self): self.panels = { identifier: panel for identifier, panel in self.panels.items() - if identifier in properties + if identifier == "structure" + or identifier in properties or (identifier == "electronic_structure" and need_electronic_structure) } @@ -82,7 +80,7 @@ def _set_tabs(self): def _add_structure_panel(self): structure_model = StructureResultsModel() structure_model.process_uuid = self._model.process_uuid - self.structure_results = StructureResults(model=structure_model) + self.structure_results = StructureResultsPanel(model=structure_model) identifier = structure_model.identifier self._model.add_model(identifier, structure_model) self.panels = { diff --git a/src/aiidalab_qe/common/panel.py b/src/aiidalab_qe/common/panel.py index f708fab93..223de660a 100644 --- a/src/aiidalab_qe/common/panel.py +++ b/src/aiidalab_qe/common/panel.py @@ -506,6 +506,8 @@ class ResultsModel(PanelModel, HasProcess): _this_process_label = "" _this_process_uuid = None + auto_render = False + CSS_MAP = { "finished": "success", "failed": "danger", @@ -522,6 +524,10 @@ def has_results(self): node = self._fetch_child_process_node() return node and node.is_finished_ok + def update(self): + if self.has_results: + self.auto_render = True + def update_process_status_notification(self): self.process_status_notification = self._get_child_process_status() @@ -616,13 +622,13 @@ def render(self): return if self.has_controls or not self._model.has_process: return - if not self._model.has_results: - self._render_controls() - else: + if self._model.auto_render: self._load_results() + else: + self._render_controls() def _on_process_change(self, _): - pass + self._model.update() def _on_monitor_counter_change(self, _): self._model.update_process_status_notification() diff --git a/tests/test_plugins_electronic_structure.py b/tests/test_plugins_electronic_structure.py index 93cc910fb..18f381be6 100644 --- a/tests/test_plugins_electronic_structure.py +++ b/tests/test_plugins_electronic_structure.py @@ -11,7 +11,7 @@ def test_electronic_structure(generate_qeapp_workchain): model = ElectronicStructureResultsModel() model.process_uuid = workchain.node.uuid result = ElectronicStructureResultsPanel(model=model) - result.render() + result._render() widget = result.children[0] model = widget._model diff --git a/tests/test_result.py b/tests/test_result.py index 4b2362a15..5faf4f66f 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -6,6 +6,10 @@ WorkChainResultsViewer, WorkChainResultsViewerModel, ) +from aiidalab_qe.app.result.components.viewer.structure import StructureResultsModel +from aiidalab_qe.app.result.components.viewer.structure.structure import ( + StructureResultsPanel, +) def test_result_step(app_to_submit, generate_qeapp_workchain): @@ -37,7 +41,7 @@ def test_workchainview(generate_qeapp_workchain): model.process_uuid = workchain.node.uuid viewer.render() assert len(viewer.tabs.children) == 4 - assert viewer.tabs._titles["0"] == "Final Geometry" # type: ignore + assert viewer.tabs._titles["0"] == "Relaxed structure" # type: ignore def test_summary_report(data_regression, generate_qeapp_workchain): @@ -76,3 +80,19 @@ def test_summary_view(generate_qeapp_workchain): for key, value in parameters.items(): td = parsed.find("td", text=key).find_next_sibling("td") assert td.text == value + + +def test_structure_results_panel(generate_qeapp_workchain): + """Test the structure results panel can be properly generated.""" + model = StructureResultsModel() + _ = StructureResultsPanel(model=model) + + wc = generate_qeapp_workchain(relax_type="none") + model.process_uuid = wc.node.uuid + assert model.title == "Initial structure" + assert "properties" in model.source # source should be inputs + + wc = generate_qeapp_workchain(relax_type="positions_cell") + model.process_uuid = wc.node.uuid + assert model.title == "Relaxed structure" + assert "properties" not in model.source # source should be outputs