Primitives
Qiskit's Sampler and Estimator on Qollab: the wrappers over the pre-created backend, where each works, and the one-job rule on IonQ backends.
Qiskit's primitives, Sampler and Estimator, are a layer over backend.run: 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, backend.run is the direct path, and the primitives sit on top of it through Qiskit's own wrappers, on every backend.
Sampler
BackendSamplerV2 wraps the pre-created backend on the local simulators and the IBM noise models. It takes a list of circuits, or of circuit-and-parameter pairs, and returns one result per entry.
It does not work on IonQ backends: the wrapper reads the per-shot record of a result (memory), which qiskit-ionq 1.0.2 does not return, and fills every shot with 0 instead, so a Bell pair comes back as all 00 with no error. On IonQ, sample with backend.run and get_counts(), as in Python / Qiskit projects.
# Local simulators and IBM noise models 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 and IBM noise models 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 backend on every kind of backend, IonQ included, because it works from counts. It takes circuit-and-observable pairs, with observables as SparsePauliOp; on IonQ backends, one call per run. For the Bell pair, the expectation value of ZZ is 1: the two qubits always agree.
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
On IonQ backends the runner accepts one job per run, so make one Estimator call there with every circuit and observable inside it, all at the same precision; the wrapper submits them as one multi-circuit job, and a loop of calls stops at the second one. That is the same rule as for backend.run on those backends. The local simulators and the IBM noise models take as many calls as you make.
What the provider ships
qiskit-ionq, the provider behind the IonQ backends, ships no primitives of its own and no per-shot memory, which is why the Estimator wrapper works there and the Sampler wrapper does not. The IBM noise-model backends and the local simulators return memory, and take both.
Related
Stay in the loop.
Get the latest tutorials, demos, and project showcases straight to your inbox. No noise, just the good stuff.
Running code