Files

5.9 KiB

Qiskit Primitives

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).

Primitive Types

Sampler

Calculates probabilities or quasi-probabilities of bitstrings from quantum circuits. Use when you need:

  • Measurement outcomes
  • Output probability distributions
  • Sampling from quantum states

Estimator

Computes expectation values of observables for quantum circuits. Use when you need:

  • Energy calculations
  • Observable measurements
  • Variational algorithm optimization

V2 Interface (Current Standard)

Qiskit uses V2 primitives (BaseSamplerV2, BaseEstimatorV2) as the current standard. V1 primitives are legacy.

Sampler Primitive

StatevectorSampler (Local Simulation)

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}

Multiple Circuits

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()

Using Parameters

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()

Estimator Primitive

StatevectorEstimator (Local Simulation)

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}")

Multiple Observables

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

Parameterized Estimator

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()

IBM Quantum Runtime Primitives

For running on real hardware, use runtime primitives:

Runtime Sampler

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()

Runtime Estimator

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 for Iterative Workloads

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 for Parallel Jobs

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 Processing

Sampler Results

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

Estimator Results

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

Differences from V1 Primitives

V2 Improvements:

  • More flexible parameter binding
  • Better result structure
  • Improved performance
  • Cleaner API design

Migration from V1:

  • Use StatevectorSampler instead of Sampler
  • Use StatevectorEstimator instead of Estimator
  • Result access changed from .result().quasi_dists[0] to .result()[0].data.meas.get_counts()