Python ≥ 3.11 License: LGPL v3 Schema v6.6.0 Docs
QPUBench is a framework for running quantum benchmarks and recording every result in one common format, so runs from different SDKs, machines, and even different quantum computing paradigms can be compared side by side.
You describe what to run as plain typed data, execute it through a pluggable
backend adapter, and every run is stored as the same self-describing
BenchmarkRecord, readable years later without any quantum SDK installed.
It is a framework for running your own benchmark campaigns, not a fixed
suite of standard benchmark circuits with a leaderboard.
| Scenario | Supported? |
|---|---|
| Absolute performance of a quantum algorithm (accuracy / cost) | Yes. Expectation values with error bars, energy error against a stored classical reference (FCI / exact diagonalization), timings, and QPU-cost estimates. See the note below on what "reference" means here. |
| Comparing implementations of the same algorithm | Yes, a core design goal. E.g. one ADAPT-VQE configuration runs unchanged against three different engines, producing directly comparable records. |
| Comparing different algorithms | Yes. Records are tagged with a package-agnostic algorithm family, so different algorithms on the same problem stay comparable in one store. |
| Comparing different hardware | Yes. Register several backends (Aer, IBM, IQM, Braket, Quantinuum, Qibo, ...) and sweep the same circuits across all of them. |
| Comparing different quantum computing modalities | Yes, this is the "modality-agnostic" in the tagline. The record format covers gate-based, MBQC, boson sampling, neutral-atom analog, and more. Runnable adapters today are mostly gate-based; other modalities enter via schemas and integrations. |
| Comparing a quantum algorithm to a classical algorithm | Partially. Classical reference values (FCI / exact diagonalization) are computed and stored so quantum results are judged against them, but classical algorithms are not benchmarked as first-class runs. |
| Modelling noise | No. Backends bring their own noise models (e.g. pass a Qiskit Aer noise model to the Aer adapter); QPUBench records what ran, it does not define noise models. |
| Measuring noise | No. It can store device-characterization results produced by external tools (e.g. Qedma's QESEM), but it does not perform characterization itself. |
A note on "chemical accuracy". QPUBench reports an energy error, the gap between a run's result and a classically computed reference (FCI or exact diagonalization), and flags whether that gap is below 1.6 mHartree. This is a numerical-convergence check against a computed value; it is not the same as "chemical accuracy" in the strict sense many chemists mean, which is agreement with an experimentally measured quantity from reality. A result within 1.6 mHartree of a computed FCI reference can still be far from the true physical value if the model (basis set, active space, Hamiltonian) is itself an approximation. Treat the stored reference as a computed baseline, not as ground truth from experiment.
pip install . # minimal; pydantic is the only dependency pip install ".[qiskit]" # + Qiskit Aer simulator backend
Other extras and full instructions for uv, Poetry 2, and conda: docs/installation.md
Copy, paste, run; it works on the minimal install, no quantum SDK or credentials needed. It prepares a two-qubit Bell state, measures the ⟨ZZ⟩ correlation, and appends the result record to an NDJSON file:
from qpubench import BenchmarkRunner, CircuitSpec, Pauli bell_qasm = """OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; h q[0]; cx q[0],q[1];""" circuit = CircuitSpec( num_qubits=2, serialized=bell_qasm, observables=[Pauli("Z0 Z1")], # ⟨ZZ⟩; Pauli() builds a SparsePauliObservable ) runner = BenchmarkRunner(store="results/bell.ndjson") # str path → NDJSONStore runner.register(name="stub", seed=42) record = runner.run(circuit, "stub", shots=4096) ev = record.result.expectation_values[0] print(f"<ZZ> = {ev.value:.4f} ± {ev.std_error:.4f}")
"stub" is a built-in placeholder backend that returns random but
seed-reproducible numbers, so you can try the full pipeline with nothing
installed. For real numbers, install ".[qiskit]" and change one line:
from qpubench.backends import AerAdapter runner.register(AerAdapter(), name="aer") record = runner.run(circuit, "aer", shots=4096) # <ZZ> ≈ 1.0 for a Bell state
That is the whole loop: describe → run → record. Everything else, from VQE to sweeps across backends, real hardware and other paradigms, is the same loop with different pieces. Continue with the user guide.
Plain VQE is not a special object or an algorithm adapter; it is exactly
that same describe → run → record loop wrapped in a classical optimizer. You
keep one unbound parametrized ansatz (with its Hamiltonian attached as an
observable), and each energy evaluation binds the current parameters into a
copy and runs it as an ordinary circuit. Here a one-qubit Ry(θ) ansatz is
driven to the ground state of H = Z (minimum ⟨Z⟩ = −1 at θ = π):
import numpy as np from scipy.optimize import minimize from qpubench import BenchmarkRunner, CircuitSpec, Pauli, VQAConfig from qpubench.backends import AerAdapter # pip install "qpubench[qiskit]" from qpubench.schemas.primitives import CircuitFormat ansatz = CircuitSpec( # the unbound master copy, reused every step num_qubits=1, format=CircuitFormat.QASM3, serialized='OPENQASM 3.0; include "stdgates.inc"; input float[64] theta; qubit[1] q; ry(theta) q[0];', parameters=["theta"], observables=[Pauli("Z0")], # H = Z, the thing we minimize ) runner = BenchmarkRunner() runner.register(AerAdapter(), name="aer") # swap for hardware here; nothing else changes def energy(theta: np.ndarray) -> float: bound = ansatz.bind({"theta": float(theta[0])}) # .bind() never mutates the master # VQAConfig only *labels* the run for the record; it sets no parameters. record = runner.run(bound, "aer", vqa=VQAConfig(problem_type="chemistry", ansatz="ry", optimizer="COBYLA")) return record.result.expectation_values[0].value res = minimize(energy, x0=[0.1], method="COBYLA") print(f"ground-state energy ≈ {res.fun:.4f}") # → -1.0000
qpubench ships no optimizer of its own; the loop is a few lines and any
optimizer works. Every evaluation is persisted as a full BenchmarkRecord, so
the stored history is the convergence trace. The full guide (real chemistry
Hamiltonians, QAOA, the interchangeable ADAPT-VQE engines) is in
VQA algorithms.
Three layers, three responsibilities:
- Schemas describe: circuits, backends, options, and results are plain Pydantic v2 models with no quantum SDK imports.
- Adapters execute: a
BackendAdapterruns a circuit you provide; anAlgorithmAdapterwraps a library that generates its own circuits from a problem (e.g. the ground state energy calculation of a molecule) and drives its own loop. Both register with the sameBenchmarkRunner. - Stores persist: every record lands in NDJSON, Parquet, or S3 through
one
save/load/queryinterface.
Just typed Python classes (Pydantic models) that you import and
instantiate; CircuitSpec in the quick start is one. You pick them from
the library; there is nothing to design or register yourself. The library
ships 48 schema modules at the current schema version (see the badge
above), split into three groups: core record format, cross-cutting
catalogues, and per-project mirrors.
- The core modules (
circuit,backend,execution,result,record, ...) define the record format every benchmark uses. For most work this is all you touch; the quick start used nothing else. - The remaining modules are optional add-ons, each mirroring one
external project (
microsoft_qdk.py,qedma_qesem.py, ...) or a shared catalogue (basis sets, Hamiltonian metadata, ...). You only need one if your benchmark involves that specific tool, and its results attach to the sameBenchmarkRecord(via dedicated fields or thevendor_datadict); there is no assembly of multiple schema files required.
Full model-by-model reference: docs/schemas.md
No credential is ever written in code. Not in src/, not in an example,
not in a notebook, not in a test. Put them in a .env file, which is
gitignored:
cp .env.example .env # then fill in your tokensAdapters do not accept a token as an argument. They accept the name of the environment variable to read it from, and read it only at the moment they connect:
IBMAdapter("ibm_torino") # reads $IBM_QUANTUM_TOKEN IBMAdapter("ibm_torino", token_ref="MY_TOKEN") # reads $MY_TOKEN instead
That name is what BackendSpec.auth stores, so a saved BenchmarkRecord
never contains a secret and can be shared as-is. S3Store goes further and
raises if you pass aws_secret_access_key= at all.
Settings that are account-specific but not secret (IBM_QUANTUM_INSTANCE,
AWS_REGION) have no built-in default either; an adapter tells you which
variable to set rather than guessing one that happens to work on someone
else's machine. .env.example lists every variable, grouped by adapter.
| User guide | The three layers, first benchmark, sweeps, hooks, writing an adapter |
| Installation | pip · uv · Poetry 2 · conda |
| Developer guide | Why the code looks like it does: protocols, super(), bind(), decorators, naming |
| Schema reference | Every Pydantic model, field, and enum |
| Schema review | Known gaps in the core record format, and what the mirror layer revealed about them |
| Roadmap | Planned schema work, phased; tracked in git-bug under the schema-review label |
| Backends & adapters | Available backends and their status; adapter protocols |
| Stores & persistence | NDJSON · Parquet · S3; querying results |
| VQA algorithms | VQE and ADAPT-VQE, including the interchangeable engines |
Algorithms & AlgorithmSpec |
Algorithm identity vs. hyperparameters, the family dispatch model, vanilla VQE vs. ADAPT |
| Integrations | Bridges to external frameworks (QForte, PySCF, QCSchema, GBS, ...) |
| Examples | Runnable guides, demos, and tutorials |
pytest tests/ # full schema test suite, no quantum SDK requiredKnown gaps and follow-ups are tracked in-repo with
git-bug (git bug bug to list them);
see docs/feedback_workflow.md.
QPUBench has a single license: LGPL-3.0-or-later. It is not dual-licensed.
| File | Contents | Role |
|---|---|---|
LICENSE |
LGPLv3 | The license QPUBench is under |
In short: you may use QPUBench in proprietary or differently-licensed applications; modifications to QPUBench itself must be released under the LGPL.