01 - Introduction to OpenQAOA: An example workflow

This section provides a walkthrough of a simple example workflow, and is intended as a quick introduction to the functionalities of the OpenQAOA library. More focused examples are provided in other sections of the documentation.

The QAOA workflow can be divided in four simple steps: - Problem definition: Define your optimization problem here, either by: - using pre-defined problem classes or, - supplying your own QUBO - Model building: - Build the QAOA circuit with the available configurations - Choose the backend (device) to run the circuit - Choose the properties of the classical optimizer - Compile model and optimize: - Compile the model by passing the problem defined in step-1 - Execute model.optimize() to run the optimization process - Extract results - Run model.results to obtain information on the optimization run

Begin by importing necessary modules

[1]:
#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

Step 1: 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. MaximumCut is a go-to problem to demonstrate QAOA in action.

For this, we first: - create a random graph using the networkx module - using the MaximumCut problem class, we translate into the QUBO formalism to optimize with QAOA

[2]:
nodes = 6
edge_probability = 0.6
g = nx.generators.fast_gnp_random_graph(n=nodes, p=edge_probability, seed=42)

# import graph plotter from openqaoa
from openqaoa.utilities import plot_graph
plot_graph(g)
../_images/notebooks_01_workflows_example_4_0.png
[3]:
# 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
[4]:
pprint(maxcut_qubo.asdict())
{'constant': 0,
 'metadata': {},
 'n': 6,
 'problem_instance': {'G': {'directed': False,
                            'graph': {},
                            'links': [{'source': 0, 'target': 2},
                                      {'source': 0, 'target': 3},
                                      {'source': 0, 'target': 4},
                                      {'source': 1, 'target': 2},
                                      {'source': 1, 'target': 3},
                                      {'source': 1, 'target': 5},
                                      {'source': 2, 'target': 4},
                                      {'source': 2, 'target': 5},
                                      {'source': 3, 'target': 5},
                                      {'source': 4, 'target': 5}],
                            'multigraph': False,
                            'nodes': [{'id': 0},
                                      {'id': 1},
                                      {'id': 2},
                                      {'id': 3},
                                      {'id': 4},
                                      {'id': 5}]},
                      'problem_type': 'maximum_cut'},
 'terms': [[0, 2],
           [0, 3],
           [0, 4],
           [1, 2],
           [1, 3],
           [1, 5],
           [2, 4],
           [2, 5],
           [3, 5],
           [4, 5]],
 'weights': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}

Extract the exact solution for a small enough problem

[5]:
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: -6.0, Solution: ['001110', '110001']
[6]:
#plot the solution on graph
g_sol = np.copy(g)
pos =  nx.spring_layout(g)
nx.draw_networkx_nodes(g, pos, nodelist=[idx for idx,bit in enumerate(configuration[0]) if bit == '1'], node_color="tab:red")
nx.draw_networkx_nodes(g, pos, nodelist=[idx for idx,bit in enumerate(configuration[0]) if bit == '0'], node_color="tab:blue")
nx.draw_networkx_edges(g, pos)
[6]:
<matplotlib.collections.LineCollection at 0x7f390ccd3640>
../_images/notebooks_01_workflows_example_9_1.png

Step 2: Build the QAOA model

  • Initialize the model (with default parameters)

  • Optionally set the following properties for the model

    • model.set_device(...): Set the device

      • The device properties include the location of the device [local, qcs, ibmq] and the device name. Full list of devices available at openqaoa.workflows.parameters.qaoa_parameters.ALLOWED_DEVICES

    • model.set_circuit_properties(...): Sets the circuit properties. Mainly used for:

      • p: the number of layers

      • param_type: the desired parameterisation to be chosen between ['standard', 'extended', 'fourier', annealing]

      • init_type: the initialisation strategy for param_type. To be chosen between ['ramp', 'random', 'custom']

    • model.set_backend_properties(...)

    • model.set_classical_optimizer(...)

For more details on the configurable properties, please refer to the documentation

[7]:
# initialize model with default configurations
q = QAOA()
[8]:
# optionally configure the following properties of the model

# device
qiskit_device = create_device(location='local', name='qiskit.statevector_simulator')
q.set_device(qiskit_device)

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

# backend properties (already set by default)
q.set_backend_properties(prepend_state=None, append_state=None)

# classical optimizer properties
q.set_classical_optimizer(method='nelder-mead', maxiter=200, tol=0.001,
                          optimization_progress=True, cost_progress=True, parameter_log=True)

Step 3: Compile and Optimize

  • Once the QAOA model is configured, we need to compile it. Compilation is necessary because the QAOA solver has to interact with the problem in to be able to create the underlying QAOA circuit.

  • The problem is ready to be optimized now. The user can call model.optimize() to initiate the optimization loop.

[9]:
q.compile(maxcut_qubo)
[10]:
q.optimize()

Step 4: Accessing the results

[11]:
opt_results = q.result
[12]:
# print the cost history
opt_results.plot_cost()
../_images/notebooks_01_workflows_example_18_0.png
[13]:
# prints a large output (commented by default)
# pprint(opt_results.intermediate)
[14]:
pprint(opt_results.optimized)
{'angles': [1.769305782568, 2.897276527017, 0.27595644591, 2.480445026305],
 'cost': -3.496189444142,
 'eval_number': 141,
 'job_id': 'a6a50e53-af90-4ec0-9072-c483d80b6324',
 'measurement_outcomes': array([ 0.04279569+0.01167147j,  0.0283793 -0.01355j   ,
       -0.00222483+0.01917223j, -0.00533755+0.07693892j,
        0.02755904+0.04136831j, -0.01193869+0.03540263j,
       -0.04662524+0.05500205j, -0.17367993+0.07204963j,
        0.0283793 -0.01355j   , -0.01946334+0.05474171j,
        0.01592744+0.01604248j, -0.07276544-0.03142119j,
        0.02799895+0.19571449j, -0.13116855+0.06518103j,
       -0.09375135+0.05517573j, -0.04662524+0.05500205j,
       -0.00222483+0.01917223j,  0.01592744+0.01604248j,
        0.00146576+0.13127059j, -0.06000884+0.24378125j,
       -0.0239379 -0.02844671j, -0.00676705-0.04078231j,
       -0.13116855+0.06518103j, -0.01193869+0.03540263j,
       -0.00533755+0.07693892j, -0.07276544-0.03142119j,
       -0.06000884+0.24378125j, -0.06935948+0.08524623j,
        0.14968271+0.31272273j, -0.0239379 -0.02844671j,
        0.02799895+0.19571449j,  0.02755904+0.04136831j,
        0.02755904+0.04136831j,  0.02799895+0.19571449j,
       -0.0239379 -0.02844671j,  0.14968271+0.31272273j,
       -0.06935948+0.08524623j, -0.06000884+0.24378125j,
       -0.07276544-0.03142119j, -0.00533755+0.07693892j,
       -0.01193869+0.03540263j, -0.13116855+0.06518103j,
       -0.00676705-0.04078231j, -0.0239379 -0.02844671j,
       -0.06000884+0.24378125j,  0.00146576+0.13127059j,
        0.01592744+0.01604248j, -0.00222483+0.01917223j,
       -0.04662524+0.05500205j, -0.09375135+0.05517573j,
       -0.13116855+0.06518103j,  0.02799895+0.19571449j,
       -0.07276544-0.03142119j,  0.01592744+0.01604248j,
       -0.01946334+0.05474171j,  0.0283793 -0.01355j   ,
       -0.17367993+0.07204963j, -0.04662524+0.05500205j,
       -0.01193869+0.03540263j,  0.02755904+0.04136831j,
       -0.00533755+0.07693892j, -0.00222483+0.01917223j,
        0.0283793 -0.01355j   ,  0.04279569+0.01167147j])}
[15]:
variational_params = q.optimizer.variational_params
[16]:
#create the optimized QAOA circuit for qiskit backend
optimized_angles = opt_results.optimized['angles']
variational_params.update_from_raw(optimized_angles)
optimized_circuit = q.backend.qaoa_circuit(variational_params)

#print the optimized QAOA circuit for qiskit backend
optimized_circuit.draw()
[16]:
      ┌───┐                                                            »
q0_0: ┤ H ├─■───────────────────■──────────────────────────────────────»
      ├───┤ │                   │                                      »
q0_1: ┤ H ├─┼───────────────────┼───────────────────■──────────────────»
      ├───┤ │ZZ(0.55191289182)  │                   │ZZ(0.55191289182) »
q0_2: ┤ H ├─■───────────────────┼───────────────────■──────────────────»
      ├───┤                     │ZZ(0.55191289182)                     »
q0_3: ┤ H ├─────────────────────■──────────────────────────────────────»
      ├───┤                                                            »
q0_4: ┤ H ├────────────────────────────────────────────────────────────»
      ├───┤                                                            »
q0_5: ┤ H ├────────────────────────────────────────────────────────────»
      └───┘                                                            »
«                          ┌─────────────────────┐                    »
«q0_0: ─■──────────────────┤ Rx(-3.538611565136) ├────────────────────»
«       │                  └─────────────────────┘                    »
«q0_1: ─┼─────────────────────■────────────────────■──────────────────»
«       │                     │                    │                  »
«q0_2: ─┼─────────────────────┼────────────────────┼──────────────────»
«       │                     │ZZ(0.55191289182)   │                  »
«q0_3: ─┼─────────────────────■────────────────────┼──────────────────»
«       │ZZ(0.55191289182)                         │                  »
«q0_4: ─■──────────────────────────────────────────┼──────────────────»
«                                                  │ZZ(0.55191289182) »
«q0_5: ────────────────────────────────────────────■──────────────────»
«                                                                     »
«                                                                        »
«q0_0: ──────────────────────────────────────────────────────────────────»
«      ┌─────────────────────┐                                           »
«q0_1: ┤ Rx(-3.538611565136) ├───────────────────────────────────────────»
«      └─────────────────────┘                    ┌─────────────────────┐»
«q0_2: ───■────────────────────■──────────────────┤ Rx(-3.538611565136) ├»
«         │                    │                  └─────────────────────┘»
«q0_3: ───┼────────────────────┼─────────────────────■───────────────────»
«         │ZZ(0.55191289182)   │                     │                   »
«q0_4: ───■────────────────────┼─────────────────────┼───────────────────»
«                              │ZZ(0.55191289182)    │ZZ(0.55191289182)  »
«q0_5: ────────────────────────■─────────────────────■───────────────────»
«                                                                        »
«                                                                        »
«q0_0: ───■──────────────────────■───────────────────────────────────────»
«         │                      │                                       »
«q0_1: ───┼──────────────────────┼────────────────────■──────────────────»
«         │ZZ(4.96089005261)     │                    │ZZ(4.96089005261) »
«q0_2: ───■──────────────────────┼────────────────────■──────────────────»
«      ┌─────────────────────┐   │ZZ(4.96089005261)                      »
«q0_3: ┤ Rx(-3.538611565136) ├───■───────────────────────────────────────»
«      └─────────────────────┘┌─────────────────────┐                    »
«q0_4: ───■───────────────────┤ Rx(-3.538611565136) ├────────────────────»
«         │ZZ(0.55191289182)  ├─────────────────────┤                    »
«q0_5: ───■───────────────────┤ Rx(-3.538611565136) ├────────────────────»
«                             └─────────────────────┘                    »
«                          ┌─────────────────────┐                    »
«q0_0: ─■──────────────────┤ Rx(-5.794553054034) ├────────────────────»
«       │                  └─────────────────────┘                    »
«q0_1: ─┼─────────────────────■────────────────────■──────────────────»
«       │                     │                    │                  »
«q0_2: ─┼─────────────────────┼────────────────────┼──────────────────»
«       │                     │ZZ(4.96089005261)   │                  »
«q0_3: ─┼─────────────────────■────────────────────┼──────────────────»
«       │ZZ(4.96089005261)                         │                  »
«q0_4: ─■──────────────────────────────────────────┼──────────────────»
«                                                  │ZZ(4.96089005261) »
«q0_5: ────────────────────────────────────────────■──────────────────»
«                                                                     »
«                                                                        »
«q0_0: ──────────────────────────────────────────────────────────────────»
«      ┌─────────────────────┐                                           »
«q0_1: ┤ Rx(-5.794553054034) ├───────────────────────────────────────────»
«      └─────────────────────┘                    ┌─────────────────────┐»
«q0_2: ───■────────────────────■──────────────────┤ Rx(-5.794553054034) ├»
«         │                    │                  └─────────────────────┘»
«q0_3: ───┼────────────────────┼─────────────────────■───────────────────»
«         │ZZ(4.96089005261)   │                     │                   »
«q0_4: ───■────────────────────┼─────────────────────┼───────────────────»
«                              │ZZ(4.96089005261)    │ZZ(4.96089005261)  »
«q0_5: ────────────────────────■─────────────────────■───────────────────»
«                                                                        »
«
«q0_0: ──────────────────────────────────────────────
«
«q0_1: ──────────────────────────────────────────────
«
«q0_2: ──────────────────────────────────────────────
«      ┌─────────────────────┐
«q0_3: ┤ Rx(-5.794553054034) ├───────────────────────
«      └─────────────────────┘┌─────────────────────┐
«q0_4: ───■───────────────────┤ Rx(-5.794553054034) ├
«         │ZZ(4.96089005261)  ├─────────────────────┤
«q0_5: ───■───────────────────┤ Rx(-5.794553054034) ├
«                             └─────────────────────┘