Skip to content

Commit

Permalink
Import from json (#23)
Browse files Browse the repository at this point in the history
* bump aiida-sssp-workflow and refacting traits for pseudos selection

* F-m, visibility of buttons

* fix-me: delta EOS compare.

* pre-commit

* minor change

* json uploader for verification
  • Loading branch information
unkcpz authored Mar 30, 2023
1 parent 51119b4 commit 925f618
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 69 deletions.
35 changes: 30 additions & 5 deletions aiidalab_sssp/inspect/subwidgets/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,9 @@ class EosComparisonWidget(ipw.VBox):

def __init__(self):
self.select_pseudo_ref = ipw.Dropdown()
self.select_pseudo_ref.observe(self._on_pseudo_select, names="value")

self.select_pseudo_comp = ipw.Dropdown()
self.select_pseudo_comp.observe(self._on_pseudo_select, names="value")

self._observer_on_for_pseudos_dropdown()

self.select_configuration = ipw.Dropdown()
self.select_configuration.observe(self._on_configuration_change, names="value")
Expand All @@ -165,15 +164,36 @@ def __init__(self):
],
)

def _observer_on_for_pseudos_dropdown(self):
self.select_pseudo_ref.observe(self._on_pseudo_select, names="value")
self.select_pseudo_comp.observe(self._on_pseudo_select, names="value")

def _unobserve_on_for_pseudos_dropdown(self):
self.select_pseudo_ref.unobserve(self._on_pseudo_select, names="value")
self.select_pseudo_comp.unobserve(self._on_pseudo_select, names="value")

@traitlets.observe("pseudos")
def _on_pseudos_change(self, change):
if change["new"] is not None and len(change["new"]) > 0:
self.layout.display = "block"
with self.hold_trait_notifications():
pseudo_list = list(self.pseudos.keys())

# remove the observer before update the dropdown menu
self._unobserve_on_for_pseudos_dropdown()

# update the dropdown menu
self.select_pseudo_ref.options = pseudo_list
self.select_pseudo_comp.options = pseudo_list

# update the configuration dropdown menu and select the first and second pseudo, respectively
self.select_pseudo_ref.value = pseudo_list[0]
self.select_pseudo_comp.value = pseudo_list[1]
self._update_configuration(pseudo_list[0], pseudo_list[1])

# add the observer back
self._observer_on_for_pseudos_dropdown()

self.update_plot()
else:
self.layout.display = "none"
Expand All @@ -186,8 +206,13 @@ def _on_pseudo_select(self, _):
if label_ref is None or label_comp is None:
return

_data_ref = self.pseudos[label_ref]["accuracy"]["delta"]
_data_comp = self.pseudos[label_comp]["accuracy"]["delta"]
self._update_configuration(label_ref, label_comp)
self.update_plot()

def _update_configuration(self, ref, comp):
"""Update configuration dropdown options"""
_data_ref = self.pseudos[ref]["accuracy"]["delta"]
_data_comp = self.pseudos[comp]["accuracy"]["delta"]

# The configuration list is the intersection of two pseudos
self.select_configuration.options = [
Expand Down
69 changes: 61 additions & 8 deletions aiidalab_sssp/inspect/subwidgets/periodic_table.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import shutil
import tarfile
Expand All @@ -7,17 +8,32 @@
import traitlets
from widget_periodictable import PTableWidget

from aiidalab_sssp.inspect import SSSP_DB

__all__ = ("PeriodicTable",)


_DB_URL = "https://github.com/unkcpz/sssp-verify-scripts/raw/main/sssp_db.tar.gz"
_DB_FOLDER = "sssp_db"


def _load_pseudos(element, db=SSSP_DB) -> dict:
"""Open result json file of element return as dict"""
if element:
json_fn = os.path.join(db, f"{element}.json")
with open(json_fn, "r") as fh:
pseudos = json.load(fh)

return {key: pseudos[key] for key in sorted(pseudos.keys(), key=str.lower)}

return dict()


class PeriodicTable(ipw.VBox):
"""Wrapper-widget for PTableWidget"""
"""Wrapper-widget for PTableWidget, select the element and update the dict of pseudos"""

selected_element = traitlets.Unicode(allow_none=True)
# (output) dict of pseudos for selected element
pseudos = traitlets.Dict(allow_none=True)

def __init__(self, cache_folder, **kwargs):
self._disabled = kwargs.get("disabled", False)
Expand All @@ -26,6 +42,9 @@ def __init__(self, cache_folder, **kwargs):
self.ptable = PTableWidget(states=1, selected_colors=["green"], **kwargs)
self._last_selected = None
self.ptable.observe(self._on_element_select)
self._element = None # selected element, for record the last selected element

self.elements = set() # elements that have json file in the db folder

# if cache empty run update: first time
self.db_version = None
Expand All @@ -43,6 +62,11 @@ def __init__(self, cache_folder, **kwargs):
)
db_update.on_click(self._update_db)

self.json_upload = ipw.FileUpload(
accept=".json", multiple=False, description="Upload json file"
)
self.json_upload.observe(self._on_json_upload, names="value")

super().__init__(
children=(
self.ptable,
Expand All @@ -52,10 +76,35 @@ def __init__(self, cache_folder, **kwargs):
ipw.HTML(f"The SSSP Database version: {self.db_version}"),
]
),
self.json_upload,
),
layout=kwargs.get("layout", {}),
)

def _on_json_upload(self, change):
if change["name"] == "value" and change["type"] == "change":
if change["new"]:
# get the first file
file_name = list(change["new"].keys())[0]
content = change["new"][file_name]["content"]
pseudos = json.loads(content.decode("utf-8"))

# check if the pseudos are valid by check the element name from the key of pseudos
for key in pseudos:
if self._element is not None and self._element not in key:
raise ValueError(
f"The element name in the json file is not {self._element}, please check the json file."
)

if self.pseudos is None or len(self.pseudos) == 0:
self.pseudos = pseudos
else:
self.pseudos = self.pseudos.update(pseudos)

# reset the upload widget to empty so that the same file can be uploaded again
# self.json_upload.value = {}
self.json_upload._counter = 0

def _on_element_select(self, event):
if event["name"] == "selected_elements" and event["type"] == "change":
if tuple(event["new"].keys()) == ("Du",):
Expand All @@ -68,16 +117,20 @@ def _on_element_select(self, event):
# If this is empty it's ok, unselect all
# If there is more than one, that's weird... to avoid problems, anyway, I pick one of the two
if newly_selected:
element = list(newly_selected)[0]
self.ptable.selected_elements = {element: 0}
self.selected_element = element
self._element = list(newly_selected)[0]
self.ptable.selected_elements = {self._element: 0}
self.update_pseudos(self._element)
else:
self.reset()
# To have the correct 'last' value for next calls
self._last_selected = self.ptable.selected_elements
else:
# first time set: len(event['new']) -> 1
self.selected_element = list(event["new"])[0]
self._element = list(event["new"])[0]
self.update_pseudos(self._element)

def update_pseudos(self, element):
self.pseudos = _load_pseudos(element)

def _update_db(self, _=None, download=True):
"""update cached db fetch from remote. and update ptable"""
Expand All @@ -96,10 +149,10 @@ def _update_db(self, _=None, download=True):

@staticmethod
def _get_enabled_elements(cache_folder):
elements = []
elements = set()
for fn in os.listdir(os.path.join(cache_folder, _DB_FOLDER)):
if "band" not in fn:
elements.append(fn.split(".")[0])
elements.add(fn.split(".")[0])

return elements

Expand Down
87 changes: 36 additions & 51 deletions aiidalab_sssp/inspect/subwidgets/select.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
import json
import os

import ipywidgets as ipw
import traitlets

from aiidalab_sssp.inspect import SSSP_DB, parse_label
from aiidalab_sssp.inspect import parse_label

BASE_DOWNLOAD_URL = (
"https://raw.githubusercontent.com/unkcpz/sssp-verify-scripts/main/libraries-pbe"
)


def _load_pseudos(element, db=SSSP_DB) -> dict:
"""Open result json file of element return as dict"""
if element:
json_fn = os.path.join(db, f"{element}.json")
with open(json_fn, "r") as fh:
pseudos = json.load(fh)

return {key: pseudos[key] for key in sorted(pseudos.keys(), key=str.lower)}

return dict()


class SelectMultipleCheckbox(ipw.VBox):
"""Widget with a search field and lots of checkboxes of pseudopotentials"""

Expand Down Expand Up @@ -129,30 +114,28 @@ def _observe_options_change(self, change):


class PseudoSelectWidget(ipw.VBox):
element = traitlets.Unicode(allow_none=True)
selected_pseudos = traitlets.Dict(allow_none=True)
# (input) all pseudos of a element, the whole dict from json fixed once element choosen
pseudos = traitlets.Dict(allow_none=True)

NO_ELEMENT_INFO = "No element is selected"
# (output) selected pseudos of a element, the whole dict once pseudos selected
selected_pseudos = traitlets.Dict(allow_none=True)

def __init__(self):
# store all pseudos of a element, the whole dict from json fixed once
# element choosen
self._element_pseudos = {}

self.help_info = ipw.HTML(self.NO_ELEMENT_INFO)
self.reset_select = ipw.Button(
self.NO_PSEUDOS_FOR_SELECT_INFO = "No pseudopotentials available for compare, please select an element or upload a verification file."
self.help_info = ipw.HTML(self.NO_PSEUDOS_FOR_SELECT_INFO)
self.unselect_all = ipw.Button(
description="Unselect All",
button_style="info",
)
self.reset_select.on_click(self._on_reset_click)
self.unselect_all.on_click(self._unselect_all_click)

self.select_all = ipw.Button(
description="Select All",
button_style="info",
)
self.select_all.on_click(self._on_select_all_click)

self.select_buttons = ipw.HBox(children=[self.reset_select, self.select_all])
self.select_buttons = ipw.HBox(children=[self.unselect_all, self.select_all])
self.select_buttons.layout.visibility = "hidden"

self.multiple_selection = SelectMultipleCheckbox(
Expand All @@ -170,7 +153,7 @@ def __init__(self):
]
)

def _on_reset_click(self, _):
def _unselect_all_click(self, _):
"""Unselect all"""
# self.selected_pseudos = {}
self.multiple_selection.unselecet_all()
Expand All @@ -180,32 +163,34 @@ def _on_select_all_click(self, _):
# self.selected_pseudos = _load_pseudos(self.element)
self.multiple_selection.selecet_all()

@traitlets.observe("element")
def _observe_elements(self, change):
if change["new"]:
self.select_buttons.layout.visibility = "visible"
# if select/unselect new element update prompt help info
self.help_info.value = (
f"Please choose pseudopotentials of element {self.element} to inspect:"
)

# If an element is chosen update checkbox list
# self.pseudos store all dict for the element the initial parsed from element json
# select all pseudos of element as default
self._element_pseudos = _load_pseudos(self.element)
self.multiple_selection.options = list(self._element_pseudos.keys())
self.selected_pseudos = self._element_pseudos.copy()
@traitlets.observe("pseudos")
def _observe_pseudos(self, change):
if change["new"] is not None and change["new"] != {}: # pseudos is not empty
with self.hold_trait_notifications():
self.select_buttons.layout.visibility = "visible"
# if select/unselect new element update prompt help info
self.help_info.value = "Please choose pseudopotentials to inspect:"

# self.pseudos store all dict for the element the initial parsed from element json
# select all pseudos of element as default
self.multiple_selection.options = list(self.pseudos.keys())
self.selected_pseudos = (
self.pseudos.copy()
) # the traitlets need to be a copy of the dict otherwise it will not trigger the change event
else:
# element is unselected reset multiple select widget
# if empty dict passed (by unseleted the element) reset multiple select widget
self.reset()

def _on_multiple_selection_change(self, change):
self.selected_pseudos = {
k: self._element_pseudos[k] for k in sorted(change["new"], key=str.lower)
}
with self.hold_trait_notifications():
self.selected_pseudos = {
k: self.pseudos[k] for k in sorted(change["new"], key=str.lower)
}

def reset(self):
self.select_buttons.layout.visibility = "hidden"
self.help_info.value = self.NO_ELEMENT_INFO
self.multiple_selection.options = list()
self.selected_pseudos = {}
"""Reset the widget to initial state, no checkbox widget at all"""
with self.hold_trait_notifications():
self.select_buttons.layout.visibility = "hidden"
self.help_info.value = self.NO_PSEUDOS_FOR_SELECT_INFO
self.multiple_selection.options = list()
self.selected_pseudos = {}
9 changes: 5 additions & 4 deletions inspect.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@
"bandstucture = BandStructureWidget()\n",
"\n",
"ipw.dlink(\n",
" (ptable, 'selected_element'), \n",
" (pseudo_select, 'element'),\n",
" (ptable, 'pseudos'),\n",
" (pseudo_select, 'pseudos'),\n",
")\n",
"\n",
"####################\n",
Expand Down Expand Up @@ -134,8 +134,9 @@
"ipw.dlink((pseudo_select, 'selected_pseudos'), (nu_preview, 'pseudos'))\n",
"ipw.dlink((pseudo_select, 'selected_pseudos'), (eos_comparison, 'pseudos'))\n",
"ipw.dlink((pseudo_select, 'selected_pseudos'), (bandchessboard, 'pseudos'))\n",
"ipw.dlink((pseudo_select, 'selected_pseudos'), (convergence, 'pseudos'))\n",
"ipw.dlink((pseudo_select, 'selected_pseudos'), (bandstucture, 'pseudos'))\n",
"ipw.dlink((pseudo_select, 'selected_pseudos'), (convergence, 'pseudos'))\n",
"\n",
"\n",
"display(ptable)\n",
"display(pseudo_select)\n",
Expand Down Expand Up @@ -167,7 +168,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.9"
"version": "3.9.13"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ project_urls =
packages = find:
install_requires =
aiida-core~=2.2
aiida-sssp-workflow~=3.0.0a0
aiida-sssp-workflow~=3.0.0a1
aiidalab-widgets-base~=2.0.0b5
widget-bandsplot~=0.5.1
widget-periodictable~=3.0
Expand Down

0 comments on commit 925f618

Please sign in to comment.