Primitives

Qiskit's Sampler and Estimator reach the pre-created backend through wrappers that work on the local simulators. On IonQ backends, use backend.run.

Qiskit's primitives, Sampler and Estimator, return two kinds of result. A Sampler returns measurement counts, and an Estimator returns the expectation value of an observable, a quantity such as ZZ averaged over the shots. On Qollab, the primitives reach backend through Qiskit's backend wrappers, BackendSamplerV2 and BackendEstimatorV2, which call backend.run. The wrappers work on the local simulators, the IBM noise models included. On IonQ backends, use backend.run directly.

Sampler

BackendSamplerV2 wraps the pre-created backend on the local simulators. It takes a list of circuits, or of circuit-and-parameter pairs, and returns one result per entry.

The Sampler wrapper fails on IonQ backends (see One job per run on IonQ). To sample on IonQ, use backend.run and get_counts(), as in Python / Qiskit projects.

# Local simulators only; see above for IonQ.
from qiskit import QuantumCircuit
from qiskit.primitives import BackendSamplerV2

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

sampler = BackendSamplerV2(backend=backend)
result = sampler.run([qc], shots=100).result()[0]
print(result.data.c.get_counts())
// Local simulators only; see above for IonQ.
import { QuantumCircuit } from 'qiskit';
import { BackendSamplerV2 } from 'qiskit.primitives';

const circuit = QuantumCircuit(2, 2);
circuit.h(0);
circuit.cx(0, 1);
circuit.measure([0, 1], [0, 1]);

const sampler = BackendSamplerV2.callKwargs({ backend });
const job = await sampler.run.callKwargs([circuit], { shots: 100 });
const result = (await job.result()).get(0);
console.log(result.data.c.get_counts().toJs());

The counts are keyed by the classical register's name, c for the default register. In JS the result is a Python object, so its first entry is .get(0) and the counts need .toJs(), as everywhere in the JS framework.

Estimator

BackendEstimatorV2 wraps the same backends as the Sampler wrapper. It takes circuit-and-observable pairs, with observables as SparsePauliOp. For the Bell pair, the expectation value of ZZ is 1: the two qubits always agree.

# Local simulators only; see below for IonQ.
from qiskit import QuantumCircuit
from qiskit.primitives import BackendEstimatorV2
from qiskit.quantum_info import SparsePauliOp

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

estimator = BackendEstimatorV2(backend=backend)
result = estimator.run([(qc, SparsePauliOp("ZZ"))]).result()[0]
print(result.data.evs)

An Estimator circuit carries no measurements; the wrapper adds the ones each observable needs.

One job per run on IonQ

Qollab accepts one job per run on IonQ backends, and that job holds one circuit. Both wrappers send their circuits to backend.run as a list, even when there is only one, and Qollab refuses a list with a 400 error saying only job type ionq.circuit.v1 is supported. The other backends take as many calls as you make.

On IonQ, call backend.run once with one circuit and work from its counts. For ZZ on the Bell pair, measure both qubits and average the sign of each shot:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

counts = backend.run(qc, shots=1000).result().get_counts()
# ZZ is +1 when the two bits agree and -1 when they differ.
zz = sum((1 if bits[0] == bits[1] else -1) * n for bits, n in counts.items()) / sum(counts.values())
print(zz)

An observable with terms in more than one basis needs one circuit per basis, so on IonQ each basis takes its own run. For the exact value to compare against, StatevectorEstimator computes it in the browser and sends no job.

What the IonQ provider ships

qiskit-ionq, the provider behind the IonQ backends, ships no primitives of its own, so on IonQ backend.run is the only path.

Stay in the loop.

Get the latest tutorials, demos, and project showcases straight to your inbox. No noise, just the good stuff.