Installation & the build (make setup)#

qc-rs is a Rust numerical core wrapped in a Python package (qc). Today you build it from source — there is no pip install qc yet — so “installing qc-rs” means: install a few standard tools, compile the qc_rs extension into a Python environment, and check that import qc works. This chapter is self-contained: every command you need is here, with an explanation of why. It is longer than a bare recipe on purpose — read the part that matches your machine and skip the rest.

Two situations:

  1. qc-rs is already installed for you (common on a shared cluster) — see §1 below and just verify.

  2. You are building it yourself — work through §2.

At the very end, §3 shows the make setup / make install shortcut that automates the whole machine-specific build once you understand the pieces.

1. Is it already installed?#

On a shared machine, an admin may have already built qc-rs into a Python environment. Activate that environment (ask which one), then run:

python -c "import qc; print('qc-rs ok:', qc.__file__)"

If that prints a path with no error, you are done — go to the Quickstart. If it raises ModuleNotFoundError: No module named 'qc' (or 'qc_rs'), build it yourself below.

2. Building it yourself#

The build needs, in order: git (to get the code), a Rust toolchain (to compile the core), uv with a Python (the environment), a BLAS/LAPACK library (the dense linear-algebra backend), and then the maturin build itself. Optional pieces — MPI and a CUDA GPU — come at the end.

Tip

A Python note A virtual environment (“venv”) is a self-contained Python installation with its own packages, so different projects never clash. qc-rs uses the uv tool to create and manage one. You activate a venv to make its python the one your shell uses.

2.1 Get the source#

Clone the repository over HTTPS (no SSH key needed) or with the GitHub CLI:

git clone https://github.com/qclo/qc_rs.git      # A) HTTPS
gh   repo  clone qclo/qc_rs                        # B) GitHub CLI (reuses your `gh auth login`)
cd qc_rs

2.2 Install Rust (via rustup)#

qc-rs compiles a Rust extension, so you need the Rust toolchain — installed through rustup, not a distribution’s rustc:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"          # put ~/.cargo/bin on PATH for this shell
which rustc                    # should print a path under ~/.cargo/bin

The -y runs non-interactively and is required on machines that already have a system /usr/bin/rustc (otherwise rustup-init aborts with “cannot install while Rust is installed”). rustup puts ~/.cargo/bin ahead of /usr/bin, so cargo/rustc resolve to the rustup toolchain, and the distro rustc is ignored. Every new shell must source ~/.cargo/env (or have it in your shell rc).

2.3 Install uv and create the project venv#

qc-rs builds into a shared external uv project — a directory that holds one library set — pointed to by the UV_PROJECT environment variable, not a ./.venv inside the repo. Set it up once per machine:

# Install uv (once per machine)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Pick a directory for the shared project and point uv at it
export UV_PROJECT=~/local/myproj
mkdir -p "$UV_PROJECT"

# A base project file: the common scientific-Python stack qc-rs expects
cat << 'EOF' > "$UV_PROJECT/pyproject.toml"
[project]
name = "myproj"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [ "numpy>=2.4", "scipy>=1.16", "pandas>=2.3",
                 "matplotlib>=3.10", "scikit-learn>=1.8" ]
EOF

uv python install 3.14        # fetch a Python interpreter (once per machine)
uv python pin 3.14            # use it for this project
uv sync                       # create the venv and install the base dependencies
source "${UV_PROJECT}/.venv/bin/activate"    # activate the project venv

Because UV_PROJECT is set, uv sync / uv add / uv python pin all act on that project without a --project flag.

2.4 Add qc-rs’s Python dependencies#

Add the packages qc-rs itself needs. Always use uv add, never uv pip install (numpy/matplotlib already come from the base project):

uv add --dev maturin   --project "$UV_PROJECT"   # builds the qc_rs extension (the key one)
uv add --dev pytest    --project "$UV_PROJECT"   # the test suite
uv add mpi4py          --project "$UV_PROJECT"   # MPI from Python (for the python-mpi-direct feature)
uv add h5py            --project "$UV_PROJECT"   # read HDF5 (.qch5) checkpoints
uv add geometric       --project "$UV_PROJECT"   # geometry-optimization driver (mychk.scf(...).opt())
uv add --dev ipykernel --project "$UV_PROJECT"   # optional: run notebooks in VS Code
uv add --dev patchelf  --project "$UV_PROJECT"   # Linux only: lets maturin bake the library rpath

That is the required set. The packages below are recommended — install them too unless you have a specific reason not to. Together they make the shared environment complete: visualization, reference cross-checks, document tooling, and the toolchain to build this manual. (The base scientific stack — numpy, scipy, pandas, matplotlib, scikit-learn — already came from the base pyproject.toml in §2.3, so it needs no separate uv add.)

Visualization — plots and in-notebook 3-D molecules/orbitals:

uv add plotly ipywidgets kaleido nbformat --project "$UV_PROJECT"  # figures/galleries/HTML (plotly), interactive panels (ipywidgets), static-PNG export (kaleido), inline notebook rendering (nbformat); also the qc.view 2-D/general-3-D backend
uv add py3dmol                            --project "$UV_PROJECT"  # in-cell isosurface / molecular-orbital rendering (qc.view 3-D viewer; self-contained HTML)
uv add anywidget                          --project "$UV_PROJECT"  # the interactive modeler (mychk.modeler(): pick / measure / emit an input)

Reference & document tooling — cross-checks against PySCF, reading papers, exporting results:

uv add --dev pyscf                 --project "$UV_PROJECT"  # reference values for tests / benchmarks
uv add --dev pyscf-dispersion      --project "$UV_PROJECT"  # DFT-D3/D4 in PySCF, to cross-check qc-rs's dispersion
uv add --dev pymupdf pymupdf4llm   --project "$UV_PROJECT"  # read PDFs / papers (-> Markdown)
uv add --dev python-pptx           --project "$UV_PROJECT"  # generate .pptx slide decks
uv add --dev openpyxl pdfplumber   --project "$UV_PROJECT"  # Excel I/O / PDF-table extraction
uv add --dev pillow                --project "$UV_PROJECT"  # image handling

Documentation toolchain — to build this manual (HTML + PDF) with Jupyter Book v1:

uv add --dev "jupyter-book<2" sphinx-design sphinx-proof sphinx-exercise sphinxcontrib-mermaid pyppeteer \
  --project "$UV_PROJECT"
# jupyter-book<2 = Jupyter Book v1 (Sphinx-based; pinned <2 to avoid v2/mystmd); sphinx-proof/sphinx-exercise
# = the textbook definition/theorem/exercise environments; sphinxcontrib-mermaid = diagrams;
# pyppeteer = HTML->PDF (CJK-friendly). Details in docs/user/README.md.

Tip

Add everything first, then build Every uv add prunes a previously-built qc_rs (see the warning at the end of this chapter). So do all your uv add operations first, and build — or rebuild with make install — last.

2.5 Install a BLAS/LAPACK backend#

BLAS (Basic Linear Algebra Subprograms) and LAPACK are the libraries that do the heavy matrix arithmetic — matrix multiplies and eigenvalue problems — at the core of every SCF. qc-rs supports two backends; pick the one for your platform (you meet them again, in depth, in Part IV):

  • OpenBLAS — the open-source default (macOS, and Linux machines that ship it);

  • Intel MKL — Intel’s highly-tuned library (common on HPC clusters, via oneAPI).

Install steps differ by OS; they are folded into the two build flows below.

2.6 Build the extension — the two common flows#

The build command is maturin develop, which compiles qc_rs and installs it into the active venv. Two things matter every time: set VIRTUAL_ENV (or maturin may install into a stray ./.venv), and select the right --features for your backend (next section).

Linux (e.g. an HPC cluster) with Intel MKL#

Many clusters have no system OpenBLAS and use Intel oneAPI MKL. Source the oneAPI environments first (MKL sets MKLROOT; MPI puts mpicc on PATH, needed only for python-mpi-direct):

source ~/.cargo/env
export UV_PROJECT=~/local/myproj
source "${UV_PROJECT}/.venv/bin/activate"
export VIRTUAL_ENV="${UV_PROJECT}/.venv"

# Intel oneAPI: MKL (sets MKLROOT) and MPI (puts mpicc on PATH) — adjust paths to your install
. /opt/intel/oneapi/mkl/latest/env/vars.sh intel64
. /opt/intel/oneapi/mpi/latest/env/vars.sh          # only for python-mpi-direct

export MKL_THREADING_LAYER=GNU                        # see the MKL notes below

"${UV_PROJECT}/.venv/bin/maturin" develop --no-default-features \
  --features intel-mkl-system,xc-bundled,pcm,python-mpi-direct
"${UV_PROJECT}/.venv/bin/pytest" tests/               # sanity check

Important

MKL gotchas (why these exact settings)

  • intel-mkl-system links MKL’s single dynamic library mkl_rt (located via MKLROOT), not the layered mkl_* libs, which break under the linker’s default --as-needed.

  • Keep MKL_INTERFACE_LAYER at its default LP64 (32-bit integers). qc-rs’s BLAS/LAPACK bindings pass 32-bit integers, so ILP64 silently corrupts them — never set ILP64.

  • MKL_THREADING_LAYER defaults to Intel, which needs libiomp5 that the MKL-only vars.sh does not expose. Set GNU (uses the system libgomp) or SEQUENTIAL, or the extension fails to load.

  • Without patchelf (§2.4), maturin cannot bake the library paths (rpath), so you must keep the oneAPI lib dirs on LD_LIBRARY_PATH at run time — the two vars.sh above already do that.

macOS with Homebrew OpenBLAS#

Install the toolchain with Homebrew (once per machine):

xcode-select --install                              # Command Line Tools (clang, ld) — skip if present
brew install gcc openblas libomp cmake pkg-config
brew install open-mpi                                # optional: for mpi4py / python-mpi-direct

Each package earns its place: gcc provides gfortran/libgfortran (OpenBLAS links against it); openblas is the BLAS/LAPACK; libomp is the OpenMP runtime the extension needs to import; cmake builds the bundled libxc; pkg-config lets the build locate libraries. Then build with the default openblas-system feature:

export UV_PROJECT=~/local/myproj
source "${UV_PROJECT}/.venv/bin/activate"
export VIRTUAL_ENV="${UV_PROJECT}/.venv"
export OPENBLAS_PATH="$(brew --prefix openblas)"
export LIBRARY_PATH="$(brew --prefix gcc)/lib/gcc/current"
export RUSTFLAGS="-L $(brew --prefix libomp)/lib -l dylib=omp -L $(brew --prefix gcc)/lib/gcc/current -l dylib=gfortran"
export DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix libomp)/lib:${OPENBLAS_PATH}/lib:${LIBRARY_PATH}:${DYLD_FALLBACK_LIBRARY_PATH:-}"

"${UV_PROJECT}/.venv/bin/maturin" develop
"${UV_PROJECT}/.venv/bin/pytest" tests/

The RUSTFLAGS/DYLD_FALLBACK_LIBRARY_PATH lines link and locate libomp and libgfortran; without them import qc fails with “symbol not found … __gfortran_… or a missing OpenMP runtime.

2.7 Feature flags (--features)#

maturin develop --features selects optional pieces at build time. The default build uses openblas-system, python-mpi-direct, xc-bundled, pcm, hdf5. The ones you will actually toggle:

Feature

What it gives you

openblas-system

Homebrew/system OpenBLAS (macOS default)

openblas-bundled

OpenBLAS built statically from source (no system OpenBLAS needed)

intel-mkl-system

Intel MKL via mkl_rt (Linux/HPC; needs MKLROOT)

xc-bundled / xc-system

libxc (DFT functionals) from the bundled source / a system package

pcm

implicit-solvation (PCM) module

python-mpi-direct

Rust MPI functions callable from mpi4py-initialized Python

hdf5

HDF5 (.qch5) checkpoint I/O

cuda

the optional NVIDIA-GPU integral/Fock path (off by default; see §5)

--no-default-features turns everything off so your --features list is exact (used in the MKL flow above). After building, the Python sentinels report what was compiled in:

import qc
qc.XC_ENABLED, qc.PCM_ENABLED, qc.MPI_DIRECT_ENABLED, qc.GPU_ENABLED

3. The easy way: make setup + make install#

Section 2 walked through the build by hand so you understand every piece. In practice you rarely type all of that again: qc-rs keeps the machine-specific settings out of the shared code base and regenerates them from a profile. There are two ways to drive it, and they produce the same generated files:

  • §3.1 — let an AI coding assistant do it (recommended on a new machine).

  • §3.2 — run make setup / make install yourself.

3.2 Doing it yourself#

If you would rather run it directly (or you do not have an assistant set up yet), the same profile machinery is one command:

make setup

What make setup does, in detail:

  • It reads a profile — a small JSON file under mytools/setup/profiles/ (e.g. local.json, yanai-linux-mkl.json, yanai-mac-openblas.json) that records your Python interpreter, BLAS backend, MPI setup, the RUSTFLAGS/oneAPI environment, and the exact Cargo --features for your machine — i.e. everything you chose by hand in §2.6–§2.7.

  • From it, it generates git-ignored, machine-specific files: your editor config (.vscode/settings.json, tasks.json, launch.json, my.env), a shell env file (mytools/setup/local/env.sh), and an install helper (mytools/setup/local/install-qc.sh) that runs the right maturin develop for you.

  • It auto-detects and remembers your profile (in mytools/setup/local/state.json), so later make setup needs no arguments.

What a profile looks like#

A profile is a small JSON file — it is worth understanding, because it is everything you did by hand in §2.6–§2.7, captured in one place. Here is the shape of a Linux + MKL profile, annotated:

{
  "os": "linux",                     // "linux" or "mac"
  "backend": "mkl",                  // "mkl" or "openblas" — picks the BLAS features
  "python": "/home/you/local/myproj/.venv/bin/python",   // the venv interpreter (→ VS Code)
  "vscode": {
    "activate_env": true,            // auto-activate the venv in VS Code terminals
    "rust_analyzer": {               // how the Rust language server should compile the core
      "no_default_features": true,
      "features": ["intel-mkl-system", "xc-bundled", "pcm"],
      "extra_env": { "MKLROOT": ".../mkl/2025.2", "MKL_THREADING_LAYER": "GNU" }
    }
  },
  "my_env": {                        // runtime env vars -> .vscode/my.env
    "MKL_THREADING_LAYER": "GNU",
    "MKL_INTERFACE_LAYER": "LP64",
    "LD_LIBRARY_PATH": ".../mpi/2021.16/lib"
  },
  "build": {
    "task_label": "maturin develop (MKL, myproj)",   // the VS Code build-task name
    "shell": "/bin/bash",
    "pre": [                         // sourced before the build (also becomes env.sh)
      "source ~/.cargo/env",
      "export UV_PROJECT=~/local/myproj",
      "source \"${UV_PROJECT}/.venv/bin/activate\"",
      "export VIRTUAL_ENV=\"${UV_PROJECT}/.venv\"",
      ". .../mkl/2025.2/env/vars.sh intel64",
      ". .../mpi/latest/env/vars.sh"
    ],
    "rustflags": "-C link-arg=-Wl,-rpath,.../mkl/lib:.../mpi/lib",   // bake the library rpath
    "maturin_args": "--no-default-features --features intel-mkl-system,xc-bundled,pcm,python-mpi-direct,hdf5"
  }
}

Field by field:

  • os, backend — your platform and BLAS choice (mkl / openblas); together they set the default --features.

  • python — the absolute path to your venv’s interpreter. VS Code uses it as the interpreter, and the build derives VIRTUAL_ENV from it.

  • vscode.activate_env — auto-activate the venv in VS Code’s integrated terminals.

  • vscode.rust_analyzer — how the Rust language server compiles the core so its analysis matches your build (no_default_features, the features list, and any extra_env such as MKLROOT). This affects only the Rust-side editor IntelliSense, not the Python build.

  • my_env — runtime environment variables written verbatim to .vscode/my.env and used when you run or debug from the editor: here the MKL threading/interface layers and the MPI LD_LIBRARY_PATH.

  • build.pre — the shell lines sourced before the build (activate the venv; source ~/.cargo/env and the oneAPI vars.sh). These same lines make mytools/setup/local/env.sh a one-shot dev shell — source mytools/setup/local/env.sh and Rust + uv + venv + MKL are all ready.

  • build.rustflags — extra linker flags, typically the -rpath that bakes the library search paths into the extension so it imports with no LD_LIBRARY_PATH.

  • build.maturin_args — the exact maturin develop feature string for this machine.

  • build.task_label — the name of the generated VS Code build task (you will see it under Tasks: Run Build Task).

On a new machine, make setup auto-detects the per-node absolute paths and writes a git-ignored profiles/local.json that overrides the reference profile — so in practice you run make setup and only edit local.json if a detected path is wrong.

Variants:

make setup --list                 # list the available profiles
make setup PROFILE=<name>         # pick a specific profile
make setup FORCE=1                # overwrite existing generated files (no .bak backup)

Then build qc-rs into your venv with the generated helper:

make install

make install runs install-qc.sh — a maturin develop with your machine’s features and the library rpath baked in, so import qc works with no extra environment variables. Run it after make setup.

Note

Everything make setup writes is git-ignored It is your machine config and is never committed. A brand-new machine may need its own profile — copy the closest file under mytools/setup/profiles/ and edit the interpreter/BLAS/MPI fields.

4. Verify#

python -c "import qc; print('qc-rs ok')"

Then run the Quickstart: if you get -76.026772, everything works end to end.

The one gotcha: uv add prunes qc-rs#

Warning

uv add, uv remove, and uv sync reconcile the venv to the project’s lockfile, which uninstalls the maturin-built qc_rs extension (it is not declared in pyproject.toml). If import qc suddenly fails after you add a package — e.g. ModuleNotFoundError: No module named 'qc_rs' or ImportError: libmkl_rt.so.2: cannot open shared object file — just rebuild:

make install         # or re-run your maturin develop command

Do the uv operation first and the rebuild last. To avoid the prune, manage extras with uv pip install / uv pip uninstall (they don’t touch the lockfile) or pass uv sync --inexact.

5. Optional: MPI and GPU#

  • MPI (running across many machines). Install an MPI implementation (brew install open-mpi, or your cluster’s module) and mpi4py (§2.4), and build with the python-mpi-direct feature (it is in the default set). Actually running over a fast interconnect (InfiniBand) has its own subtleties — that is a whole topic, taught from zero in Part IV → MPI & interconnects.

  • GPU (NVIDIA CUDA). The default build is CUDA-free and runs anywhere. To opt in you need an NVIDIA GPU (compute capability ≥ 7.0), the CUDA Toolkit (nvcc, cudart, cuBLAS), and CMake ≥ 3.19, then add the cuda feature to your maturin develop. Details are in Part IV → GPU computing.

Troubleshooting#

A few common first-build errors:

  • symbol not found __gfortran_… / OpenMP missing (macOS) — the RUSTFLAGS / DYLD_FALLBACK_LIBRARY_PATH lines in §2.6 are missing or wrong (they link libgfortran/libomp).

  • libmkl_rt.so.2: cannot open shared object file (Linux) — you did not source the MKL vars.sh in this shell, or uv add pruned the extension (rebuild with make install).

  • Wrong numbers / silent corruption at large sizes (MKL) — you set MKL_INTERFACE_LAYER=ILP64; use the default LP64.

  • xc-system cannot find libxc.pc — use the bundled build instead: --features xc-bundled.

  • ModuleNotFoundError: qc_rs right after a uv command — the prune above; run make install.

More is in Troubleshooting & FAQ. With qc-rs installed, head to the Quickstart, or set up your editor next in VSCode.