Note
This page was generated from docs/tutorials/how-to-getting-started-with-estimator.ipynb.
Get started with the Estimator primitive¶
In this tutorial we will show you how to set up the Qiskit Runtime Estimator
primitive, explore the different options you can use to configure it, and invoke the primitive efficiently inside a session.
Primitives¶
Primitives are core functions that make it easier to build modular algorithms and applications.
The initial release of Qiskit Runtime includes two primitives:
Sampler: Generates quasi-probability distribution from input circuits.
Estimator: Calculates expectation values from input circuits and observables.
In this tutorial we will focus on the Estimator
primitive. There is a separate tutorial on Getting started with the Sampler primitive.
Using the Estimator primitive¶
Similar to the Backend
base class, there is an Estimator
base class defined in Qiskit Terra that standardizes the way users interact with all Estimator
implementations. This allows users to easily change their choice of simulator or device for performing expectation value calculations, even if the underlying implementation is different.
In this section we will be using the default implementation in Qiskit Terra, which uses a local state vector simulator.
1. Create a circuit¶
For a basic expectation value calculation you will need at least one quantum circuit to prepare our system in a precise quantum state for study. Our examples all have circuits in them, but you can use Qiskit to create your own. To learn how to create circuits by using Qiskit, see the Circuit basics tutorial.
[1]:
from qiskit.circuit.random import random_circuit
circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
display(circuit.draw("mpl"))

2. Create an observable to measure¶
You will also need at least one observable to measure. Observables represent physical properties of a quantum system (such as energy or spin), and allow said properties to be measured (such as their expectation values) for a given state of our system. For simplicity, you can use the SparsePauliOp class in Qiskit to define them, as illustrated in the following example.
[2]:
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp("XZ")
print(f">>> Observable: {observable.paulis}")
>>> Observable: ['XZ']
3. Initialize an Estimator class¶
The next step is to create an instance of an Estimator
class, which can be any of the subclasses that comply with the base specification. For simplicity, we will use Qiskit Terra’s qiskit.primitives.Estimator
class, based on the Statevector construct (algebraic simulation).
[3]:
from qiskit.primitives import Estimator
estimator = Estimator()
4. Invoke the Estimator and get results¶
To calculate the expectation values, invoke the run()
method of the Estimator
instance you just created and pass in the circuit and observable as input parameters. This method call is asynchronous, and you will get a Job
object back. You can use this object to query for information like job_id()
and status()
.
[4]:
job = estimator.run(circuit, observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
>>> Job ID: 85a54b84-7ce2-49f8-b614-cc39ef8eafcc
>>> Job Status: JobStatus.DONE
The result()
method of the job will return the EstimatorResult
, which includes both the expectation values and job metadata.
[5]:
result = job.result()
print(f">>> {result}")
print(f" > Expectation value: {result.values[0]}")
>>> EstimatorResult(values=array([0.85347811]), metadata=[{}])
> Expectation value: 0.8534781134132173
You can keep invoking the run()
method again with the different inputs:
[6]:
circuit = random_circuit(2, 2, seed=1).decompose(reps=1)
observable = SparsePauliOp("IY")
job = estimator.run(circuit, observable)
result = job.result()
display(circuit.draw("mpl"))
print(f">>> Observable: {observable.paulis}")
print(f">>> Expectation value: {result.values[0]}")

>>> Observable: ['IY']
>>> Expectation value: -1.582539029327245e-16
You can also provide compound inputs to the run()
method:
[7]:
circuits = (
random_circuit(2, 2, seed=0).decompose(reps=1),
random_circuit(2, 2, seed=1).decompose(reps=1),
)
observables = (
SparsePauliOp("XZ"),
SparsePauliOp("IY"),
)
job = estimator.run(circuits, observables)
result = job.result()
[display(cir.draw("mpl")) for cir in circuits]
print(f">>> Observables: {[obs.paulis for obs in observables]}")
print(f">>> Expectation values: {result.values.tolist()}")


>>> Observables: [PauliList(['XZ']), PauliList(['IY'])]
>>> Expectation values: [0.8534781134132173, -1.582539029327245e-16]
Or use parameterized circuits:
[8]:
from qiskit.circuit.library import RealAmplitudes
circuit = RealAmplitudes(num_qubits=2, reps=2).decompose(reps=1)
observable = SparsePauliOp("ZI")
parameter_values = [0, 1, 2, 3, 4, 5]
job = estimator.run(circuit, observable, parameter_values)
result = job.result()
display(circuit.draw("mpl"))
print(f">>> Observable: {observable.paulis}")
print(f">>> Parameter values: {parameter_values}")
print(f">>> Expectation value: {result.values[0]}")

>>> Observable: ['ZI']
>>> Parameter values: [0, 1, 2, 3, 4, 5]
>>> Expectation value: -0.6485568434766461
Using Qiskit Runtime Estimator¶
In this section we will go over how to use Qiskit Runtime’s implementation of the Estimator
primitive.
1. Initialize the account¶
Since Qiskit Runtime Estimator
is a managed service, you will first need to initialize your account. You can then select the simulator or real backend you want to use to calculate the expectation value.
Follow the steps in the getting started guide if you don’t already have an account set up.
[9]:
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService(channel="ibm_quantum")
backend = service.backend("ibmq_qasm_simulator")
2. Create a circuit and an observable¶
Just like the section before, you will need at least one circuit and one observable as inputs to the Estimator
primitive.
[10]:
from qiskit.circuit.random import random_circuit
from qiskit.quantum_info import SparsePauliOp
circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
display(circuit.draw("mpl"))
observable = SparsePauliOp("XZ")
print(f">>> Observable: {observable.paulis}")

>>> Observable: ['XZ']
3. Initialize the Qiskit Runtime Estimator¶
Here we are initializing an instance of qiskit_ibm_runtime.Estimator
rather than qiskit.primitives.Estimator
to use Qiskit Runtime’s implementation of the Estimator
.
When you initialize the Estimator
, you’ll need to pass in the backend you previously selected as the target device (or simulator), using the backend
parameter.
[11]:
from qiskit_ibm_runtime import Estimator
estimator = Estimator(backend=backend)
4. Invoke the Estimator and get results¶
You can then invoke the run()
method to calculate expectation values for the input circuit(s) and observable(s).
[12]:
job = estimator.run(circuit, observable)
print(f">>> Job ID: {job.job_id()}")
print(f">>> Job Status: {job.status()}")
>>> Job ID: cdkrlhian60ka16e78dg
>>> Job Status: JobStatus.RUNNING
[13]:
result = job.result()
print(f">>> {result}")
print(f" > Expectation value: {result.values[0]}")
print(f" > Metadata: {result.metadata[0]}")
>>> EstimatorResult(values=array([0.859]), metadata=[{'variance': 0.262119, 'shots': 4000}])
> Expectation value: 0.859
> Metadata: {'variance': 0.262119, 'shots': 4000}
Options¶
Primitives come with several options that are grouped into different categories. Commonly used options, such as resilience_level
, are at the first level.
You can use the Options class to specify different options.
In the following example, we create an instance of the Options
class. optimization_level
is a first level option and can be passed as an input parameter. Options related to the execution environment are passed using the environment
parameter.
[14]:
from qiskit_ibm_runtime import Options
options = Options(optimization_level=3, environment={"log_level": "INFO"})
Options
supports auto-complete. Once you create an instance of the Options
class, you can use auto-complete to see what options are available. If you choose one of the categories, you can use auto-complete again to see what options are available under that category.
[15]:
from qiskit_ibm_runtime import Options
options = Options()
options.resilience_level = 1
options.execution.shots = 2048
When creating an instance of the Estimator
class, you can pass in the options
you just created. Those options will then be applied when you use run()
to perform the calculation.
[16]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable).result()
print(f">>> Metadata: {result.metadata[0]}")
>>> Metadata: {'variance': 0.2847862243652344, 'shots': 2048, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}
You can also pass options to the run()
method. This will overwrite the options you specified when creating the Estimator
instance for that particular execution.
Since most users will only overwrite a handful of options at the job level, it is not necessary to specify the category the options are in. The following code, for example, specifies shots=1024
rather than execution={"shots": 1024}
(which is also valid).
[17]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable, shots=1024).result()
print(f">>> Metadata: {result.metadata[0]}")
>>> Metadata: {'variance': 0.2814788818359375, 'shots': 1024, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}
Error suppression and mitigation¶
optimization_level
and resilience_level
are used to configure error suppress and mitigation.
Estimator
supports optimization_level
0-3 and resilience_level
0-3.
Advanced mitigation options can be specified under resilience
.
[18]:
from qiskit_ibm_runtime import Options
# optimization_level=3 adds dynamical decoupling
# resilience_level=1 adds readout error mitigation
options = Options(optimization_level=3, resilience_level=1)
[19]:
estimator = Estimator(backend=backend, options=options)
result = estimator.run(circuit, observable).result()
print(f">>> Expectation value: {result.values[0]}")
print(f">>> Metadata: {result.metadata[0]}")
>>> Expectation value: 0.8485
>>> Metadata: {'variance': 0.28004774999999993, 'shots': 4000, 'readout_mitigation_num_twirled_circuits': 16, 'readout_mitigation_shots_calibration': 8192}
Session¶
A Qiskit Runtime session allows you to group a collection of iterative calls to the quantum computer. A session is started when the first job within the session is started. If the session is active, subsequent jobs within the session are prioritized by the scheduler to minimize artificial delay within an iterative algorithm. Data used within a session, such as transpiled circuits, is also cached to avoid unnecessary overhead.
Session timing¶
When a session is started, it is assigned a maximum session timeout value. You can set this value by using the max_time
parameter.
If you don’t specify a timeout value, it is set to the initial job’s maximum execution time and is the smaller of these values:
The system limit (see What is the maximum execution time for a Qiskit Runtime job?).
The
max_execution_time
defined by the program.
After this time limit is reached, the session is permanently closed.
A session also has an interactive timeout value. If there are no session jobs queued within that window, the session is temporarily deactivated and normal job selection resumes. This interactive timeout value is set by the system and cannot be overwritten.
Invoking Estimator.run within a session¶
You can create a Qiskit Runtime session using the context manager (with ...:
), which automatically opens and closes the session for you. You can invoke Estimator.run
one or more times within a session:
[20]:
from qiskit_ibm_runtime import Session, Estimator
with Session(backend=backend, max_time="1h"):
estimator = Estimator()
result = estimator.run(circuit, observable).result()
print(f">>> Expectation value from the first run: {result.values[0]}")
result = estimator.run(circuit, observable).result()
print(f">>> Expectation value from the second run: {result.values[0]}")
>>> Expectation value from the first run: 0.858
>>> Expectation value from the second run: 0.85
Invoke multiple primitives in a session¶
You are not restricted to a single primitive function within a session. In this section we will show you an example of using multiple primitives.
First we prepare a circuit for the sampler primitive.
[21]:
from qiskit.circuit.random import random_circuit
sampler_circuit = random_circuit(2, 2, seed=0).decompose(reps=1)
sampler_circuit.measure_all()
display(circuit.draw("mpl"))

The following example shows how you can create both an instance of the Sampler
class and one of the Estimator
class and invoke their run()
methods within a session.
[22]:
from qiskit_ibm_runtime import Session, Sampler, Estimator
with Session(backend=backend):
sampler = Sampler()
estimator = Estimator()
result = sampler.run(sampler_circuit).result()
print(f">>> Quasi-probability distribution from the sampler job: {result.quasi_dists[0]}")
result = estimator.run(circuit, observable).result()
print(f">>> Expectation value from the estimator job: {result.values[0]}")
>>> Quasi-probability distribution from the sampler job: {2: 0.50125, 0: 0.49875}
>>> Expectation value from the estimator job: 0.8525
The calls can also be asynchronous. You don’t need to wait for the result of a previous job before submitting another one.
[23]:
from qiskit_ibm_runtime import Session, Sampler, Estimator
with Session(backend=backend):
sampler = Sampler()
estimator = Estimator()
sampler_job = sampler.run(sampler_circuit)
estimator_job = estimator.run(circuit, observable)
print(
f">>> Quasi-probability distribution from the sampler job: {sampler_job.result().quasi_dists[0]}"
)
print(f">>> Expectation value from the estimator job: {estimator_job.result().values[0]}")
>>> Quasi-probability distribution from the sampler job: {2: 0.49025, 0: 0.50975}
>>> Expectation value from the estimator job: 0.8475
Summary¶
The following code recaps using Qiskit Runtime primitives, options, and session.
[24]:
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Session,
Sampler,
Estimator,
Options,
)
# 1. Initialize account
service = QiskitRuntimeService(channel="ibm_quantum")
# 2. Specify options, such as enabling error mitigation
options = Options(resilience_level=1)
# 3. Select a backend.
backend = service.backend("ibmq_qasm_simulator")
# 4. Create a session
with Session(backend=backend):
# 5. Create primitive instances
sampler = Sampler(options=options)
estimator = Estimator(options=options)
# 6. Submit jobs
sampler_job = sampler.run(sampler_circuit)
estimator_job = estimator.run(circuit, observable)
# 7. Get results
print(
f">>> Quasi-probability distribution from the sampler job: {sampler_job.result().quasi_dists[0]}"
)
print(f">>> Expectation value from the estimator job: {estimator_job.result().values[0]}")
>>> Quasi-probability distribution from the sampler job: {0: 0.49575, 2: 0.50425}
>>> Expectation value from the estimator job: 0.8315
Reference¶
You can find more details about the Estimator
methods in the Estimator API reference.
And all the available options in the Options API reference.
[25]:
import qiskit_ibm_runtime
qiskit_ibm_runtime.version.get_version_info()
[25]:
'0.8.0'
[26]:
from qiskit.tools.jupyter import *
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.22.2 |
qiskit-aer | 0.11.0 |
qiskit-ibmq-provider | 0.19.2 |
qiskit-nature | 0.5.0 |
System information | |
Python version | 3.8.1 |
Python compiler | Clang 11.0.3 (clang-1103.0.32.62) |
Python build | default, Jul 15 2020 18:48:27 |
OS | Darwin |
CPUs | 8 |
Memory (Gb) | 16.0 |
Mon Nov 07 21:11:54 2022 EST |
This code is a part of Qiskit
© Copyright IBM 2017, 2022.
This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.