Choosing a backend
backend_id is the one decision a submission requires. The catalogue tells you enough to
make it.
Read the catalogue
Section titled “Read the catalogue”from qubitra import QubitraClient
with QubitraClient() as client: for backend in client.backends.list(): print( backend.id, backend.type, f"{backend.qubit_count}q", f"shots {backend.shots_min}-{backend.shots_max}", [fmt.value for fmt in backend.supported_formats], )The list holds only Backends you can submit against. client.backends.get(slug) reads
one, and raises NotFoundError for a slug that is not public.
A worked example
Section titled “A worked example”The catalogue changes as Backends are added and retired, so read it rather than hard-coding an identifier. Three entries of the kind you will find there:
Backend.id |
type |
Qubits | Shots | Formats |
|---|---|---|---|---|
sim-statevector-26q |
SIMULATOR |
26 | 1 – 100 000 | OPENQASM_2, OPENQASM_3, QPY |
sim-noisy-12q |
SIMULATOR |
12 | 1 – 100 000 | OPENQASM_2, OPENQASM_3, QPY |
qpu-superconducting-127q |
QPU |
127 | 1 – 20 000 | OPENQASM_3 |
Develop against a simulator. It usually accepts more circuit formats and a wider shot range, and its results are deterministic, which makes them suitable for test assertions. Move to a QPU when the hardware itself is what you are measuring.
The two simulators answer different questions. sim-statevector-26q computes exactly, so
its answer is what the circuit means with no hardware in the way — use it to check that a
circuit does what you intended. sim-noisy-12q runs the same circuit through a real
device’s measured error rates, so its answer is what that class of hardware would do to
your circuit. A result that is clean on the first and unusable on the second indicates
the circuit is too deep for current hardware — useful to establish before paying for a
QPU run.
Shots cost differently on each: on an exact state vector, one final state is sampled repeatedly, so shots are nearly free; the noisy simulator runs one trajectory per shot, so its cost is linear in the shot count.
Match the circuit to the capability
Section titled “Match the circuit to the capability”Three properties are checked at submit, before anything is charged and before any
execution starts. A mismatch is rejected with InvalidRequestError.
Qubit count. qubit_count is the width the Backend offers. The SDK does not parse
your program, so compare the circuit’s width yourself:
backend = client.backends.get("sim-statevector-26q")if qubits_needed > (backend.qubit_count or 0): raise SystemExit(f"{backend.id} offers {backend.qubit_count} qubits, need {qubits_needed}")Circuit format. supported_formats is exhaustive: submitting a format a Backend does
not accept is rejected with an error naming what it does accept. qpu-superconducting-127q
takes OPENQASM_3 only, so an OpenQASM 2 program has to go to a simulator or be
translated first.
from qubitra import CircuitFormat
backend = client.backends.get("qpu-superconducting-127q")assert CircuitFormat.OPENQASM_2 not in backend.supported_formatsA format this SDK version does not recognise is dropped from supported_formats rather
than failing the read, so the tuple only ever contains formats both you and the platform
understand.
Shot window. shots_min and shots_max bound what the Backend will take. The
default is 1024, which is inside the window of most Backends but not all of them.
def clamp_shots(backend, wanted: int) -> int: """Keep a shot count inside the backend's stated window.""" return max(backend.shots_min or 1, min(wanted, backend.shots_max or wanted))Picking one programmatically
Section titled “Picking one programmatically”Filter the catalogue by what you need rather than hard-coding a slug — a slug can be retired, and code that selects by capability keeps working when the catalogue changes.
from qubitra import BackendType, CircuitFormat
def pick_backend(client, *, qubits: int, shots: int, fmt: CircuitFormat, prefer_qpu: bool): """The narrowest Backend that can run this circuit, or None.""" candidates = [ backend for backend in client.backends.list() if (backend.qubit_count or 0) >= qubits and fmt in backend.supported_formats and (backend.shots_min or 1) <= shots <= (backend.shots_max or shots) ] wanted = BackendType.QPU if prefer_qpu else BackendType.SIMULATOR preferred = [backend for backend in candidates if backend.type is wanted] pool = preferred or candidates # Smallest sufficient machine first: leave the wide ones for circuits that need them. return min(pool, key=lambda backend: backend.qubit_count or 0, default=None)Capability metadata is absent until known
Section titled “Capability metadata is absent until known”basis_gates, operations, connectivity and calibration describe what a Backend can
execute natively: the gate set it implements, the non-gate operations it supports, its
coupling map as qubit pairs, and its most recent calibration data.
backend = client.backends.get("sim-noisy-12q")if backend.basis_gates is not None: print("native gates:", ", ".join(backend.basis_gates))An empty tuple and None mean different things: () is a Backend that reported an empty
list, None is a Backend that reported nothing. A partially-readable coupling map keeps
the pairs it can read rather than failing the whole catalogue read.
queue_size and available are snapshots
Section titled “queue_size and available are snapshots”Concretely:
queue_sizeis the queue depth reported at the last catalogue refresh. It is not a queue position, not a wait estimate, and not current. Do not route on it.availableis whether the Backend was still listed at that same refresh. The catalogue already filters on it, so every Backendlist()returns readsTrue. A Backend that has since been retired disappears from the list, and itsgetraisesNotFoundError; it does not come back asavailable=False.
There is no live availability signal in the SDK. If a Backend is gone, you find out at
submit via NotFoundError, and nothing is charged.
from qubitra import NotFoundError
try: job = client.jobs.submit(backend_id=chosen.id, circuit=BELL, shots=1024)except NotFoundError: # The Backend is no longer available. Re-read the catalogue and pick again. chosen = pick_backend(client, qubits=2, shots=1024, fmt=CircuitFormat.OPENQASM_3, prefer_qpu=False)Backends are named by Qubitra
Section titled “Backends are named by Qubitra”A slug like qpu-superconducting-127q describes capability: what kind of machine, what
technology, how wide. Neither the slug nor any field on the model names whoever runs the
hardware — there is no provider field, hardware owner, or access tier. The hardware
behind a Backend can change without your code changing, so select by capability rather
than by inferring a vendor from a slug.