Learning Optimization¶
This is a step-by-step example of how to use Classiq platform from the application level. The goal is to show you how easy it can be to use quantum algorithms to solve some problems.
The problem we are going to solve is a very 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 problem is very trivial and the solution is $x_1=1,x_2=0$. The goal is to understand how we can incorporate the problem to the platform, so then you can define more complicated problems by yourself.
This kind of optimization problem is relevant for many real-life scenarios, e.g. , if Amazon wants to find the best value it can offer to a costumer for two items in a black-friday sale, but it has some minimum value it has to earn.
How are we going to solve it?¶
We will now define our optimization problem with a classical optimization package in python called Pyomo. Once the optimization problem is defined, we will use the platform to convert it to a high-level functional model of quantum algorithms. This functional model is at the heart of our platform (as described earlier) as this is the object that is synthesized to an actual quantum circuit using our synthesis engine!
After the circuit is synthesized, we can run it on actual hardware or on a simulator to actually get the result from our quantum algorithm (In this tutorial we will run our algorithm on IBM's quantum simulator as it is the default option).
Anything we need to know about quantum algorithms?¶
We almost do not need to know anything regarding quantum algorithms beside 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 our problem, we are going to use the QAOA algorithm. The algorithm has 1 mandatory parameter that we will need to choose, as will be explained later-on.
Let's get it started!¶
First we need to import all the relevant packages. The first one we need is the pyomo package - the classical optimization package that was installed when you installed Classiq:
import pyomo.environ as pyo
Next we need to import all the relevant objects that translate the optimization problem from the pyomo language to a high-level quantum functional model. These are:
from classiq import construct_combinatorial_optimization_model
from classiq.applications.combinatorial_optimization import OptimizerConfig, QAOAConfig
Defining our problem¶
Now we are reallt ready to start!
We start by initiating a pyomo application object.
application_level_object = pyo.ConcreteModel()
This object will contain all the relevant information regarding our optimization problem. The first piece of relevent information is what are the variables. In Pyomo, the way to incorporate the information regarding the variables is with 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 we defined that our 'application_object' will have a field called 'x' that will contain our problem variables. The variables are defined with a 'pyo.Var' object, that contains several things:
The names of the variables. These are defined by $[1,2]$ and they stand for $x_1$ and $x_2$ respectively. (if it was written $[3,7]$ so we would have two variables: $x_3, x_7$ - it is just a matter of naming).
The type/domain of the variables. Our variables are non-negative itnegers so we configured it accordingly using the 'pyo.NonNegativeIntegers' command.
The bounds of the variables. Here our variables are configured to get values from 0 to 3 included.
Of course we would like to have a larger range, but unfortunately, today's quantum computers (and simulators) are not big enough, so the size of the problems we can solve is quite small (here defined by number of options we have: two variables each with 4 options i.e. 16 options).
The next thing we want to define in our applicaiton object is our cost function - our objective:
application_level_object.cost = pyo.Objective(
expr=3 * application_level_object.x[1] + 2 * application_level_object.x[2]
)
This is to say in other words: minimize $3x_1+2x_2$. Together with our objective we should define the constraint:
application_level_object.constraint = pyo.Constraint(
expr=3 * application_level_object.x[1] + application_level_object.x[2] >= 2
)
i.e. our constraint is $3x_1 + 2x_2 \ge 2$. There are several ways to define constraints in Pyomo. Here we added a field to our application object that is called constaraint
which is equal some Pyomo constraint object. Use this link to see more ways of thow to define constraints.
Let's have a look on our application object. For that we use the Pyomo method 'pprint':
application_level_object.pprint()
We can see how all the information regarding the problem is organized within this Pyomo application object.
Let's enter the quantum world!¶
So far we used only the Pyomo package, and we spoke only optimization language. We now need to use a bit of quantum knoweledge for the optimization problem.
We now need to set the number of repetitions of the QAOA sub-circuit. The QAOA algorithm contains a QAOA sub-circuit that might repeat itself several times. Roughly speaking, the more repetitions, the better the algorithm.
Having said that, as we had the issue of the limited range of parameters due to the small size of nowadays quantum computers, we have an issue with the length of the quantum circuit due to the relative low quality of today's quantum circuits (and again due to the limited power of the quantum simulators). Therefore, we will start with 1 repetition of the sub-circuit, and later on you can play and change this to see how things are changed.
Overall, we define our QAOA configuration as:
qaoa_config = QAOAConfig(num_layers=1)
Seamlessly generating the functional-level model!¶
Now with the application object and the QAOA configuration defined, we are ready to ask the platform to convert it into a high-level quantum functional model. Because we have an optimizaiton problem it is be done with construct_combinatorial_optimization_model
:
model = construct_combinatorial_optimization_model(
pyo_model=application_level_object,
qaoa_config=qaoa_config,
)
Congradulations! You just defined your first quantum model that encapsulates the functionality of your quantum algorithm, without mentionening anything related to qubits or quantum gates!
Let's ask the system to solve the model for us using the quantum algortihm:
Synthesizing our model:
from classiq import show, synthesize
qprog = synthesize(model)
Executing:
from classiq import execute
res = execute(qprog)
And we can take a look on the solution itself:
import pandas as pd
optimization_result = pd.DataFrame.from_records(res[0].value)
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]
)
That is the solution is $x_1=1, x_2=0$. Yes, we succeeded! and solved the optimization problem using a quantum algorithm :)
But wait a minute, you might ask yourself 'where are all the qubits and gates I have heard about? '
That is a good question! Although we give the option to design algorithms at the application and functional levels, we want to give you access to the qubit level in order to further understand your algorithm, and go into some more details with more options!
This is easily done by. The synthesize engine output is a quantum circuit object - we will visualize it with the 'show' command that will prompt a website that will interactively show your our circuit for deeper analysis! You can really examine how your circuit looks from a high-level to the qubit level!
show(qprog)
Now that you understand a bit more how the platform works, there is one last thing worth mentioning. When we 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 give the optimization result back.
Because we showed here the usage of the platform from the application-level, many details in the flow were made behind the scene. If you want to have more control on your design, and to design the algorithm from the functional-level so to gain more control and capabilities, we made the next tutorial exactly for you, enjoy ;)
For your convinience, all the code is given in the one 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 quntum circuit
qprog = synthesize(model)
# executing our circuit to solve the optimzation problem:
res = execute(qprog)
# post processing the results:
optimization_result = pd.DataFrame.from_records(res[0].value)
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)