Examples demonstrating SBD's capabilities for quantum chemistry calculations.
- Communication: MPI for distributed computing
- Backends: CPU (host OpenMP), GPU (NVHPC Thrust) and GPU (OpenMP target offload), switchable at runtime via
deviceparameter
The standalone run_sbd_diag.py script needs nothing beyond what
pip install -e . already installed (sbd, mpi4py, numpy).
The SQD examples — run_sqd_sbd.py and run_sqd_sbd.ipynb — wrap SBD
with the qiskit-addon-sqd self-consistent loop, which pulls in three
extra Python packages. Install them once into the same venv:
source ~/venvs/<your-sbd-venv>/bin/activate
pip install pyscf qiskit "qiskit-addon-sqd>=0.13.1"pyscf— reads FCIDUMP, restores 4-fold integral symmetry.qiskit—BitArraytype for sampled-bitstring input.qiskit-addon-sqd— the SQD loop (diagonalize_fermionic_hamiltonian). Needs the distributed (SPMD) support that callssci_solveron every MPI rank; that shipped in 0.13.1, so the PyPI release suffices.
pyscf is the heavy one (~150 MB plus h5py). qiskit-addon-sqd is a thin
layer on top of upstream qiskit, so most of qiskit's ~300 MB is what
dominates the install size.
Runs a single TPB diagonalization from an FCIDUMP file and alpha determinant file. No SQD loop, no Qiskit dependency.
# H2O with 2 MPI ranks
mpirun -np 2 python run_sbd_diag.py \
--device cpu \
--fcidump ../../vendor/sbd-upstream/data/h2o/fcidump.txt \
--adetfile ../../vendor/sbd-upstream/data/h2o/h2o-1em3-alpha.txt \
--adet_comm_size 2
# N2 with GPU
mpirun -np 8 python run_sbd_diag.py \
--device gpu \
--fcidump ../../vendor/sbd-upstream/data/n2/fcidump.txt \
--adetfile ../../vendor/sbd-upstream/data/n2/1em3-alpha.txt \
--adet_comm_size 2 --bdet_comm_size 2 --task_comm_size 2Key options: --device, --fcidump, --adetfile, --adet_comm_size,
--bdet_comm_size, --task_comm_size, --method, --tolerance, --iteration.
Run python run_sbd_diag.py --help for the full list.
Requirements: sbd, mpi4py
Runs the self-consistent SQD workflow (qiskit-addon-sqd) using SBD as the eigensolver backend. Supports two bitstring input modes:
--counts FILE— load bitstrings from a count_dict.json--samples N— generate N random bitstrings at the target Hamming weights (default). A plumbing check only: random determinants give a random subspace, so the energy is not meaningful. Use--countsfor real results.
# H2O with the bundled counts file (275 bitstrings -> ~ -76.236 Ha)
mpirun -np 4 python run_sqd_sbd.py \
--fcidump ../../vendor/sbd-upstream/data/h2o/fcidump.txt \
--counts count_dict_h2o.json \
--device cpu \
--adet_comm_size 2 --bdet_comm_size 2
# H2O with your own hardware bitstrings (FCIDUMP from ../../vendor/sbd-upstream/data/h2o/)
mpirun -np 4 python run_sqd_sbd.py \
--fcidump ../../vendor/sbd-upstream/data/h2o/fcidump.txt \
--counts /path/to/count_dict.json \
--device cpu \
--adet_comm_size 2 --bdet_comm_size 2
# Custom system with hardware bitstrings
mpirun -np 8 python run_sqd_sbd.py \
--fcidump /path/to/fci_dump.txt \
--counts /path/to/count_dict.json \
--samples_per_batch 800 --num_batches 3 --max_iterations 10 \
--device gpu \
--adet_comm_size 2 --bdet_comm_size 2 --task_comm_size 2count_dict.json format: A JSON object mapping bitstrings to shot counts, as
produced by a quantum device or simulator. Each bitstring has length 2 × NORB
and is laid out as [beta | alpha]: the first NORB bits are beta
(spin-down), the last NORB are alpha (spin-up), and within each half orbital 0
is the rightmost bit. qiskit-addon-sqd postselects the last NORB bits on
num_elec_a and the first NORB on num_elec_b.
For H2O (NORB=24, 5α+5β) the Hartree–Fock configuration — the five lowest
orbitals doubly occupied — is therefore "0"*19 + "1"*5 in both halves:
{
"000000000000000000011111000000000000000000011111": 16,
"010000000010001010000001010000000001000010100100": 12,
"000010001110000000000010001001000110000000000100": 8
}Bitstrings whose halves do not hold exactly num_elec_a / num_elec_b ones are
dropped by postselection, so a file of uniform-random strings yields nothing
usable — for H2O only C(24,5)² / 4²⁴ ≈ 6e-6 of them qualify.
count_dict_h2o.json in this directory is a ready-made
H2O example: 275 bitstrings taken from the vendored h2o-1em3-alpha.txt
determinant list, giving a 275 × 275 = 75,625-determinant subspace at
≈ -76.236 Ha.
Key options: --fcidump (required), --counts, --samples,
--samples_per_batch, --num_batches, --max_iterations, --device,
MPI decomposition flags. SBD solver flags (--method, --tolerance,
--iteration, etc.) have sensible defaults; run python run_sqd_sbd.py --help
for the full list.
Requirements: see Extra dependencies above (pyscf, qiskit, qiskit-addon-sqd).
SQD samples bitstrings from a quantum device, uses configuration recovery to correct noisy samples using an orbital occupancy vector, then subsamples into batches for diagonalization. Occupancies are averaged across batches and fed back to configuration recovery — this self-consistent loop typically converges in 3–5 iterations. On the first iteration, no occupancies are available yet, so the raw samples are simply filtered by correct electron count (Hamming weight postselection).
| Parameter | What it controls | Typical values |
|---|---|---|
--counts FILE |
Load hardware bitstrings from a JSON file (use one or the other) | 10K–1M+ shots |
--samples N |
Generate N random bitstrings at the target Hamming weights; plumbing check only, energy not meaningful | any |
--samples_per_batch |
Subspace dimension per batch (accuracy vs. cost) | 300–800 (small), 1M+ (production) |
--num_batches |
Independent subsamples for averaging occupancies | 3–10 (small), up to 100 (large) |
--max_iterations |
SQD self-consistent loop iterations (not SBD --iteration) |
3–5 |
MPI work distribution: All ranks diagonalize each batch together, then move
to the next batch sequentially. Within each diagonalization, ranks form a 3D grid:
adet_comm_size × bdet_comm_size × task_comm_size = total ranks. More batches
increases wall time linearly but does not require more ranks.
Interactive single-rank companion to run_sqd_sbd.py. Same SQD self-consistent
loop on h2o, but inside a Jupyter kernel (MPI.COMM_WORLD size 1). Uses the
bundled count_dict_h2o.json (275 bitstrings → 75,625
determinants) and reaches ≈ −76.236 Ha in a few seconds on CPU.
pytest --nbmake run_sqd_sbd.ipynb # what CI runs; needs the nbtest extra
# or open it in JupyterLab and step through the cellsTotal MPI ranks must equal task_comm_size × adet_comm_size × bdet_comm_size.
When using more than one rank, specify at least --adet_comm_size. Examples:
| Ranks | Decomposition |
|---|---|
| 1 | default (all = 1) |
| 2 | --adet_comm_size 2 |
| 4 | --adet_comm_size 2 --bdet_comm_size 2 |
| 8 | --adet_comm_size 2 --bdet_comm_size 2 --task_comm_size 2 |
All compiled backends load eagerly at import. Select per-call via --device:
--device cpu # host OpenMP (default)
--device gpu # NVHPC Thrust (requires NVIDIA GPU + HPC SDK build)
--device gpu-omp # NVHPC OpenMP target offload
--device auto # GPU if available, else CPUgpu-omp links a different OpenMP runtime (libnvomp) than cpu/gpu, so it is
normally built into its own install — see the Python Bindings README.
sbd.available_backends() reports what the current install actually has.
Within Python, backends can also be switched at runtime without re-initialization:
import sbd
# No init() needed — auto-initializes on first call
result_cpu = sbd.tpb_diag(..., device='cpu')
result_gpu = sbd.tpb_diag(..., device='gpu')H2O (../../vendor/sbd-upstream/data/h2o/): h2o-1em3 through h2o-1em8 alpha determinant files.
N2 (../../vendor/sbd-upstream/data/n2/): 1em3 through 1em7 and 3em4 through 3em7 alpha determinant files.
Smaller thresholds = more determinants = higher accuracy.
- H2O: ground state energy ≈ -76.236 Hartree
- N2: ground state energy ≈ -109.042 Hartree (with 1e-3 dets)
CPU: Set OMP_NUM_THREADS to cores per MPI rank (e.g., 8 ranks × 4 threads = 32 cores).
GPU: One MPI rank per GPU, OMP_NUM_THREADS=1. Each rank auto-assigned: gpu_id = rank % num_gpus. Use method 0 (matrix-free Davidson) for best GPU performance.
- Python Bindings README — Installation, API reference
- Upstream SBD library — C++ library overview