Qiskit Quantum Backend Selector
Noise- and queue-aware backend selection and routing for IBM Quantum
backends, built on qiskit-ibm-runtime.
QiskitRuntimeService.least_busy() only looks at queue depth. This
package scores candidate backends per-circuit — factoring in whether the
circuit actually fits, and (optionally) an estimate of expected fidelity
after transpilation — then can route job submission with automatic
failover if a backend errors out.
Install
pip install qiskit-quantum-backend-selector
Or from source:
pip install -e .
Quick start
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_backend_selector import BackendSelector, HybridScoring
service = QiskitRuntimeService()
selector = BackendSelector(service=service, strategy=HybridScoring())
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
best_backend = selector.select(qc)
With automatic failover on submission errors:
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_backend_selector import BackendRouter
router = BackendRouter(selector, max_attempts=3)
def submit(backend, circuit):
sampler = Sampler(mode=backend)
return sampler.run([circuit])
job = router.submit(qc, submit)
Scoring strategies
QueueOnlyScoring— ranks by reported pending-job count. Cheap, equivalent in spirit toleast_busy().NoiseAwareScoring— transpiles the circuit against each candidate backend's real coupling map and basis gates, then estimates fidelity by multiplying per-gate and per-readout error rates frombackend.properties()along the transpiled circuit.HybridScoring— weighted sum of the two above (queue_weight,noise_weight, defaults 0.4/0.6).
Any strategy that implements score(backend, circuit) -> float | None
can be dropped in; returning None excludes a backend (e.g. it doesn't
have enough qubits for the circuit).
QueueOnlyScoring, NoiseAwareScoring, and HybridScoring all accept
an optional CalibrationCache (see monitor.py) to avoid refetching
backend.status()/backend.properties() on every call when scoring
many circuits in a short window:
from qiskit_backend_selector import HybridScoring, CalibrationCache
queue_cache = CalibrationCache(ttl_seconds=30) # queue depth changes fast
noise_cache = CalibrationCache(ttl_seconds=3600) # calibration data doesn't
strategy = HybridScoring(queue_cache=queue_cache, noise_cache=noise_cache)
Live smoke test
scripts/live_smoke_test.py exercises this against a real
QiskitRuntimeService — real auth, real calibration data, an actual job
submission — none of which the fake-backend test suite covers. Run it
manually with your own IBM Quantum credentials configured:
python scripts/live_smoke_test.py
This has been run twice against a live service (2026-09-03). First
run caught a real bug (non-ISA circuit submission — see below, fixed in
router.py); second run, after the fix, submitted cleanly to ibm_fez
and returned a real RuntimeJobV2. Re-run after any further change to
router.py or scoring.py.
Fidelity estimate: validation status
scripts/validate_fidelity_estimate.py checks NoiseAwareScoring's
analytic prediction against a Qiskit Aer noise-model simulation built from
the same calibration data (AerSimulator.from_backend). Sample results:
| Circuit / backend | Predicted | Simulated | Diff |
|---|---|---|---|
| bell_pair / Manila | 0.9351 | 0.9395 | -0.0044 |
| bell_pair / Sherbrooke | 0.9565 | 0.9602 | -0.0037 |
| ghz3 / Manila | 0.8332 | 0.8397 | -0.0066 |
| ghz5 / Sherbrooke | 0.8805 | 0.8869 | -0.0064 |
| ghz4 / Manila | 0.8119 | 0.8292 | -0.0174 |
Agreement is within ~0.02 across these cases, and tests/test_fidelity_validation.py
regression-tests this stays under a 0.05 tolerance (skipped automatically
if the optional qiskit-aer extra isn't installed: pip install -e ".[validate]").
Read this narrowly. Aer's noise model is built from the same
calibration numbers NoiseAwareScoring reads, using thermal-relaxation and
depolarizing channels per gate — which are themselves built from
independence assumptions similar to the analytic estimate's. Close
agreement here shows the analytic math is a reasonable approximation of
that noise model, not that the noise model matches a real device on any
given day. It does not touch crosstalk, calibration drift, or correlated
errors — the gap flagged below still stands until validated against an
actual hardware run.
Known limitations
- Independent-error assumption.
NoiseAwareScoringmultiplies gate and readout error rates as if they were independent. Checked against Aer noise-model simulation (see above) it tracks within ~0.02 — but that simulation shares the same independence assumptions, so this doesn't rule out systematic overstatement on real hardware, where crosstalk, drift, and spatially correlated errors aren't Markovian per-gate channels. Treat it as a relative ranking signal until checked against real device results. - Transpile cost. Noise-aware scoring transpiles the circuit against
every candidate backend on every call.
CalibrationCache(now wired in) avoids refetchingstatus()/properties(), but transpilation itself isn't cached — for large batches of circuits against many backends this still gets expensive. - Queue score ignores job cost.
QueueOnlyScoringcounts pending jobs, not their size or expected runtime, so it can't tell a queue of five quick jobs from a queue of five expensive ones. - Hybrid weights are defaults, not tuned. The 0.4/0.6 split in
HybridScoringis a reasonable starting point, not the result of empirical calibration against real turnaround-time or fidelity data. - No live-service integration tests in CI.
scripts/live_smoke_test.pyhas been run manually twice: once caught a real bug (non-ISA circuit submission, fixed — see above), once confirmed the fix works end-to-end against real hardware (ibm_fez, jobdaciemu42tqs73as7h10). It still isn't run automatically, so nothing currently prevents a future regression from reaching a release without someone running it manually.BackendRouter's failover path itself is still verified only against injectedsubmit_fns in pytest, not real Runtime error modes like auth failures, rate limits, or mid-job backend retirement. - Static circuit assumption. Scoring assumes a fixed circuit width known ahead of time. Dynamic circuits with mid-circuit measurement and classical feedforward are transpiled and scored the same way, but the fidelity estimate doesn't account for any additional overhead specific to dynamic execution.
License
Apache-2.0
Release files for qiskit-quantum-backend-selector 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qiskit_quantum_backend_selector-0.1.0.tar.gz | 22.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qiskit_quantum_backend_selector-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.3 kB
Release files / qiskit_quantum_backend_selector-0.1.0.tar.gz
| Download URL | qiskit_quantum_backend_selector-0.1.0.tar.gz |
|---|---|
| Size | 22.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8dbb5ba234105ec3980bbd6a663506b909f8a4945a5d9f52045da27c0797b7ef
|
|
BLAKE2b-256 checksum How to use checksums |
e635c07ab59f498133090ce272625f8a84d3fc794ee931dd7f904028e9f30b95
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|
Release files / qiskit_quantum_backend_selector-0.1.0-py3-none-any.whl
| Download URL | qiskit_quantum_backend_selector-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
790480cf6b328406d0413a071e6ccdab3230745de969ead75f65a5a25d90bd5f
|
|
BLAKE2b-256 checksum How to use checksums |
e9feecda887e8ae09ecd518731791048ec168e5d8579e52fb0641f8ce6a57385
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.2
|