Backends and targets
How workflow code runs circuits through QsolveBackend, QsolveSampler and QsolveEstimator on simulators, noise models and quantum computers, and what Qsolve meters.
Updated 13 November 2026 View as markdown
On this page
Your quantum method runs circuits through ctx: ctx.backend is a standard Qiskit BackendV2, ctx.sampler a SamplerV2 and ctx.estimator an EstimatorV2, so ordinary Qiskit code runs unchanged. Only the quantum method may use them; in reference and classical_* they raise QuantumNotAllowed. Every circuit that passes through is recorded and measured.
Targets
A target is a string. These are all the forms the platform and the SDK know:
| Target | Runs on | Notes |
|---|---|---|
sim-cpu |
Qiskit Aer on a CPU | The default. At most 26 qubits per circuit. Needs cpu in the manifest's targets.simulators. |
sim-gpu |
Qiskit Aer on a GPU | At most 30 qubits per circuit. Needs gpu in targets.simulators, and qiskit-aer-gpu when run locally. The platform offers it only while it has GPU capacity. |
fake:<device>[@scale] |
Aer with a real IBM device's published noise model | For example fake:fez or fake:fez@1.6. Needs the ibm extra. Free locally. Not selectable on the platform: the run API refuses it with target_not_supported. It is for local runs and for target: in benchmark cases. |
ibm:<device> |
An IBM quantum computer | <device> is the IBM backend name, matching ^[A-Za-z0-9_.-]{1,64}$. Needs qpu: [ibm] in the manifest. Locally it uses your token in QSOLVE_IBM_TOKEN and optionally QSOLVE_IBM_INSTANCE, and needs the ibm extra. |
Anything else is an error: locally Unknown target 'x'. Use sim-cpu, sim-gpu, ibm:<device> or fake:<device>[@scale]., and on the platform target_not_supported. Only IBM QPUs are supported today. There is no Amazon Braket target: a manifest cannot list braket under qpu, the run API answers target_not_supported for a braket: target, and the platform has no Braket price.
targets.min_qubits_qpu is advisory. It is accepted and stored, but the platform does not use it to accept, refuse or price a run; use it to tell researchers the smallest problem worth sending to a QPU.
Noise-model targets
fake:<device> looks up the device by name among IBM's published fake backends, so <device> is the lowercase device name, for example fez, marrakesh or torino; an unknown name fails with no fake device 'x' (e.g. fez, marrakesh, torino). The circuit is compiled for that device and sampled with its calibrated gate errors, readout errors and T1/T2 times. The qubit limit is the device's own qubit count.
@scale multiplies every error rate and divides T1 and T2 by it. It must be between 1 and 10 inclusive, or the target fails with noise scale must be between 1 and 10. Scaled gate errors are capped at 0.5 and readout errors at 0.75. fake:fez@1.6 is a pessimistic rehearsal: real chips tend to be noisier than their published models, and 1.6 is a rule of thumb. A benchmark case that sets target: fake:fez@1.6 is how you test your workflow against noise during validation; see Benchmarks.
What the target controls on the platform
| Stage | What decides it |
|---|---|
| Whether a run is accepted | targets in your manifest, checked first; then whether the platform has a machine for the target. |
| Which circuits are allowed | At most envelope.max_qubits qubits per circuit (default 32 if a stored manifest lacks it), and at most 100,000 shots per circuit in one send. Beyond either, the executor refuses the send with circuit exceeds the allowed qubit count or shots must be in 1..100000. The shot cap is checked earlier too: a run whose shots parameter is above 100,000 is refused when it is created with 422 shots_too_high, and a workflow whose shots parameter has a default, a max or a benchmark-case value above 100,000 is rejected at submission (before the sandbox gates) with a message naming the cap. |
| Price | The target's entry in the platform rate card: sim-cpu, sim-gpu and (for noise models) sim-cpu; qpu:ibm for ibm:<device>. |
| Time limit | Waiting for hardware does not count against resources.timeout_s; see The manifest. |
Your workflow code never sees credentials or provider names. On the platform it gets a backend that forwards circuits to a trusted executor, which decides the real device. Which targets a researcher may request is set by targets in your manifest; see Machines and targets.
Running ordinary Qiskit code
With the sampler:
measured = qc.copy()
measured.measure_all()
counts = ctx.sampler.run([(measured, theta, shots)]).result()[0].data.meas.get_counts()
A pub is (circuit, parameter_values, shots). The data field is named after the circuit's classical register: meas after measure_all(), or the name you chose, as max-cut does with result.data.c.get_counts(). With no shots in the pub and no shots= argument, the sampler uses 1024.
With the backend:
job = ctx.backend.run([circuit_a, circuit_b], shots=2000)
counts = job.result().get_counts(0)
The job is finished when run returns. backend.run also defaults to 1024 shots.
With the estimator:
result = ctx.estimator.run([(circuit, observable, theta)], precision=0.01).result()
The estimator groups the observable's Pauli terms into qubit-wise commuting groups, measures each group with one circuit and combines the results. It uses ceil(1 / precision**2) shots per group; the default precision of 0.015625 gives 4096. It applies no error mitigation and returns evs and stds.
Send circuits as you built them; Qsolve compiles them for the target. The gate set includes h, x, y, z, s, t, sx, rx, ry, rz, p, u, cx, cy, cz, ch, swap, crx, cry, crz, cp, rxx, ryy, rzz, ecr and ccx, among others. A sampler result always holds exactly the number of shots you asked for.
Batching circuits
All circuits of one sampler.run call with the same shot count go to the target as one batch. On a quantum computer that is one job: one queue wait, one per-job overhead. A pub with an array of parameter values is one circuit per row.
ctx.sampler.run([(circuit, theta_grid, 2000)]) # many circuits, one batch
Pubs with different shot counts are separate batches, and batches over 300 circuits are split automatically. quantum-kernel-classifier sends a whole Gram matrix through one sampler.run.
Shots and seeds
Shots are most of the cost on hardware, so ask for what the answer needs. More shots reduce statistical noise, not systematic device error.
Every circuit gets a seed built from ctx.seed, so on a simulator the same seed gives the same counts. Hardware counts are not reproducible, which is why the reproducible gate runs on a simulator. Seed all your own randomness from ctx.seed.
What each result carries
Each pub result has per-circuit metadata under metadata["qsolve"], one dictionary per circuit:
result = ctx.sampler.run([(circuit, None, shots)]).result()[0]
meta = (result.metadata.get("qsolve") or [{}])[0]
dispatch = meta.get("dispatch") # the index to cite in report_mitigation
readout = meta.get("readout") # per measured bit: [P(1|0), P(0|1)]
| Key | Present on | Meaning |
|---|---|---|
dispatch |
every target | The index of this circuit among everything the run sent. You cite it in report_mitigation. |
readout |
noise-model and hardware targets | Per measured classical bit, the readout error [P(1 given 0), P(0 given 1)] of its qubit. Can be None. |
two_qubit_gates |
noise-model and hardware targets | Two-qubit gates in the compiled circuit. |
physical_qubits |
noise-model and hardware targets | Qubits used in the compiled circuit. |
Use .get and handle a missing key: a simulator has no readout error to correct.
What is metered
Qsolve records every circuit, its shot count and the counts that came back, at the backend.
| Measure | How |
|---|---|
| Circuits and shots | Counted at the backend. |
| Qubits | The most qubits any sent circuit uses. |
| Depth and two-qubit gates | Each distinct circuit is transpiled to the basis cx, rz, sx, x; the maximum over circuits is reported. Up to 40 distinct circuits are measured, largest first, then an even spread. |
| Fidelity | The counts compared with the ideal output of the same circuit. See Error mitigation and fidelity. |
Training on a local statevector simulator, with NumPy or qiskit.quantum_info, never touches the backend and is not metered. The train_on: backend parameter in the examples trains through ctx.sampler instead, which sends and meters one circuit per optimiser step, so the example's estimate() counts them.
The cost quote comes from your estimate(); a run that needs more than was quoted stops with budget_exhausted. See Credits and pricing.
Designing for hardware
- Keep circuits shallow. Fidelity falls with depth and two-qubit gate count. The examples cap QAOA at two layers on noise-model and hardware targets, because a third only added noise.
- Rehearse on
fake:<device>@1.6before spending hardware time. - Optimise on a local simulator and use the target only for the final measurement, as the examples do.
- Circuits above 20 qubits cannot be scored for fidelity, because the ideal output is simulated exactly only up to 20 qubits.