The Primitives Family

qc/programming qc/qiskit

Qiskit doesn’t “run a circuit and read qubits directly” — it runs a circuit through a primitive, an object that abstracts over simulator-vs-real-hardware and returns one of two things: bitstring counts, or expectation values.

The two base primitives

PrimitiveReturnsNeeds
SamplerBitstring counts/probabilitiesCircuit with measurement gates
EstimatorExpectation values of an observableCircuit without measurement — a SparsePauliOp instead
from qiskit_aer.primitives import SamplerV2, EstimatorV2
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
 
qc = QuantumCircuit(1)
qc.h(0)
 
# Sampler needs an explicit measurement
sampler_qc = qc.copy()
sampler_qc.measure_all()
SamplerV2().run([sampler_qc], shots=1000).result()[0].data.meas.get_counts()
 
# Estimator needs an observable, not a measurement
obs = SparsePauliOp('Z')
EstimatorV2().run([(qc, obs)]).result()[0].data.evs

A third family member

Executor is the box-aware counterpart used by Samplomatic — same family, but it consumes a QuantumProgram built from a template + samplex instead of a plain circuit. See Executor Primitive for the full workflow.

PUBs (Primitive Unified Blocs)

Every primitive call takes a list of PUBs — tuples describing one job of work:

  • Sampler PUB: (circuit,) or (circuit, parameter_values)
  • Estimator PUB: (circuit, observable) or (circuit, observable, parameter_values)

Submitting a list of PUBs in one .run() call batches many circuits/observables/parameter sets into a single job — see Parameterized Circuits for why the parameter-values slot matters.

Local vs Runtime

Same PUB-based interface either way — swapping qiskit_aer.primitives.SamplerV2 for qiskit_ibm_runtime.SamplerV2 is usually the only code change needed to go from local testing to real hardware.

Official reference: Qiskit primitives documentation.

Self-Check

  • How would you explain the difference between Sampler and Estimator to someone who’s never used either?
  • Why does Sampler need a measurement gate in the circuit while Estimator doesn’t?
  • What does going from local testing to real hardware actually require you to change in your code?