scientific-skills/qiskit/references/primitives.md
Primitives are the fundamental building blocks for executing quantum circuits. Qiskit provides two main primitives: Sampler (for measuring bitstrings) and Estimator (for computing expectation values).
Calculates probabilities or quasi-probabilities of bitstrings from quantum circuits. Use when you need:
Computes expectation values of observables for quantum circuits. Use when you need:
Qiskit uses V2 primitives (BaseSamplerV2, BaseEstimatorV2) as the current standard. V1 primitives are legacy.
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorSampler
# Create circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Run with Sampler
sampler = StatevectorSampler()
result = sampler.run([qc], shots=1024).result()
# Access results
counts = result[0].data.meas.get_counts()
print(counts) # e.g., {'00': 523, '11': 501}
qc1 = QuantumCircuit(2)
qc1.h(0)
qc1.measure_all()
qc2 = QuantumCircuit(2)
qc2.x(0)
qc2.measure_all()
# Run multiple circuits
sampler = StatevectorSampler()
job = sampler.run([qc1, qc2], shots=1000)
results = job.result()
# Access individual results
counts1 = results[0].data.meas.get_counts()
counts2 = results[1].data.meas.get_counts()
from qiskit.circuit import Parameter
theta = Parameter('θ')
qc = QuantumCircuit(1)
qc.ry(theta, 0)
qc.measure_all()
# Run with parameter values
sampler = StatevectorSampler()
param_values = [[0], [np.pi/4], [np.pi/2]]
result = sampler.run([(qc, param_values)], shots=1024).result()
from qiskit import QuantumCircuit
from qiskit.primitives import StatevectorEstimator
from qiskit.quantum_info import SparsePauliOp
# Create circuit WITHOUT measurements
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
# Define observable
observable = SparsePauliOp(["ZZ", "XX"])
# Run Estimator
estimator = StatevectorEstimator()
result = estimator.run([(qc, observable)]).result()
# Access expectation values
exp_value = result[0].data.evs
print(f"Expectation value: {exp_value}")
from qiskit.quantum_info import SparsePauliOp
qc = QuantumCircuit(2)
qc.h(0)
obs1 = SparsePauliOp(["ZZ"])
obs2 = SparsePauliOp(["XX"])
estimator = StatevectorEstimator()
result = estimator.run([(qc, obs1), (qc, obs2)]).result()
ev1 = result[0].data.evs
ev2 = result[1].data.evs
from qiskit.circuit import Parameter
import numpy as np
theta = Parameter('θ')
qc = QuantumCircuit(1)
qc.ry(theta, 0)
observable = SparsePauliOp(["Z"])
# Run with multiple parameter values
estimator = StatevectorEstimator()
param_values = [[0], [np.pi/4], [np.pi/2], [np.pi]]
result = estimator.run([(qc, observable, param_values)]).result()
For running on real hardware, use runtime primitives:
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
# Run on real hardware
sampler = Sampler(backend)
job = sampler.run([qc], shots=1024)
result = job.result()
counts = result[0].data.meas.get_counts()
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
from qiskit.quantum_info import SparsePauliOp
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
observable = SparsePauliOp(["ZZ"])
# Run on real hardware
estimator = Estimator(backend)
job = estimator.run([(qc, observable)])
result = job.result()
exp_value = result[0].data.evs
Sessions group multiple jobs to reduce queue wait times:
from qiskit_ibm_runtime import Session
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
with Session(backend=backend) as session:
sampler = Sampler(session=session)
# Run multiple jobs in session
job1 = sampler.run([qc1], shots=1024)
result1 = job1.result()
job2 = sampler.run([qc2], shots=1024)
result2 = job2.result()
Batch mode runs independent jobs in parallel:
from qiskit_ibm_runtime import Batch
service = QiskitRuntimeService()
backend = service.backend("ibm_brisbane")
with Batch(backend=backend) as batch:
sampler = Sampler(session=batch)
# Submit multiple independent jobs
job1 = sampler.run([qc1], shots=1024)
job2 = sampler.run([qc2], shots=1024)
# Retrieve results when ready
result1 = job1.result()
result2 = job2.result()
result = sampler.run([qc], shots=1024).result()
# Get counts
counts = result[0].data.meas.get_counts()
# Get probabilities
probs = {k: v/1024 for k, v in counts.items()}
# Get metadata
metadata = result[0].metadata
result = estimator.run([(qc, observable)]).result()
# Expectation value
exp_val = result[0].data.evs
# Standard deviation (if available)
std_dev = result[0].data.stds
# Metadata
metadata = result[0].metadata
V2 Improvements:
Migration from V1:
StatevectorSampler instead of SamplerStatevectorEstimator instead of Estimator.result().quasi_dists[0] to .result()[0].data.meas.get_counts()