Density functional theory predicts where the electrons sit in a material, and most of what you want to know follows from that: the total energy of a structure, the forces pulling each atom toward equilibrium, the stress on the unit cell, and the band structure that decides whether the material conducts.
Q3 solves that problem the way most codes for crystalline solids do — the plane-wave pseudopotential method. Three ideas carry the whole calculation:
You supply a crystal structure, an exchange-correlation functional, cutoffs, a k-point grid, and pseudopotential files. Q3 returns structured JSON — total energy, forces, stress, eigenvalues — plus binary checkpoints you can restart from. Inputs come either as a Q3 YAML manifest or as an unmodified Quantum ESPRESSO (pw.x) namelist input (§2.4), and you can drive it from the command line or from Python (§3).
One caveat worth internalizing early: a calculation that converges is not the same as a calculation that is correct. Cutoffs, k-grids and smearing all have to be converged deliberately for the system in front of you, which is what tutorial 03 exists to teach.
q3_core nanobind bindings, and a q3 Python package with an ASE calculator and band-structure / relaxation / phonon workflows.q3.jax; the full gradient API is charted in docs/differentiability.md.number_of_plane_waves instead of npw, pseudopotential_set instead of ppset).libxc:X:C combinations, addressed by LibXC id or by name.Four ways in, in increasing order of setup cost: download a release archive (§2.6), pull the container (§2.1), build from source (§2.2), or install the Python package on top of a build (§2.5). For a guided path — from a fresh clone to a converged result you can defend — start with the tutorials instead; if something breaks along the way, TROUBLESHOOTING.md collects the failures seen so far and their root causes.
A pre-built container is available for HPC clusters where installing dependencies is impractical. Each merge to main tags the repository and cuts a release, which in turn builds and publishes an OCI image to ghcr.io/camsai/q3. Images are built natively (not emulated) for x86_64 (amd64) and aarch64 (arm64), and published under a single multi-architecture tag. The image contains q3 and q3_utility installed beside it.
One image serves every runtime. Apptainer, podman-hpc and Shifter all read OCI images, and Apptainer converts to its own SIF format on the way in, so the same tag works whichever of the three your centre provides:
podman-hpc** — the preferred runtime at centres that provide it.latest tracks main and is republished on every merge, so the image behind it changes without the name changing. Pin the date-based tag (YYYY.M.D-N) for anything whose results have to stay comparable — a rerun months later against latest is not the same calculation. See §4.1 Branches. The plain tags are OCI image indexes covering both architectures, so a pull resolves the one matching the machine you are on; each architecture is also published under its own -amd64 / -arm64 tag if you would rather be explicit.
The plain tag holds the serial q3, scaled within one node by OMP_NUM_THREADS. Running it under srun gives you independent single-rank processes, not a parallel calculation. Two suffixed tags carry the parallel executables (§4.2):
| Tag | q3 is the | Also named | Architectures |
|---|---|---|---|
latest | serial build | — | amd64, arm64 |
latest-mpi | MPI build | q3_mpi | amd64, arm64 |
latest-cuda | CUDA build | q3_gpu_cuda | amd64 |
Every image carries one engine binary and runs it as q3; the variant name is a symlink to it, for scripts that already use it. The MPI and CUDA builds run the self-consistent field only, so the other steps of a workflow (NSCF, band structure) still need the plain image.
Running the parallel ones needs runtime flags that differ per container runtime, which docs/podman_hpc_and_shifter.md covers.
Releases cut before the images became OCI hold a Singularity image pushed as an ORAS artifact instead. Those tags predate this section and only Apptainer can read them, with apptainer pull oras://… rather than the docker:// above.
Override the resolved architecture with --arch (apptainer pull --arch arm64 …) when preparing a container on one architecture for a cluster of another.
Apptainer mounts your home directory and the current working directory into the container by default, so relative paths in an input file resolve the way they do outside it.
Some centres provide these instead of Apptainer, podman-hpc being the one they prefer for new work. Both read this same image:
Neither mounts the working directory for you the way Apptainer does, and podman-hpc does not pass your environment through either, so the commands differ enough to be worth their own page: see docs/podman_hpc_and_shifter.md for both runtimes, batch scripts, and what the MPI flags on each do not yet do for this image.
The Dockerfile is the single source of truth for what goes into the image; q3.def only converts the published result to SIF. Build either with:
You need a C++17 compiler (GCC 9 or newer, or Clang) and a few system libraries:
brew install cmake fftw libomp lapacksudo apt-get install -y cmake build-essential libfftw3-dev libomp-dev libopenblas-devlibopenblas-dev rather than liblapack-dev libblas-dev: those resolve to the reference netlib implementation, which is correct but untuned and single-threaded. Q3 spends most of its time outside the FFT in dense BLAS3, and the difference is a factor of ten serial and forty threaded on a metal supercell — see the container images, which made the same mistake until 2026-09-11.
All other dependencies (such as LibXC, spglib, yaml-cpp, spdlog, nlohmann_json, and GoogleTest) are fetched automatically during configuration via CMake FetchContent.
Clone, and create the Python environment the build itself needs — generating C++ from the JSON schemas happens during cmake --build, and ESSE's schemas come from the mat3ra-esse package, so these modules must be in place before you configure:
This generates the main executable build/q3 and the test suite build/q3_tests. For MPI and GPU (CUDA / Metal / HIP / SYCL) builds, see §4.2.
To install the binaries somewhere specific:
Every configuration variable, with example values for Homebrew, Ubuntu and an HPC system, is in docs/build_reference.md; build failures and their fixes are in TROUBLESHOOTING.md.
Run a self-consistent field calculation using one of the pre-configured examples:
Results (energies, eigenvalues, charges) are written to the directory named by control.outdir in the manifest — output/ for the examples. That path resolves against your current working directory, not the manifest's, so the command above writes output/si_nc_results.json under the repository root. Everything else in a manifest (material, pseudopotentials) resolves relative to the manifest itself.
What the numbers in that file mean, and how to pull them out, is tutorial 02; the exhaustive field reference is docs/io_reference.md.
Q3 reads an unmodified Quantum ESPRESSO (pw.x) namelist input — the &CONTROL, &SYSTEM and &ELECTRONS namelists and their cards — and resolves it to the same internal model a YAML manifest does. Run it from the directory it lives in, because its pseudo_dir resolves against your current directory:
The namelist reader refuses what it cannot honor: an unknown key, or a recognized key Q3 does not support, is a hard error naming the key. A drop-in that silently computes different physics is worse than one that stops.
Worked end to end — what the namelist resolves to, and the path trap above — in tutorial 06 — Bringing existing inputs.
Q3 ships a q3 Python package with high-level workflows and an ASE calculator. It drives the build/q3 binary from §2.2, so build first. Python 3.12 or newer is required. Install from the repository root (the root pyproject.toml packages src/py/q3):
The workflows it provides are shown in §3, and tutorial 05 walks through driving Q3 from Python end to end.
Every GitHub release carries native archives for Linux x86_64, Linux arm64 and macOS on Apple Silicon:
The executables are in bin/. They link FFTW and BLAS/LAPACK from your system rather than bundling them, and INSTALL.txt in the archive gives the exact apt-get install line for Ubuntu 24.04 — the release pipeline runs that line in a clean container and then an SCF with the unpacked binary before anything is attached to a release. licenses/ holds the notices of the libraries compiled in, and sbom.cdx.json is a CycloneDX bill of materials listing every component, statically and dynamically linked, with its version. On the public repository each archive also carries a signed build-provenance attestation:
A workflow chains several engine runs into one result — an SCF followed by a band-structure pass, or a long series of SCF runs driving a geometry optimizer. The band-structure workflow can be requested directly from a YAML manifest; the rest are driven from the q3 Python package (§2.5), which wraps the engine in ASE's optimizers and analysis tools.
A band structure takes two passes: an SCF run to converge the charge density, then a non-self-consistent (NSCF) run that holds that density fixed and evaluates eigenvalues along a path through high-symmetry points of the Brillouin zone. Set calculation: band_structure and Q3 does both:
This automatically:
Γ–X–W–K–Γ–Loutput/<prefix>_results.jsonThe same workflow via the q3 Python package (see §2.5):
For FCC lattices: G (Γ), X, W, K, L, U. The k-path is interpolated in crystal-fractional coordinates using the primitive reciprocal lattice matrix.
Generate band structure plots with the included script:
See docs/band_structure_comparison.md for validated Q3 vs Quantum ESPRESSO band structure comparisons (Si and Cu, NC and US).
Relaxation moves the atoms downhill along the forces until the structure sits at a local energy minimum. Q3 supplies energies and forces; ASE's optimizers choose each step:
The same thresholds can be set in the manifest instead:
From a Quantum ESPRESSO deck. A pw.x relax or vc-relax deck runs as it is — any ibrav, with its if_pos flags honoured — on any Q3 binary:
ExecutionPolicy (from q3.binary) describes the engine execution policy from outside — ranks, k-point pools, band distribution, GPU precision and the MPI launcher (mpirun -np N unless given) — and every q3.workflows function takes it: each engine run the workflow makes is started with it — every relaxation step, the SCF and the NSCF of a band structure, DOS or PDOS, every strain of an equation of state or elastic-constant fit, every displacement of a phonon calculation. q3_mpi runs the SCF and the NSCF on one rank per k-point pool, which its automatic topology chooses when number_of_kpoint_pools is left unset; the SCF needs it to write the checkpoint the NSCF reads. So use at most as many processes as the SCF has irreducible k-points (and the NSCF lists): beyond that ranks would share a pool, and q3_mpi refuses with a message saying how many to use.
Each geometry is one SCF of the deck itself, run by that binary in the deck's own directory, so the deck's relative pseudo_dir and outdir resolve as they do for pw.x. The first run is the deck as written; the engine reports the structure it read (the results' material) and Python's optimizer continues from it, passing each new geometry back with --material. Every step starts from the density the previous one saved in outdir. The deck's forc_conv_thr and nstep become the defaults for fmax and steps. A vc-relax deck goes through run_vc_relax_workflow the same way; give its press as target_pressure_gpa=press / 10 and remove it from the deck, which Q3 refuses so that a target pressure is never silently dropped.
Running the deck directly — q3 relax.in — is not a relaxation: it runs one SCF at the starting geometry and says so in the log.
See PERFORMANCE.md for relaxation benchmarks (single-core, Q3 is 1.6–2.4× faster than Quantum ESPRESSO across the Si and Cu supercell relaxation cases).
Variable-cell relaxation adds the unit cell itself to the optimization, so both the atomic positions and the lattice vectors relax together — driven by ASE's FrechetCellFilter:
The cell is NOT held fixed: the filter relaxes it to the zero-external-pressure equilibrium, so a strained starting cell will expand or contract as needed. This workflow always runs in subprocess mode (not the nanobind q3_core bindings mode), since the bindings' SCFSolver has no API to update the cell mid-run — see FUNCTIONALITY.md's Variable-Cell Relaxation note for validation details (a +2% isotropically strained Si cell recovers the unstrained equilibrium lattice constant and matches an independent Quantum ESPRESSO variable-cell run to <0.01%).
Phonons are the quantized vibrations of the lattice. Q3 obtains them from finite differences — displace atoms slightly, read the restoring forces — by two routes. The Γ-point (q=0) route displaces every atom along every Cartesian axis in the original cell, via ASE's Phonons class:
The cell is fixed throughout (only atomic positions are perturbed), so this workflow can safely use the faster nanobind bindings mode. The reduced-symmetry force bugs originally found through this workflow (a symmetrize_vector transpose error and an IBZ k-point-reduction error) have both been fixed and regression-tested — silicon's Γ-optical modes now come out exactly triply degenerate, as required by diamond-structure symmetry (see FUNCTIONALITY.md's Γ-Point Phonons note for the validation status and the remaining cross-check caveat).
The second route uses Phonopy as a library and answers at every q, not only at Γ. Phonopy generates the symmetry-reduced set of displaced supercells, Q3ASECalculator evaluates the forces on each, and the force sets go back to Phonopy as force constants:
Symmetry is what makes this cheap: silicon's 2x2x2 supercell needs one displacement direction, two SCF runs with the central difference the workflow insists on, and out of them come the whole dispersion plus the harmonic free energy, entropy and heat capacity. The dispersion, the phonon density of states and the zero-point energy come back as ESSE properties. ESSE has no schema for the temperature-dependent thermal properties, so each comes back as ESSE's abstract two-dimensional plot against temperature, and none for force constants, which stay in phonopy_params.yaml, named by an ESSE file_content property. Install it with pip install q3[phonons]; without Phonopy the rest of q3.workflows is unaffected.
Everything for working on Q3 itself: the branch model, the parallel and GPU build variants, the test suites, the code layout, and how to contribute.
| Branch | What it is | Who it is for |
|---|---|---|
main | The integration branch. Every green merge tags a release and publishes a container automatically. | Development, and anyone who wants the newest fixes as soon as they land. |
stable | Conservatively managed and advanced deliberately, well behind main. | Production and long-running studies, where a moving base is the problem. |
main moves on every merge, which is what makes a fix reach users the same day it lands — and also what makes two checkouts taken a fortnight apart genuinely different codebases. stable exists for the case where that churn is the thing you are trying to avoid: pin to it when a study needs the code underneath it to stop moving, and take an update as a deliberate step rather than a default.
Container images are published from main for both amd64 and arm64 (see §2.1), so latest follows main rather than stable. To hold a container fixed, pull a specific date-based tag rather than latest.
Each parallel backend is an opt-in CMake flag that selects which entry point a build tree compiles. A tree builds exactly one engine binary, always q3, and links the variant name to it in the build and install trees alike, so a downstream workflow names q3 whatever the build. Configure one tree per variant; at most one GPU flag may be on, and MPI combines only with CUDA (q3_mpi_gpu_cuda).
| CMake flag | Also named | Target hardware |
|---|---|---|
Q3_WITH_MPI | q3_mpi | MPI k-point pools + band groups (needs MPI, FFTW3-MPI; ScaLAPACK optional) |
Q3_WITH_GPU | q3_gpu | Apple Silicon (Metal / Accelerate) |
Q3_WITH_CUDA | q3_gpu_cuda | NVIDIA (cuBLAS / cuSOLVER / cuFFT) |
Q3_WITH_HIP | q3_gpu_hip | AMD (ROCm) |
Q3_WITH_SYCL | q3_gpu_sycl | Intel (oneAPI / oneMKL) |
See FUNCTIONALITY.md §2 for per-backend validation status and measured speedups, and KNOWN-ISSUES.md for the current MPI limitations — notably that the stress tensor is skipped under band parallelism.
To run the full suite of unit and integration tests:
Validation tests cross-check physical convergence against reference results; regression tests verify binary outputs. Set up a virtual environment:
Then run the fast tier:
Or the full SCF supercell convergence tier, which is considerably slower:
With an MPI build present (auto-skipped when mpirun is unavailable), verify that the total energy is invariant across rank/pool/band topologies:
Q3 is organized into six numbered layers, each depending only on the ones below it. Layer 0 knows nothing about physics; layer 1 knows about crystals but not electrons; the quantum-mechanical concepts enter at layer 2, become concrete plane-wave representations at layer 3, get operated on by algorithms at layer 4, and turn into observable properties at layer 5. I/O is deliberately a vertical slice that may reach into any layer.
See docs/architecture.md for the layer-by-layer component map and the dependency rules that keep the layering honest, and docs/io_reference.md for the full input/output field reference.
Contributions are welcome, and tutorial 07 walks through making one — finding your way around the layers, adding a test, opening a PR. CONTRIBUTING.md covers the license (Apache-2.0, inbound = outbound), the required DCO sign-off (git commit -s), and the provenance policy every change must honor; engineering conventions live in AGENTS.md. Because Q3 is written from scratch rather than ported, PROVENANCE.md is the one to read before consulting any other engine's source: it defines what makes code a derivative work and the procedure that avoids writing one.
Start here
Using Q3
podman-hpc or Shifter instead of Apptainer, with batch scriptsWorking on Q3
Q3 is licensed under the Apache License 2.0. Copyright and licensing are declared for every file in REUSE.toml rather than in per-file headers, including the vendored pseudopotential tables, which stay under their upstream licenses.