Skip to content

Solving MaxCut with QAOA

This tutorial shows how to solve the maximum cut (MaxCut) combinatorial optimization problem on a graph using the Quantum Approximate Optimization Algorithm (QAOA), first introduced by Farhi et al. in 2014 1.

Given an arbitrary graph, the MaxCut problem consists in finding a graph cut which partitions the nodes into two disjoint sets, such that the number of edges in the cut is maximized. This is a very common combinatorial optimization problem known to be computationally hard (NP-hard).

The graph used for this tutorial is an unweighted graph randomly generated using the networkx library with a certain probability \(p\) of having an edge between two arbitrary nodes (known as Erdős–Rényi graph).

import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import random

# ensure reproducibility
seed = 42
np.random.seed(seed)
random.seed(seed)

# Create random graph
n_nodes = 4
edge_prob = 0.8
graph = nx.gnp_random_graph(n_nodes, edge_prob)

nx.draw(graph)
2024-04-03T16:37:14.226781 image/svg+xml Matplotlib v3.7.5, https://matplotlib.org/

The goal of the MaxCut algorithm is to maximize the following cost function:

\[\mathcal{C}(p) = \sum_{\alpha}^m \mathcal{C}_{\alpha}(p)\]

where \(p\) is a given cut of the graph, \(\alpha\) is an index over the edges and \(\mathcal{C}_{\alpha}(p)\) is written such that if the nodes connected by the \(\alpha\) edge are in the same set, it returns \(0\), otherwise it returns \(1\). We will represent a cut \(p\) as a bitstring of length \(N\), where \(N\) is the number of nodes, and where the bit in position \(i\) shows to which partition node \(i\) belongs. We assign value 0 to one of the partitions defined by the cut and 1 to the other. Since this choice is arbitrary, every cut is represented by two bitstrings, e.g. "0011" and "1100" are equivalent.

Since in this tutorial we are only dealing with small graphs, we can find the maximum cut by brute force to make sure QAOA works as intended.

# Function to calculate the cost associated with a cut
def calculate_cost(cut: str, graph: nx.graph) -> float:
    """Returns the cost of a given cut (represented by a bitstring)"""
    cost = 0
    for edge in graph.edges():
        (i, j) = edge
        if cut[i] != cut[j]:
            cost += 1
    return cost


# Function to get a binary representation of an int
get_binary = lambda x, n: format(x, "b").zfill(n)

# List of all possible cuts
all_possible_cuts = [bin(k)[2:].rjust(n_nodes, "0") for k in range(2**n_nodes)]

# List with the costs associated to each cut
all_costs = [calculate_cost(cut, graph) for cut in all_possible_cuts]

# Get the maximum cost
maxcost = max(all_costs)

# Get all cuts that correspond to the maximum cost
maxcuts = [get_binary(i, n_nodes) for i, j in enumerate(all_costs) if j == maxcost]
print(f"The maximum cut is represented by the bitstrings {maxcuts}, with a cost of {maxcost}")
The maximum cut is represented by the bitstrings ['0011', '0101', '0110', '1001', '1010', '1100'], with a cost of 4

The QAOA quantum circuit

The Max-Cut problem can be solved by using the QAOA algorithm. QAOA belongs to the class of Variational Quantum Algorithms (VQAs), which means that its quantum circuit contains a certain number of parametrized quantum gates that need to be optimized with a classical optimizer. The QAOA circuit is composed of two operators:

  • The cost operator \(U_c\): a circuit generated by the cost Hamiltonian which encodes the cost function described above into a quantum circuit. The solution to the optimization problem is encoded in the ground state of the cost Hamiltonian \(H_c\). The cost operator is simply the evolution of the cost Hamiltonian parametrized by a variational parameter \(\gamma\) so that \(U_c = e^{i\gamma H_c}.\)
  • The mixing operator \(U_b\): a simple set of single-qubit rotations with adjustable angles which are tuned during the classical optimization loop to minimize the cost

The cost Hamiltonian of the MaxCut problem can be written as:

\[H_c = \frac12 \sum_{\langle i,j\rangle} (\mathbb{1} - Z_iZ_j)\]

where \(\langle i,j\rangle\) represents the edge between nodes \(i\) and \(j\). The solution of the MaxCut problem is encoded in the ground state of the above Hamiltonian.

The QAOA quantum circuit consists of a number of layers, each layer containing a cost and a mixing operator. Below, the QAOA quantum circuit is defined using qadence operations. First, a layer of Hadamard gates is applied to all qubits to prepare the initial state \(|+\rangle ^{\otimes n}\). The cost operator of each layer can be built "manually", implementing the \(e^{iZZ\gamma}\) terms with CNOTs and a \(\rm{RZ}(2\gamma)\) rotation, or it can also be automatically decomposed into digital single and two-qubits operations via the .digital_decomposition() method. The decomposition is exact since the Hamiltonian generator is diagonal.

from qadence import tag, kron, chain, RX, RZ, Z, H, CNOT, I, add
from qadence import HamEvo, QuantumCircuit, Parameter

n_qubits = graph.number_of_nodes()
n_edges = graph.number_of_edges()
n_layers = 6

# Generate the cost Hamiltonian
zz_ops = add(Z(edge[0]) @ Z(edge[1]) for edge in graph.edges)
cost_ham = 0.5 * (n_edges * kron(I(i) for i in range(n_qubits)) - zz_ops)


# QAOA circuit
def build_qaoa_circuit(n_qubits, n_layers, graph):
    layers = []
    # Layer of Hadamards
    initial_layer = kron(H(i) for i in range(n_qubits))
    layers.append(initial_layer)
    for layer in range(n_layers):

        # cost layer with digital decomposition
        # cost_layer = HamEvo(cost_ham, f"g{layer}").digital_decomposition(approximation="basic")
        cost_layer = []
        for edge in graph.edges():
            (q0, q1) = edge
            zz_term = chain(
                CNOT(q0, q1),
                RZ(q1, Parameter(f"g{layer}")),
                CNOT(q0, q1),
            )
            cost_layer.append(zz_term)
        cost_layer = chain(*cost_layer)
        cost_layer = tag(cost_layer, "cost")

        # mixing layer with single qubit rotations
        mixing_layer = kron(RX(i, f"b{layer}") for i in range(n_qubits))
        mixing_layer = tag(mixing_layer, "mixing")

        # putting all together in a single ChainBlock
        layers.append(chain(cost_layer, mixing_layer))

    final_b = chain(*layers)
    return QuantumCircuit(n_qubits, final_b)


circuit = build_qaoa_circuit(n_qubits, n_layers, graph)

# Print a single layer of the circuit
%3 cluster_a2bb286f8dd04772b46efdea7a4271bf mixing cluster_1f247ebcb694436a81271e502f6d5c13 cost aee3171eb00741b78034c4d4b9dcc079 0 af74c8749cfe42ce8c3899f944bdb3b5 H aee3171eb00741b78034c4d4b9dcc079--af74c8749cfe42ce8c3899f944bdb3b5 e6f64eaa188d4547b5f4d3f6a411225a 1 9bb2158c0d3845fba4ba8e5ff735cc70 af74c8749cfe42ce8c3899f944bdb3b5--9bb2158c0d3845fba4ba8e5ff735cc70 e2efa7f72f20485582b08137bb487a2a 9bb2158c0d3845fba4ba8e5ff735cc70--e2efa7f72f20485582b08137bb487a2a 56526536f6fa4caabcaad75dd9ed953b e2efa7f72f20485582b08137bb487a2a--56526536f6fa4caabcaad75dd9ed953b ae85469256a7462b8dca920eb9109506 56526536f6fa4caabcaad75dd9ed953b--ae85469256a7462b8dca920eb9109506 0426263b733c4f67ae57a4aa432b5735 ae85469256a7462b8dca920eb9109506--0426263b733c4f67ae57a4aa432b5735 9886cc3bb5134a739eaf98c6dc02eeec 0426263b733c4f67ae57a4aa432b5735--9886cc3bb5134a739eaf98c6dc02eeec d622238f823f46b2a7367c3a827ce4b3 9886cc3bb5134a739eaf98c6dc02eeec--d622238f823f46b2a7367c3a827ce4b3 a26c4d2bc3e844f8a8bd4dc886b3e4b3 d622238f823f46b2a7367c3a827ce4b3--a26c4d2bc3e844f8a8bd4dc886b3e4b3 ca83a6ec85764c60ade7836e332b86a6 a26c4d2bc3e844f8a8bd4dc886b3e4b3--ca83a6ec85764c60ade7836e332b86a6 8ce49e2f8cd94ddd8c49e57b30915d22 ca83a6ec85764c60ade7836e332b86a6--8ce49e2f8cd94ddd8c49e57b30915d22 0517615ecdee4c67a309d5ca0f300d41 8ce49e2f8cd94ddd8c49e57b30915d22--0517615ecdee4c67a309d5ca0f300d41 cbab8d4071b644f7abe610ab647fe33a 0517615ecdee4c67a309d5ca0f300d41--cbab8d4071b644f7abe610ab647fe33a 1a0cc7ce60434e569afa53001edbce8c cbab8d4071b644f7abe610ab647fe33a--1a0cc7ce60434e569afa53001edbce8c 0e0bd7fadb524feb8b7ae8710d7533c2 1a0cc7ce60434e569afa53001edbce8c--0e0bd7fadb524feb8b7ae8710d7533c2 e0f807031b304372a45b3c8da9f432dd 0e0bd7fadb524feb8b7ae8710d7533c2--e0f807031b304372a45b3c8da9f432dd 0cff004c7aa94d23a0c9e47d0cdd457c e0f807031b304372a45b3c8da9f432dd--0cff004c7aa94d23a0c9e47d0cdd457c b6d8abbe2057404a89eb3cfdc47de144 0cff004c7aa94d23a0c9e47d0cdd457c--b6d8abbe2057404a89eb3cfdc47de144 ac3953e0b86742cf8c65f0d9c380e98f b6d8abbe2057404a89eb3cfdc47de144--ac3953e0b86742cf8c65f0d9c380e98f 6e3cde38952847689ef181e3feb21926 RX(b0) ac3953e0b86742cf8c65f0d9c380e98f--6e3cde38952847689ef181e3feb21926 a1825e3fca684ba490bc3dbb44c13718 6e3cde38952847689ef181e3feb21926--a1825e3fca684ba490bc3dbb44c13718 bfab38c8526a4279bbc3bb4cd4be08d3 32a95acc994242fabb97fd4056eb05a3 H e6f64eaa188d4547b5f4d3f6a411225a--32a95acc994242fabb97fd4056eb05a3 de5664ec123d46c49b48c7448e5fbdc4 2 308839971da34fb2add6a5cacb53eacb X 32a95acc994242fabb97fd4056eb05a3--308839971da34fb2add6a5cacb53eacb 308839971da34fb2add6a5cacb53eacb--9bb2158c0d3845fba4ba8e5ff735cc70 a3a3c4d3d0614d9db120d022400a56ef RZ(g0) 308839971da34fb2add6a5cacb53eacb--a3a3c4d3d0614d9db120d022400a56ef 2cb656b1b7c849f4ae2a21ec5328dcf0 X a3a3c4d3d0614d9db120d022400a56ef--2cb656b1b7c849f4ae2a21ec5328dcf0 2cb656b1b7c849f4ae2a21ec5328dcf0--56526536f6fa4caabcaad75dd9ed953b 8eaea20b5548444695479ab68798917d 2cb656b1b7c849f4ae2a21ec5328dcf0--8eaea20b5548444695479ab68798917d 7144e0c788624d2a8341af6e441c3dac 8eaea20b5548444695479ab68798917d--7144e0c788624d2a8341af6e441c3dac a3221dcc308544f79ccd38b8f6b0efe2 7144e0c788624d2a8341af6e441c3dac--a3221dcc308544f79ccd38b8f6b0efe2 ae3fd5c4b68b47448f9d864645ffd743 a3221dcc308544f79ccd38b8f6b0efe2--ae3fd5c4b68b47448f9d864645ffd743 1061965b05ed4b408d77f97463e9d33a ae3fd5c4b68b47448f9d864645ffd743--1061965b05ed4b408d77f97463e9d33a d864151ed58444f08c12f3bcd2d1d7f2 1061965b05ed4b408d77f97463e9d33a--d864151ed58444f08c12f3bcd2d1d7f2 7ff03edbcd4a4a7980873fa74e9453bb d864151ed58444f08c12f3bcd2d1d7f2--7ff03edbcd4a4a7980873fa74e9453bb 2809a2511f5447db8e90c02ec2edbd95 7ff03edbcd4a4a7980873fa74e9453bb--2809a2511f5447db8e90c02ec2edbd95 29a970186f0f47e6b4ad774a63ea1614 2809a2511f5447db8e90c02ec2edbd95--29a970186f0f47e6b4ad774a63ea1614 6636351fb669456cb29ad5f8b40a7a22 29a970186f0f47e6b4ad774a63ea1614--6636351fb669456cb29ad5f8b40a7a22 458b8c23818446d0a2798495e247164a 6636351fb669456cb29ad5f8b40a7a22--458b8c23818446d0a2798495e247164a 3dedb49ad29b42b1b666151f4c1f82ef 458b8c23818446d0a2798495e247164a--3dedb49ad29b42b1b666151f4c1f82ef ed9a2c0d6cbf4570a2b3a2c9eb77d4ae 3dedb49ad29b42b1b666151f4c1f82ef--ed9a2c0d6cbf4570a2b3a2c9eb77d4ae aea7a39c0c1f4cae835b2d81c77abddd ed9a2c0d6cbf4570a2b3a2c9eb77d4ae--aea7a39c0c1f4cae835b2d81c77abddd 3aacdd6d76ff4b2995c3808b8e3f890e aea7a39c0c1f4cae835b2d81c77abddd--3aacdd6d76ff4b2995c3808b8e3f890e 342deddb1b0c489982db49c7e2a573fc RX(b0) 3aacdd6d76ff4b2995c3808b8e3f890e--342deddb1b0c489982db49c7e2a573fc 342deddb1b0c489982db49c7e2a573fc--bfab38c8526a4279bbc3bb4cd4be08d3 4abdbd70d0ef4c70bdc332d21002e31c 0879fe21b2f94b588d314f1882ac3c04 H de5664ec123d46c49b48c7448e5fbdc4--0879fe21b2f94b588d314f1882ac3c04 581bd463813c4bf899b146f9bee7a64f 3 65efc662ac2f4444a51b65739cdaec4a 0879fe21b2f94b588d314f1882ac3c04--65efc662ac2f4444a51b65739cdaec4a d725c4c8e4944d58939fe1bc9f69e2ce 65efc662ac2f4444a51b65739cdaec4a--d725c4c8e4944d58939fe1bc9f69e2ce a479ee0375e8421d9776eb0f8d9591d6 d725c4c8e4944d58939fe1bc9f69e2ce--a479ee0375e8421d9776eb0f8d9591d6 c3225004155b47158d001be300128944 X a479ee0375e8421d9776eb0f8d9591d6--c3225004155b47158d001be300128944 c3225004155b47158d001be300128944--ae85469256a7462b8dca920eb9109506 e0be54dc65474a2299d66aa2d205b9f4 RZ(g0) c3225004155b47158d001be300128944--e0be54dc65474a2299d66aa2d205b9f4 3c1acfda963645c09ae2797a11ee1539 X e0be54dc65474a2299d66aa2d205b9f4--3c1acfda963645c09ae2797a11ee1539 3c1acfda963645c09ae2797a11ee1539--9886cc3bb5134a739eaf98c6dc02eeec 9776b4a5337a4804b7bbc019f2b2f7c5 3c1acfda963645c09ae2797a11ee1539--9776b4a5337a4804b7bbc019f2b2f7c5 9327bc77d00a4ea99bb399565b044fbd 9776b4a5337a4804b7bbc019f2b2f7c5--9327bc77d00a4ea99bb399565b044fbd 6c0817a730234c198670b5892ca79b36 9327bc77d00a4ea99bb399565b044fbd--6c0817a730234c198670b5892ca79b36 3859a2bd84b94abcade486ccc10aa1c8 X 6c0817a730234c198670b5892ca79b36--3859a2bd84b94abcade486ccc10aa1c8 3859a2bd84b94abcade486ccc10aa1c8--7ff03edbcd4a4a7980873fa74e9453bb 76eccd77bfc64611a727ee53cbd768f1 RZ(g0) 3859a2bd84b94abcade486ccc10aa1c8--76eccd77bfc64611a727ee53cbd768f1 5698f5c6027e42bba89187694c5e2e79 X 76eccd77bfc64611a727ee53cbd768f1--5698f5c6027e42bba89187694c5e2e79 5698f5c6027e42bba89187694c5e2e79--29a970186f0f47e6b4ad774a63ea1614 a0327448ed114d8caddc79ce44c2e7dd 5698f5c6027e42bba89187694c5e2e79--a0327448ed114d8caddc79ce44c2e7dd 9044d23041454ed78abc2d18238bb685 a0327448ed114d8caddc79ce44c2e7dd--9044d23041454ed78abc2d18238bb685 6811e6cabfe64abc987746ebe15ff27e 9044d23041454ed78abc2d18238bb685--6811e6cabfe64abc987746ebe15ff27e 421254bae15c4132bb38033c8332a210 6811e6cabfe64abc987746ebe15ff27e--421254bae15c4132bb38033c8332a210 aedeff25952b4e079a6e178f3e4f5f37 421254bae15c4132bb38033c8332a210--aedeff25952b4e079a6e178f3e4f5f37 ee9d4996b6004a568d05a8f4b89b079e aedeff25952b4e079a6e178f3e4f5f37--ee9d4996b6004a568d05a8f4b89b079e 183af4b0247f49dfbefe9bd9a9a23d06 RX(b0) ee9d4996b6004a568d05a8f4b89b079e--183af4b0247f49dfbefe9bd9a9a23d06 183af4b0247f49dfbefe9bd9a9a23d06--4abdbd70d0ef4c70bdc332d21002e31c 988e0b8202264b5899e1b62ae34e9c51 0c2d1daa3381482a96090c92b0c6ba32 H 581bd463813c4bf899b146f9bee7a64f--0c2d1daa3381482a96090c92b0c6ba32 6a3793110dcf485eae2c2b9ed279b7da 0c2d1daa3381482a96090c92b0c6ba32--6a3793110dcf485eae2c2b9ed279b7da ad407e79df0a4ff891402b4caeb84a28 6a3793110dcf485eae2c2b9ed279b7da--ad407e79df0a4ff891402b4caeb84a28 0ffea3ec33234f599521b77724991b3c ad407e79df0a4ff891402b4caeb84a28--0ffea3ec33234f599521b77724991b3c c8f82a6e35984dddbde73979288490df 0ffea3ec33234f599521b77724991b3c--c8f82a6e35984dddbde73979288490df 6d1bce93e8214d828054b961dec3248c c8f82a6e35984dddbde73979288490df--6d1bce93e8214d828054b961dec3248c 3bae34e857b24341b625b1a8ea25cdf5 6d1bce93e8214d828054b961dec3248c--3bae34e857b24341b625b1a8ea25cdf5 31f2c196e86947efae2c5fe93d77f560 X 3bae34e857b24341b625b1a8ea25cdf5--31f2c196e86947efae2c5fe93d77f560 31f2c196e86947efae2c5fe93d77f560--d622238f823f46b2a7367c3a827ce4b3 37850e56318b4a42a3a3b840a981c24f RZ(g0) 31f2c196e86947efae2c5fe93d77f560--37850e56318b4a42a3a3b840a981c24f bc48b561f9b94948a698bbd7aaa93685 X 37850e56318b4a42a3a3b840a981c24f--bc48b561f9b94948a698bbd7aaa93685 bc48b561f9b94948a698bbd7aaa93685--ca83a6ec85764c60ade7836e332b86a6 e584809a10154f86a68972ac71285de9 bc48b561f9b94948a698bbd7aaa93685--e584809a10154f86a68972ac71285de9 06a15f2b67b342c38dc441fac472ceaf e584809a10154f86a68972ac71285de9--06a15f2b67b342c38dc441fac472ceaf 87881a48039c40d6a981db8a85e82d58 06a15f2b67b342c38dc441fac472ceaf--87881a48039c40d6a981db8a85e82d58 f977df8d61724eafbc0b5ade86b3cc60 X 87881a48039c40d6a981db8a85e82d58--f977df8d61724eafbc0b5ade86b3cc60 f977df8d61724eafbc0b5ade86b3cc60--6636351fb669456cb29ad5f8b40a7a22 69e650afdabd4876824274138d950af5 RZ(g0) f977df8d61724eafbc0b5ade86b3cc60--69e650afdabd4876824274138d950af5 2e42944e0cea4f2e8f8ad974faab2f7b X 69e650afdabd4876824274138d950af5--2e42944e0cea4f2e8f8ad974faab2f7b 2e42944e0cea4f2e8f8ad974faab2f7b--3dedb49ad29b42b1b666151f4c1f82ef 76972af7c4e84147b8472c73a658e9b2 X 2e42944e0cea4f2e8f8ad974faab2f7b--76972af7c4e84147b8472c73a658e9b2 76972af7c4e84147b8472c73a658e9b2--421254bae15c4132bb38033c8332a210 fde2805c5c72419b92667a52151e7d5e RZ(g0) 76972af7c4e84147b8472c73a658e9b2--fde2805c5c72419b92667a52151e7d5e b7d51506e41744daa4e666e4c256a58c X fde2805c5c72419b92667a52151e7d5e--b7d51506e41744daa4e666e4c256a58c b7d51506e41744daa4e666e4c256a58c--ee9d4996b6004a568d05a8f4b89b079e 4d1cdd19b6f740969df59b4980dae672 RX(b0) b7d51506e41744daa4e666e4c256a58c--4d1cdd19b6f740969df59b4980dae672 4d1cdd19b6f740969df59b4980dae672--988e0b8202264b5899e1b62ae34e9c51

Train the QAOA circuit to solve MaxCut

Given the QAOA circuit above, one can construct the associated Qadence QuantumModel and train it using standard gradient based optimization.

The loss function to be minimized reads:

\[\mathcal{L} =-\langle \psi | H_c| \psi \rangle= -\frac12 \sum_{\langle i,j\rangle} \left(1 - \langle \psi | Z_i Z_j | \psi \rangle \right)\]

where \(|\psi\rangle(\beta, \gamma)\) is the wavefunction obtained by running the QAQA quantum circuit and the sum runs over the edges of the graph \(\langle i,j\rangle\).

import torch
from qadence import QuantumModel

torch.manual_seed(seed)


def loss_function(model: QuantumModel):
    # The loss corresponds to the expectation
    # value of the cost Hamiltonian
    return -1.0 * model.expectation().squeeze()


# initialize the parameters to random values
model = QuantumModel(circuit, observable=cost_ham)
model.reset_vparams(torch.rand(model.num_vparams))
initial_loss = loss_function(model)
print(f"Initial loss: {initial_loss}")

# train the model
n_epochs = 100
lr = 0.1

optimizer = torch.optim.Adam(model.parameters(), lr=lr)

for i in range(n_epochs):
    optimizer.zero_grad()
    loss = loss_function(model)
    loss.backward()
    optimizer.step()
    if (i + 1) % (n_epochs // 10) == 0:
        print(f"MaxCut cost at iteration {i+1}: {-loss.item()}")
Initial loss: -2.1782381363858794
MaxCut cost at iteration 10: 3.7470706807026373
MaxCut cost at iteration 20: 3.8378810288930207
MaxCut cost at iteration 30: 3.9424197899236146
MaxCut cost at iteration 40: 3.998125625576603
MaxCut cost at iteration 50: 3.9964705285082114
MaxCut cost at iteration 60: 3.9991374608876598
MaxCut cost at iteration 70: 3.999467854291969
MaxCut cost at iteration 80: 3.999872558672824
MaxCut cost at iteration 90: 3.999947583412106
MaxCut cost at iteration 100: 3.9999793311641065

Qadence offers some convenience functions to implement this training loop with advanced logging and metrics track features. You can refer to this tutorial for more details.

Results

Given the trained quantum model, one needs to sample the resulting quantum state to recover the bitstring with the highest probability which corresponds to the maximum cut of the graph.

samples = model.sample(n_shots=100)[0]
most_frequent = max(samples, key=samples.get)

print(f"Most frequently sampled bitstring corresponding to the maximum cut: {most_frequent}")

# let's now draw the cut obtained with the QAOA procedure
colors = []
labels = {}
for node, b in zip(graph.nodes(), most_frequent):
    colors.append("green") if int(b) == 0 else colors.append("red")
    labels[node] = "A" if int(b) == 0 else "B"

nx.draw_networkx(graph, node_color=colors, with_labels=True, labels=labels)
Most frequently sampled bitstring corresponding to the maximum cut: 1001 2024-04-03T16:37:18.439033 image/svg+xml Matplotlib v3.7.5, https://matplotlib.org/

References


  1. Farhi et al. - A Quantum Approximate Optimization Algorithm