Skip to content

Solve Optimization Problem

So far, we've gone through the optimization model formulation. We shall now present the core Classiq capabilities, generation of a designated quantum solution, and execution of the generated algorithm on a quantum backend.

We present our method with a common example problem, the Max Independent Set (MIS) with networkx.star_graph(4) solved by QAOAMixer algorithm with a single QAOA layer (see the problem library).

import networkx as nx
import pyomo.core as pyo


def mis(graph: nx.Graph) -> pyo.ConcreteModel:
    model = pyo.ConcreteModel()
    model.x = pyo.Var(graph.nodes, domain=pyo.Binary)

    @model.Constraint(graph.edges)
    def independent_rule(model, node1, node2):
        return model.x[node1] + model.x[node2] <= 1

    model.cost = pyo.Objective(expr=sum(model.x.values()), sense=pyo.maximize)

    return model

The method consists of building a PYOMO model, indicating the QAOA and optimizer preferences and then using one of the following commands:

  • generate - generates the quantum circuit.
  • solve - solves the optimization problem.
  • get_operator - returns the Ising hamiltonian representing the problem's objective.
  • get_objective - returns the PYOMO object representing the problem's objective.
  • get_initial_point - returns the initial parameters for a parametric ansatz.
  • classical_solution.solve - solves the optimization problem classically.
{
    "name": "mis",
    "graph": [
        [0.0, 1.0, 1.0, 1.0, 1.0],
        [1.0, 0.0, 0.0, 0.0, 0.0],
        [1.0, 0.0, 0.0, 0.0, 0.0],
        [1.0, 0.0, 0.0, 0.0, 0.0],
        [1.0, 0.0, 0.0, 0.0, 0.0]
        ],
    "qaoa_preferences" : {
        "qsolver": "QAOAMixer",
        "qaoa_reps": 1,
    }
}

The file should include the name of the problem, and the required arguments for the model building function. In the case of MIS problem, the only argument is the underline graph in the form of adjacency matrix. Please make sure the given name is equal to the file name of the model definition.

Two extension commands are available: generate circuit and solve problem.

This is done by opening the Command Palette (Ctrl+Shift+P / Command+Shift+P) and choosing either the "Generate circuit for combinatorial optimization" command or "Solve combinatorial optimization" command, respectively.

First, we create the desired optimization problem as a PYOMO model: The following code snippet is a concise example of the application of the optimization engine.

import networkx as nx

graph = nx.star_graph(4)
mis_model = mis(graph)

We are now ready to send the model to the Classiq backend. This is done using the CombinatorialOptimization package and its "synthesize" and "solve" commands.

from classiq.applications.combinatorial_optimization import (
    CombinatorialOptimization,
    QSolver,
    QAOAPreferences,
)

qaoa_preferences = QAOAPreferences(qsolver=QSolver.QAOAMixer, qaoa_reps=3)
mis_problem = CombinatorialOptimization(
    model=mis_model, qsolver_preferences=qaoa_preferences
)

Results

"Model Designer" command

The model property exposes the functional-level model of the resulting ansatz. Afterwards, the user can synthesize from it the explicit circuit.

model = mis_problem.get_model()

or,

model = mis_problem.model

"Synthesize" command

The quantum circuit is returned as GeneratedCircuit class, which contains both textual and visual representations. The textual data is available in multiple formats. The visual data consists of a static image (see figure below) and an interactive image.

qc = mis_problem.synthesize()

or,

qc = mis_problem.ansatz

 qaoa example

"Solve problem" command

result = mis_problem.solve()

The results are organized in the VQESolverResult class and may be observed in several formats.

serialized output

print(result)
=== OPTIMAL SOLUTION ===
solution           cost
---------------  ------
(0, 1, 1, 1, 1)       4
=== SOLUTION DISTRIBUTION ===
solution           cost    probability
---------------  ------  -------------
(0, 1, 1, 1, 1)       4           0.71
(0, 1, 1, 1, 0)       3           0.02
(0, 1, 1, 0, 1)       3           0.04
(0, 1, 0, 1, 1)       3           0.03
........             ..           ....
=== OPTIMAL_PARAMETERS ===
[3.064572795460487, 0.5370940203297239, 2.869550922366777, 0.526740029882901, 3.0904332803941017, 1.417496935210913]
=== TIME ===
00:00:02.258639

Histogram

print(result.histogram())

 histogram

Convergence Graph

print(result.convergence_graph)

 energy_convergence

Optimal Parameters

result.optimal_parameters_graph()

 optimal_parameters

Operator command

operator = mis_problem.get_operator()
print(operator.show())
-2.500 * IIIII
+0.500 * IIIIZ
+0.500 * IIIZI
+0.500 * IIZII
+0.500 * IZIII
+0.500 * ZIIII

Objective command

print(mis_problem.get_objective())
x[0] + x[1] + x[2] + x[3] + x[4] + x[5]

Initial parameters command

initial_parameters = mis_problem.get_initial_point()
[0.22.., 3.09..., 0.95..., 2.83...]

Solve classically command

result = mis_problem.solve_classically()
best_cost=4.0 time=None solution=(0, 1, 1, 1, 1)