Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/pip/docs/urllib3-2.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
superstar54 authored Aug 19, 2024
2 parents e3488dd + 8855969 commit b3da3bb
Show file tree
Hide file tree
Showing 14 changed files with 468 additions and 438 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ COPY --from=qe_conda_env ${QE_DIR} ${QE_DIR}

USER root
COPY ./before-notebook.d/* /usr/local/bin/before-notebook.d/
RUN fix-permissions "${CONDA_DIR}"

# Remove content of $HOME
# '-mindepth=1' ensures that we do not remove the home directory itself.
RUN find /home/${NB_USER}/ -mindepth 1 -delete
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

## About

This is a early-development implementation of an AiiDAlab application for Quantum ESPRESSO workflow.
The app allows the execution of a workflow with Quantum ESPRESSO that includes the selection of an input structure, its relaxation, and the bands structure calculation.
This is an AiiDAlab application for Quantum ESPRESSO workflows.
The app allows the execution of a workflow with Quantum ESPRESSO that includes the selection of an input structure, its relaxation, the bands structure calculation, and more!

**The app is currently in an early development stage!**

Expand Down Expand Up @@ -41,6 +41,16 @@ Then, you can run the integration tests with:
pytest --driver Chrome tests_integration
```

### Published Docker images

Supported tags released on [Github Container Registry](https://ghcr.io/aiidalab):

- `edge` – the latest commit on the default branch (`main`)
- `latest` – the latest stable release
- `$version` – the version of a specific release (ex. `2022.1001`)

Pull requests into the default branch are further released on ghcr.io with the `pr-###` tag to simplify the testing of development versions.

## For maintainers

To create a new release, clone the repository, install development dependencies with `pip install '.[dev]'`, and then execute `bumpver update`.
Expand Down
18 changes: 10 additions & 8 deletions src/aiidalab_qe/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@

import click

from aiida import load_profile
from aiidalab_qe.common.setup_codes import codes_are_setup
from aiidalab_qe.common.setup_codes import install as install_qe_codes

# The default profile name of AiiDAlab container.
_DEFAULT_PROFILE = "default"

Expand All @@ -22,9 +18,12 @@ def cli():
@click.option("-f", "--force", is_flag=True)
@click.option("-p", "--profile", default=_DEFAULT_PROFILE)
def install_qe(force, profile):
from aiida import load_profile
from aiidalab_qe.setup.codes import codes_are_setup, install

load_profile(profile)
try:
for msg in install_qe_codes(force=force):
for msg in install(force=force):
click.echo(msg)
assert codes_are_setup()
click.secho("Codes are setup!", fg="green")
Expand All @@ -45,7 +44,8 @@ def install_pseudos(profile, source):
"""Install pseudopotentials from a local folder if source is specified,
otherwise download from remote repositories.
"""
from aiidalab_qe.common.setup_pseudos import install
from aiida import load_profile
from aiidalab_qe.setup.pseudos import install

load_profile(profile)

Expand All @@ -68,7 +68,7 @@ def install_pseudos(profile, source):
type=click.Path(exists=True, path_type=Path, resolve_path=True),
)
def download_pseudos(dest):
from aiidalab_qe.common.setup_pseudos import EXPECTED_PSEUDOS, _install_pseudos
from aiidalab_qe.setup.pseudos import EXPECTED_PSEUDOS, _install_pseudos

try:
for progress in _install_pseudos(
Expand All @@ -90,9 +90,11 @@ def download_pseudos(dest):
)
@click.option("-p", "--profile", default=_DEFAULT_PROFILE)
def test_plugin(plugin_name, profile):
load_profile(profile)
from aiida import load_profile
from aiidalab_qe.app.utils import test_plugin_functionality

load_profile(profile)

try:
success, message = test_plugin_functionality(plugin_name)
if success:
Expand Down
2 changes: 1 addition & 1 deletion src/aiidalab_qe/app/configuration/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from aiida_quantumespresso.workflows.pw.base import PwBaseWorkChain
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.common.panel import Panel
from aiidalab_qe.common.setup_pseudos import PseudoFamily
from aiidalab_qe.common.widgets import HubbardWidget
from aiidalab_qe.setup.pseudos import PseudoFamily

from .pseudos import PseudoFamilySelector, PseudoSetter

Expand Down
2 changes: 1 addition & 1 deletion src/aiidalab_qe/app/configuration/pseudos.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from aiida.plugins import DataFactory, GroupFactory
from aiida_quantumespresso.workflows.pw.base import PwBaseWorkChain
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.common.setup_pseudos import (
from aiidalab_qe.setup.pseudos import (
PSEUDODOJO_VERSION,
SSSP_VERSION,
PseudoFamily,
Expand Down
201 changes: 2 additions & 199 deletions src/aiidalab_qe/common/setup_codes.py
Original file line number Diff line number Diff line change
@@ -1,215 +1,18 @@
from pathlib import Path
from shutil import which
from subprocess import CalledProcessError, run
from threading import Thread

import ipywidgets as ipw
import traitlets
from filelock import FileLock, Timeout

from aiida.common.exceptions import NotExistent
from aiida.orm import load_code
from aiidalab_qe.common.widgets import ProgressBar
from ..setup.codes import QE_VERSION, install
from .widgets import ProgressBar

__all__ = [
"QESetupWidget",
]

FN_LOCKFILE = Path.home().joinpath(".install-qe-on-localhost.lock")
FN_DO_NOT_SETUP = Path.cwd().joinpath(".do-not-setup-on-localhost")

QE_VERSION = "7.2"


def get_qe_env():
# QE is already pre-installed in the QE image
path = Path(f"/opt/conda/envs/quantum-espresso-{QE_VERSION}")
if path.exists():
return path
else:
return Path.home().joinpath(".conda", "envs", f"quantum-espresso-{QE_VERSION}")


# Add all QE codes with the calcjob entry point in the aiida-quantumespresso.
CODE_NAMES = (
"pw",
"projwfc",
"dos",
"cp",
"epw",
"matdyn",
"neb",
"open_grid",
"ph",
"pp",
"pw2gw",
"pw2wannier90",
"q2r",
"xspectra",
"hp",
)


def qe_installed():
return get_qe_env().exists()


def install_qe():
run(
[
"conda",
"create",
"--yes",
"--override-channels",
"--channel",
"conda-forge",
"--prefix",
str(get_qe_env()),
f"qe={QE_VERSION}",
],
capture_output=True,
check=True,
)


def _code_is_setup(name):
try:
load_code(f"{name}-{QE_VERSION}@localhost")
except NotExistent:
return False
else:
return True


def codes_are_setup():
return all(_code_is_setup(code_name) for code_name in CODE_NAMES)


def _generate_header_to_setup_code():
"""Generate the header string to setup a code for a given computer."""
header_code = """
from aiida.orm.nodes.data.code.installed import InstalledCode
from aiida.orm import load_computer
from aiida import load_profile
load_profile()
"""
return header_code


def _generate_string_to_setup_code(code_name, computer_name="localhost"):
"""Generate the Python string to setup an AiiDA code for a given computer.
Tries to load an existing code and if not existent,
generates Python code to create and store a new code setup."""
try:
load_code(f"{code_name}-{QE_VERSION}@{computer_name}")
except NotExistent:
label = f"{code_name}-{QE_VERSION}"
description = f"{code_name}.x ({QE_VERSION}) setup by AiiDAlab."
filepath_executable = get_qe_env().joinpath("bin", f"{code_name}.x")
default_calc_job_plugin = f"quantumespresso.{code_name}"
prepend_text = f'eval "$(conda shell.posix hook)"\\nconda activate {get_qe_env()}\\nexport OMP_NUM_THREADS=1'
python_code = """
computer = load_computer('{}')
code = InstalledCode(computer=computer,
label='{}',
description='{}',
filepath_executable='{}',
default_calc_job_plugin='{}',
prepend_text='{}'
)
code.store()
""".format( # noqa: UP032
computer_name,
label,
description,
filepath_executable,
default_calc_job_plugin,
prepend_text,
)
return python_code
else:
# the code already exists
return ""


def setup_codes():
python_code = _generate_header_to_setup_code()
for code_name in CODE_NAMES:
python_code += _generate_string_to_setup_code(code_name)
try:
run(["python", "-c", python_code], capture_output=True, check=True)
except CalledProcessError as error:
raise RuntimeError(f"Failed to setup codes: {error}") from None


def install(force=False):
"""Install Quantum ESPRESSO and the corresponding AiiDA codes.
Args:
force: Ignore previously failed attempts and install anyways.
"""
# Check for "do not install file" and skip actual check. The purpose of
# this file is to not re-try this process on every app start in case that
# there are issues.
if not force and FN_DO_NOT_SETUP.exists():
raise RuntimeError("Installation failed in previous attempt.")

yield "Checking installation status..."

conda_installed = which("conda")
try:
with FileLock(FN_LOCKFILE, timeout=5):
# We assume that if the codes are already setup, everything is in
# order. Only if they are not present, should we take action,
# however we only do so if the environment has a conda binary
# present (`which conda`). If that is not the case then we assume
# that this is a custom user environment in which case we also take
# no further action.
if codes_are_setup():
return # Already setup

if not conda_installed:
raise RuntimeError(
"Unable to automatically install Quantum ESPRESSO, conda "
"is not available."
)

if not qe_installed():
# First, install Quantum ESPRESSO.
yield "Installing QE..."
try:
install_qe()
except CalledProcessError as error:
raise RuntimeError(
f"Failed to create conda environment: {error}"
) from None

# After installing QE, we install the corresponding
# AiiDA codes:
python_code = _generate_header_to_setup_code()
for code_name in CODE_NAMES:
if not _code_is_setup(code_name):
yield f"Preparing setup script for ({code_name})..."
code_string = _generate_string_to_setup_code(code_name)
python_code += code_string
try:
yield "Setting up all codes..."
run(["python", "-c", python_code], capture_output=True, check=True)
except CalledProcessError as error:
raise RuntimeError(f"Failed to setup codes: {error}") from None

except Timeout:
# Assume that the installation was triggered by a different process.
yield "Installation was already started, waiting for it to finish..."
with FileLock(FN_LOCKFILE, timeout=120):
if not codes_are_setup():
raise RuntimeError(
"Installation process did not finish in the expected time."
) from None


class QESetupWidget(ipw.VBox):
installed = traitlets.Bool(allow_none=True).tag(readonly=True)
Expand Down
Loading

0 comments on commit b3da3bb

Please sign in to comment.