English
Languages
English
Japanese
Spanish

Calculate expectation values in an algorithm

The Estimator primitive is used to design an algorithm that calculates expectation values.

Background

The role of the Estimator primitive is two-fold: it acts as an entry point to quantum devices or simulators, replacing the Backend interface (commonly referred to as backend.run()). Additionally, it is an algorithmic abstraction for expectation value calculations, so you don’t have to manually construct the final expectation circuit. This results in a considerable reduction of the code complexity and a more compact algorithm design.

Note

Backend.run() model: In this model, you accessed real backends and remote simulators using the qiskit-ibmq-provider module (now migrated to qiskit-ibm-provider). To run local simulations, you could import a specific backend from qiskit-aer. All of them followed the backend.run() interface.

Code example for qiskit-ibmq-provider & backend.run()
from qiskit import IBMQ

# Select provider
provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main")

# Get backend
backend = provider.get_backend("ibmq_qasm_simulator") # cloud simulator

# Run
result = backend.run(expectation_circuits)

Code example for qiskit-aer & backend.run()
from qiskit_aer import AerSimulator # former import: from qiskit import Aer

# Get local simulator backend
backend = AerSimulator()

# Run
result = backend.run(expectation_circuits)

Primitives model: Access real backends and remote simulators through the qiskit-ibm-runtime primitives (Sampler and Estimator). To run local simulations, you can import specific local primitives from qiskit_aer.primitives and qiskit.primitives. All of them follow the BaseSampler and BaseEstimator interfaces, but only the Runtime primitives offer access to the Runtime service, sessions, and built-in error mitigation.

Code example for Runtime Estimator
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator

# Define service
service = QiskitRuntimeService()

# Get backend
backend = service.backend("ibmq_qasm_simulator") # cloud simulator

# Define Estimator
estimator = Estimator(backend=backend)

# Run Expectation value calculation
result = estimator.run(circuits, observables).result()

Code example for Aer Estimator
from qiskit_aer import Estimator

# Get local simulator Estimator
estimator = Estimator()

# Run expectation value calculation
result = estimator.run(circuits, observables).result()

If your code previously calculated expectation values using backend.run(), you most likely used the qiskit.opflow module to handle operators and state functions. To support this scenario, the following migration example shows how to replace the (qiskit.opflow & backend.run()) workflow with an Estimator-based workflow.

End-to-end example

1. Problem definition

We want to compute the expectation value of a quantum state (circuit) with respect to a certain operator. In this example, we are using the H2 molecule and an arbitrary circuit as the quantum state:

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp

# Step 1: Define operator
op = SparsePauliOp.from_list(
    [
        ("II", -1.052373245772859),
        ("IZ", 0.39793742484318045),
        ("ZI", -0.39793742484318045),
        ("ZZ", -0.01128010425623538),
        ("XX", 0.18093119978423156),
    ]
)

# Step 2: Define quantum state
state = QuantumCircuit(2)
state.x(0)
state.x(1)

1.a. [Legacy] Convert problem to opflow

qiskit.opflow provided its own classes to represent both operators and quantum states, so the problem defined previously would be wrapped as:

from qiskit.opflow import CircuitStateFn, PauliSumOp

opflow_op = PauliSumOp(op)
opflow_state = CircuitStateFn(state)

This step is no longer necessary when using the primitives.

Note

For instructions to migrate from qiskit.opflow, see the opflow migration guide .

2. Calculate expectation values on real device or cloud simulator

2.a. [Legacy] Use opflow & backend.run()

The legacy workflow required many steps to compute an expectation value:

Note

Replace ibmq_qasm_simulator with your device name to see the complete workflow for a real device.

from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler
from qiskit import IBMQ

# Define the state to sample
measurable_expression = StateFn(opflow_op, is_measurement=True).compose(opflow_state)

# Convert to expectation value calculation object
expectation = PauliExpectation().convert(measurable_expression)

# Define provider and backend
provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main")
backend = provider.get_backend("ibmq_qasm_simulator")

# Inject backend into circuit sampler
sampler = CircuitSampler(backend).convert(expectation)

# Evaluate
expectation_value = sampler.eval().real
>>> print("expectation: ", expectation_value)
expectation:  -1.065734058826613

2.b. [New] Use the Estimator Runtime primitive

The Estimator simplifies the user-side syntax, making it a more convenient tool for algorithm design.

Note

Replace ibmq_qasm_simulator with your device name to see the complete workflow for a real device.

from qiskit_ibm_runtime import QiskitRuntimeService, Estimator

service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibmq_qasm_simulator")

estimator = Estimator(backend=backend)

expectation_value = estimator.run(state, op).result().values

Note that the Estimator returns a list of values, as it can perform batched evaluations.

>>> print("expectation: ", expectation_value)
expectation:  [-1.06329149]

The Estimator Runtime primitive offers a series of features and tuning options that do not have a legacy alternative to migrate from, but can help improve your performance and results. For more information, refer to the following:

3. Other execution alternatives (non-Runtime)

This section describes how to use non-Runtime primitives to test an algorithm using local simulation. Let’s assume that we want to solve the problem defined previously with a local state vector simulation.

3.a. [Legacy] Using the Qiskit Aer simulator

from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler
from qiskit_aer import AerSimulator

# Define the state to sample
measurable_expression = StateFn(opflow_op, is_measurement=True).compose(opflow_state)

# Convert to expectation value calculation object
expectation = PauliExpectation().convert(measurable_expression)

# Define statevector simulator
simulator = AerSimulator(method="statevector", shots=100)

# Inject backend into circuit sampler
circuit_sampler = CircuitSampler(simulator).convert(expectation)

# Evaluate
expectation_value = circuit_sampler.eval().real
>>> print("expectation: ", expectation_value)
expectation:  -1.0636533500290943

3.b. [New] Use the Reference Estimator or Aer Estimator primitive

The Reference Estimator lets you perform either an exact or a shot-based noisy simulation based on the Statevector class in the qiskit.quantum_info module.

from qiskit.primitives import Estimator

estimator = Estimator()

expectation_value = estimator.run(state, op).result().values

# for shot-based simulation:
expectation_value = estimator.run(state, op, shots=100).result().values
>>> print("expectation: ", expectation_value)
expectation:  [-1.03134297]

You can still access the Aer Simulator through its dedicated Estimator. This can be handy for performing simulations with noise models. In this example, the simulation method has been updated to match the result from 3.a.

from qiskit_aer.primitives import Estimator # import change!!!

estimator = Estimator(run_options= {"method": "statevector"})

expectation_value = estimator.run(state, op, shots=100).result().values
>>> print("expectation: ", expectation_value)
expectation:  [-1.06365335]

For more information about using the Aer primitives, see the VQE tutorial .

For more information about running noisy simulations with the Runtime primitives, see this topic.