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:
qc-rs is already installed for you (common on a shared cluster) — see §1 below and just verify.
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-systemlinks MKL’s single dynamic librarymkl_rt(located viaMKLROOT), not the layeredmkl_*libs, which break under the linker’s default--as-needed.Keep
MKL_INTERFACE_LAYERat 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_LAYERdefaults to Intel, which needslibiomp5that the MKL-onlyvars.shdoes not expose. SetGNU(uses the systemlibgomp) orSEQUENTIAL, 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 onLD_LIBRARY_PATHat run time — the twovars.shabove 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 |
|---|---|
|
Homebrew/system OpenBLAS (macOS default) |
|
OpenBLAS built statically from source (no system OpenBLAS needed) |
|
Intel MKL via |
|
libxc (DFT functionals) from the bundled source / a system package |
|
implicit-solvation (PCM) module |
|
Rust MPI functions callable from mpi4py-initialized Python |
|
HDF5 ( |
|
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 installyourself.
3.1 The AI-assisted way (recommended)#
Getting the build right on a fresh machine — the right BLAS backend, the right RUSTFLAGS, the exact
--features, the profile — is exactly the kind of environment-specific chore an AI coding assistant
handles well. This is not a gimmick: it is the same AI-first (“vibe coding”) workflow
qc-rs itself is built with, applied to your own setup. The next chapter, AI coding assistants, shows how to install
Claude Code or Codex; once you have one, prefer this path.
Important
Prerequisites the assistant does not install for you
The assistant configures and builds qc-rs — it does not install system toolchains. You must have already
done §2.1–§2.5 yourself: git, uv + a Python, Rust (rustup), a BLAS/LAPACK library, and —
only if you want parallel runs — MPI. Assume those five are in place before you start; the assistant
takes over from there.
With the prerequisites installed, open the assistant in the repository directory and ask it, in plain language — for example:
Build qc-rs with
make setupandmake install, and save the build configuration to a profile for this machine.
It will inspect your environment (which OS, which Python/venv, MKL vs OpenBLAS, whether nvcc and MPI are
present), write a matching profile under mytools/setup/profiles/, run make setup to generate the
machine-specific files, run make install to compile the extension, and — this is the real payoff — read
any build errors and fix the flags itself, iterating until import qc works in your venv. You describe
the goal; it handles the machine-specific details. The rest of this section explains what it (or you) are
doing, so you can check its work or reproduce it by hand.
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, theRUSTFLAGS/oneAPI environment, and the exact Cargo--featuresfor 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 rightmaturin developfor you.It auto-detects and remembers your profile (in
mytools/setup/local/state.json), so latermake setupneeds 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 derivesVIRTUAL_ENVfrom 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, thefeatureslist, and anyextra_envsuch asMKLROOT). This affects only the Rust-side editor IntelliSense, not the Python build.my_env— runtime environment variables written verbatim to.vscode/my.envand used when you run or debug from the editor: here the MKL threading/interface layers and the MPILD_LIBRARY_PATH.build.pre— the shell lines sourced before the build (activate the venv; source~/.cargo/envand the oneAPIvars.sh). These same lines makemytools/setup/local/env.sha one-shot dev shell —source mytools/setup/local/env.shand Rust + uv + venv + MKL are all ready.build.rustflags— extra linker flags, typically the-rpaththat bakes the library search paths into the extension so it imports with noLD_LIBRARY_PATH.build.maturin_args— the exactmaturin developfeature 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) andmpi4py(§2.4), and build with thepython-mpi-directfeature (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 thecudafeature to yourmaturin develop. Details are in Part IV → GPU computing.
Troubleshooting#
A few common first-build errors:
symbol not found … __gfortran_…/ OpenMP missing (macOS) — theRUSTFLAGS/DYLD_FALLBACK_LIBRARY_PATHlines in §2.6 are missing or wrong (they linklibgfortran/libomp).libmkl_rt.so.2: cannot open shared object file(Linux) — you did not source the MKLvars.shin this shell, oruv addpruned the extension (rebuild withmake install).Wrong numbers / silent corruption at large sizes (MKL) — you set
MKL_INTERFACE_LAYER=ILP64; use the default LP64.xc-systemcannot findlibxc.pc— use the bundled build instead:--features xc-bundled.ModuleNotFoundError: qc_rsright after auvcommand — the prune above; runmake install.
More is in Troubleshooting & FAQ. With qc-rs installed, head to the Quickstart, or set up your editor next in VSCode.