QPUBench

Benchmark any quantum computer
and quantum computing paradigm

QPUBench allows you to easily set up quantum computing benchmark studies and generate one common results format for easy and rigorous scientific comparison. The data-schema-oriented framework allows you to use QPUBench with an agentic system in the driver's seat, setting up benchmark studies with just a single prompt.

Read the guide Install Supported packages

# One circuit, one backend, one data point.
from qpubench import BenchmarkRunner, CircuitSpec, Pauli
from qpubench.backends import AerAdapter

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")],
)

runner = BenchmarkRunner(store="results/bell.ndjson")
runner.register(AerAdapter(), name="aer")
record = runner.run(circuit, "aer", shots=4096)

ev = record.result.expectation_values[0]
# <ZZ> = 0.9986 ± 0.0156

Maintained by

Supported by

Three framework layers

1

Schemas

CircuitSpec, BackendSpec, ExecutionOptions and QuantumResult are plain Pydantic v2 models. They serialize to JSON, validate on construction, and never import a quantum SDK.

Schema Reference →
2

Adapters

Any object with spec, validate() and run() is a backend. Libraries that build their own circuits implement the sibling AlgorithmAdapter protocol instead. No base class to inherit.

Adapters & Backends →
3

Stores

NDJSONStore (append-only, zero-dependency), ParquetStore (columnar, for pandas) and S3Store (one object per record, safe for distributed sweeps) share one interface.

Persistence →

Supported packages, backends and integrations

Every project below is reachable from QPUBench, either as a runnable adapter that executes circuits, or as a schema bridge that captures its run configuration and results in the shared record format. Schema bridges never import the external library, so installing QPUBench pulls in none of them. Follow a tile for that project's own QPUBench documentation, or the “Upstream” link for the repository QPUBench integrates against — and, where the schema was read off a publication, the paper beside it.

Execution backends

Simulators and QPUs with a runnable adapter in qpubench.backends. Register one with a BenchmarkRunner and every run lands in the same record format.

Algorithms & quantum chemistry

Packages whose run configurations, wavefunctions and results are mirrored as typed schemas, so their output is directly comparable with everything else in the store.

Modalities & hardware vendors

Beyond gate-based superconducting qubits. ComputingModel and QubitModality are independent axes on every record, so these runs share one store with everything above.

Mitigation, compilation, decomposition & orchestration

The layers around execution: making a noisy run usable, making a large circuit fit, and running the whole campaign as scheduled compute.

Marks shown here are generated placeholder wordmarks, not vendor artwork; see docs/site/README for how to drop in a real logo.

What QPUBench does and does not do

Yes

  • Absolute performance of an algorithm. Expectation values with error bars, energy error against a stored classical reference, timings and QPU-cost estimates.
  • Comparing implementations of the same algorithm. One ADAPT-VQE configuration runs unchanged against three different engines.
  • Comparing different algorithms. Records carry a package-agnostic algorithm family, so different algorithms on the same problem stay comparable.
  • Comparing hardware. Register several backends and sweep the same circuits across all of them.
  • Comparing modalities. The record format spans gate-based, MBQC, boson sampling and neutral-atom analog runs.

No

  • Modelling noise. Backends bring their own noise models; QPUBench records what ran, it does not define noise.
  • Measuring noise. It can store device-characterization results from external tools, but performs no characterization itself.
  • Benchmarking classical algorithms as first-class runs. Classical references (FCI, exact diagonalization) are computed and stored for comparison only.
  • Shipping a fixed benchmark suite. This is a framework for running your own campaigns, not a leaderboard.

A note on “chemical accuracy”. QPUBench reports an energy error, the gap between a run and a classically computed reference; it flags whether that error is below 1.6 mHartree. That is a numerical-convergence check against a computed value, not agreement with an experimentally measured quantity. Treat the stored reference as a computed baseline, not as ground truth.

Documentation

Every page published on this site, grouped by what it is for. The integration pages each document one upstream project and the schemas that mirror it.

Start here

Reference

Development

Integrations: chemistry

  • PySCF: molecules, cells, mean-field, embedding
  • SlowQuant: UCC, orbital optimization, linear response
  • Microsoft QDK: SCF to QPE, resource estimation
  • QCSchema: QCElemental, PennyLane datasets
  • Basis sets: Basis Set Exchange, Grimme group

Integrations: photonics & atoms

  • Photonics: LOQC chips, fusion-based QC
  • GBS: Gaussian states, hafnians, vibronic spectra
  • Neutral atoms: Bloqade, Aquila, AHS programs

Integrations: algorithms

Integrations: simulators & SDKs

  • QuEST: state-vector and density-matrix simulation
  • HQS stack: struqture, qoqo, ActiveSpaceFinder
  • Cebule SDK: MQS cloud chemistry and materials tasks
  • Classiq: synthesis under width and depth constraints

Integrations: services

  • QESEM: Qedma error suppression and mitigation
  • IBM cost estimator: QPU seconds and price, before submitting
  • Kubeflow: pipelines, scheduling, resourced runs

Plugging in your own backend

An adapter is a plain class. Two rules keep the ecosystem healthy: SDK imports live inside methods, so importing your adapter never requires the SDK; and recoverable failures return status=FAILED rather than raising, so one bad point does not kill a sweep.

class MySimulatorAdapter:
    @property
    def spec(self) -> BackendSpec:
        return BackendSpec(name="my_sim", provider="me", simulator=True)

    def validate(self, circuit: CircuitSpec) -> list[str]:
        return [] if circuit.num_qubits <= 30 else ["max 30 qubits"]

    def run(self, circuit: CircuitSpec, options: ExecutionOptions) -> QuantumResult:
        import my_sdk                    # SDK imports stay inside methods
        counts = my_sdk.sample(circuit.serialized, shots=options.shots)
        return QuantumResult(
            computing_model=circuit.computing_model,
            shots=ShotResult(num_qubits=circuit.num_qubits,
                             num_shots=options.shots, counts=counts),
            status=JobStatus.SUCCEEDED,
        )

Templates for both protocols live in integrations/template/, and Adapters & Backends walks through testing an adapter with the SDK mocked out.