Introduction to the Model
Class¶
The Model
class serves as the core interface through which you can build your optimization problems inside luna_quantum
. You can define your Model
once and then use it across all classical or quantum solvers. Although normally a solver may be limited to a specific optimization format, all solver specific optimization formulations are handled in the background. This means you can quickly test your Model
across different solvers without any added overhead.
If you are working with an existing optimization model in formats like Linear Program (LP), Constrained Quadratic Model (CQM), Binary Quadratic Model (BQM), or QUBO, you won’t need to start from scratch! luna_quantum
makes it easy to bring these into its framework. With its flexible translation tools, you can import your current models directly into a Model
and get started immediately. Otherwise, in case you need to go the other way around or switch formats midstream, it is just as simple. You can effortlessly convert models between supported formats, giving you the freedom and flexibility to experiment with different formats via a single step.
The cohesive and flexible design of the Model
class empowers users to model complex optimization problems in just a few intuitive and reproducible steps. Through its central Model
type and translation methods, the Model
class fosters fast experimentation and smooth integration into real-world optimization workflows.
A Model
encapsulates all basic building blocks of an optimization problem:
- Decision Variables: The unknown parameters the model seeks to optimize.
- Objective Function: Defines the goal (minimize or maximize).
- Constraints: Conditions restricting feasible solutions.
- Environment: A shared context, storing meta information on the decision variables
Next we will be demonstrating how to model a simple optimization problem with the Model
component, defining variables, constraints, environments, and an objective function. We will explain the following model:
from luna_quantum import Model, Sense, Variable, Vtype
# Create the model
model = Model("Example")
# Set the Model to minimize its objective function
model.set_sense(Sense.Min)
# Declare decision variables within the model's environment
with model.environment:
x = Variable("x", vtype=Vtype.Integer)
y = Variable("y", vtype=Vtype.Integer)
z = Variable("z", vtype=Vtype.Real)
model.objective = (x - y) * z
model.constraints += x >= 4, "x lower bound"
model.constraints += y <= 3, "y upper bound"
model.constraints += x - y >= 0, "two variable constraint"
model.constraints += z >= 2
model.constraints += z <= 5
Creating the Model¶
Let’s create the optimization model. First, initialize the Model
and optionally name it. Depending on your optimization task, you may want to minimize or maximize the objective. The default setting is a minimization task but you can also set the sense manually with model.set_sense()
.
from luna_quantum import Model, Sense
# Create the model
model = Model("Example")
# Set the Model to minimize its objective function
model.set_sense(Sense.Min)
Defining the Variables and an Environment¶
A Model
and its Variables
operate within an Environment
to which they are exclusive. The environment acts as a container to automatically manage scoping and indexing of variables, ensuring their correct interaction. Therefore, all variables in a Model
must be created within the same environment. In the case that variables from different environments interact, the error VariablesFromDifferentEnvsError
will be raised.
The Variable(name: str, vtype: Vtype, bounds: Bounds)
class is an elementary part of the optimization problem and can be defined with a name, a variable type, and optional bounds.
Vtype
: The available types in the Model
class are:
Real (ℝ)
Integer (ℕ)
Binary ({0,1})
Spin ({-1,+1})
Bounds
:
Bounds cannot be set for Binary
and Spin
variables. For Real
and Integer
variables, you can specify an inclusive range by providing a Bounds(lower, upper)
object to the bounds
parameter. This restricts the variable's values to lie within the specified range.
from luna_quantum import Variable, Vtype
# Declare decision variables within the model's environment
with model.environment:
x = Variable("x", vtype=Vtype.Integer)
y = Variable("y", vtype=Vtype.Integer)
z = Variable("z", vtype=Vtype.Real)
A Model
's decision variables can be combined through algebraic operations such as addition (+
), subtraction (-
), multiplication (*
), and exponentiation (**
) to create an Expression
. An expression represents a mathematical formula involving one or more decision variables, constants, and operations, and can be used to define objectives and constraints. As expressions consist of decision variables, expressions deriving from variables of different environments also cannot be mixed.
Creating an Objective Function¶
The objective function is a mathematical expression within the optimization model that represents the goal of the optimization. The optimization occurs by evaluating and adjusting decision variables within the feasible solution space, which is the set of all possible solutions satisfying the given constraints. The objective function thus guides the optimization process toward the most desirable solution that complies with all specified restrictions. You can define the objective by passing the desired expression to the model.objective
.
Adding Constraints¶
Constraints restrict the feasible solution space defined by the decision variables. A constraint is specified as an expression involving one or more variables, combined with a comparator (==
, <=
, >=
) and a constant value.
Constraints can be added to the model in two ways, where providing a name is optional:
-
Using
model.add_constraint(expression, name)
: -
Using the
+=
shorthand syntax onmodel.constraints
:
In this example, we add simple constraints involving one and two variables:
model.constraints += x >= 4, "x lower bound"
model.constraints += y <= 3, "y upper bound"
model.constraints += x - y >= 0, "two variable constraint"
model.constraints += z >= 2
model.constraints += z <= 5
You may assign a name to each constraint for better debugging and readability, though it is not required.
Use Bounds
for single-variable constraints
When a constraint involves only a single variable, it's often cleaner to express it using Bounds
directly in the variable definition.
Note: Bounds are inclusive.
The following example shows how to define equivalent constraints using bounds:
from luna_quantum import Variable, Vtype, Bounds
with model.environment:
x = Variable("x", vtype=Vtype.Integer, bounds=Bounds(lower=4))
y = Variable("y", vtype=Vtype.Integer, bounds=Bounds(upper=3))
z = Variable("z", vtype=Vtype.Real, bounds=Bounds(lower=2, upper=5))
model.constraints += x - y >= 0, "two variable constraint"
Access and Analyze your Model¶
Now that we have built a Model
, we can inspect different aspects of it.
Check out the Model
s summary with:
Output:
Model: Example
Minimize
x * z - y * z
Subject To
x lower bound: x >= 4
y upper bound: y <= 3
two variable constraint: x - y >= 0
c3: z >= 2
c4: z <= 5
Bounds
0 <= x
0 <= y
0 <= z
Integer
x y
Real
z
To inspect the objective function, we can simply call the objective of the model:
Output:It is also possible to retrieve different aspects of an expression, which can help you to better analyze and debug your optimization model. For example, if you define your expression to be the objective function, you can quickly see the number of variables included in your objective by calling expression.num_variables()
.
expression = model.objective
print(f"Number of variables included in the expression: {expression.num_variables()}")
Most importantly, it is possible to break down an expression into its constituent parts. An expression in an optimization model can be broken down into its static and variable-dependent components. The static part, or offset, is the constant value that does not depend on any variables, it represents the base value of the expression. Identifying the offset helps you recognize invariant shifts and helps to compare different expressions. You can retrieve this value using expression.get_offset()
. On the other hand, the variable-dependent part of the expression consists of the linear, quadratic, and/or higher-order coefficients (prefactors) associated with the expression's variables. Linear coefficients indicate how strongly each variable individually influences the expression, they show the direct scaling effect a variable has when altered. Quadratic and higher-order coefficients reveal interdependencies between variables, nonzero values indicate that variables are interdependant within the expression, while zero coefficients mean there is no interaction between those variables. Larger coefficients signify a greater impact on the expression from the corresponding variable or combination of variables. Using the expression.get_linear(x)
, expression.get_quadratic(x,y)
and expression.get_higher_order((x,y,z,..))
methods, you can access the coefficients related to the variables and the expression.
print(f"Offset of expression: {expression.get_offset()}")
print("linear coefficient of varibale x =", expression.get_linear(x))
print("quadratic coefficient of varibales (x, y) =", expression.get_quadratic(x, y))
print("quadratic coefficient of varibales (x, z) =", expression.get_quadratic(x, z))
Output:
Offset of expression: 0.0
linear coefficient of varibale x = 0.0
quadratic coefficient of varibales (x, y) = 0.0
quadratic coefficient of varibales (x, z) = 1.0
You can also focus on the constraints of the model. For a quick overview of the total number of constraints you can call model.num_constraints()
.
Or if you want to inspect each constraint individually, it is possible to list all constraints and their names:
for idx, constraint in enumerate(model.constraints):
print(f"Constraint: {constraint.name} -> {constraint}")
Constraint: x lower bound -> x >= 4
Constraint: y upper bound -> y <= 3
Constraint: two variable constraint -> x - y >= 0
Constraint: None -> z >= 2
Constraint: None -> z <= 5
Solving the Optimization Problem¶
Once your model is defined with an objective and constraints, you're ready to solve the optimization problem using your preferred algorithm and backend. For a walk-through of a more complex optimization problem, go to the following section where we describe the Travelling-Salesperson-Problem