Skip to content

Commit

Permalink
Merge pull request #136 from aiidalab/release/1.0.0b11
Browse files Browse the repository at this point in the history
Release/1.0.0b11
  • Loading branch information
yakutovicha committed Oct 6, 2020
2 parents d7a2533 + cf605f2 commit b13ae7d
Show file tree
Hide file tree
Showing 16 changed files with 362 additions and 92 deletions.
7 changes: 7 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
build:
image: latest
python:
version: 3.7
pip_install: true
extra_requirements:
- "docs"
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ before_install:
- pip install -U wheel setuptools coveralls

install:
- pip install .[testing,pre-commit]
- pip install .[pre-commit,docs]
- reentry scan -r aiida

script:
- pre-commit install; pre-commit run --all-files || ( git status --short; git diff ; exit 1 );
- cd docs && make
40 changes: 6 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# aiidalab-widgets-base
[![Documentation Status](https://readthedocs.org/projects/aiidalab-widgets-base/badge/?version=latest)](https://aiidalab-widgets-base.readthedocs.io/en/latest/?badge=latest)

Reusable widgets for applications in the AiiDA lab.
# Base widgets for AiiDAlab

## Installation

Expand All @@ -9,50 +9,22 @@ Install the `aiidalab_widgets_base` python package via:
pip install aiidalab-widgets-base
```

Install the corresponding `aiidalab-widgets-base` AiiDA lab application
Install the corresponding `aiidalab-widgets-base` AiiDAlab application
via the app manager as usual.

### Optional dependencies

* The `SmilesWidget` widget requires the [OpenBabel](http://openbabel.org/) library.

## Documentation
The documentation can be found on the [following web page](https://aiidalab-widgets-base.readthedocs.io).

## Usage

Using the widgets usually just involves importing and displaying them.
For demos, have a look at the jupyter notebooks (`.ipynb` extension) in
this folder.

### Structures

Uploading structures
```python
from aiidalab_widgets_base import StructureUploadWidget
from IPython.display import display

widget = StructureUploadWidget()
# Enforce node format to be CifData:
# widget = StructureUploadWidget(node_class='CifData')
display(widget)
```

![Demo](https://image.ibb.co/fjnHco/structure.gif "Using the StructureUploadWidget.")

### Codes

Selecting codes
```python
from aiidalab_widgets_base import CodeDropdown
from IPython.display import display

# Select from installed codes for 'zeopp.network' input plugin
dropdown = CodeDropdown(input_plugin='zeopp.network')
display(dropdown)

dropdown.selected_code # returns selected code
```

![Demo](https://image.ibb.co/gSFFf8/codes.gif "Using the CodeDropDown.")

## License

MIT
Expand Down
6 changes: 3 additions & 3 deletions aiidalab_widgets_base/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Reusable widgets for AiiDA lab applications."""
"""Reusable widgets for AiiDAlab applications."""
# pylint: disable=unused-import,wrong-import-position
from aiida import load_profile
load_profile()
Expand All @@ -7,7 +7,7 @@
from .computers import SshComputerSetup, valid_sshcomputer_args
from .computers import AiidaComputerSetup, valid_aiidacomputer_args
from .computers import ComputerDropdown
from .databases import CodQueryWidget
from .databases import CodQueryWidget, OptimadeQueryWidget
from .export import ExportButtonWidget
from .process import ProgressBarWidget, ProcessFollowerWidget, ProcessInputsWidget, ProcessOutputsWidget
from .process import ProcessCallStackWidget, RunningCalcJobOutputWidget, SubmitButtonWidget, ProcessReportWidget
Expand All @@ -18,4 +18,4 @@
from .structures_multi import MultiStructureUploadWidget
from .viewers import viewer

__version__ = "1.0.0b10"
__version__ = "1.0.0b11"
21 changes: 11 additions & 10 deletions aiidalab_widgets_base/codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ def valid_aiidacode_args(arguments):
class CodeDropdown(ipw.VBox):
"""Code selection widget.
Attributes:
selected_code(Unicode or Code): Trait that points to the selected Code instance.
It can be set either to an AiiDA Code instance or to a code label (will
automatically be replaced by the corresponding Code instance).
It is linked to the 'value' trait of the `self.dropdown` widget.
codes(Dict): Trait that contains a dictionary (label => Code instance) for all
codes found in the AiiDA database for the selected plugin. It is linked
to the 'options' trait of the `self.dropdown` widget.
selected_code(Unicode or Code): Trait that points to the selected Code instance.
It can be set either to an AiiDA Code instance or to a code label (will automatically
be replaced by the corresponding Code instance). It is linked to the 'value' trait of
the `self.dropdown` widget.
allow_hidden_codes(Bool): Trait that defines whether to show hidden codes or not.
codes(Dict): Trait that contains a dictionary (label => Code instance) for all
codes found in the AiiDA database for the selected plugin. It is linked
to the 'options' trait of the `self.dropdown` widget.
allow_disabled_computers(Bool): Trait that defines whether to show codes on disabled
computers.
allow_hidden_codes(Bool): Trait that defines whether to show hidden codes or not.
allow_disabled_computers(Bool): Trait that defines whether to show codes on disabled
computers.
"""
selected_code = Union([Unicode(), Instance(Code)], allow_none=True)
codes = Dict(allow_none=True)
Expand Down
49 changes: 49 additions & 0 deletions aiidalab_widgets_base/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from traitlets import Instance, default
from ase import Atoms

from optimade_client.query_filter import OptimadeQueryFilterWidget
from optimade_client.query_provider import OptimadeQueryProviderWidget

from aiida.tools.dbimporters.plugins.cod import CodDbImporter


Expand Down Expand Up @@ -110,3 +113,49 @@ def _on_select_structure(self, change):
@default('structure')
def _default_structure(self): # pylint: disable=no-self-use
return None


class OptimadeQueryWidget(ipw.VBox):
"""AiiDAlab-specific OPTIMADE Query widget
Useful as a widget to integrate with the
:class:`aiidalab_widgets_base.structures.StructureManagerWidget`,
embedded into applications.
:param embedded: Whether or not to show extra database and provider information.
When set to `True`, the extra information will be hidden, this is useful
in situations where the widget is used in a Tab or similar, e.g., for the
:class:`aiidalab_widgets_base.structures.StructureManagerWidget`.
:type embedded: bool
:param title: Title used for Tab header if employed in
:class:`aiidalab_widgets_base.structures.StructureManagerWidget`.
:type title: str
"""

structure = Instance(Atoms, allow_none=True)

def __init__(
self,
embedded: bool = True,
title: str = None,
**kwargs,
) -> None:
providers = OptimadeQueryProviderWidget(embedded=embedded)
filters = OptimadeQueryFilterWidget()

ipw.dlink((providers, 'database'), (filters, 'database'))

filters.observe(self._update_structure, names='structure')

self.title = title if title is not None else 'OPTIMADE'
layout = kwargs.pop('layout') if 'layout' in kwargs else {'width': 'auto', 'height': 'auto'}

super().__init__(
children=(providers, filters),
layout=layout,
**kwargs,
)

def _update_structure(self, change: dict) -> None:
"""New structure chosen"""
self.structure = change['new'].as_ase if change['new'] else None
77 changes: 49 additions & 28 deletions aiidalab_widgets_base/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import os
from inspect import isclass
from time import sleep
import warnings
import pandas as pd
import ipywidgets as ipw
from IPython.display import HTML, Javascript, clear_output, display
from traitlets import Instance, Int, List, Unicode, Union, observe, validate

# AiiDA imports.
from aiida.engine import submit, Process
from aiida.engine import submit, Process, ProcessBuilder
from aiida.orm import CalcFunctionNode, CalcJobNode, Node, ProcessNode, WorkChainNode, WorkFunctionNode, load_node
from aiida.cmdline.utils.common import get_calcjob_report, get_workchain_report, get_process_function_report
from aiida.cmdline.utils.ascii_vis import format_call_graph
Expand Down Expand Up @@ -39,18 +40,22 @@ class SubmitButtonWidget(ipw.VBox):
"""Submit button class that creates submit button jupyter widget."""
process = Instance(ProcessNode, allow_none=True)

def __init__(self,
process_class,
input_dictionary_function,
description="Submit",
disable_after_submit=True,
append_output=False,
**kwargs):
def __init__( # pylint: disable=too-many-arguments
self,
process_class,
inputs_generator=None,
input_dictionary_function=None,
description="Submit",
disable_after_submit=True,
append_output=False,
**kwargs):
"""Submit Button widget.
process_class (Process): Process class to submit.
input_dictionary_function (func): Function that generates input parameters dictionary.
inputs_generator (func): Function that returns inputs dictionary or inputs builder.
input_dictionary_function (DEPRECATED): Function that generates input parameters dictionary.
description (str): Description written on the submission button.
Expand All @@ -63,14 +68,26 @@ def __init__(self,
if isclass(process_class) and issubclass(process_class, Process):
self._process_class = process_class
else:
raise ValueError("process_class argument must be a sublcass of {}, got {}".format(Process, process_class))

if callable(input_dictionary_function):
self.input_dictionary_function = input_dictionary_function
raise ValueError(f"process_class argument must be a sublcass of {Process}, got {process_class}")

# Handling the deprecation.
if inputs_generator is None and input_dictionary_function is None:
raise ValueError("The `inputs_generator` argument must be provided.")
if inputs_generator and input_dictionary_function:
raise ValueError("You provided both: `inputs_generator` and `input_dictionary_function` "
"arguments. Please provide `inpust_generator` only.")
if input_dictionary_function:
inputs_generator = input_dictionary_function
warnings.warn(("The `input_dictionary_function` argument is deprecated and "
"will be removed in the release 1.1 of the aiidalab-widgets-base package. "
"Please use the `inputs_generator` argument instead."), DeprecationWarning)

# Checking if the inputs generator is
if callable(inputs_generator):
self.inputs_generator = inputs_generator
else:
raise ValueError(
"input_dictionary_function argument must be a function that returns input dictionary, got {}".format(
input_dictionary_function))
raise ValueError("The `inputs_generator` argument must be a function that "
f"returns input dictionary, got {inputs_generator}")

self.disable_after_submit = disable_after_submit
self.append_output = append_output
Expand All @@ -91,24 +108,28 @@ def on_btn_submit_press(self, _=None):
if not self.append_output:
self.submit_out.value = ''

input_dict = self.input_dictionary_function()
if input_dict is None:
inputs = self.inputs_generator()
if inputs is None:
if self.append_output:
self.submit_out.value += "SubmitButtonWidget: did not recieve input dictionary.<br>"
self.submit_out.value += "SubmitButtonWidget: did not recieve the process inputs.<br>"
else:
self.submit_out.value = "SubmitButtonWidget: did not recieve input dictionary."
self.submit_out.value = "SubmitButtonWidget: did not recieve the process inputs."
else:
self.btn_submit.disabled = self.disable_after_submit
self.process = submit(self._process_class, **input_dict)
if self.disable_after_submit:
self.btn_submit.disabled = True
if isinstance(inputs, ProcessBuilder):
self.process = submit(inputs)
else:
self.process = submit(self._process_class, **inputs)

if self.append_output:
self.submit_out.value += """Submitted process {0}. Click
<a href={1}aiidalab-widgets-base/process.ipynb?id={2} target="_blank">here</a>
to follow.<br>""".format(self.process, self.path_to_root, self.process.pk)
self.submit_out.value += f"""Submitted process {self.process}. Click
<a href={self.path_to_root}aiidalab-widgets-base/process.ipynb?id={self.process.pk}
target="_blank">here</a> to follow.<br>"""
else:
self.submit_out.value = """Submitted process {0}. Click
<a href={1}aiidalab-widgets-base/process.ipynb?id={2} target="_blank">here</a>
to follow.""".format(self.process, self.path_to_root, self.process.pk)
self.submit_out.value = f"""Submitted process {self.process}. Click
<a href={self.path_to_root}aiidalab-widgets-base/process.ipynb?id={self.process.pk}
target="_blank">here</a> to follow."""

for func in self._run_after_submitted:
func(self.process)
Expand Down
14 changes: 8 additions & 6 deletions aiidalab_widgets_base/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
import ipywidgets as ipw
from traitlets import Instance, Int, List, Unicode, Union, dlink, link, default, observe

from sklearn.decomposition import PCA

# ASE imports
import ase
from ase import Atom, Atoms
from ase.data import chemical_symbols, covalent_radii

# AiiDA and AiiDA lab imports
# AiiDA imports
from aiida.engine import calcfunction
from aiida.orm import CalcFunctionNode, CalcJobNode, Data, QueryBuilder, Node, WorkChainNode
from aiida.plugins import DataFactory

from sklearn.decomposition import PCA

# Local imports
from .utils import get_ase_from_file
from .viewers import StructureDataViewer
from .data import LigandSelectorWidget
Expand Down Expand Up @@ -106,7 +108,7 @@ def __init__(self, importers, viewer=None, editors=None, storable=True, node_cla
ipw.HBox(store_and_description + [self.structure_label, self.structure_description])
]

structure_editors = self._struture_editors(editors)
structure_editors = self._structure_editors(editors)
if structure_editors:
structure_editors = ipw.VBox([btn_undo, structure_editors])
accordion = ipw.Accordion([structure_editors])
Expand Down Expand Up @@ -137,7 +139,7 @@ def _structure_importers(self, importers):
dlink((importer, 'structure'), (self, 'input_structure'))
return importers_tab

def _struture_editors(self, editors):
def _structure_editors(self, editors):
"""Preparing structure editors."""
if editors and len(editors) == 1:
link((editors[0], 'structure'), (self, 'structure'))
Expand All @@ -150,7 +152,7 @@ def _struture_editors(self, editors):
# If more than one editor was defined.
if editors:
editors_tab = ipw.Tab()
editors_tab.children = [i for i in editors]
editors_tab.children = tuple(editors)
for i, editor in enumerate(editors):
editors_tab.set_title(i, editor.title)
link((editor, 'structure'), (self, 'structure'))
Expand Down
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/

0 comments on commit b13ae7d

Please sign in to comment.