Solving MaxCut with QITE#

In this example, we demonstrate how MaxCut can be solved with Quantum Imaginary-Time Evolution. A detailed introduction to solving MaxCut with Quantum Approximate Optimization Algorithm can be found in this tutorial.

We start by defining the problem instance.

from qrisp import QuantumVariable, h, rzz
import networkx as nx

G = nx.Graph()
G.add_edges_from([[0,3],[0,4],[1,3],[1,4],[2,3],[2,4]])
N = G.number_of_nodes()

Next, we define the functions U_0 and exp_H for the QITE algorithm, and the classical cost function (similar to the tutorial):

def U_0(qv):
    h(qv)

def exp_H(qv, gamma):
    for pair in list(G.edges()):
        rzz(2*gamma, qv[pair[0]], qv[pair[1]])

def maxcut_obj(x):
    cut = 0
    for i, j in G.edges():
        if x[i] != x[j]:
            cut -= 1
    return cut

def maxcut_cost(meas_res):
    energy = 0
    for meas, p in meas_res.items():
        obj_for_meas = maxcut_obj(meas)
        energy += obj_for_meas * p
    return energy

With all the necessary ingredients, we use QITE to find an approximate solution to the MaxCut problem instance. At each iteration k, the optimal evolution time s_k is found by selecting the time that corresponds to the minimal value of the classical cost function maxcut_cost for a list of evolution times s_values = np.linspace(.01,.3,10). This could also be replaced by employing a more sophisticated optimization loop.

import numpy as np
import sympy as sp
from qrisp.qite import QITE

steps = 4
s_values = np.linspace(.01,.3,10)

theta = sp.Symbol('theta')
optimal_s = [theta]

# Caculate energy for initial state
qv = QuantumVariable(N)
U_0(qv)
E_0 = maxcut_cost(qv.get_measurement())

optimal_energies = [E_0]

for k in range(1,steps+1):

    # Perform k steps of QITE
    qv = QuantumVariable(N)
    QITE(qv, U_0, exp_H, optimal_s, k)
    qc = qv.qs.compile()

    # Find optimal evolution time
    # Use "precompliled_qc" keyword argument to avoid repeated compilation of the QITE circuit
    energies = [maxcut_cost(qv.get_measurement(subs_dic={theta:s_},precompiled_qc=qc)) for s_ in s_values]
    index = np.argmin(energies)
    s_min = s_values[index]

    optimal_s.insert(-1,s_min)
    optimal_energies.append(energies[index])

print(optimal_energies)

In the following, we print the 5 most likely solutions (for the optimal evolution times) together with their cost values.

qv = QuantumVariable(N)
QITE(qv, U_0, exp_H, optimal_s, steps)
results = qv.get_measurement()

print("5 most likely solutions")
max_five = sorted(results.items(), key=lambda item: item[1], reverse=True)[:5]
for res, prob in max_five:
    print(res, prob, maxcut_cost({res : 1}))

Finally, we visualize the most likely solution.

most_likely = max_five[0][0]
nx.draw(G, with_labels = True,
        node_color=['#FFCCCB' if most_likely[node]=='0' else '#ADD8E6' for node in G.nodes()],
        edge_color='#D3D3D3',
        pos = nx.bipartite_layout(G, [node for node in G.nodes() if most_likely[node]=='0']))
../../_images/maxcut_qite.png