Skip to content

Learning Optimization

View on GitHub

This is a step-by-step example of how to use the Classiq platform at the application level. The goal is to see how easy it can be to use quantum algorithms to solve problems.

This is a basic optimization problem:

minimize the expression \(3x_1+2x_2\) for the non-negative integers \(x_1,x_2\), given the constraint that \(3x_1+x_2\ge2\).

Of course this is trivial and the solution is \(x_1=1,x_2=0\). The goal is to understand how to incorporate the problem with the platform, so you can then continue on to define more complicated problems.

This kind of optimization problem is relevant for many real-life scenarios. For example, Amazon wants to determine the best value it can offer to a customer for two items on a Black Friday sale, but it has to earn some minimum value.

How to Solve It?

Define the optimization problem with the classical Pyomo optimization package in Python. Then, use the platform to convert it to a high-level functional model of quantum algorithms. This functional model is at the heart of the platform as this is the object that is synthesized to an actual quantum circuit using the synthesis engine!

After the circuit is synthesized, run it on actual hardware or on a simulator to actually get the result from the quantum algorithm. This tutorial runs the algorithm on the IBM quantum simulator as it is the default option.

What Do You Need to Know about Quantum Algorithms?

You need to know almost nothing regarding quantum algorithms, besides one thing. There are two common algorithms used for optimization problems (as well as chemistry): QAOA and VQE. Both are very similar, where QAOA could be seen as a specific type of VQE.

For this problem, use the QAOA algorithm. The algorithm has a mandatory parameter that you need to choose, as explained below.

Getting Started

Import the relevant packages. The first is the Pyomo package; the classical optimization package that was installed when you installed Classiq:

import pyomo.environ as pyo

Import the objects that translate the optimization problem from the Pyomo language to a high-level quantum functional model:

from classiq import construct_combinatorial_optimization_model
from classiq.applications.combinatorial_optimization import OptimizerConfig, QAOAConfig

Defining the Problem

Initiate a Pyomo application object.

application_level_object = pyo.ConcreteModel()

This object will contain all the relevant information regarding the optimization problem. The first piece of relevant information is what are the variables. In Pyomo, the way to incorporate the information regarding the variables is using the pyo.Var object:

application_level_object.x = pyo.Var(
    [1, 2],  # variables names
    domain=pyo.NonNegativeIntegers,  # variables type
    bounds=(0, 3),  # variables range
)

In the first line, define 'application_object' with a field called 'x' to contain the problem variables. The variables are defined with a 'pyo.Var' object, containing several things:

  1. The names of the variables. These are defined by \([1,2]\), indicating \(x_1\) and \(x_2\), respectively. (Likewise, \([3,7]\) would indicate two variables: \(x_3, x_7\).)

  2. The type/domain of the variables. The variables are non-negative integers, so configure them accordingly using the 'pyo.NonNegativeIntegers' command.

  3. The bounds of the variables. The variables are configured to get values from 0 to 3, inclusive.

While you may prefer a larger range, today's quantum computers (and simulators) are not big enough, so the size of the problems you can solve is quite small (here defined by the number of options: two variables each with four options; i.e., 16 options total).

In the application object, define the cost function, which is the objective:

application_level_object.cost = pyo.Objective(
    expr=3 * application_level_object.x[1] + 2 * application_level_object.x[2]
)

In other words, minimize \(3x_1+2x_2\). Together with the objective, define the constraint:

application_level_object.constraint = pyo.Constraint(
    expr=3 * application_level_object.x[1] + application_level_object.x[2] >= 2
)

I.e., the constraint is \(3x_1 + 2x_2 \ge 2\). There are several ways to define constraints in Pyomo. Here, add a field to the application object called constraint, which is equal to some Pyomo constraint object. Read link for more ways of defining constraints.

Examine the application object using the Pyomo method 'pprint':

application_level_object.pprint()
1 Set Declarations
    x_index : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    2 : {1, 2}

1 Var Declarations
    x : Size=2, Index=x_index
        Key : Lower : Value : Upper : Fixed : Stale : Domain
          1 :     0 :  None :     3 : False :  True : NonNegativeIntegers
          2 :     0 :  None :     3 : False :  True : NonNegativeIntegers

1 Objective Declarations
    cost : Size=1, Index=None, Active=True
        Key  : Active : Sense    : Expression
        None :   True : minimize : 3*x[1] + 2*x[2]

1 Constraint Declarations
    constraint : Size=1, Index=None, Active=True
        Key  : Lower : Body          : Upper : Active
        None :   2.0 : 3*x[1] + x[2] :  +Inf :   True

4 Declarations: x_index x cost constraint

See how all the information regarding the problem is organized in this Pyomo application object.

Entering the Quantum World

So far, you have only used the Pyomo package and spoken the optimization language. You now need some quantum knowledge for the optimization problem.

Set the number of repetitions of the QAOA sub-circuit. The QAOA algorithm contains a QAOA sub-circuit that might repeat several times. Roughly speaking, the more repetitions, the better the algorithm.

Having said that, as you saw, there is a limited range of parameters due to the small size of today's quantum computers. There is also an issue with the length of the quantum circuit due to the relatively low quality of today's quantum circuits (again due to the limited power of the quantum simulators). Therefore, start with one repetition of the sub-circuit, and later you can change it to see how the results change.

Define the QAOA configuration:

qaoa_config = QAOAConfig(num_layers=1)

Seamlessly Generating the Functional Level Model

Now with the application object and the QAOA configuration defined, ask the platform to convert it into a high-level quantum functional model. Because there is an optimization problem, use construct_combinatorial_optimization_model:

model = construct_combinatorial_optimization_model(
    pyo_model=application_level_object,
    qaoa_config=qaoa_config,
)

Congratulations! You just defined your first quantum model that encapsulates the functionality of your quantum algorithm, without mentioning anything related to qubits or quantum gates!

Ask the system to solve the model using the quantum algorithm:

Synthesizing the model:

from classiq import show, synthesize

qprog = synthesize(model)

Executing:

from classiq import execute

res = execute(qprog).result()

Examine the solution:

import pandas as pd

from classiq.applications.combinatorial_optimization import (
    get_optimization_solution_from_pyo,
)

solution = get_optimization_solution_from_pyo(
    application_level_object,
    vqe_result=res[0].value,
    penalty_energy=qaoa_config.penalty_energy,
)
optimization_result = pd.DataFrame.from_records(solution)
optimization_result.sort_values(by="cost", ascending=True).head(5)
probability cost solution count
34 0.008 3.0 [1, 0] 8
115 0.002 4.0 [0, 1] 2
187 0.001 4.0 [0, 2] 1
120 0.002 5.0 [1, 0] 2
136 0.002 5.0 [1, 0] 2
idx = optimization_result.cost.idxmin()
print(
    "x =", optimization_result.solution[idx], ", cost =", optimization_result.cost[idx]
)
x = [1, 0] , cost = 2.9999999999999964

The solution is \(x_1=1, x_2=0\). Yes, you succeeded in solving the optimization problem using a quantum algorithm! :)

Wait a minute, you might ask, 'Where are all the qubits and gates I have heard about? '

That is a good question! While you can design algorithms at the application and functional levels, you also have access to the qubit level to further understand the algorithm and get into detail with more options!

This is easily done. The synthesis engine output is a quantum circuit object, so visualize it with the 'show' command that prompts a website to interactively display the circuit for deeper analysis. You can examine how your circuit looks, from high level to the qubit level.

show(qprog)
Opening: https://platform.classiq.io/circuit/16c77e9c-2adc-4173-b8b0-9f4655339242?version=0.41.0.dev39%2B79c8fd0855

Now that you understand better how the platform works, there is one last thing worth mentioning. When you solved the model by executing the synthesis engine's output quantum circuit, behind the scenes the circuit was sent to the default Executor (IBM simulator) with classical optimization preferences to return the optimization result.

Because this example shows how to use the platform at the application level, many details in the flow were determined behind the scenes. For more control of your design, and to design the algorithm from the functional level so to gain more control and capabilities, do the next tutorial ;)

For your convenience, all the code is provided in the following block:

# This tutorial was tested on Classiq version 0.19

import pandas as pd
import pyomo.environ as pyo

from classiq import (
    construct_combinatorial_optimization_model,
    execute,
    show,
    synthesize,
)
from classiq.applications.combinatorial_optimization import OptimizerConfig, QAOAConfig

# Application object definitions and fields
application_object = pyo.ConcreteModel()

application_object.x = pyo.Var(
    [1, 2],  # variables names
    domain=pyo.NonNegativeIntegers,  # variables type
    bounds=(0, 3),  # variables range
)

application_object.cost = pyo.Objective(
    expr=3 * application_object.x[1] + 2 * application_object.x[2]
)

application_object.constraint = pyo.Constraint(
    expr=3 * application_object.x[1] + application_object.x[2] >= 2
)

application_object.pprint()

# going quantum - QAOA preferences
qaoa_config = QAOAConfig(num_layers=1)  # QAOA sub-circuit number of repetitions

# defining the model
model = construct_combinatorial_optimization_model(
    pyo_model=application_level_object,
    qaoa_config=qaoa_config,
)

# synthesizing a quantum circuit
qprog = synthesize(model)

# executing the circuit to solve the optimzation problem:
res = execute(qprog).result()

# post-processing the results:
from classiq.applications.combinatorial_optimization import (
    get_optimization_solution_from_pyo,
)

solution = get_optimization_solution_from_pyo(
    application_level_object,
    vqe_result=res[0].value,
    penalty_energy=qaoa_config.penalty_energy,
)
optimization_result = pd.DataFrame.from_records(solution)
optimization_result.sort_values(by="cost", ascending=True).head(5)
idx = optimization_result.cost.idxmin()
print(
    "x =", optimization_result.solution[idx], ", cost =", optimization_result.cost[idx]
)

# view and analyze the quantum circuit:
show(qprog)
1 Set Declarations
    x_index : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    2 : {1, 2}

1 Var Declarations
    x : Size=2, Index=x_index
        Key : Lower : Value : Upper : Fixed : Stale : Domain
          1 :     0 :  None :     3 : False :  True : NonNegativeIntegers
          2 :     0 :  None :     3 : False :  True : NonNegativeIntegers

1 Objective Declarations
    cost : Size=1, Index=None, Active=True
        Key  : Active : Sense    : Expression
        None :   True : minimize : 3*x[1] + 2*x[2]

1 Constraint Declarations
    constraint : Size=1, Index=None, Active=True
        Key  : Lower : Body          : Upper : Active
        None :   2.0 : 3*x[1] + x[2] :  +Inf :   True

4 Declarations: x_index x cost constraint


x = [1, 0] , cost = 2.9999999999999964
Opening: https://platform.classiq.io/circuit/c5c1b8d5-6c30-419c-a238-ad5d40b037d3?version=0.41.0.dev39%2B79c8fd0855