Using IBM Quantum cloud-based simulators
Set up ibmq_qasm_simulator
and map a basic noise model for an IBM Quantum hardware device in Qiskit Runtime, then use this noise model to perform noisy simulations of QuantumCircuits
by using Sampler
and Estimator
to study the effects of errors that occur on real devices.
Set up your local development environment
If you haven’t already set up a Qiskit Runtime service instance, follow the steps in Install and set up to do so.
# load necessary Runtime libraries
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options
service = QiskitRuntimeService()
Prepare the environment
First, we run an example routine. One of the major benefits of using primitives is simplification of binding multiple parameters in parameterized circuits. To illustrate this, we start with is an example circuit with a controlled P-gate as implemented in the following code. Here, we parameterize the P-gate
with a rotation parameter theta
.
from qiskit.circuit import Parameter
from qiskit import QuantumCircuit
theta = Parameter('theta')
qc = QuantumCircuit(2,1)
qc.x(1)
qc.h(0)
qc.cp(theta,0,1)
qc.h(0)
qc.measure(0,0)
qc.draw('mpl')

The circuit shown previously is parameterized and the eigenvalue is put back into qubit 0 to be measured. The eigenvalue's rotation is determined by the parameter theta. Next, we define the circuit's parameters as a list. The parameters in this example range from to , divided over 50 evenly spaced points.
import numpy as np
phases = np.linspace(0, 2*np.pi, 50)
# phases need to be expressed as a list of lists
individual_phases = [[phase] for phase in phases]
Running on the ideal simulator
Set the backend and options to use
Our first run assumes an ideal case, without any noise_model
, optimization_level
or resilience_level
for both Sampler and Estimator. We will define the options in the following code:
backend = "ibmq_qasm_simulator" # use the simulator
options = Options()
options.simulator.seed_simulator = 42
options.execution.shots = 1000
options.optimization_level = 0 # no optimization
options.resilience_level = 0 # no error mitigation
Run the circuits on Sampler
Next, we use the Sampler primitive to sample the circuit and get the resultant quasi-probability distribution. Visit the Get started with Sampler section for more information about the Sampler primitive.
sampler = Sampler(options=options, backend=backend)
job = sampler.run(
circuits=[qc]*len(phases),
parameter_values=individual_phases
)
result = job.result()
import matplotlib.pyplot as plt
# the probablity of being in the 1 state for each of these values
prob_values = [dist.get(1, 0) for dist in result.quasi_dists]
plt.plot(phases, prob_values, 'o', label='Simulator')
plt.plot(phases, np.sin(phases/2,)**2, label='Theory')
plt.xlabel('Phase')
plt.ylabel('Probability')
plt.legend()
<matplotlib.legend.Legend at 0x7f7fd233b6d0>

Run the circuits on Estimator
Visit the Get started with Estimator section for more information on the Estimator primitive.
The Estimator binds single-qubit rotations to get Hamiltonians before it returns expectation values of quantum operators. Therefore, the circuit doesn’t require any measurements. Currently the circuit qc
has measurements, so we will remove these with remove_final_measurements
.
qc_no_meas = qc.remove_final_measurements(inplace=False)
qc_no_meas.draw('mpl')

from qiskit.quantum_info import SparsePauliOp
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
print(f" > Observable: {ZZ.paulis}")
> Observable: ['ZZ']
With this observable, the expectation value is calculated by the following equation.
The following code implements the expectation value equation.
with Session(service=service, backend=backend):
estimator = Estimator(options=options)
job = estimator.run(
circuits=[qc_no_meas]*len(phases),
parameter_values=individual_phases,
observables=[ZZ]*len(phases)
)
result = job.result()
exp_values = result.values
plt.plot(phases, exp_values, 'o', label='Simulator')
plt.plot(phases, 2*np.sin(phases/2)**2-1, label='Theory')
plt.xlabel('Phase')
plt.ylabel('Expectation')
plt.legend()
<matplotlib.legend.Legend at 0x7f7fd0ed8820>

Running a noisy simulation
Now we’ll set up our simulator to run a noisy simulation rather than the ideal one. We can pass a custom noise_model
to the Qiskit Runtime simulator by specifying it in the Options
parameter. Here we will try to mimic a real backend by using the noise_model
from a FakeBackend
class. The noise model can be extracted from the FakeBackend
and passed as a simulator
parameter in options. For more details, visit the Fake Provider documentation in the Qiskit Terra API reference.
Since we are trying to mimic a real backend, we can also pass in the backend topology's coupling_map
and its supported basis_gates
to have a more realistic noisy simulation.
from qiskit.providers.fake_provider import FakeManila
from qiskit_aer.noise import NoiseModel
# Make a noise model
fake_backend = FakeManila()
noise_model = NoiseModel.from_backend(fake_backend)
# Set options to include the noise model
options = Options()
options.simulator = {
"noise_model": noise_model,
"basis_gates": fake_backend.configuration().basis_gates,
"coupling_map": fake_backend.configuration().coupling_map,
"seed_simulator": 42
}
# Set number of shots, optimization_level and resilience_level
options.execution.shots = 1000
options.optimization_level = 0
options.resilience_level = 0
The ibmq_qasm_simulator
allows for the activation of the resilience_levels
offered by the Qiskit Runtime service, and use of these levels on simulators is best demonstrated using the noisy simulation as we have described previously.
To illustrate the comparison, we will define two set of Options
. Here, options
is set to resilience level = 0
to represent a normal run without error mitigation, and options with em
is set to resilience level = 1
to represent a run with error mitigation enabled.
# Set options to include the noise model with error mitigation
options_with_em = Options()
options_with_em.simulator = {
"noise_model": noise_model,
"basis_gates": fake_backend.configuration().basis_gates,
"coupling_map": fake_backend.configuration().coupling_map,
"seed_simulator": 42
}
# Set number of shots, optimization_level and resilience_level
options_with_em.execution.shots = 1000
options_with_em.optimization_level = 0 # no optimization
options_with_em.resilience_level = 1 # M3 for Sampler and T-REx for Estimator
When you set the resilience_level
to 1, M3 is activated in Sampler.
All available resilience level configurations are described on the Configure error mitigation page.
with Session(service=service, backend=backend):
# include the noise model without M3
sampler = Sampler(options=options)
job = sampler.run(
circuits=[qc]*len(phases),
parameter_values=individual_phases
)
result = job.result()
prob_values = [1-dist[0] for dist in result.quasi_dists]
# include the noise model with M3
sampler = Sampler(options=options_with_em)
job = sampler.run(
circuits=[qc]*len(phases),
parameter_values=individual_phases
)
result = job.result()
prob_values_with_em = [1-dist[0] for dist in result.quasi_dists]
plt.plot(phases, prob_values, 'o', label='Noisy')
plt.plot(phases, prob_values_with_em, 'o', label='Mitigated')
plt.plot(phases, np.sin(phases/2,)**2, label='Theory')
plt.xlabel('Phase')
plt.ylabel('Probability')
plt.legend()
<matplotlib.legend.Legend at 0x7f7fb4230700>

T-REx
is triggered in Estimator when the resilience level is set to
1.
with Session(service=service, backend=backend):
# include the noise model without T-REx
estimator = Estimator(options=options)
job = estimator.run(
circuits=[qc_no_meas]*len(phases),
parameter_values=individual_phases,
observables=[ZZ]*len(phases)
)
result = job.result()
exp_values = result.values
# include the noise model with T-REx
estimator = Estimator(options=options_with_em)
job = estimator.run(
circuits=[qc_no_meas]*len(phases),
parameter_values=individual_phases,
observables=[ZZ]*len(phases))
result = job.result()
exp_values_with_em = result.values
plt.plot(phases, exp_values, 'o', label='Noisy')
plt.plot(phases, exp_values_with_em, 'o', label='Mitigated')
plt.plot(phases, 2*np.sin(phases/2)**2-1, label='Theory')
plt.xlabel('Phase')
plt.ylabel('Expectation')
plt.legend()
<matplotlib.legend.Legend at 0x7f7f7006ca00>

Resilience levels are currently in beta so sampling overhead and
solution quality will vary from circuit to circuit. New features,
advanced options, and management tools will be released on a rolling
basis. You can also test out higher levels of resilience and
explore the additional options they offer. For more information
about activating features like Digital-ZNE
and PEC
, in addition to M3
and T-REx
as shown in the previous examples, see the Error suppression and error mitigation with Qiskit Runtime (opens in a new tab) tutorial.
import qiskit_ibm_runtime
qiskit_ibm_runtime.version.get_version_info()
'0.8.0'
from qiskit.tools.jupyter import *
%qiskit_version_table