Skip to content

Commit

Permalink
fix after rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
unkcpz committed Sep 10, 2024
1 parent f0cb9d7 commit 37151e0
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 284 deletions.
2 changes: 2 additions & 0 deletions before-notebook.d/42_start-hq.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ set -x
# if the container is open in system where it has cgroupv1 it will fail.
# Since the image is mostly for demo server where we know the machine and OS I supposed
# it should have cgroupv2 (> Kubernetes v1.25).
# We only build the server for demo server so it does not require user to have new cgroup.
# But for developers, please update your cgroup version to v2.
# See: https://kubernetes.io/docs/concepts/architecture/cgroups/#using-cgroupv2

# computer memory from runtime
Expand Down
6 changes: 3 additions & 3 deletions src/aiidalab_qe/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ def cli():
@click.option("-f", "--force", is_flag=True)
@click.option("--computer")
@click.option("-p", "--profile", default=_DEFAULT_PROFILE)
def install_qe(force, profile):
def install_qe(force, profile, computer):
from aiida import load_profile
from aiidalab_qe.setup.codes import codes_are_setup, install
from aiidalab_qe.setup.codes import codes_are_setup, install_and_setup

load_profile(profile)
try:
for msg in install_and_setup_qe_codes(computer=computer, force=force):
for msg in install_and_setup(computer=computer, force=force):
click.echo(msg)
assert codes_are_setup(computer=computer)
click.secho("Codes are setup!", fg="green")
Expand Down
238 changes: 1 addition & 237 deletions src/aiidalab_qe/common/setup_codes.py
Original file line number Diff line number Diff line change
@@ -1,254 +1,18 @@
import subprocess
from pathlib import Path
from shutil import which
from threading import Thread

import ipywidgets as ipw
import traitlets

from ..setup.codes import QE_VERSION, install
from ..setup.codes import QE_VERSION, install_and_setup
from .widgets import ProgressBar

__all__ = [
"QESetupWidget",
]

FN_INSTALL_LOCKFILE = Path.home().joinpath(".install-qe-on-localhost.lock")
FN_SETUP_LOCKFILE = Path.home().joinpath(".setup-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():
env_exist = get_qe_env().exists()
proc = subprocess.run(
["conda", "list", "-n", f"{get_qe_env().name}", "qe"],
check=True,
capture_output=True,
)

# XXX: "qe" in check is not future proof if there are similar packages such as qe-tool, better solution?? JSON output??
return env_exist and "qe" in str(proc.stdout)


def install_qe():
subprocess.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, computer):
try:
load_code(f"{name}-{QE_VERSION}@{computer}")
except NotExistent:
return False
else:
return True


def codes_are_setup(computer):
return all(_code_is_setup(code_name, computer) 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):
"""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}")
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,
label,
description,
filepath_executable,
default_calc_job_plugin,
prepend_text,
)
return python_code
else:
# the code already exists
return ""


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


def install_and_setup(computer="localhost", force=False):
"""Install Quantum ESPRESSO and the corresponding AiiDA codes.
Args:
force: Ignore previously failed attempts and install anyways.
computer: computer label in AiiDA where the code is setup for
"""
# 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.
# XXX: use filelock to control `FN_DO_NOT_SETUP` as well
if not force and FN_DO_NOT_SETUP.exists():
raise RuntimeError("Installation failed in previous attempt.")

yield from _install()
yield from _setup(computer)


def _install():
"""Install Quantum ESPRESSO."""
yield "Checking installation status..."

conda_installed = which("conda")
try:
with FileLock(FN_INSTALL_LOCKFILE, timeout=5):
if not conda_installed:
raise RuntimeError(
"Unable to automatically install Quantum ESPRESSO, conda "
"is not available."
)

if qe_installed():
return

# Install Quantum ESPRESSO.
yield "Installing QE..."
try:
install_qe()
except subprocess.CalledProcessError as error:
raise RuntimeError(
f"Failed to create conda environment: {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_INSTALL_LOCKFILE, timeout=120):
if not qe_installed():
raise RuntimeError(
"Installation process did not finish in the expected time."
) from None


def _setup(computer):
"""Setup the corresponding AiiDA codes after QE installation."""
yield "Checking setup status..."

try:
with FileLock(FN_SETUP_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(computer=computer):
return # Already setup

# 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, computer=computer):
yield f"Preparing setup script for ({code_name}) on ({computer})..."
code_string = _generate_string_to_setup_code(code_name, computer)
python_code += code_string
try:
yield "Setting up all codes..."
subprocess.run(
["python", "-c", python_code], capture_output=True, check=True
)
except subprocess.CalledProcessError as err:
raise RuntimeError(
f"Failed to setup codes, exit_code={err.returncode}, {err.stderr}"
) 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_SETUP_LOCKFILE, timeout=120):
if not codes_are_setup(computer=computer):
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 37151e0

Please sign in to comment.