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)
2025-03-13T17:14:38.379407 image/svg+xml Matplotlib v3.10.1, 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_eabda9c0d16847b195666ff9e7884b72 mixing cluster_4e7be4dc51d34fdd9f859b6ab14f22ac cost dd2443bda366448aa2e36713d5bcaa2f 0 6f07d2bdd0564abebe825a3b847e8e74 H dd2443bda366448aa2e36713d5bcaa2f--6f07d2bdd0564abebe825a3b847e8e74 cf357f74710240d3b75dae68894879af 1 ee6d6f7156834a758841faf0e823d8f9 6f07d2bdd0564abebe825a3b847e8e74--ee6d6f7156834a758841faf0e823d8f9 bc58e03dc46e4f888e3c67e3cbc7f8a7 ee6d6f7156834a758841faf0e823d8f9--bc58e03dc46e4f888e3c67e3cbc7f8a7 b7f6eadcaa30464c98c26d480c6cac88 bc58e03dc46e4f888e3c67e3cbc7f8a7--b7f6eadcaa30464c98c26d480c6cac88 f9449f9aa79a40a7891a1b841608338c b7f6eadcaa30464c98c26d480c6cac88--f9449f9aa79a40a7891a1b841608338c 15572da365a44b2f849e39b207ff251b f9449f9aa79a40a7891a1b841608338c--15572da365a44b2f849e39b207ff251b cd7c6463632a4b52955dc466fff3a66e 15572da365a44b2f849e39b207ff251b--cd7c6463632a4b52955dc466fff3a66e 140dd734f1b44ecf85e906ffc554a41b cd7c6463632a4b52955dc466fff3a66e--140dd734f1b44ecf85e906ffc554a41b d15c168f4a894efd91a8ce1bda2ec7fa 140dd734f1b44ecf85e906ffc554a41b--d15c168f4a894efd91a8ce1bda2ec7fa 9f6e6f68015944eda851f745eedd271b d15c168f4a894efd91a8ce1bda2ec7fa--9f6e6f68015944eda851f745eedd271b 1647ba69c4a8498ebaa99f268dfb7dfd 9f6e6f68015944eda851f745eedd271b--1647ba69c4a8498ebaa99f268dfb7dfd d354d90b325e4c97bb6e05a3198e51cc 1647ba69c4a8498ebaa99f268dfb7dfd--d354d90b325e4c97bb6e05a3198e51cc 4820c64f292f4c45aad5df54d0449dc2 d354d90b325e4c97bb6e05a3198e51cc--4820c64f292f4c45aad5df54d0449dc2 a686ad8559564c88aa77910a2ed92a7a 4820c64f292f4c45aad5df54d0449dc2--a686ad8559564c88aa77910a2ed92a7a b5ea867718ee4cdea16b43f6b3f48b96 a686ad8559564c88aa77910a2ed92a7a--b5ea867718ee4cdea16b43f6b3f48b96 ec7e218a0c2049b38a13368aff661644 b5ea867718ee4cdea16b43f6b3f48b96--ec7e218a0c2049b38a13368aff661644 d1d62126d8f84f02835fadcc6fde7686 ec7e218a0c2049b38a13368aff661644--d1d62126d8f84f02835fadcc6fde7686 02dabe8680be4d9985982d04c8e18d18 d1d62126d8f84f02835fadcc6fde7686--02dabe8680be4d9985982d04c8e18d18 b0fdc85778e742e5a9e2e6548866715f 02dabe8680be4d9985982d04c8e18d18--b0fdc85778e742e5a9e2e6548866715f f41c358107ba42eda0349a2b487171f1 RX(b0) b0fdc85778e742e5a9e2e6548866715f--f41c358107ba42eda0349a2b487171f1 2a7951fd4a6940b8b773c3c9142ecd18 f41c358107ba42eda0349a2b487171f1--2a7951fd4a6940b8b773c3c9142ecd18 39f1ee2252d34c6d8358a317ee44e8d5 279fb4e9ccde4aa88bd333c7d03dfafe H cf357f74710240d3b75dae68894879af--279fb4e9ccde4aa88bd333c7d03dfafe 805283dfc4bd43c0ab7098cf4a07ee5d 2 374cd1796f004d3f915c6e4bd101578a X 279fb4e9ccde4aa88bd333c7d03dfafe--374cd1796f004d3f915c6e4bd101578a 374cd1796f004d3f915c6e4bd101578a--ee6d6f7156834a758841faf0e823d8f9 5acb0c6c80fb4475af0e0f889781a383 RZ(g0) 374cd1796f004d3f915c6e4bd101578a--5acb0c6c80fb4475af0e0f889781a383 b26b2a19808e415d9c51ebb40c90ef6d X 5acb0c6c80fb4475af0e0f889781a383--b26b2a19808e415d9c51ebb40c90ef6d b26b2a19808e415d9c51ebb40c90ef6d--b7f6eadcaa30464c98c26d480c6cac88 9a116315713e4199bf3bdc4da6488e17 b26b2a19808e415d9c51ebb40c90ef6d--9a116315713e4199bf3bdc4da6488e17 2504d486bcad498b8a8d6d5c4e5f5b21 9a116315713e4199bf3bdc4da6488e17--2504d486bcad498b8a8d6d5c4e5f5b21 1a49e7dc6d354900b0ea51fbb19ab1d4 2504d486bcad498b8a8d6d5c4e5f5b21--1a49e7dc6d354900b0ea51fbb19ab1d4 e2b3c671b6124ac6918c0636323aa849 1a49e7dc6d354900b0ea51fbb19ab1d4--e2b3c671b6124ac6918c0636323aa849 7a74220cfc99474282375c16a78f7f07 e2b3c671b6124ac6918c0636323aa849--7a74220cfc99474282375c16a78f7f07 373113d8472144b5b4fd96657d3af8a2 7a74220cfc99474282375c16a78f7f07--373113d8472144b5b4fd96657d3af8a2 ba4d7677a7b84e45bd103bc92b4f9dfb 373113d8472144b5b4fd96657d3af8a2--ba4d7677a7b84e45bd103bc92b4f9dfb a3c3542f7ad440fe9757204ca8eb5377 ba4d7677a7b84e45bd103bc92b4f9dfb--a3c3542f7ad440fe9757204ca8eb5377 ae25bd7aaa1345fa90e66b4f3094dd87 a3c3542f7ad440fe9757204ca8eb5377--ae25bd7aaa1345fa90e66b4f3094dd87 7149a6d973a548ad9901aa1ba5abe123 ae25bd7aaa1345fa90e66b4f3094dd87--7149a6d973a548ad9901aa1ba5abe123 50b795a3f8784b37afd43b16c875fc6d 7149a6d973a548ad9901aa1ba5abe123--50b795a3f8784b37afd43b16c875fc6d 7a14ecc2b942472183a0f26d69aeb943 50b795a3f8784b37afd43b16c875fc6d--7a14ecc2b942472183a0f26d69aeb943 eb287bc580d3482cb387f58ea5df36f2 7a14ecc2b942472183a0f26d69aeb943--eb287bc580d3482cb387f58ea5df36f2 ee46d9ff8c604fd690f0656ed778bb0c eb287bc580d3482cb387f58ea5df36f2--ee46d9ff8c604fd690f0656ed778bb0c d0189c8ee11943bdacc274e8cfad27bf ee46d9ff8c604fd690f0656ed778bb0c--d0189c8ee11943bdacc274e8cfad27bf 86f238f4217043eca334ccefa4781c47 RX(b0) d0189c8ee11943bdacc274e8cfad27bf--86f238f4217043eca334ccefa4781c47 86f238f4217043eca334ccefa4781c47--39f1ee2252d34c6d8358a317ee44e8d5 9289ac4e87dc433db47545adc444a84f feb6d23ff70c4be781f12bacb9e2754b H 805283dfc4bd43c0ab7098cf4a07ee5d--feb6d23ff70c4be781f12bacb9e2754b 59b9566b9f794d7e89e5d0ae520b9807 3 ec92709309164042a7cbf77d518f55dd feb6d23ff70c4be781f12bacb9e2754b--ec92709309164042a7cbf77d518f55dd 9c14a909601a4ad2a386c7979a004c03 ec92709309164042a7cbf77d518f55dd--9c14a909601a4ad2a386c7979a004c03 50d7ec9e6e9b4c3a9e401a4184f52433 9c14a909601a4ad2a386c7979a004c03--50d7ec9e6e9b4c3a9e401a4184f52433 15266c73a2c643628527645f1dab8c4a X 50d7ec9e6e9b4c3a9e401a4184f52433--15266c73a2c643628527645f1dab8c4a 15266c73a2c643628527645f1dab8c4a--f9449f9aa79a40a7891a1b841608338c aafc6230c52d47cab246ed908be44ea6 RZ(g0) 15266c73a2c643628527645f1dab8c4a--aafc6230c52d47cab246ed908be44ea6 b3be9d2d7af14624bf774a941fa2a84b X aafc6230c52d47cab246ed908be44ea6--b3be9d2d7af14624bf774a941fa2a84b b3be9d2d7af14624bf774a941fa2a84b--cd7c6463632a4b52955dc466fff3a66e f588e8ed1625405b9371efe2f25b1ab7 b3be9d2d7af14624bf774a941fa2a84b--f588e8ed1625405b9371efe2f25b1ab7 717875849542469a81a36f1f76206596 f588e8ed1625405b9371efe2f25b1ab7--717875849542469a81a36f1f76206596 fe83ef9f99844c5bab7bc54f96633e13 717875849542469a81a36f1f76206596--fe83ef9f99844c5bab7bc54f96633e13 130a0a6c71154ad0851298e2387b4e44 X fe83ef9f99844c5bab7bc54f96633e13--130a0a6c71154ad0851298e2387b4e44 130a0a6c71154ad0851298e2387b4e44--ba4d7677a7b84e45bd103bc92b4f9dfb 613a8069fd004af78deaec9348650f28 RZ(g0) 130a0a6c71154ad0851298e2387b4e44--613a8069fd004af78deaec9348650f28 80bebc67c97f472a975d284a1ea9ee85 X 613a8069fd004af78deaec9348650f28--80bebc67c97f472a975d284a1ea9ee85 80bebc67c97f472a975d284a1ea9ee85--ae25bd7aaa1345fa90e66b4f3094dd87 4b4a1020b0b1414c8a1e4efdcdee227d 80bebc67c97f472a975d284a1ea9ee85--4b4a1020b0b1414c8a1e4efdcdee227d 7b167fac6a3a4885a4a1220c6e211e05 4b4a1020b0b1414c8a1e4efdcdee227d--7b167fac6a3a4885a4a1220c6e211e05 058de7681b454ad1adaabb821d37bcac 7b167fac6a3a4885a4a1220c6e211e05--058de7681b454ad1adaabb821d37bcac e8b94667a6584e0b873f5e74bc3a5645 058de7681b454ad1adaabb821d37bcac--e8b94667a6584e0b873f5e74bc3a5645 45fc4dfbc4694c35b18c1c4b735debb5 e8b94667a6584e0b873f5e74bc3a5645--45fc4dfbc4694c35b18c1c4b735debb5 4901f1b1e78347beba04e932755ae410 45fc4dfbc4694c35b18c1c4b735debb5--4901f1b1e78347beba04e932755ae410 00768160fbd94ac390f42c4672f94520 RX(b0) 4901f1b1e78347beba04e932755ae410--00768160fbd94ac390f42c4672f94520 00768160fbd94ac390f42c4672f94520--9289ac4e87dc433db47545adc444a84f 6f3aa45d7461468db307b5eb56ea3256 fc4a0bab54a2445aa32315f3cd94fc65 H 59b9566b9f794d7e89e5d0ae520b9807--fc4a0bab54a2445aa32315f3cd94fc65 b71149a54b7246bf8019bf499e6e0841 fc4a0bab54a2445aa32315f3cd94fc65--b71149a54b7246bf8019bf499e6e0841 6785cf83f174470ab0b976f678f72986 b71149a54b7246bf8019bf499e6e0841--6785cf83f174470ab0b976f678f72986 56fbc0b6256348c9ad981cf8e68da614 6785cf83f174470ab0b976f678f72986--56fbc0b6256348c9ad981cf8e68da614 8b902eca48034f249d3cc09f39521f3c 56fbc0b6256348c9ad981cf8e68da614--8b902eca48034f249d3cc09f39521f3c a7580a32a2b14bae98d39b49b1a10704 8b902eca48034f249d3cc09f39521f3c--a7580a32a2b14bae98d39b49b1a10704 81506eae636a4e95b1ad95864c094e17 a7580a32a2b14bae98d39b49b1a10704--81506eae636a4e95b1ad95864c094e17 f476c7adfe6d40cbb8cdbdf5c8e2b7dc X 81506eae636a4e95b1ad95864c094e17--f476c7adfe6d40cbb8cdbdf5c8e2b7dc f476c7adfe6d40cbb8cdbdf5c8e2b7dc--140dd734f1b44ecf85e906ffc554a41b 8c113ec0ee57459b9a6e845722935c40 RZ(g0) f476c7adfe6d40cbb8cdbdf5c8e2b7dc--8c113ec0ee57459b9a6e845722935c40 c97b215c035d4aefaf0e3eb25dea9c76 X 8c113ec0ee57459b9a6e845722935c40--c97b215c035d4aefaf0e3eb25dea9c76 c97b215c035d4aefaf0e3eb25dea9c76--9f6e6f68015944eda851f745eedd271b a26d99183baa4313a38d26ebe8416bdc c97b215c035d4aefaf0e3eb25dea9c76--a26d99183baa4313a38d26ebe8416bdc d0852d89cfb64e09a02cc58343d1ddde a26d99183baa4313a38d26ebe8416bdc--d0852d89cfb64e09a02cc58343d1ddde ece97bf7e2a14141a55e9b1ab3a6e7a4 d0852d89cfb64e09a02cc58343d1ddde--ece97bf7e2a14141a55e9b1ab3a6e7a4 13e53819889f4c408772b472cc9885fa X ece97bf7e2a14141a55e9b1ab3a6e7a4--13e53819889f4c408772b472cc9885fa 13e53819889f4c408772b472cc9885fa--7149a6d973a548ad9901aa1ba5abe123 5baca6ddbd224a11abb9ddc668339e45 RZ(g0) 13e53819889f4c408772b472cc9885fa--5baca6ddbd224a11abb9ddc668339e45 1c47d8c80e344993b9f7abf276112f0b X 5baca6ddbd224a11abb9ddc668339e45--1c47d8c80e344993b9f7abf276112f0b 1c47d8c80e344993b9f7abf276112f0b--7a14ecc2b942472183a0f26d69aeb943 87d735cf735040d2a595a2801604779d X 1c47d8c80e344993b9f7abf276112f0b--87d735cf735040d2a595a2801604779d 87d735cf735040d2a595a2801604779d--e8b94667a6584e0b873f5e74bc3a5645 5efd9034f29145d59237405a906bded0 RZ(g0) 87d735cf735040d2a595a2801604779d--5efd9034f29145d59237405a906bded0 a57e5596533d47198f54fb2c2c91528e X 5efd9034f29145d59237405a906bded0--a57e5596533d47198f54fb2c2c91528e a57e5596533d47198f54fb2c2c91528e--4901f1b1e78347beba04e932755ae410 8a9e37f5a927443391faf505593cbbfb RX(b0) a57e5596533d47198f54fb2c2c91528e--8a9e37f5a927443391faf505593cbbfb 8a9e37f5a927443391faf505593cbbfb--6f3aa45d7461468db307b5eb56ea3256

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.7470706807026417
MaxCut cost at iteration 20: 3.8378810288930216
MaxCut cost at iteration 30: 3.9424197899236133
MaxCut cost at iteration 40: 3.9981256255766002
MaxCut cost at iteration 50: 3.996470528508214
MaxCut cost at iteration 60: 3.9991374608876606
MaxCut cost at iteration 70: 3.9994678542919555
MaxCut cost at iteration 80: 3.999872558672829
MaxCut cost at iteration 90: 3.9999475834121063
MaxCut cost at iteration 100: 3.9999793311641003

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 2025-03-13T17:14:42.716998 image/svg+xml Matplotlib v3.10.1, https://matplotlib.org/

References


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