QAOA Example

QAOA Example#

import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
from iqm.qiskit_iqm import IQMFakeAdonis
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.primitives import BackendEstimatorV2 as BackendEstimator
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit_aer import AerSimulator
from scipy.optimize import minimize

import QCut as ck
from QCut import cut
edges = [(0,1), (1,2), (2,0), (2,3), (2,4), (3,4)]
G = nx.Graph(edges)
nx.draw(G, with_labels=True, alpha=0.8, node_size=500)
../_images/b0e3c12d48df5fa8ebec1c5949ac4f58c3efe340200354aaf8c818ff0de97732.png
reformattedHamiltonian = {'paulis': PauliList(['IIIZZ', 'IIZIZ', 'IIZZI', 'IZZII', 'ZIZII', 'ZZIII']),
 'coefs': [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
 'const': 0.0}

nqubits = 5
observables = SparsePauliOp(reformattedHamiltonian["paulis"], reformattedHamiltonian["coefs"])
def create_cut_qaoa_circ(n_layers):
    beta = [Parameter(f'beta_{i}') for i in range(n_layers)]
    gamma = [Parameter(f'gamma_{i}') for i in range(n_layers)]
    
    #create actual circuit object
    qc = QuantumCircuit(nqubits)
    #entagling layer
    for i in range(nqubits):
        qc.h(i)
    for layer_index in range(n_layers):
        #cost layer
        count = 0
        for i,j in G.edges():
            qc.rzz(2*gamma[layer_index], i, j)
            if count == 2:
                qc.append(cut(), [max(i,j)])
            count += 1
        #mixing layer
        for i in range(nqubits):
            qc.rx(2*beta[layer_index], i)
    return qc

def create_qaoa_circ(n_layers):

    beta = [Parameter(f'beta_{i}') for i in range(n_layers)]
    gamma = [Parameter(f'gamma_{i}') for i in range(n_layers)]

    #create actual circuit object
    qc = QuantumCircuit(nqubits)
    #entagling layer
    for i in range(nqubits):
        qc.h(i)
    for layer_index in range(n_layers):
        #cost layer
        for i,j in G.edges():
            qc.rzz(2*gamma[layer_index], i, j)
        #mixing layer
        for i in range(nqubits):
            qc.rx(2*beta[layer_index], i)
    return qc
qc = create_qaoa_circ(1)

qc.draw("mpl")
../_images/05ff4d264f7140a85245a8a358b4788d1780dca42b32eaf88527c4014ae89ea2.png
qca = create_cut_qaoa_circ(1)

qca.draw("mpl")
../_images/d3b62cc3fde8b8fb95aae6aa6729babd9345aada2285f2170911ea8e3ba8d17b.png
backend = IQMFakeAdonis()
sim = AerSimulator()

cut_circuit = ck.get_locations_and_subcircuits(qca)

transpiled = ck.transpile_subcircuits(cut_circuit, backend, optimization_level=3)

cut_experiment = ck.get_experiment_circuits(transpiled, observables)


tr_qc = transpile(qc, backend, optimization_level=3)
tr_obs = observables.apply_layout(tr_qc.layout)
def get_expectation_backend(circuit, hamiltonian, observables, backend):
    exps_uncut = np.empty(0)
    def execute_circ(theta):
        
        params = {}
        n_layers = len(theta)//2 # number of alternating unitaries
        for i in range(n_layers):
            params[f'beta_{i}'] = theta[i]
            params[f'gamma_{i}'] = theta[i + n_layers]

        p_circuit = circuit.assign_parameters(params, inplace=False)

        fake_estimator = BackendEstimator(backend=backend)
        
        exps = [e.data.evs for e in
            fake_estimator.run([(x) for x in zip([p_circuit] * len([observables]), [observables])]).result()
        ]

        f = hamiltonian["const"] + sum(exps)

        global exps_uncut
        exps_uncut = np.append(exps_uncut, f)

        return f
    
    return execute_circ, exps_uncut



def get_expectation_cut(cut_experiment, hamiltonian, observables, backend):
    exps_cut = np.empty(0) #store expectation value from each optimization step
    #execute circuit and calculate expectation value
    def execute_circ(theta):

        params = {}
        n_layers = len(theta)//2 # number of alternating unitaries
        for i in range(n_layers):
            params[f'beta_{i}'] = theta[i]
            params[f'gamma_{i}'] = theta[i + n_layers]

        p_cut_experiment = cut_experiment.assign_parameters(params, inplace=False)
        
        #run the experiments, apply the {0,1} -> [-1,1] post-processing function
        results = ck.run_experiments(p_cut_experiment, shots=2**12, backend=backend)

        reconstructed_expvals = ck.estimate_expectation_values(results, p_cut_experiment.expv_data())

        #Calculate the hamiltonian expectation value
        f = hamiltonian["const"]
        for i, j in zip(reconstructed_expvals, hamiltonian["coefs"]):
            f += i*j
        global exps_cut
        exps_cut =np.append(exps_cut, f)
        return f
    return execute_circ, exps_cut
initial_params = [-np.pi, -np.pi]

expectation, exps_uncut = get_expectation_backend(tr_qc, reformattedHamiltonian, tr_obs, sim)
#minimize uncut qaoa cost using COBYLA
res_uncut = minimize(expectation,
                initial_params,
                method='COBYLA',
                options={'maxiter': 100}
                )
res_uncut
 message: Optimization terminated successfully.
 success: True
  status: 1
     fun: -1.833984375
       x: [-1.864e+00 -2.832e+00]
    nfev: 33
   maxcv: 0.0
expectation_cut, exps_cut = get_expectation_cut(cut_experiment, reformattedHamiltonian, observables, sim)
#minimize uncut qaoa cost using COBYLA
res_cut = minimize(expectation_cut,
                initial_params,
                method='COBYLA',
                options={'maxiter': 100}
                )
res_cut
 message: Optimization terminated successfully.
 success: True
  status: 1
     fun: -1.8255871534347388
       x: [-4.417e+00 -3.411e+00]
    nfev: 31
   maxcv: 0.0
x = range(len(exps_cut))
x1 = range(len(exps_uncut))
plt.plot(x, exps_cut, label='QCut')
plt.plot(x1, exps_uncut, label='uncut')
plt.axhline(y=-2, color='r', linestyle='--', label = 'Actual minimum')
plt.xlabel('Optimization round')
plt.ylabel('Cost')
plt.title('cut vs uncut Max-Cut QAOA cost on IQMFakeAdonis')
plt.legend()
plt.grid(True)
../_images/29f583eb339f5a8ec4c3364fc67b4c26af4cb8f6f403fefcc58edd95eb84c8c9.png