Skip to content

Commit

Permalink
Merge branch 'doc_plugin' of https://github.com/aiidalab/aiidalab-qe
Browse files Browse the repository at this point in the history
…into doc_plugin
  • Loading branch information
superstar54 committed Nov 3, 2023
2 parents 9602b18 + f766a42 commit fa0010f
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 126 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ aiidalab_qe.app.structure.examples = *
aiidalab_qe.properties =
bands = aiidalab_qe.plugins.bands:bands
pdos = aiidalab_qe.plugins.pdos:pdos
electronic_structure = aiidalab_qe.plugins.electronic_structure:electronic_structure

[aiidalab]
title = Quantum ESPRESSO
Expand Down
131 changes: 30 additions & 101 deletions src/aiidalab_qe/app/result/workchain_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
from filelock import FileLock, Timeout
from IPython.display import HTML, display
from jinja2 import Environment
from widget_bandsplot import BandsPlotWidget

from aiidalab_qe.app import static
from aiidalab_qe.app.utils import get_entry_items

from .electronic_structure import export_data
from .summary_viewer import SummaryView


Expand Down Expand Up @@ -51,52 +49,33 @@ def __init__(self, node, **kwargs):
self.workflows_summary = SummaryView(self.node)

self.summary_tab = ipw.VBox(children=[self.workflows_summary])
self.structure_tab = ipw.VBox(
[ipw.Label("Structure not available.")],
layout=ipw.Layout(min_height="380px"),
)
self.bands_tab = ipw.VBox(
[ipw.Label("Electronic Structure not available.")],
layout=ipw.Layout(min_height="380px"),
)
self.result_tabs = ipw.Tab(
children=[self.summary_tab, self.structure_tab, self.bands_tab]
)
# Only the summary tab is shown by default
self.result_tabs = ipw.Tab(children=[self.summary_tab])

self.result_tabs.set_title(0, "Workflow Summary")
self.result_tabs.set_title(1, "Final Geometry (n/a)")
self.result_tabs.set_title(2, "Electronic Structure (n/a)")

# add plugin specific settings
entries = get_entry_items("aiidalab_qe.properties", "result")
# get plugin result panels
# and save them the results dictionary
self.results = {}
entries = get_entry_items("aiidalab_qe.properties", "result")
for identifier, entry_point in entries.items():
# only show the result tab if the property is selected to be run
# this will be repalced by the ui_parameters in the future PR
# if this is the old version without plugin specific ui_parameters, just skip
if identifier not in ui_parameters.get("workchain", {}).get(
"properties", []
):
continue
result = entry_point(self.node)
self.results[identifier] = result
self.results[identifier].identifier = identifier
self.result_tabs.children += (result,)
self.result_tabs.set_title(len(self.result_tabs.children) - 1, result.title)

# An ugly fix to the structure appearance problem
# https://github.com/aiidalab/aiidalab-qe/issues/69
def on_selected_index_change(change):
index = change["new"]
# Accessing the viewer only if the corresponding tab is present.
if self.result_tabs._titles[str(index)] == "Final Geometry":
self._structure_view._viewer.handle_resize()
self.structure_tab._viewer.handle_resize()

def toggle_camera():
"""Toggle camera between perspective and orthographic."""
self._structure_view._viewer.camera = (
self.structure_tab._viewer.camera = (
"perspective"
if self._structure_view._viewer.camera == "orthographic"
if self.structure_tab._viewer.camera == "orthographic"
else "orthographic"
)

Expand All @@ -121,89 +100,39 @@ def _update_view(self):
with self.hold_trait_notifications():
if self.node.is_finished:
self._show_workflow_output()
# if the structure is present in the workchain,
# the structure tab will be added.
if (
"structure" not in self._results_shown
and "structure" in self.node.outputs
):
self._show_structure()
self.result_tabs.children += (self.structure_tab,)
# index of the last tab
index = len(self.result_tabs.children) - 1
self.result_tabs.set_title(index, "Final Geometry")
self._results_shown.add("structure")

if "electronic_structure" not in self._results_shown and (
"bands" in self.node.outputs or "pdos" in self.node.outputs
):
self._show_electronic_structure()
self._results_shown.add("electronic_structure")
# update the plugin specific results
for result in self.result_tabs.children[3:]:
for result in self.results.values():
# check if the result is already shown
# check if the plugin workchain result is in the outputs
if (
result.identifier not in self._results_shown
and result.identifier in self.node.outputs
):
result._update_view()
self._results_shown.add(result.identifier)

def _show_structure(self):
self._structure_view = StructureDataViewer(
structure=self.node.outputs.structure
)
self.result_tabs.children[1].children = [self._structure_view]
self.result_tabs.set_title(1, "Final Geometry")

def _show_electronic_structure(self):
group_dos_by = ipw.ToggleButtons(
options=[
("Atom", "atom"),
("Orbital", "angular"),
],
value="atom",
)
settings = ipw.VBox(
children=[
ipw.HBox(
children=[
ipw.Label(
"DOS grouped by:",
layout=ipw.Layout(
justify_content="flex-start", width="120px"
),
),
group_dos_by,
if result.identifier not in self._results_shown:
# check if the all required results are in the outputs
results_ready = [
label in self.node.outputs for label in result.workchain_labels
]
),
],
layout={"margin": "0 0 30px 30px"},
)
#
data = export_data(self.node, group_dos_by=group_dos_by.value)
bands_data = data.get("bands", None)
dos_data = data.get("dos", None)
_bands_plot_view = BandsPlotWidget(
bands=bands_data,
dos=dos_data,
)
if all(results_ready):
result._update_view()
self._results_shown.add(result.identifier)
# add this plugin result panel
self.result_tabs.children += (result,)
# index of the last tab
index = len(self.result_tabs.children) - 1
self.result_tabs.set_title(index, result.title)

def response(change):
data = export_data(self.node, group_dos_by=group_dos_by.value)
bands_data = data.get("bands", None)
dos_data = data.get("dos", None)
_bands_plot_view = BandsPlotWidget(
bands=bands_data,
dos=dos_data,
)
self.result_tabs.children[2].children = [
settings,
_bands_plot_view,
]

group_dos_by.observe(response, names="value")
# update the electronic structure tab
self.result_tabs.children[2].children = [
settings,
_bands_plot_view,
]
self.result_tabs.set_title(2, "Electronic Structure")
def _show_structure(self):
"""Show the structure of the workchain."""
self.structure_tab = StructureDataViewer(structure=self.node.outputs.structure)

def _show_workflow_output(self):
self.workflows_output = WorkChainOutputs(self.node)
Expand Down
4 changes: 3 additions & 1 deletion src/aiidalab_qe/common/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class ResultPanel(Panel):
"""

title = "Result"
# to specify which plugins (outputs) are needed for this result panel.
workchain_labels = []

def __init__(self, node=None, **kwargs):
self.node = node
Expand All @@ -116,7 +118,7 @@ def outputs(self):
if self.node is None:
return None

return getattr(self.node.outputs, self.identifier)
return self.node.outputs

def _update_view(self):
"""Update the result in the panel.
Expand Down
5 changes: 3 additions & 2 deletions src/aiidalab_qe/plugins/bands/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ class Result(ResultPanel):
"""Result panel for the bands calculation."""

title = "Bands"
workchain_labels = ["bands"]

def __init__(self, node=None, **kwargs):
super().__init__(node=node, identifier="bands", **kwargs)
super().__init__(node=node, **kwargs)

def _update_view(self):
from widget_bandsplot import BandsPlotWidget

bands_data = export_bands_data(self.outputs)
bands_data = export_bands_data(self.outputs.bands)
_bands_plot_view = BandsPlotWidget(
bands=bands_data,
)
Expand Down
5 changes: 5 additions & 0 deletions src/aiidalab_qe/plugins/electronic_structure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .result import Result

electronic_structure = {
"result": Result,
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""Electronic structure results view widgets"""
import json
import random

import ipywidgets as ipw
from aiida import orm
from monty.json import jsanitize
from widget_bandsplot import BandsPlotWidget

from aiidalab_qe.common.panel import ResultPanel


def export_data(work_chain_node, group_dos_by="atom"):
Expand Down Expand Up @@ -182,3 +187,48 @@ def cmap(label: str) -> str:
random.seed(ascn)

return "#%06x" % random.randint(0, 0xFFFFFF)


class Result(ResultPanel):
title = "Electronic Structure"
workchain_labels = ["bands", "pdos"]

def __init__(self, node=None, **kwargs):
self.dos_group_label = ipw.Label(
"DOS grouped by:",
layout=ipw.Layout(justify_content="flex-start", width="120px"),
)
self.group_dos_by = ipw.ToggleButtons(
options=[
("Atom", "atom"),
("Orbital", "angular"),
],
value="atom",
)
self.settings = ipw.HBox(
children=[
self.dos_group_label,
self.group_dos_by,
],
layout={"margin": "0 0 30px 30px"},
)
self.group_dos_by.observe(self._observe_group_dos_by, names="value")
super().__init__(node=node, **kwargs)

def _observe_group_dos_by(self, change):
"""Update the view of the widget when the group_dos_by value changes."""
self._update_view()

def _update_view(self):
"""Update the view of the widget."""
#
data = export_data(self.node, group_dos_by=self.group_dos_by.value)
_bands_plot_view = BandsPlotWidget(
bands=data.get("bands", None),
dos=data.get("dos", None),
)
# update the electronic structure tab
self.children = [
self.settings,
_bands_plot_view,
]
9 changes: 6 additions & 3 deletions src/aiidalab_qe/plugins/pdos/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,10 @@ def export_pdos_data(outputs, group_dos_by="atom"):

class Result(ResultPanel):
title = "PDOS"
workchain_labels = ["pdos"]

def __init__(self, node=None, **kwargs):
super().__init__(node=node, identifier="pdos", **kwargs)
super().__init__(node=node, **kwargs)

def _update_view(self):
"""Update the view of the widget."""
Expand Down Expand Up @@ -190,13 +191,15 @@ def _update_view(self):
layout={"margin": "0 0 30px 30px"},
)
#
dos_data = export_pdos_data(self.outputs, group_dos_by=group_dos_by.value)
dos_data = export_pdos_data(self.outputs.pdos, group_dos_by=group_dos_by.value)
_bands_plot_view = BandsPlotWidget(
dos=dos_data,
)

def response(change):
dos_data = export_pdos_data(self.outputs, group_dos_by=group_dos_by.value)
dos_data = export_pdos_data(
self.outputs.pdos, group_dos_by=group_dos_by.value
)
_bands_plot_view = BandsPlotWidget(
dos=dos_data,
)
Expand Down
1 change: 0 additions & 1 deletion tests/test_plugins_bands.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ def test_result(generate_qeapp_workchain):
assert data is not None
# generate structure for scf calculation
result = Result(wkchain.node)
assert result.identifier == "bands"
result._update_view()
assert isinstance(result.children[0], BandsPlotWidget)

Expand Down
20 changes: 20 additions & 0 deletions tests/test_plugins_electronic_structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
def test_electronic_structure(generate_qeapp_workchain):
"""Test the electronic structure tab."""
from aiida import engine

from aiidalab_qe.app.result.workchain_viewer import WorkChainViewer

wkchain = generate_qeapp_workchain()
wkchain.node.set_exit_status(0)
wkchain.node.set_process_state(engine.ProcessState.FINISHED)
wcv = WorkChainViewer(wkchain.node)
# find the tab with the identifier "electronic_structure"
# the built-in summary and structure tabs is not a plugin panel,
# thus don't have identifiers
tab = [
tab
for tab in wcv.result_tabs.children
if getattr(tab, "identifier", "") == "electronic_structure"
][0]
# It should have two children: settings and the _bands_plot_view
assert len(tab.children) == 2
1 change: 0 additions & 1 deletion tests/test_plugins_pdos.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ def test_result(generate_qeapp_workchain):
assert data is not None
# generate structure for scf calculation
result = Result(node=wkchain.node)
assert result.identifier == "pdos"
result._update_view()
assert len(result.children) == 2

Expand Down
17 changes: 0 additions & 17 deletions tests/test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,6 @@ def test_workchainview(generate_qeapp_workchain):
assert len(wcv.result_tabs.children) == 5
assert wcv.result_tabs._titles["0"] == "Workflow Summary"
assert wcv.result_tabs._titles["1"] == "Final Geometry"
assert wcv.result_tabs._titles["2"] == "Electronic Structure"


def test_electronic_structure(generate_qeapp_workchain):
"""test the report can be properly generated from the builder without errors"""
from aiida import engine

from aiidalab_qe.app.result.workchain_viewer import WorkChainViewer

wkchain = generate_qeapp_workchain()
wkchain.node.set_exit_status(0)
wkchain.node.set_process_state(engine.ProcessState.FINISHED)
wcv = WorkChainViewer(wkchain.node)
assert (
"DOS grouped by:"
== wcv.result_tabs.children[2].children[0].children[0].children[0].value
)


def test_summary_report(data_regression, generate_qeapp_workchain):
Expand Down

0 comments on commit fa0010f

Please sign in to comment.