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-02-22T14:06:32.751732 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_74f268e1790a406db8df32df2142300f mixing cluster_56c1dff7a96a4c87ad217121d92d0ae7 cost f1defa05516e452f8fbd613883868956 0 360622b25042489b95532dda5dd89f24 H f1defa05516e452f8fbd613883868956--360622b25042489b95532dda5dd89f24 00e23d494a3c44788d72060e5b7c5897 1 0a4d6c6289ec4c0ca9fe7222146d47de 360622b25042489b95532dda5dd89f24--0a4d6c6289ec4c0ca9fe7222146d47de ac5f0ddfc3b740bf90793ae97a79dfab 0a4d6c6289ec4c0ca9fe7222146d47de--ac5f0ddfc3b740bf90793ae97a79dfab c2d54a2896504b92a465d35bc966dd9b ac5f0ddfc3b740bf90793ae97a79dfab--c2d54a2896504b92a465d35bc966dd9b abade9a3edc34ac99ff8b5f953f5aacf c2d54a2896504b92a465d35bc966dd9b--abade9a3edc34ac99ff8b5f953f5aacf 8729f7964d30419ca473c38aefb3f2af abade9a3edc34ac99ff8b5f953f5aacf--8729f7964d30419ca473c38aefb3f2af 08d46490a8514ba7a163c95121c3f732 8729f7964d30419ca473c38aefb3f2af--08d46490a8514ba7a163c95121c3f732 b623d7266a6049699ba3b1139dab0b3e 08d46490a8514ba7a163c95121c3f732--b623d7266a6049699ba3b1139dab0b3e 12533760487a47dcb51488b8d86182e9 b623d7266a6049699ba3b1139dab0b3e--12533760487a47dcb51488b8d86182e9 5b37050ef8eb4bf18fa3c256be6599a9 12533760487a47dcb51488b8d86182e9--5b37050ef8eb4bf18fa3c256be6599a9 26796c82c04d498ba838c65579c49cb1 5b37050ef8eb4bf18fa3c256be6599a9--26796c82c04d498ba838c65579c49cb1 d5c52e836b084e98b7c36a0cf5a07d42 26796c82c04d498ba838c65579c49cb1--d5c52e836b084e98b7c36a0cf5a07d42 a8564d1e8df445c39dd99644cde69c5b d5c52e836b084e98b7c36a0cf5a07d42--a8564d1e8df445c39dd99644cde69c5b 0a07e9d768644a298b9b84dd8d3d3185 a8564d1e8df445c39dd99644cde69c5b--0a07e9d768644a298b9b84dd8d3d3185 e22b69b91f894ed0b7ebb09b6df60a90 0a07e9d768644a298b9b84dd8d3d3185--e22b69b91f894ed0b7ebb09b6df60a90 e52de8b2b3c24ec5ad9839e4c5584aff e22b69b91f894ed0b7ebb09b6df60a90--e52de8b2b3c24ec5ad9839e4c5584aff bda41ce745c243489e098d9dc70cc9ff e52de8b2b3c24ec5ad9839e4c5584aff--bda41ce745c243489e098d9dc70cc9ff 5b67b6be93fd4b289b48025b129e4f36 bda41ce745c243489e098d9dc70cc9ff--5b67b6be93fd4b289b48025b129e4f36 42786952174b4e10b1e914a45446b791 5b67b6be93fd4b289b48025b129e4f36--42786952174b4e10b1e914a45446b791 814734a574fa4ee0baeed06b9c132348 RX(b0) 42786952174b4e10b1e914a45446b791--814734a574fa4ee0baeed06b9c132348 fd290ccc17c7456fb39745e851dc7098 814734a574fa4ee0baeed06b9c132348--fd290ccc17c7456fb39745e851dc7098 47c03956b3364fb7bcbdfcc80d48b681 9b5ace233bfa4a7294e0a56c1f35e07b H 00e23d494a3c44788d72060e5b7c5897--9b5ace233bfa4a7294e0a56c1f35e07b c51ce7ad927b422baebe1158f6201412 2 0b7d8d3c2b48448daef3b08323b43040 X 9b5ace233bfa4a7294e0a56c1f35e07b--0b7d8d3c2b48448daef3b08323b43040 0b7d8d3c2b48448daef3b08323b43040--0a4d6c6289ec4c0ca9fe7222146d47de 34463305d80549e18600d5f84daad2cc RZ(g0) 0b7d8d3c2b48448daef3b08323b43040--34463305d80549e18600d5f84daad2cc 2bd2623b49374df0aa66bf0a24667f82 X 34463305d80549e18600d5f84daad2cc--2bd2623b49374df0aa66bf0a24667f82 2bd2623b49374df0aa66bf0a24667f82--c2d54a2896504b92a465d35bc966dd9b 38a2fa7580854e58a927e1fd46b612a6 2bd2623b49374df0aa66bf0a24667f82--38a2fa7580854e58a927e1fd46b612a6 05d4910c868a47d7b3244fbfd23c4b5c 38a2fa7580854e58a927e1fd46b612a6--05d4910c868a47d7b3244fbfd23c4b5c 68f9736a1f1f46a6930f1373215a83d8 05d4910c868a47d7b3244fbfd23c4b5c--68f9736a1f1f46a6930f1373215a83d8 f48454a8d81f4a559e99665a926008d1 68f9736a1f1f46a6930f1373215a83d8--f48454a8d81f4a559e99665a926008d1 7a988aa4dda141fdb1c295bb75989e39 f48454a8d81f4a559e99665a926008d1--7a988aa4dda141fdb1c295bb75989e39 3a2327da291b4a35adfad68413e1f434 7a988aa4dda141fdb1c295bb75989e39--3a2327da291b4a35adfad68413e1f434 4e633fea21e8417fb5f1d57cafd4c4e7 3a2327da291b4a35adfad68413e1f434--4e633fea21e8417fb5f1d57cafd4c4e7 6a692f79188748978189d77db7455838 4e633fea21e8417fb5f1d57cafd4c4e7--6a692f79188748978189d77db7455838 6e39a3440a244cd8bd07cc32c2e1ff0f 6a692f79188748978189d77db7455838--6e39a3440a244cd8bd07cc32c2e1ff0f ac7ad0d6552340a18ad5ab5e290e550c 6e39a3440a244cd8bd07cc32c2e1ff0f--ac7ad0d6552340a18ad5ab5e290e550c b0de9bd8102a493398210b36bbd0f5e8 ac7ad0d6552340a18ad5ab5e290e550c--b0de9bd8102a493398210b36bbd0f5e8 9d101947a07543fca7df6a0bfbd76a64 b0de9bd8102a493398210b36bbd0f5e8--9d101947a07543fca7df6a0bfbd76a64 616a4ccdea194509861d809b02420b5d 9d101947a07543fca7df6a0bfbd76a64--616a4ccdea194509861d809b02420b5d 33e51503e423404f9c7c22545214ee30 616a4ccdea194509861d809b02420b5d--33e51503e423404f9c7c22545214ee30 816b276cd91241548e9eaeeb23cb3c14 33e51503e423404f9c7c22545214ee30--816b276cd91241548e9eaeeb23cb3c14 a9842eb75cfd4cfe93e770bd8669b6fd RX(b0) 816b276cd91241548e9eaeeb23cb3c14--a9842eb75cfd4cfe93e770bd8669b6fd a9842eb75cfd4cfe93e770bd8669b6fd--47c03956b3364fb7bcbdfcc80d48b681 c2ce0f6df3c84703bc77f099376e3c7f 85a4269bee4644b49630e36cdc7209e3 H c51ce7ad927b422baebe1158f6201412--85a4269bee4644b49630e36cdc7209e3 1289d7c2c9374d738c8d83c217506b06 3 7e40c76d0afb4f8e8f505291a81c055e 85a4269bee4644b49630e36cdc7209e3--7e40c76d0afb4f8e8f505291a81c055e 909a61092d684a65b88513df172840cc 7e40c76d0afb4f8e8f505291a81c055e--909a61092d684a65b88513df172840cc 453b93450f264d40840b7fac197001ce 909a61092d684a65b88513df172840cc--453b93450f264d40840b7fac197001ce 4fb0034fb40a492b83ea9bd5254ec3ab X 453b93450f264d40840b7fac197001ce--4fb0034fb40a492b83ea9bd5254ec3ab 4fb0034fb40a492b83ea9bd5254ec3ab--abade9a3edc34ac99ff8b5f953f5aacf 499fae5f17484ee4a6cda44c7040737e RZ(g0) 4fb0034fb40a492b83ea9bd5254ec3ab--499fae5f17484ee4a6cda44c7040737e e3a548b22294444ea8e5fc9050ce7dbb X 499fae5f17484ee4a6cda44c7040737e--e3a548b22294444ea8e5fc9050ce7dbb e3a548b22294444ea8e5fc9050ce7dbb--08d46490a8514ba7a163c95121c3f732 12c57d709d2e4bfdb86e1d4ce2e3a2eb e3a548b22294444ea8e5fc9050ce7dbb--12c57d709d2e4bfdb86e1d4ce2e3a2eb ba632fe718c241a19dd05b602fc2a9e2 12c57d709d2e4bfdb86e1d4ce2e3a2eb--ba632fe718c241a19dd05b602fc2a9e2 27602a1eef8d47e6b3cd013d4b74ebfa ba632fe718c241a19dd05b602fc2a9e2--27602a1eef8d47e6b3cd013d4b74ebfa e795712e7e7b42ddba59b21a8526cc16 X 27602a1eef8d47e6b3cd013d4b74ebfa--e795712e7e7b42ddba59b21a8526cc16 e795712e7e7b42ddba59b21a8526cc16--4e633fea21e8417fb5f1d57cafd4c4e7 405737b5143b4d60a7e384a65357e802 RZ(g0) e795712e7e7b42ddba59b21a8526cc16--405737b5143b4d60a7e384a65357e802 7811301b3f5d4555971b3d048cc71ac3 X 405737b5143b4d60a7e384a65357e802--7811301b3f5d4555971b3d048cc71ac3 7811301b3f5d4555971b3d048cc71ac3--6e39a3440a244cd8bd07cc32c2e1ff0f b55e1f4cee8e45bbb03de759c6050b71 7811301b3f5d4555971b3d048cc71ac3--b55e1f4cee8e45bbb03de759c6050b71 a7d1d410cf2646eeb6185ebeb86e2efb b55e1f4cee8e45bbb03de759c6050b71--a7d1d410cf2646eeb6185ebeb86e2efb a880f43ed4e84c0096de13810b3792fd a7d1d410cf2646eeb6185ebeb86e2efb--a880f43ed4e84c0096de13810b3792fd bd3b647a75c7447aa052f8f46a2bd3bc a880f43ed4e84c0096de13810b3792fd--bd3b647a75c7447aa052f8f46a2bd3bc dfd3da93398f46cdb1f0acdd7707a28b bd3b647a75c7447aa052f8f46a2bd3bc--dfd3da93398f46cdb1f0acdd7707a28b 631525c9e282471e86aa2c623708a1d9 dfd3da93398f46cdb1f0acdd7707a28b--631525c9e282471e86aa2c623708a1d9 38e0cfe7f9764f0ea900304fad87aae6 RX(b0) 631525c9e282471e86aa2c623708a1d9--38e0cfe7f9764f0ea900304fad87aae6 38e0cfe7f9764f0ea900304fad87aae6--c2ce0f6df3c84703bc77f099376e3c7f 12d9f4dea6434d6c9252711045d60ced 9c7e46d0846e4a689c6c51022f4b4220 H 1289d7c2c9374d738c8d83c217506b06--9c7e46d0846e4a689c6c51022f4b4220 2bccdb810dcc4ff2be8bbfa1e639f189 9c7e46d0846e4a689c6c51022f4b4220--2bccdb810dcc4ff2be8bbfa1e639f189 cdf23df14696457c893b22ccb8a145b3 2bccdb810dcc4ff2be8bbfa1e639f189--cdf23df14696457c893b22ccb8a145b3 f685ab2a15d74eb387452803da60ee98 cdf23df14696457c893b22ccb8a145b3--f685ab2a15d74eb387452803da60ee98 8c3c9f6e875849958d5ebad39439203d f685ab2a15d74eb387452803da60ee98--8c3c9f6e875849958d5ebad39439203d 16bf1b132db74a07b5ca07cf75350631 8c3c9f6e875849958d5ebad39439203d--16bf1b132db74a07b5ca07cf75350631 c9281697d4c94ff89e189b4f0f49e5ef 16bf1b132db74a07b5ca07cf75350631--c9281697d4c94ff89e189b4f0f49e5ef f44e3ff07b0a4efbb918210b2b22fd0c X c9281697d4c94ff89e189b4f0f49e5ef--f44e3ff07b0a4efbb918210b2b22fd0c f44e3ff07b0a4efbb918210b2b22fd0c--b623d7266a6049699ba3b1139dab0b3e 07cefc982c014e568eff6a644e19ad51 RZ(g0) f44e3ff07b0a4efbb918210b2b22fd0c--07cefc982c014e568eff6a644e19ad51 0c0493bff3e540d1be2c25a4fab49cdb X 07cefc982c014e568eff6a644e19ad51--0c0493bff3e540d1be2c25a4fab49cdb 0c0493bff3e540d1be2c25a4fab49cdb--5b37050ef8eb4bf18fa3c256be6599a9 862c3e4578d743fdb295134e1762b785 0c0493bff3e540d1be2c25a4fab49cdb--862c3e4578d743fdb295134e1762b785 156ab0c280384583a77116b5ed635926 862c3e4578d743fdb295134e1762b785--156ab0c280384583a77116b5ed635926 7000692118a84e2baf04df07851a542b 156ab0c280384583a77116b5ed635926--7000692118a84e2baf04df07851a542b 4d018b76280842ef9532ed4370fad5b2 X 7000692118a84e2baf04df07851a542b--4d018b76280842ef9532ed4370fad5b2 4d018b76280842ef9532ed4370fad5b2--ac7ad0d6552340a18ad5ab5e290e550c 46b234bf2e8f4a06a9a5f2c583a4da55 RZ(g0) 4d018b76280842ef9532ed4370fad5b2--46b234bf2e8f4a06a9a5f2c583a4da55 5c28a43e804c48c78145b577350631a0 X 46b234bf2e8f4a06a9a5f2c583a4da55--5c28a43e804c48c78145b577350631a0 5c28a43e804c48c78145b577350631a0--9d101947a07543fca7df6a0bfbd76a64 44c53ad952ae472ebc3e66e280496d5a X 5c28a43e804c48c78145b577350631a0--44c53ad952ae472ebc3e66e280496d5a 44c53ad952ae472ebc3e66e280496d5a--bd3b647a75c7447aa052f8f46a2bd3bc 57c3864813624f7eb4aa463c02dc6465 RZ(g0) 44c53ad952ae472ebc3e66e280496d5a--57c3864813624f7eb4aa463c02dc6465 1c267abdd6244ff09a4d03dd759be94f X 57c3864813624f7eb4aa463c02dc6465--1c267abdd6244ff09a4d03dd759be94f 1c267abdd6244ff09a4d03dd759be94f--631525c9e282471e86aa2c623708a1d9 ae0ffc6b5b304431a91773bc3bfdb6e8 RX(b0) 1c267abdd6244ff09a4d03dd759be94f--ae0ffc6b5b304431a91773bc3bfdb6e8 ae0ffc6b5b304431a91773bc3bfdb6e8--12d9f4dea6434d6c9252711045d60ced

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-02-22T14:06:36.870365 image/svg+xml Matplotlib v3.7.5, https://matplotlib.org/

References


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