Compute an expectation value with Estimator
primitive#
This guide shows how to get the expected value of an observable for a given quantum circuit with the Estimator
primitive.
Nota
While this guide uses Qiskit’s reference implementation, the Estimator
primitive can be run with any provider using BackendEstimator
.
from qiskit.primitives import BackendEstimator
from <some_qiskit_provider> import QiskitProvider
provider = QiskitProvider()
backend = provider.get_backend('backend_name')
estimator = BackendEstimator(backend)
There are some providers that implement primitives natively (see this page for more details).
Initialize observables#
The first step is to define the observables whose expected value you want to compute. Each observable can be any BaseOperator
, like the operators from qiskit.quantum_info
.
Among them it is preferable to use SparsePauliOp
.
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])
Initialize quantum circuit#
Then you need to create the QuantumCircuit
s for which you want to obtain the expected value.
from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)
qc.draw("mpl")

Nota
The QuantumCircuit
you pass to Estimator
must not include any measurements.
Initialize the Estimator
#
Then, you need to instantiate an Estimator
.
from qiskit.primitives import Estimator
estimator = Estimator()
Run and get results#
Now that you have defined your estimator
, you can run your estimation by calling the run()
method,
which returns an instance of PrimitiveJob
(subclass of JobV1
). You can get the results from the job (as a EstimatorResult
object)
with the result()
method.
job = estimator.run(qc, observable)
result = job.result()
print(result)
EstimatorResult(values=array([4.]), metadata=[{}])
While this example only uses one QuantumCircuit
and one observable, if you want to get expectation values for multiple circuits and observables you can
pass a list
of QuantumCircuit
s and a list of BaseOperator
s to the run()
method. Both list
s must have
the same length.
Get the expected value#
From these results you can extract the expected values with the attribute values
.
values
returns a numpy.ndarray
whose i
-th element is the expectation value corresponding to the i
-th circuit and i
-th observable.
exp_value = result.values[0]
print(exp_value)
3.999999999999999
Parameterized circuit with Estimator
#
The Estimator
primitive can be run with unbound parameterized circuits like the one below.
You can also manually bind values to the parameters of the circuit and follow the steps
of the previous example.
from qiskit.circuit import Parameter
theta = Parameter('θ')
param_qc = QuantumCircuit(2)
param_qc.ry(theta, 0)
param_qc.cx(0,1)
print(param_qc.draw())
┌───────┐
q_0: ┤ Ry(θ) ├──■──
└───────┘┌─┴─┐
q_1: ─────────┤ X ├
└───┘
The main difference with the previous case is that now you need to specify the sets of parameter values
for which you want to evaluate the expectation value as a list
of list
s of float
s.
The i
-th element of the outer``list`` is the set of parameter values
that corresponds to the i
-th circuit and observable.
import numpy as np
parameter_values = [[0], [np.pi/6], [np.pi/2]]
job = estimator.run([param_qc]*3, [observable]*3, parameter_values=parameter_values)
values = job.result().values
for i in range(3):
print(f"Parameter: {parameter_values[i][0]:.5f}\t Expectation value: {values[i]}")
Parameter: 0.00000 Expectation value: 2.0
Parameter: 0.52360 Expectation value: 3.0
Parameter: 1.57080 Expectation value: 4.0
Change run options#
Your workflow might require tuning primitive run options, such as the amount of shots.
By default, the reference Estimator
class performs an exact statevector
calculation based on the Statevector
class. However, this can be
modified to include shot noise if the number of shots
is set.
For reproducibility purposes, a seed
will also be set in the following examples.
There are two main ways of setting options in the Estimator
:
Set keyword arguments for run()
#
If you only want to change the settings for a specific run, it can be more convenient to
set the options inside the run()
method. You can do this by
passing them as keyword arguments.
job = estimator.run(qc, observable, shots=2048, seed=123)
result = job.result()
print(result)
EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}])
print(result.values[0])
3.999999998697238
Modify Estimator
options#
If you want to keep some configuration values for several runs, it can be better to
change the Estimator
options. That way you can use the same
Estimator
object as many times as you wish without having to
rewrite the configuration values every time you use run()
.
Modify existing Estimator
#
If you prefer to change the options of an already-defined Estimator
, you can use
set_options()
and introduce the new options as keyword arguments.
estimator.set_options(shots=2048, seed=123)
job = estimator.run(qc, observable)
result = job.result()
print(result)
EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}])
print(result.values[0])
3.999999998697238
Define a new Estimator
with the options#
If you prefer to define a new Estimator
with new options, you need to
define a dict
like this one:
options = {"shots": 2048, "seed": 123}
And then you can introduce it into your new Estimator
with the
options
argument.
estimator = Estimator(options=options)
job = estimator.run(qc, observable)
result = job.result()
print(result)
EstimatorResult(values=array([4.]), metadata=[{'variance': 3.552713678800501e-15, 'shots': 2048}])
print(result.values[0])
3.999999998697238