diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 29363553a..812bcc083 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -31,6 +31,7 @@ RUN apt-get update && \ gnupg \ build-essential \ cmake \ + ccache \ clang \ pkg-config \ python3 \ @@ -54,6 +55,11 @@ RUN apt-get update && \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Install the memory-/core-aware build parallelism helper. It is used during +# dependency installation and may also be sourced by development scripts and +# interactive shells. +COPY .devcontainer/scripts/parallelism.sh /usr/local/share/qdk/parallelism.sh + # Install C++ dependencies COPY .devcontainer/scripts/install_cpp_dependencies.sh /tmp/install_cpp_dependencies.sh COPY cpp/manifest/qdk-chemistry/cgmanifest.json /tmp/cpp_cgmanifest.json @@ -62,7 +68,7 @@ COPY external/macis/manifest/cgmanifest.json /tmp/macis_cgmanifest.json # Delete build directory after installation ENV KEEP_BUILD_DIR=0 RUN bash /tmp/install_cpp_dependencies.sh /tmp/cpp_cgmanifest.json /tmp/macis_cgmanifest.json && \ - rm /tmp/install_cpp_dependencies.sh /tmp/cpp_cgmanifest.json /tmp/macis_cgmanifest.json + rm /tmp/install_cpp_dependencies.sh /tmp/cpp_cgmanifest.json /tmp/macis_cgmanifest.json # Create/align the non-root user and group (safe for repeated builds) RUN if getent group "$USER_GID" >/dev/null; then \ @@ -89,3 +95,23 @@ USER $USERNAME # Setup bash prompt RUN echo 'export PS1="\[\033[1;34m\]\u@qdk-dev:\w$ \[\033[0m\]"' >> ~/.bashrc + +# Create the Python virtual environment and pre-install workspace-independent +# Python dev dependencies. +ENV VIRTUAL_ENV=/home/$USERNAME/qdk_chemistry_venv \ + PATH=/home/$USERNAME/qdk_chemistry_venv/bin:$PATH + +RUN python -m venv "$VIRTUAL_ENV" \ + && pip install --no-cache-dir --upgrade pip setuptools wheel \ + && pip install --no-cache-dir \ + "ipykernel>=6.0" \ + "pandas>=2.0.0" \ + "pre-commit>=4.6,<5" \ + "rdkit" \ + && echo "source $VIRTUAL_ENV/bin/activate" >> ~/.bashrc + +# Set the safe CMAKE_BUILD_PARALLEL_LEVEL for interactive shells +RUN echo "source /usr/local/share/qdk/parallelism.sh" >> ~/.bashrc + +# Make user-local C++ installations discoverable +ENV CMAKE_PREFIX_PATH=/home/$USERNAME/.local diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 000000000..9dbb8ff3a --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,19 @@ +{ + "features": { + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.5", + "resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251", + "integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "1.7.1", + "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6", + "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6" + }, + "ghcr.io/devcontainers/features/rust:1": { + "version": "1.5.0", + "resolved": "ghcr.io/devcontainers/features/rust@sha256:0c55e65f2e3df736e478f26ee4d5ed41bae6b54dac1318c443e31444c8ed283c", + "integrity": "sha256:0c55e65f2e3df736e478f26ee4d5ed41bae6b54dac1318c443e31444c8ed283c" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 412ed3be0..66c3c7124 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,12 +20,12 @@ "ms-toolsai.jupyter" ], "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.defaultInterpreterPath": "${containerEnv:VIRTUAL_ENV}/bin/python", "python.formatting.provider": "none", "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.fixAll.ruff": true, - "source.organizeImports.ruff": true + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" }, "python.linting.enabled": true, "python.linting.lintOnSave": true, @@ -55,16 +55,26 @@ "C_Cpp.codeAnalysis.clangTidy.runAutomatically": true, "C_Cpp.formatting": "clangFormat", "C_Cpp.clang_format_style": "file", - "C_Cpp.clang_format_fallbackStyle": "Google" + "C_Cpp.clang_format_fallbackStyle": "Google", + "C_Cpp.default.cppStandard": "c++20", + "cmake.buildType": "Debug" } } }, "features": { - "ghcr.io/devcontainers/features/git:1": {} + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + }, + "ghcr.io/devcontainers/features/rust:1": { + "version": "stable", + "profile": "default" + } }, "remoteEnv": { - "CPATH": "${containerEnv:CPATH}:/usr/include/hdf5/serial:/usr/include/eigen3" + "CPATH": "${containerEnv:CPATH}:/usr/include/hdf5/serial:/usr/include/eigen3", + "CMAKE_BUILD_TYPE": "Debug" }, - "postCreateCommand": "bash .devcontainer/scripts/venv.sh", + "postCreateCommand": "bash .devcontainer/scripts/post_create.sh", "remoteUser": "vscode" } diff --git a/.devcontainer/scripts/install_cpp_dependencies.sh b/.devcontainer/scripts/install_cpp_dependencies.sh index d7e32aca8..07d88660b 100644 --- a/.devcontainer/scripts/install_cpp_dependencies.sh +++ b/.devcontainer/scripts/install_cpp_dependencies.sh @@ -37,13 +37,30 @@ BUILD_DIR="${BUILD_DIR:-/tmp/qdk_deps_build}" INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local}" BUILD_TYPE="${BUILD_TYPE:-Release}" BUILD_SHARED_LIBS="${BUILD_SHARED_LIBS:-OFF}" # Default to static -LIBINT_JOBS=${LIBINT_JOBS:-4} # Limit libint build jobs to 4 due to high memory usage + KEEP_BUILD_DIR="${KEEP_BUILD_DIR:-0}" -if command -v nproc >/dev/null 2>&1; then - JOBS=$(nproc) # Linux + +PARALLELISM_HELPER="/usr/local/share/qdk/parallelism.sh" +if [ -f "$PARALLELISM_HELPER" ]; then + # shellcheck source=/dev/null + source "$PARALLELISM_HELPER" +fi + +if command -v parallel_jobs_for_memory >/dev/null 2>&1; then + # Use the parallelism helper to determine job counts based on memory + JOBS="${JOBS:-$(parallel_jobs_for_memory 1)}" + LIBINT_JOBS="${LIBINT_JOBS:-$(parallel_jobs_for_memory 4)}" else - JOBS=$(sysctl -n hw.logicalcpu) # macOS + # Fallback when helper is unavailable + if command -v nproc >/dev/null 2>&1; then + DEFAULT_JOBS=$(nproc) # Linux + else + DEFAULT_JOBS=$(sysctl -n hw.logicalcpu) # macOS + fi + JOBS="${JOBS:-$DEFAULT_JOBS}" + LIBINT_JOBS="${LIBINT_JOBS:-4}" fi + MAC_BUILD="OFF" if [[ "$OSTYPE" == "darwin"* ]]; then MAC_BUILD="ON" @@ -224,7 +241,6 @@ cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_INSTALL_PREFIX="$INSTALL_PREFIX" \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBS" -# libint's compilation is memory intensive so parallel jobs are limited to 4 to prevent OOM errors make -j"$LIBINT_JOBS" make install cd "$BUILD_DIR" diff --git a/.devcontainer/scripts/parallelism.sh b/.devcontainer/scripts/parallelism.sh new file mode 100644 index 000000000..7d883b065 --- /dev/null +++ b/.devcontainer/scripts/parallelism.sh @@ -0,0 +1,56 @@ +# shellcheck shell=bash +# +# This file is intended to be sourced. +# +# It provides: +# - parallel_jobs_for_memory() +# - a default CMAKE_BUILD_PARALLEL_LEVEL (unless already set) +# +# parallel_jobs_for_memory +# +# Compute a safe parallel job count based on available RAM and CPU cores. +# +# Result: +# min(cpu_cores, floor(total_ram_gb / memory_per_job_gb)) +# +# with a minimum value of 1. +# +parallel_jobs_for_memory() { + local memory_per_job_gb="$1" + local cores mem_bytes jobs + + if [ -r /proc/meminfo ] && command -v nproc >/dev/null 2>&1; then + # Linux + cores=$(nproc) + mem_bytes=$(awk '/MemTotal/ {print $2 * 1024}' /proc/meminfo) + else + # macOS (or other non-/proc environments) + cores=$(sysctl -n hw.logicalcpu) + mem_bytes=$(sysctl -n hw.memsize) + fi + + # If running inside a cgroup with a memory limit, respect it. + local cgroup_limit_bytes="" + if [ -r /sys/fs/cgroup/memory.max ]; then + cgroup_limit_bytes="$(cat /sys/fs/cgroup/memory.max)" # cgroup v2 + elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then + cgroup_limit_bytes="$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)" # cgroup v1 + fi + if [[ "$cgroup_limit_bytes" =~ ^[0-9]+$ ]] && (( cgroup_limit_bytes > 0 )) && (( cgroup_limit_bytes < mem_bytes )); then + mem_bytes="$cgroup_limit_bytes" + fi + + # tiny overhead to get common cases correct (e.g., 15.9GB RAM, 8GB per job -> 2 jobs) + mem_bytes=$(( mem_bytes * 103 / 100 )) + + jobs=$(( mem_bytes / (memory_per_job_gb * 1024 * 1024 * 1024) )) + + (( jobs < 1 )) && jobs=1 + (( jobs > cores )) && jobs=$cores + + echo "$jobs" +} + +if [ -z "${CMAKE_BUILD_PARALLEL_LEVEL:-}" ]; then + export CMAKE_BUILD_PARALLEL_LEVEL="$(parallel_jobs_for_memory 8)" +fi diff --git a/.devcontainer/scripts/post_create.sh b/.devcontainer/scripts/post_create.sh new file mode 100644 index 000000000..c45c37155 --- /dev/null +++ b/.devcontainer/scripts/post_create.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Post-create step: install QDK Chemistry from the mounted source. +set -euo pipefail +source "$HOME/qdk_chemistry_venv/bin/activate" + +# Set a memory-/core-aware CMAKE_BUILD_PARALLEL_LEVEL (unless already set) so the +# initial build below does not oversubscribe CPU or OOM on constrained machines. +source /usr/local/share/qdk/parallelism.sh + +# Build C++ and install to a user-local prefix. +# Use the in-tree macis (external/macis) per INSTALL.md; prevents a +# full rebuild on reconfigure. +cmake -S cpp -B cpp/build -G Ninja \ + -DCMAKE_INSTALL_PREFIX="$HOME/.local" \ + -DCMAKE_DISABLE_FIND_PACKAGE_macis=ON +cmake --build cpp/build +cmake --install cpp/build + +# Install python library +cd ./python +pip install -v .[all] diff --git a/.devcontainer/scripts/venv.sh b/.devcontainer/scripts/venv.sh deleted file mode 100644 index 28d16c59f..000000000 --- a/.devcontainer/scripts/venv.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -python -m venv $HOME/qdk_chemistry_venv -source $HOME/qdk_chemistry_venv/bin/activate - -pip install --upgrade pip - -# Install node -export NVM_DIR="$HOME/.nvm" -mkdir -p $NVM_DIR -wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash -source $NVM_DIR/nvm.sh && nvm install node && nvm use node - -# Install Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -source $HOME/.cargo/env -which rustc - -# Install ipykernel -pip install ipykernel pandas pre-commit rdkit - -# Install QDK Chemistry package -cd ./python -export CMAKE_BUILD_PARALLEL_LEVEL=2 -QDK_UARCH=native pip install -v .[all] - -# Add venv to bash -echo "source $HOME/qdk_chemistry_venv/bin/activate" >> $HOME/.bashrc diff --git a/INSTALL.md b/INSTALL.md index bdfdc7d5d..342a71854 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -125,6 +125,10 @@ Alternatively, click the green button in the bottom-left corner of VS Code and s After the initial build, restart VS Code and reopen in the container to ensure the Python virtual environment is properly loaded. +### Step 4: Develop + +The dev container builds the C++ library and links it to the Python package in separate steps, as shown in `.devcontainer/scripts/post_create.sh`. After changing the source code, you need to rebuild and install accordingly. + **NOTE:** - The first build can take up to two hours on slower systems.