Skip to main contentIBM Quantum Documentation

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', style="iqp")

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 00 to 2π2\pi, 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>
This image shows that the value found by the simulator is very close to the theoretical value.
Simulated versus theoretical value

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', style="iqp")
 
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.

ZZ=ψZZψ=ψ(0011)(0011)ψ=00ψ201ψ210ψ2+11ψ2 \langle ZZ\rangle =\langle \psi | ZZ | \psi\rangle=\langle \psi|(|0\rangle\langle 0| -|1\rangle\langle 1|)\otimes(|0\rangle\langle 0| - |1\rangle\langle 1|) |\psi\rangle =|\langle 00|\psi\rangle|^2 - |\langle 01 | \psi\rangle|^2 - |\langle 10 | \psi\rangle|^2 + |\langle 11|\psi\rangle|^2

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>
This image shows that the value found by the simulator is very close to the theoretical value.
Simulated versus theoretical values

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>
This image shows that the value found by a "noisy" simulator is not very close to the theoretical value, but the approximation is better when mitigated by using M3.
Noisy and mitigated (M3) values versus theoretical values

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>
This image shows that the value found by a "noisy" simulator is not very close to the theoretical value, but the approximation is better when mitigated by using T-REX.
Noisy and mitigated (T-REX) values versus theoretical values

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

Next steps

Recommendations
Was this page helpful?