02 - Compare Qiskit Simulators with different simulation methods

This notebook is a tutorial on how to use different simulator backends to solve the same problem and compare their simulation results. This tutorial compares the performance of a perfect noise-free statvector simulator, a noise-free shot-based simulator, and finally a noisy simulator emulating a real QPU. This notebook uses qiskit simulators for each of the three categories. We employ: - qiskit.statevector_simulator - noise-free qiskit.qasm_simulator - noisy qiskit.qasm_simulator with a real device noise profile

Begin by importing necessary modules

#some regular python libraries
import networkx as nx
import numpy as np
from pprint import pprint
import matplotlib.pyplot as plt

#import problem classes from OQ for easy problem creation
from openqaoa.problems import MaximumCut, NumberPartition

#import the QAOA workflow model
from openqaoa import QAOA

#import method to specify the device
from openqaoa.backends import create_device

Create a problem instance

We begin by creating a problem instance for a simple MaximumCut problem for a random graph created using the python networkx module.

nodes = 5
edge_probability = 0.5
g = nx.generators.fast_gnp_random_graph(n=nodes,p=edge_probability, seed=30)

# import graph plotter from openqaoa
from openqaoa.utilities import plot_graph
# Use the MaximumCut class to instantiate the problem.
maxcut_prob = MaximumCut(g)

# The property `qubo` translates the problem into a binary Qubo problem.
# The binary values can be access via the `asdict()` method.
maxcut_qubo = maxcut_prob.qubo

Extract the exact solution for a small enough problem

hamiltonian = maxcut_qubo.hamiltonian

# import the brute-force solver to obtain exact solution
from openqaoa.utilities import ground_state_hamiltonian
energy, configuration = ground_state_hamiltonian(hamiltonian)
print(f"Ground State energy: {energy}, Solution: {configuration}")
Ground State energy: -3.0, Solution: ['11000', '10100', '11100', '11010', '00101', '00011', '01011', '00111']

QAOA models with different devices

Here we create, three different QAOA models with each of the three devices mentioned above

# initialize model with statevector_simulator
q_sv = QAOA()

# device
qiskit_sv = create_device(location='local', name='qiskit.statevector_simulator')

# circuit properties
q_sv.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')

# classical optimizer properties
q_sv.set_classical_optimizer(method='COBYLA', maxiter=200, tol=0.001,
                             cost_progress=True, parameter_log=True)

# initialize model with shot-based simulator
q_shot = QAOA()

# device
qiskit_shot = create_device(location='local', name='qiskit.qasm_simulator')

# circuit properties
q_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')

# backend properties
q_shot.set_backend_properties(n_shots = 200)

# classical optimizer properties
q_shot.set_classical_optimizer(method='COBYLA', maxiter=200, tol=0.001,
                               cost_progress=True, parameter_log=True)


Import a noise-model

Finally, import a noise model from a qiskit QPU to be used for noisy simulation with qiskit simulator.

from qiskit import Aer
from qiskit.test.mock import FakeVigo
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer import QasmSimulator
device_backend = FakeVigo()
device = QasmSimulator.from_backend(device_backend)
noise_model = NoiseModel.from_backend(device)
/tmp/ipykernel_32816/3184544667.py:2: DeprecationWarning: The module 'qiskit.test.mock' is deprecated since Qiskit Terra 0.21.0, and will be removed 3 months or more later. Instead, you should import the desired object directly 'qiskit.providers.fake_provider'.
  from qiskit.test.mock import FakeVigo
# initialize model with noisy shot-based simulator
q_noisy_shot = QAOA()

# device

qiskit_noisy_shot = create_device(location='local', name='qiskit.qasm_simulator')

# circuit properties
q_noisy_shot.set_circuit_properties(p=2, param_type='standard', init_type='rand', mixer_hamiltonian='x')

# backend properties
q_noisy_shot.set_backend_properties(n_shots = 200, noise_model = noise_model)

# classical optimizer properties
q_noisy_shot.set_classical_optimizer(method='COBYLA', maxiter=200, tol=0.001,
                                     cost_progress=True, parameter_log=True)



Once the models are compiled, we can begin the optimization process. We optimize the problem with all three backends and then plot the results for comparison.



results_sv = q_sv.result
results_shot = q_shot.result
results_noisy_shot = q_noisy_shot.result
fig, ax = plt.subplots(1,1,figsize=(12,8))

results_sv.plot_cost(ax=ax,label='Statevector Simulator')
results_shot.plot_cost(ax=ax,color='teal', label='Noise-free shot simulator with 500 shots')
results_noisy_shot.plot_cost(ax=ax,color='green', label='Noisy shot simulator with 500 shots and noise model from ibmq_manila')