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-01-28T16:51:39.150490 image/svg+xml Matplotlib v3.10.0, 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_5bc9658999f84645bce59b86c9ab6623 mixing cluster_eadf06f3b2974c45af56c8969c311fb7 cost 77a27556eee8442e889b03646011f3d5 0 f94aa900a318401db636ba66b9162e3a H 77a27556eee8442e889b03646011f3d5--f94aa900a318401db636ba66b9162e3a 7f62f2c2a2ea464a8be5060028acd86a 1 297b9f01b8bf48f585ac0bd828318166 f94aa900a318401db636ba66b9162e3a--297b9f01b8bf48f585ac0bd828318166 81b72c5596e34180992fd52679ba5f25 297b9f01b8bf48f585ac0bd828318166--81b72c5596e34180992fd52679ba5f25 1a0c5f25c7d64851b567014bc8244d79 81b72c5596e34180992fd52679ba5f25--1a0c5f25c7d64851b567014bc8244d79 d41fbab942634f24b769efb0227b1739 1a0c5f25c7d64851b567014bc8244d79--d41fbab942634f24b769efb0227b1739 ce0748cdf432468a89e5fec727853982 d41fbab942634f24b769efb0227b1739--ce0748cdf432468a89e5fec727853982 a3fcdb72fb384ee6898b68c30938e2f2 ce0748cdf432468a89e5fec727853982--a3fcdb72fb384ee6898b68c30938e2f2 712b4fcda81f470bb2cda7b47ae477a7 a3fcdb72fb384ee6898b68c30938e2f2--712b4fcda81f470bb2cda7b47ae477a7 7273c53d77094a109e695598adfd4af3 712b4fcda81f470bb2cda7b47ae477a7--7273c53d77094a109e695598adfd4af3 4b93345264b14daaa09d88654a46f7a3 7273c53d77094a109e695598adfd4af3--4b93345264b14daaa09d88654a46f7a3 dcaab6f32e74471a9faa787c5d538684 4b93345264b14daaa09d88654a46f7a3--dcaab6f32e74471a9faa787c5d538684 d0612864240b495ebdc8068d66d9eade dcaab6f32e74471a9faa787c5d538684--d0612864240b495ebdc8068d66d9eade c639a43c2d7d4a5ebdaba087bc104e48 d0612864240b495ebdc8068d66d9eade--c639a43c2d7d4a5ebdaba087bc104e48 83633dff9f5b44fa9de6d30268767422 c639a43c2d7d4a5ebdaba087bc104e48--83633dff9f5b44fa9de6d30268767422 b1f80d7500814a0890cc3d2b65c8e0f8 83633dff9f5b44fa9de6d30268767422--b1f80d7500814a0890cc3d2b65c8e0f8 4304ba7ce76a4c05a87d17a49b2ee9fb b1f80d7500814a0890cc3d2b65c8e0f8--4304ba7ce76a4c05a87d17a49b2ee9fb 8c4156aceaa9446aa1e4710f4d1a6d2a 4304ba7ce76a4c05a87d17a49b2ee9fb--8c4156aceaa9446aa1e4710f4d1a6d2a 8f831d0a5b2f4660916fd08a5b443153 8c4156aceaa9446aa1e4710f4d1a6d2a--8f831d0a5b2f4660916fd08a5b443153 488713325b1f4cc8b4e85e5ee6c392d0 8f831d0a5b2f4660916fd08a5b443153--488713325b1f4cc8b4e85e5ee6c392d0 f27625d9396a4fb996316aabac43f8c7 RX(b0) 488713325b1f4cc8b4e85e5ee6c392d0--f27625d9396a4fb996316aabac43f8c7 6b4d4d5abef041809612d1102c211d5c f27625d9396a4fb996316aabac43f8c7--6b4d4d5abef041809612d1102c211d5c b84a7d5cd835408880009e9459bb61d8 ab38a7570d8b4ceaae8c4864d5c9b309 H 7f62f2c2a2ea464a8be5060028acd86a--ab38a7570d8b4ceaae8c4864d5c9b309 ed8e8c2f3c4f4e62b6227b71a59473c8 2 0078e6e2a845451baecd6f59351705fa X ab38a7570d8b4ceaae8c4864d5c9b309--0078e6e2a845451baecd6f59351705fa 0078e6e2a845451baecd6f59351705fa--297b9f01b8bf48f585ac0bd828318166 d4f49ea0c22a47c488c5308896bd012a RZ(g0) 0078e6e2a845451baecd6f59351705fa--d4f49ea0c22a47c488c5308896bd012a 9a595e5d1f974d179988777a8e519ead X d4f49ea0c22a47c488c5308896bd012a--9a595e5d1f974d179988777a8e519ead 9a595e5d1f974d179988777a8e519ead--1a0c5f25c7d64851b567014bc8244d79 dc42410d0ad340f28c7340948a651ce3 9a595e5d1f974d179988777a8e519ead--dc42410d0ad340f28c7340948a651ce3 dc1e267d18154d34954b8c5a9332100c dc42410d0ad340f28c7340948a651ce3--dc1e267d18154d34954b8c5a9332100c 8e1ee6ade074406e9f70831545b5f829 dc1e267d18154d34954b8c5a9332100c--8e1ee6ade074406e9f70831545b5f829 ecce38284ae54acfa4265e62cbf0e560 8e1ee6ade074406e9f70831545b5f829--ecce38284ae54acfa4265e62cbf0e560 0699b0bc8e924eef8adfba42983d2811 ecce38284ae54acfa4265e62cbf0e560--0699b0bc8e924eef8adfba42983d2811 e052b08b8ae64cf99f047cade33be40e 0699b0bc8e924eef8adfba42983d2811--e052b08b8ae64cf99f047cade33be40e c32f3ec55c5a444e82259c6a94242f22 e052b08b8ae64cf99f047cade33be40e--c32f3ec55c5a444e82259c6a94242f22 093257c8dba94142b446aeeabac9b4ef c32f3ec55c5a444e82259c6a94242f22--093257c8dba94142b446aeeabac9b4ef d7c3e4bc5fb94185bd2c2d7e84538233 093257c8dba94142b446aeeabac9b4ef--d7c3e4bc5fb94185bd2c2d7e84538233 c5ee4d09218145808053e7ec597e7602 d7c3e4bc5fb94185bd2c2d7e84538233--c5ee4d09218145808053e7ec597e7602 e05958afe94b491486fc298896bdfc4f c5ee4d09218145808053e7ec597e7602--e05958afe94b491486fc298896bdfc4f a524035e65384d72a730bcd4f31b1a37 e05958afe94b491486fc298896bdfc4f--a524035e65384d72a730bcd4f31b1a37 eb4000a4b301428489992430aee0b1c9 a524035e65384d72a730bcd4f31b1a37--eb4000a4b301428489992430aee0b1c9 30b15383d6004a65982369c7ddbb215b eb4000a4b301428489992430aee0b1c9--30b15383d6004a65982369c7ddbb215b 7ab8c22cdc0b465196e4bfc42a04097b 30b15383d6004a65982369c7ddbb215b--7ab8c22cdc0b465196e4bfc42a04097b 658970ab1f6d497d9c069ece589327ec RX(b0) 7ab8c22cdc0b465196e4bfc42a04097b--658970ab1f6d497d9c069ece589327ec 658970ab1f6d497d9c069ece589327ec--b84a7d5cd835408880009e9459bb61d8 af2478cfb3dd49b996d71319e6196e86 d06433f9fa1349f7b496e90e3276b27d H ed8e8c2f3c4f4e62b6227b71a59473c8--d06433f9fa1349f7b496e90e3276b27d fdbef891dbee4f52ac1c6fd03aab5fb8 3 dfc152a8478844d6b7a180b5cfb214be d06433f9fa1349f7b496e90e3276b27d--dfc152a8478844d6b7a180b5cfb214be 3abb92d1cc174408a33676fe48edb205 dfc152a8478844d6b7a180b5cfb214be--3abb92d1cc174408a33676fe48edb205 ff6fd097678a4ba78ef47a7082d7a41e 3abb92d1cc174408a33676fe48edb205--ff6fd097678a4ba78ef47a7082d7a41e 5e69fb783f9644509e210c8aafab0277 X ff6fd097678a4ba78ef47a7082d7a41e--5e69fb783f9644509e210c8aafab0277 5e69fb783f9644509e210c8aafab0277--d41fbab942634f24b769efb0227b1739 d0ad16df86994e3396e80592265e2144 RZ(g0) 5e69fb783f9644509e210c8aafab0277--d0ad16df86994e3396e80592265e2144 d425ea8f9ec84b9b9d0c398aa707211a X d0ad16df86994e3396e80592265e2144--d425ea8f9ec84b9b9d0c398aa707211a d425ea8f9ec84b9b9d0c398aa707211a--a3fcdb72fb384ee6898b68c30938e2f2 6667fcfee2904809bfe18d2e19178e0d d425ea8f9ec84b9b9d0c398aa707211a--6667fcfee2904809bfe18d2e19178e0d 19d932915344448ba81c723c5eb04bf8 6667fcfee2904809bfe18d2e19178e0d--19d932915344448ba81c723c5eb04bf8 322f0245aeee49fb9e03598bc52d4caa 19d932915344448ba81c723c5eb04bf8--322f0245aeee49fb9e03598bc52d4caa ee2db13cd7f14d7eace6cff480482dc6 X 322f0245aeee49fb9e03598bc52d4caa--ee2db13cd7f14d7eace6cff480482dc6 ee2db13cd7f14d7eace6cff480482dc6--c32f3ec55c5a444e82259c6a94242f22 65882b337fff447680cd22f3c54390fd RZ(g0) ee2db13cd7f14d7eace6cff480482dc6--65882b337fff447680cd22f3c54390fd 72e312af8a114a88b1a64d4e461e4cf2 X 65882b337fff447680cd22f3c54390fd--72e312af8a114a88b1a64d4e461e4cf2 72e312af8a114a88b1a64d4e461e4cf2--d7c3e4bc5fb94185bd2c2d7e84538233 362e77e4d6b44b1e90f8ab94fe62ac45 72e312af8a114a88b1a64d4e461e4cf2--362e77e4d6b44b1e90f8ab94fe62ac45 66b7d08fcfc7421ca0a718dea74bfddd 362e77e4d6b44b1e90f8ab94fe62ac45--66b7d08fcfc7421ca0a718dea74bfddd f25d906cedc94fefa185fc3fbe0fee7c 66b7d08fcfc7421ca0a718dea74bfddd--f25d906cedc94fefa185fc3fbe0fee7c 162850045b02446ab4cdb9c24e7a3272 f25d906cedc94fefa185fc3fbe0fee7c--162850045b02446ab4cdb9c24e7a3272 381bd9baae204701a974421d00ea6df8 162850045b02446ab4cdb9c24e7a3272--381bd9baae204701a974421d00ea6df8 b3bb6a554da14807ae31e9d44b1d53fb 381bd9baae204701a974421d00ea6df8--b3bb6a554da14807ae31e9d44b1d53fb d7e9c4502fe7448ab89fd8e6a5405f4f RX(b0) b3bb6a554da14807ae31e9d44b1d53fb--d7e9c4502fe7448ab89fd8e6a5405f4f d7e9c4502fe7448ab89fd8e6a5405f4f--af2478cfb3dd49b996d71319e6196e86 b008e9085b314a70bfc1d330e9a63002 91b241221efb4afd8267fa296ab6d063 H fdbef891dbee4f52ac1c6fd03aab5fb8--91b241221efb4afd8267fa296ab6d063 e9f8758a3bd44aa9acebeda68488c9bd 91b241221efb4afd8267fa296ab6d063--e9f8758a3bd44aa9acebeda68488c9bd a5a6814c7cb74af1bf9ee5d0d764899c e9f8758a3bd44aa9acebeda68488c9bd--a5a6814c7cb74af1bf9ee5d0d764899c 91f3ef445fe14d7fb513e66cfd83d558 a5a6814c7cb74af1bf9ee5d0d764899c--91f3ef445fe14d7fb513e66cfd83d558 3f5356d4fdae485293e44358fe70947f 91f3ef445fe14d7fb513e66cfd83d558--3f5356d4fdae485293e44358fe70947f 2fa880f38eb64eca88e79264bf985d94 3f5356d4fdae485293e44358fe70947f--2fa880f38eb64eca88e79264bf985d94 a620e42cbc7641d4bbc94045cdfdb79d 2fa880f38eb64eca88e79264bf985d94--a620e42cbc7641d4bbc94045cdfdb79d a42329fc3a4f415382cc6d4bf8120f57 X a620e42cbc7641d4bbc94045cdfdb79d--a42329fc3a4f415382cc6d4bf8120f57 a42329fc3a4f415382cc6d4bf8120f57--712b4fcda81f470bb2cda7b47ae477a7 02fc3d1beab441e4b7da43f861207e51 RZ(g0) a42329fc3a4f415382cc6d4bf8120f57--02fc3d1beab441e4b7da43f861207e51 021482dd6fa04f61b13b965beb30a10d X 02fc3d1beab441e4b7da43f861207e51--021482dd6fa04f61b13b965beb30a10d 021482dd6fa04f61b13b965beb30a10d--4b93345264b14daaa09d88654a46f7a3 1fab46fbcea44480a9e40c92b12d6fdd 021482dd6fa04f61b13b965beb30a10d--1fab46fbcea44480a9e40c92b12d6fdd 3cd04dc9a7db4d8abf4b0328e5a3f2f8 1fab46fbcea44480a9e40c92b12d6fdd--3cd04dc9a7db4d8abf4b0328e5a3f2f8 b3bc634139ab4d01b73a4cb6900ad821 3cd04dc9a7db4d8abf4b0328e5a3f2f8--b3bc634139ab4d01b73a4cb6900ad821 8697a60f84bf44c3a55acb323276c196 X b3bc634139ab4d01b73a4cb6900ad821--8697a60f84bf44c3a55acb323276c196 8697a60f84bf44c3a55acb323276c196--c5ee4d09218145808053e7ec597e7602 05cd336cf14d4c77b6cbbe0f7d905d11 RZ(g0) 8697a60f84bf44c3a55acb323276c196--05cd336cf14d4c77b6cbbe0f7d905d11 93ece927e66549d19654b3fa2a9841f3 X 05cd336cf14d4c77b6cbbe0f7d905d11--93ece927e66549d19654b3fa2a9841f3 93ece927e66549d19654b3fa2a9841f3--a524035e65384d72a730bcd4f31b1a37 a6a2a1eca95a4090b6ad9b1e7f45b7a2 X 93ece927e66549d19654b3fa2a9841f3--a6a2a1eca95a4090b6ad9b1e7f45b7a2 a6a2a1eca95a4090b6ad9b1e7f45b7a2--162850045b02446ab4cdb9c24e7a3272 a80f6d0e841a4c59869c4aedfbabc27f RZ(g0) a6a2a1eca95a4090b6ad9b1e7f45b7a2--a80f6d0e841a4c59869c4aedfbabc27f 3f5196110a7846e3959265127890bcca X a80f6d0e841a4c59869c4aedfbabc27f--3f5196110a7846e3959265127890bcca 3f5196110a7846e3959265127890bcca--b3bb6a554da14807ae31e9d44b1d53fb 52abfab8b44b4303a6eff55e3bae275e RX(b0) 3f5196110a7846e3959265127890bcca--52abfab8b44b4303a6eff55e3bae275e 52abfab8b44b4303a6eff55e3bae275e--b008e9085b314a70bfc1d330e9a63002

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-01-28T16:51:43.176558 image/svg+xml Matplotlib v3.10.0, https://matplotlib.org/

References


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