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-11-27T09:05:37.860081 image/svg+xml Matplotlib v3.9.2, 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_db30a249d81f4e6fa6c940df399d1a04 mixing cluster_cfc7ecdde542403d9841d7e53368b3a9 cost f8a6e3164a834315a2927ea186629bc3 0 690db2434bfe48c19fb415caee535a0b H f8a6e3164a834315a2927ea186629bc3--690db2434bfe48c19fb415caee535a0b a81643f945a945d49736814ea95e50e3 1 a1c028c81f6b4fe3a676fdd718bc42fb 690db2434bfe48c19fb415caee535a0b--a1c028c81f6b4fe3a676fdd718bc42fb acc3a06a688e4b0384e6de8cef211fb0 a1c028c81f6b4fe3a676fdd718bc42fb--acc3a06a688e4b0384e6de8cef211fb0 7f794ecea58e45638f946b74fc3367d3 acc3a06a688e4b0384e6de8cef211fb0--7f794ecea58e45638f946b74fc3367d3 553b752e9ead49e0a9f896ea5e19485c 7f794ecea58e45638f946b74fc3367d3--553b752e9ead49e0a9f896ea5e19485c a2fe18dc968c4f7a90e5fb13c859ec0d 553b752e9ead49e0a9f896ea5e19485c--a2fe18dc968c4f7a90e5fb13c859ec0d d260dd4c83e8437a92c390cdcc85a1f7 a2fe18dc968c4f7a90e5fb13c859ec0d--d260dd4c83e8437a92c390cdcc85a1f7 0b7355480e85433da6ae6e197f10503a d260dd4c83e8437a92c390cdcc85a1f7--0b7355480e85433da6ae6e197f10503a 5897a1b9e5994061aad121a5c73191b8 0b7355480e85433da6ae6e197f10503a--5897a1b9e5994061aad121a5c73191b8 3b20acb07f8644fdb3730c388b6c14a2 5897a1b9e5994061aad121a5c73191b8--3b20acb07f8644fdb3730c388b6c14a2 f594d2b267974dc1a3f2bf40ec671146 3b20acb07f8644fdb3730c388b6c14a2--f594d2b267974dc1a3f2bf40ec671146 52b35c9eeed44daeb713cd8554db73c0 f594d2b267974dc1a3f2bf40ec671146--52b35c9eeed44daeb713cd8554db73c0 547cfeb8433046e496d5254b203fdceb 52b35c9eeed44daeb713cd8554db73c0--547cfeb8433046e496d5254b203fdceb f48ce20b2bcf4e1b8c85aa6be93dc217 547cfeb8433046e496d5254b203fdceb--f48ce20b2bcf4e1b8c85aa6be93dc217 415059a80aee465dbe679df0b55f09a0 f48ce20b2bcf4e1b8c85aa6be93dc217--415059a80aee465dbe679df0b55f09a0 833c956c472e4f76a1a59b865f8128c9 415059a80aee465dbe679df0b55f09a0--833c956c472e4f76a1a59b865f8128c9 ef5e866a6b5f4e479cc7dd3fb5af1ce8 833c956c472e4f76a1a59b865f8128c9--ef5e866a6b5f4e479cc7dd3fb5af1ce8 2c90b6927c884034a07db95b0f7824e5 ef5e866a6b5f4e479cc7dd3fb5af1ce8--2c90b6927c884034a07db95b0f7824e5 51ca2fd6e479445da7dcbf4e49d0c684 2c90b6927c884034a07db95b0f7824e5--51ca2fd6e479445da7dcbf4e49d0c684 9b6ca52f57fd4d72b1b7ffca989bf22e RX(b0) 51ca2fd6e479445da7dcbf4e49d0c684--9b6ca52f57fd4d72b1b7ffca989bf22e ac5c9fbdb4ff40d8b50c744da9c56426 9b6ca52f57fd4d72b1b7ffca989bf22e--ac5c9fbdb4ff40d8b50c744da9c56426 854d0e838a174ec8a4cffdc0508b3fe1 c4bf1b57005e4b9d96244f4f6b5b62de H a81643f945a945d49736814ea95e50e3--c4bf1b57005e4b9d96244f4f6b5b62de a6f064378b164be18387f0c369686e86 2 8d51837c731b4f4bbb618970efdf257c X c4bf1b57005e4b9d96244f4f6b5b62de--8d51837c731b4f4bbb618970efdf257c 8d51837c731b4f4bbb618970efdf257c--a1c028c81f6b4fe3a676fdd718bc42fb ad8db260d56943b1b97c4a8affc57cf7 RZ(g0) 8d51837c731b4f4bbb618970efdf257c--ad8db260d56943b1b97c4a8affc57cf7 cae8ad3802e44e2cb01a7d9cdd53396b X ad8db260d56943b1b97c4a8affc57cf7--cae8ad3802e44e2cb01a7d9cdd53396b cae8ad3802e44e2cb01a7d9cdd53396b--7f794ecea58e45638f946b74fc3367d3 db3b918856cd4cf4b6a00f5746ad8274 cae8ad3802e44e2cb01a7d9cdd53396b--db3b918856cd4cf4b6a00f5746ad8274 9a5068f134d741098996b60062196e70 db3b918856cd4cf4b6a00f5746ad8274--9a5068f134d741098996b60062196e70 17c28aea73e9430799a2927694a3b253 9a5068f134d741098996b60062196e70--17c28aea73e9430799a2927694a3b253 a74bb22e141b40808022e91338fb3c8f 17c28aea73e9430799a2927694a3b253--a74bb22e141b40808022e91338fb3c8f 280d0414338e491a82f7947871360e08 a74bb22e141b40808022e91338fb3c8f--280d0414338e491a82f7947871360e08 4b84ebc7a97b45dfb32366defcdf4dcf 280d0414338e491a82f7947871360e08--4b84ebc7a97b45dfb32366defcdf4dcf ff95322dc60140aba0c74eb5b4d8054c 4b84ebc7a97b45dfb32366defcdf4dcf--ff95322dc60140aba0c74eb5b4d8054c 4d0369672aca4d9c9226cdaf3b8161f3 ff95322dc60140aba0c74eb5b4d8054c--4d0369672aca4d9c9226cdaf3b8161f3 c6efe02dbf404a41ae5a8abe90451aa8 4d0369672aca4d9c9226cdaf3b8161f3--c6efe02dbf404a41ae5a8abe90451aa8 f6854e66a3e241adba5a428d6d0c60ef c6efe02dbf404a41ae5a8abe90451aa8--f6854e66a3e241adba5a428d6d0c60ef 39b2552211134efc897749384ae03359 f6854e66a3e241adba5a428d6d0c60ef--39b2552211134efc897749384ae03359 e5a1102b44f044f4b813883a237c58d1 39b2552211134efc897749384ae03359--e5a1102b44f044f4b813883a237c58d1 11b9a9df763741858334f4516a462f9a e5a1102b44f044f4b813883a237c58d1--11b9a9df763741858334f4516a462f9a aca87b84209a49b0baf681205e6a2dbd 11b9a9df763741858334f4516a462f9a--aca87b84209a49b0baf681205e6a2dbd 0ec8b52bf8c84740a25189e01ffd03ee aca87b84209a49b0baf681205e6a2dbd--0ec8b52bf8c84740a25189e01ffd03ee 5b45e79560424d5db0368e9a0e9df355 RX(b0) 0ec8b52bf8c84740a25189e01ffd03ee--5b45e79560424d5db0368e9a0e9df355 5b45e79560424d5db0368e9a0e9df355--854d0e838a174ec8a4cffdc0508b3fe1 2dce65ae5cf54566a932a3fb6fbd3d5b e15b75baae1b49029eedc9ec4cf1e027 H a6f064378b164be18387f0c369686e86--e15b75baae1b49029eedc9ec4cf1e027 75afe2a34e4546b482793c589b838b75 3 9ad6c7f664744a40bb0f83773215b665 e15b75baae1b49029eedc9ec4cf1e027--9ad6c7f664744a40bb0f83773215b665 17aea4bdd60e4d24b9486b81af4c6b10 9ad6c7f664744a40bb0f83773215b665--17aea4bdd60e4d24b9486b81af4c6b10 c7f2885d7dee49809a524e7d028490ab 17aea4bdd60e4d24b9486b81af4c6b10--c7f2885d7dee49809a524e7d028490ab e18b2250dea04c96bb33e7d5c0cfa37b X c7f2885d7dee49809a524e7d028490ab--e18b2250dea04c96bb33e7d5c0cfa37b e18b2250dea04c96bb33e7d5c0cfa37b--553b752e9ead49e0a9f896ea5e19485c 66e82a829df6428d9718758c0a2c9470 RZ(g0) e18b2250dea04c96bb33e7d5c0cfa37b--66e82a829df6428d9718758c0a2c9470 b7969f0c0035491ca6b1c04ec082b681 X 66e82a829df6428d9718758c0a2c9470--b7969f0c0035491ca6b1c04ec082b681 b7969f0c0035491ca6b1c04ec082b681--d260dd4c83e8437a92c390cdcc85a1f7 91be9c2320dc4ba68332101d2b32ca25 b7969f0c0035491ca6b1c04ec082b681--91be9c2320dc4ba68332101d2b32ca25 21dcaf7d1fb0416897d45da3a83c14f8 91be9c2320dc4ba68332101d2b32ca25--21dcaf7d1fb0416897d45da3a83c14f8 7d9bb8cf94a447398fcebcbe470de38a 21dcaf7d1fb0416897d45da3a83c14f8--7d9bb8cf94a447398fcebcbe470de38a 10edf8507fd64f2782ad90b079a4afcf X 7d9bb8cf94a447398fcebcbe470de38a--10edf8507fd64f2782ad90b079a4afcf 10edf8507fd64f2782ad90b079a4afcf--ff95322dc60140aba0c74eb5b4d8054c 8bc317db80984ff3b1ba50fa4975b0cd RZ(g0) 10edf8507fd64f2782ad90b079a4afcf--8bc317db80984ff3b1ba50fa4975b0cd 2cc04d7d94f34a5aa79d7cc79436ece2 X 8bc317db80984ff3b1ba50fa4975b0cd--2cc04d7d94f34a5aa79d7cc79436ece2 2cc04d7d94f34a5aa79d7cc79436ece2--c6efe02dbf404a41ae5a8abe90451aa8 9cc0702005e3472fa12843d462cea32e 2cc04d7d94f34a5aa79d7cc79436ece2--9cc0702005e3472fa12843d462cea32e 3e7686d5beac47339be93867d4def345 9cc0702005e3472fa12843d462cea32e--3e7686d5beac47339be93867d4def345 c338c79012424b2d85c98e866d78f1f9 3e7686d5beac47339be93867d4def345--c338c79012424b2d85c98e866d78f1f9 fd0a689f869e4dcda83617592fc76999 c338c79012424b2d85c98e866d78f1f9--fd0a689f869e4dcda83617592fc76999 995c27ddc69340a19628b0128fd594dd fd0a689f869e4dcda83617592fc76999--995c27ddc69340a19628b0128fd594dd 2e5d788efe3841468da43c541adf9104 995c27ddc69340a19628b0128fd594dd--2e5d788efe3841468da43c541adf9104 c2faf8a5644741ac988b186476e80d40 RX(b0) 2e5d788efe3841468da43c541adf9104--c2faf8a5644741ac988b186476e80d40 c2faf8a5644741ac988b186476e80d40--2dce65ae5cf54566a932a3fb6fbd3d5b 0764004617c441999b79838f4b4e29e0 d36d3708d3dc4246a85a46e5a24aa8c3 H 75afe2a34e4546b482793c589b838b75--d36d3708d3dc4246a85a46e5a24aa8c3 6c333fbdb39d4047ab030dcf85a0b1f5 d36d3708d3dc4246a85a46e5a24aa8c3--6c333fbdb39d4047ab030dcf85a0b1f5 82f3cd9cca3846ddace447239d02368a 6c333fbdb39d4047ab030dcf85a0b1f5--82f3cd9cca3846ddace447239d02368a fab3e6135125409188a713bc826f3802 82f3cd9cca3846ddace447239d02368a--fab3e6135125409188a713bc826f3802 06d7007e320f4e48a7b841017e2e9c98 fab3e6135125409188a713bc826f3802--06d7007e320f4e48a7b841017e2e9c98 9796fbea99d64fe88883fd23f20946c0 06d7007e320f4e48a7b841017e2e9c98--9796fbea99d64fe88883fd23f20946c0 cd6d610e5234404aa382c1aa58afd852 9796fbea99d64fe88883fd23f20946c0--cd6d610e5234404aa382c1aa58afd852 e38ad25eb48c4cd494d0aeac8b8e29b4 X cd6d610e5234404aa382c1aa58afd852--e38ad25eb48c4cd494d0aeac8b8e29b4 e38ad25eb48c4cd494d0aeac8b8e29b4--0b7355480e85433da6ae6e197f10503a 982f814fcdff439a982c5396e75ac0f7 RZ(g0) e38ad25eb48c4cd494d0aeac8b8e29b4--982f814fcdff439a982c5396e75ac0f7 c5ace6d3c4054d959dc2892d7219ae2b X 982f814fcdff439a982c5396e75ac0f7--c5ace6d3c4054d959dc2892d7219ae2b c5ace6d3c4054d959dc2892d7219ae2b--3b20acb07f8644fdb3730c388b6c14a2 0f57feaddab647b6896d6129b9a35cd1 c5ace6d3c4054d959dc2892d7219ae2b--0f57feaddab647b6896d6129b9a35cd1 1552a7c0608640e9aa1a2b6f859b8a11 0f57feaddab647b6896d6129b9a35cd1--1552a7c0608640e9aa1a2b6f859b8a11 b016229a765440959ffb3b906ecc4d3f 1552a7c0608640e9aa1a2b6f859b8a11--b016229a765440959ffb3b906ecc4d3f 4b771b5032a94c47beb3edd513491fb0 X b016229a765440959ffb3b906ecc4d3f--4b771b5032a94c47beb3edd513491fb0 4b771b5032a94c47beb3edd513491fb0--f6854e66a3e241adba5a428d6d0c60ef ea5ffeefce05435fbe28b3460805d1ed RZ(g0) 4b771b5032a94c47beb3edd513491fb0--ea5ffeefce05435fbe28b3460805d1ed 985a758166d84047a7fb846a91d2742f X ea5ffeefce05435fbe28b3460805d1ed--985a758166d84047a7fb846a91d2742f 985a758166d84047a7fb846a91d2742f--e5a1102b44f044f4b813883a237c58d1 a92a375a082644f688964e6f8c11a875 X 985a758166d84047a7fb846a91d2742f--a92a375a082644f688964e6f8c11a875 a92a375a082644f688964e6f8c11a875--fd0a689f869e4dcda83617592fc76999 7cc4a3f10e4149e9a9c6fe9c407d2c39 RZ(g0) a92a375a082644f688964e6f8c11a875--7cc4a3f10e4149e9a9c6fe9c407d2c39 395ce1fba4764184a639300b6ec0a5ba X 7cc4a3f10e4149e9a9c6fe9c407d2c39--395ce1fba4764184a639300b6ec0a5ba 395ce1fba4764184a639300b6ec0a5ba--2e5d788efe3841468da43c541adf9104 3144b0bd08a44f6d8869975f2f606102 RX(b0) 395ce1fba4764184a639300b6ec0a5ba--3144b0bd08a44f6d8869975f2f606102 3144b0bd08a44f6d8869975f2f606102--0764004617c441999b79838f4b4e29e0

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 2024-11-27T09:05:42.554346 image/svg+xml Matplotlib v3.9.2, https://matplotlib.org/

References


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