# Quantum-Informed Recursive Optimization#

This tutorial will give you an overview of problem specific implementations for solving optimization problems with the Quantum Approximate Optimization Algorithm (QAOA).

The Quantum Alternating Operator Algorithm (QAOA) is used to solve combinatorical optimization instances of NP-hard instances. For further information see our tutorial on QAOA!

The paper Quantum-Informed Recursive Optimization Algorithms (2023) by J. Finzgar et. al. establishes a blueprint for developing alogrithms that update the problem instance recursively, based on the correlations in results obtained by QAOA.

While QAOA cost functions are designed with specific problem instances in mind, this research has shown promising improvements to this to approach by further leveraging the problem structure.

We have implemented this approach for different problem instances, namely MaxClique, MaxIndependentSet, MaxSat and MaxSetPacking. The explanation below tackles the Maximum Indepent Set (MIS) instance, in analogy to the original paper by Finzgar et. al.

## Starting point of the algorithm#

The algorithm evaluates the result of a QAOA optimization procedure to establish correlations in the solution space, then recursively updates the problem structure. We will further assume that you are already mostly familiar with QAOA.

The MIS is defined via a graph with a set of vertices \(V\) and a set of edges \(E\) . To solve this optimization problem, one has to find the maximum number of vertices, where none of the vertices share a common edge, i.e. we want to find

The QAOA cost operator has the form

and the mixer operator is

Executing the optimization loop of our QAOA implementation of depth \(p\) will result in the state

with \(n\) being the number of vertices in the graph. The \(\{\gamma^{*}_{p-1}, \beta^{*}_{p-1}, ... \gamma^{*}_0 , \beta^{*}_0 \}\) represent the optimized QAOA angles.
This state \(\ket{\psi}\) is the starting point of the **Quantum-Informed Recursive Optimization** considerations.

## Establishing correlations#

We quickly want to motivate the search correlations with the arguments made in the original paper. For further detail we again refer to the original paper by J. Finzgar et. al.

Firstly, QAOA is a local algorithm, which was shown to severely limit its performance. In turn, applying non-local updates classically has been proposed, i.e. in recursive QAOA (RQAOA) implementations. Values of variables are iteratively frozen as given by the correlations in the measurements of quantum state prepared in a QAOA optimization routine. In QIRO, this information is used to recursively decrease the size of the problem instance by introducing problem-specific classical optimization routines. With this, new connections between previously unconnected nodes non-local effects are introduced, counterbalancing the locality inherent to QAOA.

For any problem specific QIRO implementation the next step is to evaluate the expected correlations in the solution space, i.e. computing the values of the matrix \(\text{M}\), where \(\text{M}_{ii} = \bra{\psi} Z_i \ket{\psi}\) and \(\text{M}_{ij} = \bra{\psi} Z_i Z_j \ket{\psi}\).

We then need to find the **maximum absolute value** of \(\text{M}\).

## Reducing the problem#

Based on the **maximum absolute entry** of M and its sign, one of the following replacements is employed:

If \(\text{M}_{ii} \geq 0\) is the maximum absolute value, then the \(i\)-th vertex is set to be in the independent set (IS). In turn, we can remove all vertices that share an edge with this vertex can be removed from the graph, since including them in the solution would violate the problem constraints

If \(\text{M}_{ii} < 0\) is the maximum absolute value we remove \(i\)-th vertex from the graph

If \(\text{M}_{ij} > 0, (i, j) ∈ E\) was selected, we remove both nodes from the graph with the argument, that, since both of them would be in the same state in the final solution, including both as part of the solution would violate the constraint, as they share an edge. In turn, they can be removed from the graph.

If \(\text{M}_{ij} < 0, (i, j) ∈ E\) was selected, we remove all nodes that share an edge with both vertices \(i\) and \(j\). Since one of the vertices \(i\) and \(j\) will be part of the final solution (but not both), any vertex that is connected to both \(i\) and \(j\) is guaranteed to violate the problem constraints, and can be removed from the graph. In this case it may be possible, that no vertex is found to be as a canditate for removing. We will then simple chose second biggest absolute value of

**M**for the replacement routine.

These operations are undertaken directly on the `networkx`

graph that has been fed to instance of the `QIROProblem`

class, see the code example below.

We then hand over the reduced problem graph to a new `QAOAProblem`

instance, optimize the parameter, and reduce the problem again with the same subroutine as above.

## The final solution#

The after a specific number of recursions the final solution is returned as the result of a `QAOAProblem`

optimization routine,
where we consider the excluded and included vertices from the above steps in the `cost_operator`

, `mixer`

and `init_function`

of the `QAOAProblem`

.

The final result is therefore a the classic `dictionary`

return from the `QAOAProblem`

class and poses an optimized solution to the initial full problem instance.

Try it out yourself with the example code below!

# QIRO implementation#

## The QIRO class#

Upon instanciation, the QIROProblem class requires five arguments:

The

`problem`

to be solved, which not necessarly a graph, since QIRO is also implemented for MaxSat.The

`replacement_routine`

, which has the job of performing the aforementioned specific reductions to the`problem`

object.The

`cost_operator`

,`mixer`

,`init_function`

and`cl_cost_function`

in analogy to QAOAProblem instanciation.

Why the `cost_operator`

, `mixer`

, and `init_function`

undergo some slight adjustements, will be made clear in the code example below, aswell as the necessity
for directly assigning a `cost_operator`

, a `mixer`

, and and `init_function`

.

To run the instance and solve the optimization problem we use the `.run_qiro`

function, which takes the following arguments:
qarg, depth, n_recursions, mes_kwargs = {}, max_iter = 50

The QuantumVariable

`qarg`

, which is the quantum argument the algorithm is evaluated on, in analogy to the QAOA moduleThe integer

`depth`

, which is the depth of QAOA optimization circuit.The integer

`n_recursions`

, representing the number of QIRO update steps.The dictionary

`mes_kwargs = {}`

, empty by default, to define further specifications of the measurements.The integer

`max_iter = 50`

, set to 50 by default, which defines the maximum number of the classical optimization loop with the`COBYLA`

optimizer as part of the QAOA optimization routine

# Maximum independent set example#

We now investigate a code example for the Maximum independent set problem instance.

## Preliminaries#

Before we get to the superficial code let us first do some explaining of the relevant aspects, starting with the `replacment_routine`

.

All in all, the function remains straight forward. We employ a `find_max`

subroutine to find the entry and the sign of the maximum correlation value, to then adjust the graph.

```
def create_maxIndep_replacement_routine( res, Graph, solutions= [], exclusions= []):
# For multi qubit correlations
orig_edges = [list(item) for item in Graph.edges()]
# For single qubit correlations
orig_nodes = list(Graph.nodes())
# find the max_item
max_item, sign = find_max(orig_nodes, orig_edges , res, solutions)
# create a copy of the graph to prevent unwanted side effects
newGraph = copy.deepcopy(Graph)
# we just directly remove vertices from the graph, as suggested by the replacement rules
# if the item is an int, its a single node, else its an edge
if isinstance(max_item, int):
if sign > 0:
# remove all adjacent nodes
newGraph.remove_nodes_from(Graph.adj[max_item])
solutions.append(max_item)
exclusions.append(max_item)
elif sign < 0:
# remove the nodes
newGraph.remove_node(max_item)
exclusions.append(max_item)
else:
if sign > 0:
# remove both nodes
newGraph.remove_nodes_from(max_item)
exclusions += max_item
elif sign < 0:
# remove all nodes connected to both nodes
intersect = list(set( list(Graph.adj[max_item[0]].keys()) ) & set( list(Graph.adj[max_item[0]].keys()) ))
newGraph.remove_nodes_from(intersect)
exclusions += intersect
return newGraph, solutions, sign, exclusions
```

As you might gave noticed in the code above, we add the nodes that are included into (respective excluded from) the solution to a list `solutions`

(`exclusions`

).
This allows us to directly recycle the QAOA code for the `cost_operator`

, `mixer`

and `init_function`

of the original QAOA implementation with minor adjustments.

Since we have to consider nodes that are already asigned to be in the solution set, or exluded from the algorithm, we do not want to apply these functions to said nodes.
We therefore include some simple lines of code into the functions, for example in the `qiro_RXMixer`

:

```
def qiro_RXMixer(solutions = [], exclusions = []):
union = solutions + exclusions
def RX_mixer(qv, beta):
for i in range(len(qv)):
# DONT mix solutions or exclusions
if not i in union:
rx(2 * beta, qv[i])
return RX_mixer
```

With the preliminaries out of the way, let us jump right into the code example:

## Code execution#

We start off by importing all the relevant code and defining the graph of our problem, aswell as the QuantumVariable to run our instance on:

```
# imports
from qrisp.qiro import QIROProblem, qiro_init_function, qiro_RXMixer, create_maxIndep_replacement_routine, create_maxIndep_cost_operator_reduced
from qrisp.qaoa.problems.maxIndepSetInfrastr import maxIndepSetclCostfct
from qrisp import QuantumVariable
import networkx as nx
#Define a graph via the number of nodes, and the QuantumVariable arguments
num_nodes = 13
G = nx.erdos_renyi_graph(num_nodes, 0.4, seed = 107)
qarg = QuantumVariable(G.number_of_nodes())
```

With this, we can directly throw everything thats relevant at the QIROProblem class and create an instance.

```
# assign the correct new update functions for qiro from above imports
qiro_instance = QIROProblem(G,
replacement_routine=create_maxIndep_replacement_routine,
cost_operator= create_maxIndep_cost_operator_reduced,
mixer= qiro_RXMixer,
cl_cost_function= maxIndepSetclCostfct,
init_function= qiro_init_function
)
```

We think of arguments for the `run_qiro`

function, run the algorithm, et violà!

```
# We run the qiro instance and get the results!
res_qiro = qiro_instance.run_qiro(qarg=qarg, depth = 3, n_recursions = 2)
```

All done! We have solved the NP-hard MIS problem using Quantum-Informed Recursive Optimization!

## Results#

But of course we also want to investigate our results, so lets find out about the five most likely solutions the algorithm came up with:

```
print("QIRO 5 best results")
maxfive = sorted(res_qiro, key=res_qiro.get, reverse=True)[:5]
costFunc = maxIndepSetclCostfct(G)
for key, val in res_qiro.items():
if key in maxfive:
# print the result bitstring and value of the costfunction
print(key)
print(costFunc({key:1}))
```

We do not put the example output here, since the algorithm is not deterministic, and the output you receive may differ from what an example might show. Just go ahead and try it yourself!

We can further compare our results to the NetworkX MIS algorithm for solving the MIS problem:

```
print("Networkx solution")
print(nx.approximation.maximum_independent_set(G))
```

Chances are, you will see a result in the QIRO implementation, that is better than the classical algorithm provided by Networkx!

We can also compare these results with the standard QAOA implementation.

```
from qrisp.qaoa.qaoa_problem import QAOAProblem
from qrisp.qaoa.problems.maxIndepSetInfrastr import maxIndepSetCostOp
from qrisp.qaoa.mixers import RX_mixer
Gtwo = nx.erdos_renyi_graph(num_nodes, 0.4, seed = 107)
qarg2 = QuantumVariable(Gtwo.number_of_nodes())
maxindep_instance = QAOAProblem(maxIndepSetCostOp(G), RX_mixer, maxIndepSetclCostfct(G))
res_qaoa = maxindep_instance.run( qarg = qarg2, depth = 3)
print("QAOA 5 best results")
maxfive = sorted(res_qaoa, key=res_qaoa.get, reverse=True)[:5]
for key, val in res_qaoa.items():
if key in maxfive:
print(key)
print(costFunc({key:1}))
```

As expected, the improvements are drastic, but you will have to find that out for yourself!

As a final caveat, we can look at the graph we are left with after all reduction steps

```
final_Graph = qiro_instance.problem
```

Congratulations, you have reached the end of the tutorial and are now capable of solving the MIS problem in Qrisp! Should your appetite not be satisfied, we advise you to check out our other QIRO implementations:

MaxSat and

and of course all the other material in the tutorial section!