Creating a Mathematical Programming Model

This page provides a step-by-step guide on how to build mathematical programming models using the docplex.mp Python library. It covers the entire process from defining decision variables and constructing various types of mathematical expressions (like linear and quadratic expressions and piecewise-linear functions), to formulating different kinds of constraints and setting objective functions. Finally, it explains how to solve the model and retrieve the solution, with practical examples and concise code illustrations for each concept.

1. What is a Mathematical Programming Model?

A mathematical programming model consists of:

  1. Decision Variables: These represent the unknown choices you need to make (e.g., how much of a product to produce, whether to invest in a project). Each variable has a defined scope or domain (e.g., binary, integer, continuous).

  2. Constraints: These are mathematical statements (equations or inequalities) that describe the limitations, requirements, or relationships between the decision variables (e.g., resource limitations, demand satisfaction). Only solutions that satisfy all constraints are considered feasible.

  3. Objective Function (Optional): This is a mathematical expression, usually involving decision variables, that quantifies the goal you want to achieve (e.g., maximize profit, minimize cost). The solver will try to find a feasible solution that yields the best possible objective value. If no objective is defined, the solver aims to find any feasible solution.

2. The Core: The Model Object

The first step in the Python script is typically to import the Model class and create an instance of it. Everything in the mathematical program revolves around this Model object—it’s your main interface for building, modifying, and solving the model.

from docplex.mp.model import Model
mdl = Model(name='my_first_model') # Create a new model instance

3. Defining Decision Variables

Decision variables are the unknowns in the model to determine. DOcplex provides factory methods on the Model object to create these variables, offering several distinct types to suit different modeling needs.

3.1. Variable Types

There are three primary types:

  • Binary: Variables that can only take values 0 or 1 (e.g., for yes/no decisions).

  • Integer: Variables that can only take integer values within their bounds (e.g., number of units to produce).
    • Semi-Integer: A special case of integer variables that can either be 0 or take integer values within a specified range starting from a minimum positive value (e.g., a resource is either unused or used in batches of a minimum size).

  • Continuous: Variables that can take any real value within their bounds (e.g., amount of a substance).
    • Semi-Continuous: A special case of continuous variables that can either be 0 or take any real value within a specified range above a minimum threshold (e.g., a machine is either off or produces at least a minimum required amount).

3.2. Creating Single Variables

This section describes how to define individual decision variables, each representing a specific and distinct choice or measurable quantity in the model. The method Model.binary_var(name=None) creates a binary decision variable and adds it to the model.

To define integer and continuous decision variables, use Model.integer_var() and Model.continuous_var() respectively. These functions accept parameters such as lb (lower bound), ub (upper bound), and name, where the default lower bound is 0 and the default upper bound is infinity.

a = mdl.binary_var(name='select_a')              # Single binary variable
b = mdl.integer_var(name='num_b', lb=10, ub=20)  # Single integer variable for number of products, between 10 and 20
c = mdl.continuous_var(name='amount_c', lb=100)  # Single continuous variable for an amount, greater than 100

For semi-integer and semi-continuous decision variables, use Model.semiinteger_var() and Model.semicontinuous_var(). These also accept lb, ub, and name as parameters. In these cases, the lower bound lb must be strictly positive.

d = mdl.semicontinuous_var(name='usage_d', lb=50)  # Single semi-continuous variable for usage, either 0 or a continuous value ≥ 50

The following table summarizes the factory methods provided by the Model class to create collections of decision variables. These methods allow you to define groups of variables—organized as lists, dictionaries, or matrices—according to their type: binary, (semi-)integer, or (semi-)continuous. Each method returns a structured collection of decision variables, making it easier to model problems that require multiple related variables.

Collection Type

Binary Variable

(Semi-)Integer Variable

(Semi-)Continuous Variable

List

binary_var_list()

integer_var_list() semiinteger_var_list()

continuous_var_list() semicontinuous_var_list()

Dictionary

binary_var_dict()

integer_var_dict() semiinteger_var_dict()

continuous_var_dict() semicontinuous_var_dict()

Matrix

binary_var_matrix()

integer_var_matrix() semiinteger_var_matrix()

continuous_var_matrix() semicontinuous_var_matrix()

3.2.1. Supported Parameters

All these methods support the name parameter. When a name is provided, individual variables are automatically named using indices or keys appended to that name. For example:

# Creating a list of binary variables
my_list = mdl.binary_var_list(3, name='status')   # List of 3 binary variables

This creates variables named status_0, status_1, and status_2, which can be accessed as my_list[0], my_list[1], my_list[2].

# Creating a dictionary of integer variables indexed by product names, with bounds
products = ['apple', 'banana', 'cherry']  # List of product names
production_qty = mdl.integer_var_dict(products, lb=0, ub=100, name='prod')

This creates variables named prod_apple, prod_banana, and prod_cherry, each bounded between 0 and 100. Access them as production_qty['apple'], production_qty['banana'], production_qty['cherry'].

# Creating a matrix of continuous variables indexed by city pairs, with bounds
cities = ['London', 'Paris', 'Berlin']  # List of cities
transport_flow_matrix = mdl.continuous_var_matrix(keys1=cities, keys2=cities, lb=20.0, ub=1000.0, name='flow')

This creates variables named flow_London_Paris, flow_Paris_Berlin, etc., each bounded between 20.0 and 1000.0. Access them as transport_flow_matrix[‘London’, ‘Paris’], transport_flow_matrix[‘Paris’, ‘Berlin’].

# Creating a dictionary of continuous variables for city pairs excluding self-flows, with bounds
transport_flow_dict = mdl.continuous_var_dict([(c1, c2) for c1 in cities for c2 in cities if c1 != c2], lb=20.0, ub=1000.0 name='flow')

This creates variables such as flow_London_Paris and flow_Paris_London, with bounds from 20.0 to 1000.0. It excludes same-city pairs (e.g., no flow_London_London). Access them as transport_flow_dict['London', 'Paris'], transport_flow_dict['Paris', 'London'].

4. Building Expressions

Expressions are mathematical formulas that combine decision variables, constants, and arithmetic operations. They form the foundation of optimization models by defining the objective function (what you want to optimize) and the constraints (rules that must be satisfied). Using expressions, you can model relationships such as totals, differences, ratios, logical expressions, and more, allowing the solver to evaluate and compare solutions effectively.

4.1. Linear Expressions

Linear expressions are composed of terms in the form of constant * variable, optionally summed together. They represent relationships where each variable appears to the power of one, making them suitable for linear programming models. These expressions are used to define linear objectives and constraints that the solver can efficiently optimize.

4.1.1. Using Python Arithmetic Operators

Python’s standard arithmetic operators +, -, and * (for scalar multiplication) are overloaded in DOcplex to allow intuitive construction of expressions involving decision variables. This makes it easy to build linear and other expressions using natural Python syntax, improving readability and reducing the need for complex function calls.

linear_expr = 3 * a + 2 * b - 5 * c + 10

4.1.2. Sum Expressions

In DOcplex, several methods are available to create sum expressions efficiently when working with decision variables and expressions. These are optimized for use in mathematical programming models and provide much better performance than Python’s built-in sum() function.

Model.sum() - This is the most general-purpose summation method. It accepts any iterable containing decision variables, linear or quadratic expressions (see Section 4.2.), or constants.

num_vars = mdl.integer_var_list(1000, name='n') # num_vars is a list of 1000 integer variables
total_sum = mdl.sum(num_vars)                   # sum of the variables

You can also sum mixed expressions:

expr_sum = mdl.sum([a + 1, b * 2, 5, c])

Model.sum_vars() - This method is optimized for summing only decision variables. It is faster than Model.sum() because it avoids handling expressions.

expr_sum = mdl.sum_vars(num_vars)

Model.sum_vars_all_different() - This is a method for summing a list of distinct decision variables. It assumes the input variables are all different and improves speed accordingly.

expr_sum = mdl.sum_vars_all_different(num_vars)

Model.sums() - This is a shortcut to Model.sum() that accepts a variable number of arguments instead of a single iterable.

expr_sum = mdl.sums(a + 1, b + 2, c + 3)

4.1.3. Scalar Product

To compute the scalar product (also known as the dot product) of a list of variables and a list of coefficients, DOcplex provides the Model.dot() or Model.scal_prod() methods. These methods allow you to efficiently calculate the sum of the element-wise product of two lists, one of decision variables and one of constants or coefficients. This is commonly used in optimization models to represent linear relationships between variables and their coefficients, such as in the objective function or constraints. Both methods are optimized for performance, ensuring quick computations even in large-scale models.

costs = [10.5, 12.0, 9.75]
quantities = mdl.continuous_var_list(3, name='q', lb=0)
total_cost_expr = mdl.dot(quantities, costs)
# Equivalent to: quantities[0]*costs[0] + quantities[1]*costs[1] + quantities[2]*costs[2]
# OR total_cost_expr = mdl.scal_prod(quantities, costs)

4.1.4. Discrete Linear Expresssions

A linear expression can be discrete, that is it contains only integer or binary variables having integer coefficents and the constant of the expression is integer. Discrete linear expressions are the only one allowed in some logical operators like and, or, if_then, and not (see Section 6.4).

For instance, consider the following variables declarations:

x = mdl.integer_var(lb=0, ub=5, name="x")
b = mdl.binary_var(name="b")
c = mdl.continuous_var(ub = 1000, name="y")
  • The terms x, x+2, and x + 2*b + 3 are discrete because they are made up of integer and binary variables with integer coefficients and constants.

  • The term x + 1.2*b + 1 is not discrete because the variable b has a non-integer coefficient.

  • The term c + x + y is not discrete because it contains the continuous variable c.

The member function docplex.mp.linear.LinearExpr.is_discrete() returns true if the invoked term is discrete. It can be used to make sure that an expression is a discrete linear one.

4.2. Quadratic Expressions

Quadratic expressions involve products of two decision variables (e.g., variable * variable or variable1 * variable2). Such terms and their sums form quadratic expressions used in optimization models, which are often required to be convex in optimization models.

quadratic_expr = 3 * a * a + 4 * b * b + 2 * a * b + 5 * a + 7

DOcplex provides the Model.sum_squares() method to create a quadratic expression that sums the squares of linear expressions or decision variables. Each term is squared and added to the result.

sum_sq_expr = mdl.sum_squares([a, b + 2, c - 1])

This gives the expression equivalent to a^2 + (b+2)^2 + (c-1)^2.

4.3. Piecewise-Linear Function

Piecewise-linear function defines a relationship where the slope changes at specific breakpoints, creating a series of linear segments. DOcplex provides two methods for defining a piecewise-linear (PWL) function:

4.3.1. Defining PWL function using coordinate breakpoints

The method Model.piecewise(preslope, breaksxy, postslope, name=None) defines a PWL function y = f(x), where f is composed of linear segments, by specifying

  • preslope: Before the first segment of the PWL function there is a half-line; its slope is specified by this argument.

  • breaksxy: A list (x[i], y[i]) of coordinate pairs defining segments of the PWL function.

  • postslope: After the last segment of the the PWL function there is a half-line; its slope is specified by this argument.

  • name: An optional name.

Example:

Suppose we want a function f(x) with the following behavior:

Value of x

Piecewise Linear Value ``f(x)``

x < 100

5 * x

100 <= x <= 500

3 * x + 200

x > 500

2 * x + 700

This can be written as follows:

x = mdl.continuous_var(name='domain', ub=1000)
pw_expr = mdl.piecewise(preslope=5, breaksxy=[(100, 500), (500, 1700)], postslope=2, name='cost_expr')
pw_val = pw_expr(x)    # Evaluate the piecewise function at the variable x
pw_ct = (c == pw_val)  # Create a constraint: c == f(x)

4.3.2. Defining PWL function using slopes and X-breakpoints

The method Model.piecewise_as_slopes(slopebreaksx, lastslope, anchor=(0, 0), name=None) defines a PWL function y = f(x) using slopes and the x values where slope changes.

Parameters: - slopebreaksx: List of (slope, x) pairs, defining slope changes at each x. - lastslope: The slope after the last specified breakpoint. - anchor: the starting point of the function. - name: optional name.

Example:

The piecewise-linear expression of example above can be written using this function as follows:

pw_expr2 = mdl.piecewise_as_slopes(slopebreaksx=[(5, 100), (3, 500)], lastslope=2, anchor=(0, 0), name='cost_expr_alt')

4.4 Functional Expressions – max, min, abs

These methods are used to build expressions involving the maximum, minimum, or absolute value of decision variables or other expressions. The table below summarizes the commonly used Docplex methods for constructing these expressions within optimization models.

Function

Description

Model.max()

Returns the maximum of a list of expressions or variables

Model.min()

Returns the minimum of a list of expressions or variables

Model.abs()

Returns the absolute value of an expression

Example:

max_val = mdl.max(a, b, c)  # Returns the maximum of variables a, b, and c
min_val = mdl.min(a, b, c)  # Returns the minimum of variables a, b, and c

You can also compute the minimum and maximum over linear expressions:

max_expr = mdl.max(2 * b, c + 10, a + b)   # Maximum of three expressions
min_expr = mdl.min(3 * b, c + 3, a - b)    # Minimum of three expressions

You can also use a list instead of passing each item individually:

var_list = [a, b, c]
max_var_list = mdl.max(var_list)        # Maximum of variables in a list
min_var_list = mdl.min(var_list)        # Minimum of variables in a list

Linear expressions can be combined into a list as well:

expr_list = [2 * b, c + 10, a]
max_expr_list = mdl.max(expr_list)      # Maximum of expressions in a list
min_expr_list = mdl.min(expr_list)      # Minimum of expressions in a list

5. Defining the Objective Function

The objective function specifies the goal of the optimization—either to minimize or maximize a particular expression. It is typically defined using decision variables and expressions that represent costs, profits, resource usage, or other quantities of interest. In DOcplex, you define the objective function using Model.minimize() or Model.maximize() methods.

5.1. Minimization and Maximization

To specify the optimization direction, use Model.minimize() when the goal is to find the smallest possible value (e.g., minimizing cost or time), or Model.maximize() when aiming to find the largest value (e.g., maximizing profit or efficiency). These methods take a linear (possibly over piecewise linear expressions) or quadratic expression as input and define it as the objective function.

# Define a linear expression
obj_expr = 3 * a + 2 * b + c

# Minimize the expression
mdl.minimize(obj_expr)

5.2. No Objective (Feasibility Problems)

In some cases, the goal is not to optimize a value but simply to find a solution that satisfies all the constraints. These are called feasibility problems. If you do not define an objective function using Model.minimize() or Model.maximize(), the solver will attempt to find any feasible solution that meets all constraints.

6. Building Constraints

Constraints define the rules, constraints, or limitations that must be satisfied by any feasible solution to the model. They are built by forming expressions using decision variables and Python’s overloaded comparison operators like <=, >=, ==, and !=.

6.1. Linear Constraints

Linear constraints are the most common type of constraints in mathematical programming models. They involve linear expressions (see section 4.1) that relate decision variables through the comparison operators.

linear_expr = 3 * a + 2 * b - 5 * c + 10
linear_ct = (linear_expr <= 5)

These constraints are not yet part of the model. They need to be added to the model.

6.2. Quadratic Constraints

Quadratic constraints are constraints involving quadratic expression. In DOcplex, quadratic constraints are allowed as long as they define a convex feasible region, that is, the underlying mathematical problem must remain convex. Convexity ensures that the quadratic matrix of the expression is positive semi-definite.

quadratic_expr = 3 * a * a + 4 * b * b + 2 * a * b + 5 * a + 7
quadratic_ct = (quadratic_expr <= 4)

For more detailed information see https://www.ibm.com/docs/en/icos/22.1.2?topic=areas-quadratic-programming.

6.3. Range Constraints

A range constraint specifies that a linear expression must fall between a lower and an upper bound. In DOcplex, the method Model.range_constraint() allows you to create such a constraint.

# Define a range constraint: 10 ≤ a + 2 * b ≤ 25
range_expr = a + 2 * b
range_ct = mdl.range_constraint(10.0, range_expr, 25.0, rng_name='bounded_sum')

6.4. Logical Constraints

Logical constraints are used to express complex relations between decision variables by way of logical relations over linear constraints. Apart from the indicator constraint, all relationship over linear constraints need discrete linear constraints. That is constraints whose linear expression is discrete and right-hand side is integer. For instance, assume x and y are integer variables, the constraint 2*x + 3*y <= 4 is a discrete linear constraint because it is made of a discrete linear term and an integer right-hand-side.

6.4.1. Linking a binary variable and a constraint

Indicator Constraint

An indicator constraint is a compact and efficient way to model conditional linear constraints based on the value of a binary decision variable. It allows you to specify that a linear constraint should only be enforced when a given binary variable takes a specific value. In DOcplex the Model.indicator_constraint() function is used to define such behavior.

linear_ct = (b + c <= 10)
indicator_ct = mdl.indicator_constraint(a, linear_ct, active_value=1)
# meaning: if a == 1, then b + c <= 10

By default, active_value=1, so the linear constraint is enforced when the binary variable is 1. However, if you want the constraint to be enforced when the binary variable is 0, you can set active_value=0.

indicator_ct2 = mdl.indicator_constraint(a, linear_ct, active_value=0)
# meaning: if a == 0, then b + c <= 10

The constraint appearing in an indicator constraint does not have to be discrete (it can contain continuous variables and coefficients) but it has to be linear (it cannot be quadratic for instance).

Equivalence Constraint

It enforces that the binary variable equals the specified true_value (0 or 1) if and only if the discrete linear constraint is satisfied. The method Model.equivalence_constraint(binary_var, discrete_linear_ct, true_value=1, name=None) is used to define this where the default true_value is 1.

test = mdl.binary_var(name = "test")
ct = (b + a <= 10)
eqv_ct = mdl.equivalence_constraint(test, ct, true_value=1)

This means that test = 1 if and only if b + a <= 10.

6.4.2 Logical expressions

Logical expressions are expressions made of

  • binary variables,

  • discrete linear constraints,

  • logical operators and, or, not over logical expressions.

Docplex provides the following functions for building and, or, and not expressions :

  • conjunction (AND) : Model.logical_and()

  • disjunctions (OR) : Model.logical_or()

  • negation (NOT) : Model.logical_not()

A logical expression can be equated to a binary variable. It can also be added to a model, becoming a logical constraint. Logical expressions are not handled as such by DOcplex engine but they are translated to a set of indicator or equivalence constraints by introducing extra binary variables.

Logical OR

The Model.logical_or() function models a disjunction: at least one of the input constraints must hold.

Example 1: Binary control variable

Variable c is set to 1 if and only if either a or b is equal to 1.

or_expr = mdl.logical_or(a == 1, b == 1)
or_ct1 = (c == or_expr)

Example 2: Scheduling – Non-overlapping activities

In a scheduling problem, two tasks A and B with respective durations 5 and 7 cannot overlap. Either A starts after B ends or vice versa:

startA = mdl.integer_var(name="startA", lb = 0, ub = 1000)
startB = mdl.integer_var(name="startB", lb = 0, ub = 1000)
durationA = 5
durationB = 7

non_overlap_ct = mdl.logical_or(startA >= startB + durationB, startB >= startA + durationA)

This constraint ensures that activities A and B do not overlap in time.

Logical AND

The Model.logical_and() function models a conjunction: all constraints must hold for the expression to be true.

Example 1: Binary conjunction

Binary variable c is set to 1 if and only if a == 1 and b <= 10.

and_expr = mdl.logical_and(a == 1, b <= 10)
and_ct1 = (c == and_expr)

Example 2: Enforcing bonus eligibility

An employee receives a bonus (bonus == 1) only if performance ≥ 90 and attendance ≥ 95.

performance = mdl.integer_var(name="performance")
attendance = mdl.integer_var(name="attendance")
bonus = mdl.binary_var(name="bonus")

bonus_condition = mdl.logical_and(performance >= 90, attendance >= 95)
and_ct2 = (bonus == bonus_condition)

Logical NOT

The Model.logical_not() function returns the negation of a constraint.

Example 1: Negation

Variable a is 1 if and only if variable b is not equal to 1.

a = mdl.binary_var(name="a")
negation_ct = (b == mdl.logical_not(a))

Example 2: Modeling a denial

In a configuration model, if optionA is selected (optionA == 1), then optionB must not be selected. This can be modeled by:

optionA = mdl.binary_var(name="optionA")
optionB = mdl.binary_var(name="optionB")

denial_ct = (optionB == mdl.logical_not(optionA == 1))

6.4.3. If-Then Constraints

The Model.if_then(if_ct, then_ct, negate=False) function models the implication relationships between two discrete linear constraints. This function creates a logical implication - if the constraint if_ct is satisfied, then the constraint then_ct must also be satisfied, where if_ct and then_ct should be linear constraints. The negate parameter is optional and defaults to False; when set to True, it negates the meaning of the if_ct part.

if_ct = (a == 1)
then_ct = (c <= 2)
if_then_ct1 = mdl.if_then(if_ct, then_ct)

This means that if a == 1 then c <= 2.

if_then_ct2 = mdl.if_then(if_ct, then_ct, negate=True)

This means that if a != 1 then c <= 2.

6.4.5 Constraints as expressions

Any discrete linear constraint and any logical expression built using Model.logical_and(), Model.logical_or(), or Model.logical_not() can be treated as an expression term inside a broader linear constraint.

For instance, the non-overlapping constraint illustrating logical_or above:

non_overlap_ct_1 = mdl.logical_or(startA >= startB + durationB, startB >= startA + durationA)

can be also written

non_overlap_ct_2 = ((startA >= startB + durationB) + (startB >= startA + durationA) >= 1)

Since the two constraints cannot be satisfied at the same time, the formulation can be tighthened to

non_overlap_ct_3 = ((startA >= startB + durationB) + (startB >= startA + durationA) == 1)

The implication constraint illustrating if_then constraint above:

if_then_ct1 = mdl.if_then(a == 1, c <= 2)

can be also written

implication_ct = ((a == 1) <= (c <= 2))

Let us illustrate the use of logical expression inside linear constraints with an example:

p = mdl.integer_var(name="performance", lb = 0, ub = 100)
at = mdl.integer_var(name="attendance", lb = 0, ub = 200)
bonus = mdl.binary_var(name="bonus")

Here, p and at represent discrete measures (for example, an employee’s performance and attendance), and bonus is a binary decision variable that indicates whether a bonus is awarded. We can now define some discrete linear constraints:

u_ct = (p >= 90)
v_ct = (at >= 95)

These constraints describe eligibility thresholds for performance and attendance. We can build logical expressions from them:

and_uv = mdl.logical_and(u_ct, v_ct)
or_uv = mdl.logical_or(u_ct, v_ct)

The and_uv expression will be true (i.e., equal to 1) only if both conditions are met; or_uv will be true if at least one is satisfied. We can also combine constraints using nested logical constructs:

and1 = mdl.logical_and(p <= 90, at <= 95)
or1  = mdl.logical_or(at + p == 20, and1)
or2  = mdl.logical_or(at + p == 170, and_uv)

In these lines:

  • and1 captures whether both performance and attendance are below a certain level,

  • or1 checks whether the sum of the two is 20 or both are low,

  • or2 verifies whether the sum is 170 or both exceed the threshold.

Finally, we can express an implication directly as a constraint:

combined_ct = mdl.logical_and(at <= 200, p <= 100) <= (at+p == 20)

This final line reads as: If a <= 200 and p <= 100, then a + p == 20.

Note: A constraint only affects the solution if it has been added to the model. See Section 7.1 for details on how to add constraints.

6.5. Special Ordered Sets (SOS)

The Model.add_sos(dvars, sos_arg, weights=None, name=None) method is used to define Special Ordered Sets (SOS) in a mathematical programming model. These sets impose restrictions on which variables in a list can take non-zero values, and are particularly useful in piecewise linear approximations and branching decisions. Here the parameters:

  • dvars: A sequence (e.g. list or array) of decision variables that form the SOS.

  • sos_arg: The type of SOS, either:
    • 1 : At most one variable in the set can be non-zero.

    • 2 : At most two non-zero variables, and they must be adjacent in the ordered list.

  • weights (optional): A list of numerical weights that define the order in which the variables appear in the SOS. Must match the length of dvars.

  • name (optional): A string name for the SOS constraint.

sos1 = mdl.add_sos(dvars=[a, b, c], sos_arg=1, weights=[1, 2, 3])

The Model.add_sos() method adds the SOS constraint directly to the model. The methods Model.add_sos1(dvars, name=None) and Model.add_sos2(dvars, name=None) are shortcut functions for adding SOS constraints of Type 1 and Type 2, respectively.

Note:
  • The Model.add_sos() method adds the SOS constraint directly to the model.

  • SOS constraints are not standard algebraic constraints and they don’t appear in the standard count of constraints.

  • Order Matters: For SOS Type 2, adjacency is determined by the increasing order of the weights. If weights is not provided, the default order is the order in which the decision variables appear in the dvars list.

  • No Ties Allowed:: Tied weights (i.e., duplicate values) are not permitted.

The methods Model.add_sos1(dvars, name=None) and Model.add_sos2(dvars, name=None) are shortcut functions for adding SOS constraints of Type 1 and Type 2, respectively. These methods do not accept a weights argument directly, as they assume that the order of variables in the list dvars defines the SOS sequence.

7. Assembling and Solving the Model

After defining decision variables, expressions, and constraints, the next step is to assemble the components into a complete model and solve it. This phase brings all parts of the optimization problem together and invokes the solver to find the best solution.

7.1. Adding Constraints to the Model

Constraints define the feasible region of a mathematical programming model. Once a constraint expression is created, it must be explicitly added to the model for it to take effect.

For all types of constraints except Special Ordered Sets (SOS), the common methods to add a single constraint to the model are Model.add(ct) and Model.add_constraint(ct). These general-purpose methods work for most standard constraint types including linear, quadratic, range, logical, indicator and equivalence. In addition, the following specialized methods are also available for different constraint types.

Constraint type

Specialized method for single constraint addition

Range

Model.add_range(lb, linear_expr, ub, rng_name=None)

If-Then

Model.add_if_then(if_ct, then_ct, negate=False)

Indicator

Model.add_indicator(binary_var, linear_ct, active_value=1, name=None)

Equivalence

Model.add_equivalence(binary_var, discrete_ct, true_value=1, name=None)

For linear, range, and equivalence constraints, the common method to add multiple constraints is Model.add_constraints(cts). Additionally, the following specialized methods are available for different constraint types.

Constraint type

Specialized methods for multiple constraint addition

Quadratic

  • Model.add_quadratic_constraints(quadratic_cts)

Indicator

  • Model.add_indicator_constraints(ind_cts)

  • Model.add_indicators(binary_vars, linear_cts, true_values=1, names=None)

Equivalence

  • Model.add_equivalence_constraints(eq_cts)

  • Model.add_equivalences(binary_vars, discrete_cts, true_values=1, names=None)

SOS constraints define variable selections with ordering or priority, and must be added using the following dedicated methods.

  • Model.add_sos(dvars, sos_arg, weights=None, name=None)

  • Model.add_sos1(dvars, name=None)

  • Model.add_sos2(dvars, name=None)

7.2. Printing Model Information

Once the model is assembled, you can print a summary of its components—such as the number of variables, constraints, and types of constraints—using Model.print_information().

mdl.print_information()

This method is useful for verifying the model structure before solving and for debugging purposes.

8. Retrieving and Analyzing Results

After solving the model using the Model.solve() method, the results are stored in a SolveSolution object. This object contains the values for the decision variables corresponding to the best solution found (if one was found), the objective function, and additional information about the solving process. The SolveSolution object allows you to retrieve and analyze the results of your optimization model.

8.1. Checking Solution Status

Before retrieving any solution data, it is important to verify whether a solution was actually found. You can do this by checking the result returned by the Model.solve() method. If the model is feasible and a solution is found, the Model.solve() method will return a docplex.mp.solution.SolveSolution object. If the model is infeasible, or if the solver encounters an error, the method will return None. It is crucial to always check if a solution was found to avoid errors or incorrect assumptions when accessing the results.

solution = mdl.solve()
if solution:
    print("Solution found")
else:
    print("No solution found or model is infeasible.")

8.2. Printing Solution Summary

After confirming a solution exists, you can quickly print a summary of the results — including the best objective value found and all decision variables with nonzero values — by calling Model.print_solution()

mdl.print_solution()

This method is a convenient way to inspect the key outcomes of your optimization without manually iterating over variables and objectives.

8.3. Accessing Objective Value

If an objective function was defined in the model and a valid solution is found, you can access the objective value using the objective_value attribute of the SolveSolution object. This provides the value of the objective function corresponding to the best solution found by the solver. The objective value represents the result of evaluating the objective function with the decision variable values obtained in the solution. For minimization problems, this value will be the minimum objective value, and for maximization problems, it will be the maximum objective value.

print(f"Objective value: {solution.objective_value}")

8.4. Accessing Variable Values

After solving the model, you can access the values of decision variables from the SolveSolution object. These values of the decision variables corresponding to the best solution found that satisfy all constraints in the model. To retrieve the value of a specific decision variable, use the .solution_value attribute of the variable, which holds the solution value for that variable.

print(f"Value of a: {a.solution_value}")

Alternatively, solution[variable_object] another way to access the solution values.

print(f"Value of a: {solution[a]}")

9. Exporting the Model (LP File)

Docplex provides several ways to export an optimization model to different formats, include LP (Linear Programming), MPS (Mathematical Programming System), and SAV (CPLEX binary format). This method is useful for debugging, sharing the model in a standard format, or using the model with other solvers. Some commonly used export methods described in the following table.

Method

Description

Model.export_as_lp("filename.lp")

Exports the model in LP (Linear Programming) format.

Model.export_as_mps("filename.mps")

Exports the model in MPS (Mathematical Programming System) format.

Model.export_as_sav("filename.sav")

Saves the model in CPLEX’s binary SAV format.

Model.export("filename.extn")

General export method that infers the format from the file extension.

Model.export_to_stream("filename.lp")

Writes the model to a stream in LP format.

10. A complete example

The following is a condensed example of a sudoku problem that uses the default import policy. More comments are available in the files in the directory docplex/mp/examples.

from docplex.mp.model import Model

myInput =[[8, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 3, 6, 0, 0, 0, 0, 0],
 [0, 7, 0, 0, 9, 0, 2, 0, 0],
 [0, 5, 0, 0, 0, 7, 0, 0, 0],
 [0, 0, 0, 0, 4, 5, 7, 0, 0],
 [0, 0, 0, 1, 0, 0, 0, 3, 0],
 [0, 0, 1, 0, 0, 0, 0, 6, 8],
 [0, 0, 8, 5, 0, 0, 0, 1, 0],
 [0, 9, 0, 0, 0, 0, 4, 0, 0]]

model = Model("sudoku")
R = range(1, 10)
idx = [(i, j, k) for i in R for j in R for k in R]

x = model.binary_var_dict(idx, "X")

for i in R:
    for j in R:
        if myInput[i - 1][j - 1] != 0:
            model.add_constraint(x[i, j, myInput[i - 1][j - 1]] == 1)

for i in R:
    for j in R:
        model.add_constraint(model.sum(x[i, j, k] for k in R) == 1)
for j in R:
    for k in R:
        model.add_constraint(model.sum(x[i, j, k] for i in R) == 1)
for i in R:
    for k in R:
        model.add_constraint(model.sum(x[i, j, k] for j in R) == 1)

solution = model.solve()
solution.print_information()

The solve() method returns an object of class SolveSolution that contains the result of solving, or None if the model has no solution. This object is described in the Section 8.

The method print_information() prints a default view of the status of the solve and the values of all variables. The object SolveSolution contains all the necessary accessors to create a customized solution output.