Sei sulla pagina 1di 33

Mixed Integer Linear Programming

with Python

Haroldo G. Santos Túlio A.M. Toffolo

May 03, 2019


Contents:

1 Introduction 1

2 Installation 3
2.1 Gurobi Installation and Configuration (optional) . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.1 Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.1.2 Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Pypy installation (optional) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

3 Quick start 5
3.1 Creating Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.1.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.1.2 Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.1.3 Objective Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.2 Saving, Loading and Checking Model Properties . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 Optimizing and Querying Optimization Results . . . . . . . . . . . . . . . . . . . . . . . 7
3.3.1 Performance Tuning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

4 Modeling Examples 9
4.1 The 0/1 Knapsack Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
4.2 The Traveling Salesman Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

5 Developing Customized Branch-&-Cut algorithms 13


5.1 Cutting Planes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.2 Cut Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
5.3 Providing initial feasible solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

6 Classes 19
6.1 Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6.2 LinExpr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.3 Var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
6.4 Constr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
6.5 Column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
6.6 CutsGenerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
6.7 CutPool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

Python Module Index 27

i
ii
Chapter 1

Introduction

The Python-MIP package provides tools for modeling and solving Mixed Integer Linear Programming
Problems (MIPs) in Python. The default installation includes the COIN-OR Linear Programming Solver
- CLP, which is currently the fastest open source linear programming solver and the COIN-OR Branch-
and-Cut solver - CBC, a highly configurable MIP solver. It also works with the state-of-the-art Gurobi
MIP solver. Python-MIP was written in modern, statically typed Python and works with the fast
just-in-time Python compiler Pypy.
In the modeling layer, models can be written very concisely, as in high-level mathematical programming
languages such as MathProg. Modeling examples for some applications can be viewed in Chapter 3 .
Python-MIP eases the development of high-performance MIP based solvers for custom applications by
providing a tight integration with the branch-and-cut algorithms of the supported solvers. Strong for-
mulations with an exponential number of constraints can be handled by the inclusion of Cut Generators.
Heuristics can be integrated for providing initial feasible solutions to the MIP solver. These features can
be used in both solver engines, CBC and GUROBI, without changing a single line of code.
This document is organized as follows: in the next Chapter installation and configuration instructions
for different platforms are presented. In Chapter 3 an overview of some common model creation and
optimization code included. Commented examples are included in Chapter 4 . Chapter 5 includes some
common solver customizations that can be done to improve the performance of application specific
solvers. Finally, the detailed reference information for the main classes is included in Chapter 6 .

1
Mixed Integer Linear Programming with Python

2 Chapter 1. Introduction
Chapter 2

Installation

Python-MIP requires Python 3.5 or newer. Since Python-MIP is included in the Python Package Index,
once you have a Python installation, installing it is as easy as entering:

pip install mip

in the command prompt. If this command fails, maybe you don’t have permission to install globally
available Python modules. In this case, use:

pip install mip --user

The default installation includes the open source MIP Solver CBC, which is used by default. Pre-compiled
CBC libraries for Windows, Linux and MacOS are shipped. If you have the commercial solver Gurobi
installed in your computer Python-MIP will automatically use it as long as it finds the Gurobi dynamic
loadable library. Gurobi is free for academic use and has an outstanding performance for solving hard
MIPs. Instructions to make it accessible on different operating systems are included bellow.

2.1 Gurobi Installation and Configuration (optional)

2.1.1 Linux

Linux Gurobi installation notes are available here. In Linux, the Gurobi dynamic loadable library file
is libgurobixx.so, where xx is the Gurobi version. You must add the library installation directory
to the /etc/ld.so.conf file and call ldconfig after to make the library visible for all applications. If
Gurobi was installed in /opt/gurobi810 then you would have to add /opt/gurobi810/linux64/lib/ to
/etc/ld.so.conf. Since this is a system wide configuration file, you will require super user permission
to modify it. To add this Path as a configuration only for your user account, thus not requiring super
user privileges, enter this command before re-starting your session:

echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/gurobi810/linux64/lib/' >> ~/.profile

2.1.2 Windows

Windows Gurobi installation notes are available here. In Windows, the Gurobi dynamically loadable
library is the file gurobixx.dll, where xx is the Gurobi version. Be sure to set your Path environment
variable to the installation folder of this file.

3
Mixed Integer Linear Programming with Python

2.2 Pypy installation (optional)

Python-MIP is compatible with the just-in-time Python compiler Pypy. Usually Python code executes
much faster in Pypy. Pypy is also more memory efficient. To install Python-MIP as a Pypy package just
call:

pypy3 -m pip install mip

4 Chapter 2. Installation
Chapter 3

Quick start

This chapter presents the main components needed to build and optimize models in Python-MIP. A full
description of the methods and their parameters can be found at Chapter 4 .
The first step to enable Python-MIP in your Python code is to add:

from mip.model import *

when loaded, Python-MIP will display its installed version:

Using Python-MIP package version 1.0.29

3.1 Creating Models

The model class represents the optimization model. The code:

m = Model()

Creates an empty Mixed Integer Linear Programming problem setting some defaults: the optimization
sense is set to Minimize and the selected solver is set to CBC or to Gurobi, if this is installed and
configured. You can change the model objective sense or force a solver to be selected at the model
creation using additional parameters for the constructor:

m = Model(sense=MAXIMIZE, solver_name="cbc")

After creating the model, you should include your decision variables, objective function and constraints.
These tasks will be discussed in the next sections:

3.1.1 Variables

Decision variables are added to the model using the add_var() method. Without parameters, a single
variable with domain in R+ is created and its reference is returned:

x = m.add_var()

Using Python list initialization syntax you and easily create a vector of variables. Let’s say that your
model will have n binary decision variables indicating if specified items were selected or not:

y = [m.add_var(var_type=BINARY) for i in range(n)]

This code would create binary variables y[0], . . . , y[n-1]. Additional variable types are CONTINUOUS
(default) and INTEGER. Some additional properties that can be specified for variables are their lower

5
Mixed Integer Linear Programming with Python

and upper bounds (lb and ub, respectively) and their names. Entering variable names is optional but
specially useful if you plan to save you model (Section Section 3.2) in .LP or .MPS file formats, for
instance. The following code creates a variable named zCost that is restricted to be integral in the range
{−10, . . . , 10}, the reference for this variable is stored in a Python variable named z.
z=m.add_var(name='zCost', var_type=INTEGER, lb=-10, ub=10)

You don’t need to store references for variables, even though it is usually easier to do so to write the
constraints. If you do not store these references, you can get them after using the get_var_by_name
Model method. The following code retrieves a reference to a variable named zCost and sets the upper
bound for this variable to 5:
vz = m.get_var_by_name('zCost')
vz.ub = 5

3.1.2 Constraints

Constraints are linear expressions involving variables, a sense of ==, <= or >= for equal, less or equal
and greater or equal, respectively and a constant in the right-hand side. The addition of constraint
𝑥 + 𝑦 ≤ 10 to model m can be done with:
m += x + y <= 10

Summation expressions can be used with the function xsum. If for a knapsack problem with 𝑛 items,
each one with weight 𝑤𝑖 we would like to include a constraint to select items with binary variables 𝑥𝑖
respecting the knapsack capacity 𝑐, then the following code could be used to enter this constraint to our
model m:
m += xsum(w[i]*x[i] for i in range(n)) <= c

Conditional inclusion of variables in the summation is also easy. Let’s say that only even indexed items
are subjected to the capacity constraint:
m += xsum(w[i]*x[i] for i in range(n) if i%2==0) <= c

3.1.3 Objective Function

By default a model is created with the Minimize sense. You can change by setting the sense model
property to MAXIMIZE, or just multiply the objective function by -1. The following code adds 𝑛 𝑥 variables
to the objective function, each one with cost 𝑐𝑖 by setting the objective attribute of our example model
m:
m.objective = xsum(c[i]*x[i] for i in range(n))

3.2 Saving, Loading and Checking Model Properties

Model methods write() and read() can be used to save and load, respectively, MIP models. Supported
file formats for models are the LP file format, which is more readable and suitable for debugging and
the MPS file format, which is recommended for extended compatibility, since it is an older and more
widely adopted format. When calling the write() method, the file name extension (.lp or .mps) is used
to define the file format. Thus, to save our model in the lp file format in the file model.lp we can use:
m.write('model.lp')

After a model is read, you can query its attributes, like the number of variables, constraints and non-zeros
in the constraint matrix:

6 Chapter 3. Quick start


Mixed Integer Linear Programming with Python

m.read('model.lp')
print('model has {} vars, {} constraints and {} nzs'.format(m.num_cols, m.num_rows, m.num_nz))

3.3 Optimizing and Querying Optimization Results

MIP solvers execute a Branch-&-Cut (BC) algorithm that in finite time will provide the optimal solution.
This time may be, in many cases, too large for your needs. Fortunately, even when the complete tree
search is too expensive, results are often available in the beginning of the search. Sometimes a feasible
solution is produced when the first tree nodes are processed and a lot of additional effort is spent
improving the dual bound, which is a valid estimate for the cost of the optimal solution. When this
estimate, the lower bound for minimization, matches exactly the cost of the best solution found, the
upper bound, the search is concluded. For practical applications, usually a truncated search is executed.
The optimize() method, that executes the optimization of a model, accepts optionally processing limits
as parameters. The following code executes the branch-&-cut algorithm for a maximum processing time
of 300 seconds.
1 m.max_gap = 0.05
2 status = m.optimize(max_seconds=300)
3 if status==OPTIMAL:
4 print('optimal solution cost {} found'.format(m.objective_value))
5 elif status==FEASIBLE:
6 print('sol.cost {} found, best possible: {}'.format(m.objective_value, m.objective_bound))
7 elif status==NO_SOLUTION_FOUND:
8 print('no feasible solution found, lower bound is: {}'.format(m.objective_bound))
9 if status==OPTIMAL or status==FEASIBLE:
10 print('solution:')
11 for v in m.vars:
12 if abs(v.x)<=1e-7:
13 continue
14 print('{} : {}'.format(v.name, v.x))

Additional processing limits may be used: max_nodes to restrict the maximum number of explored nodes
in the search tree and max_solutions, which stops the BC algorithm after a number of feasible solutions
are found. It is also wise to specify how tight the bounds should be to conclude the search. The model
attribute max_gap specifies the allowable percentage deviation of the upper bound from the lower bound
for concluding the search. In our example, whenever the distance of the lower and upper bounds is less
or equal 5% (line 1) the search can be finished.
The optimize method returns the status of the BC search: OPTIMAL if the search was concluded and
the optimal solution was found; FEASIBLE if a feasible solution was found but there was no time to
prove whether the current solution was optimal or not; NO_SOLUTION_FOUND if in the truncated search
no solution was found; INFEASIBLE or INT_INFEASIBLE if no feasible solution exists for the model;
UNBOUNDED if there are missing constraints or ERROR if some error occurred during optimization. In the
example above, if a feasible solution is available (line 8), variables which have value different from zero
are printed. Observe also that even when no feasible solution is available the lower bound is available
(line 8).
TODO: solution pool TODO: enumerations

3.3.1 Performance Tuning

Tree search algorithms of MIP solvers deliver a set of improved feasible solutions and lower bounds.
Depending on your application you will be more interested in the quick production of feasible solutions
than in improved lower bounds that may require expensive computations, even if in the long term these
computations prove worthy to prove the optimality of the solution found. The model property emphasis
provides three different settings:
0. default setting: tries to balance between the search of improved feasible

3.3. Optimizing and Querying Optimization Results 7


Mixed Integer Linear Programming with Python

solutions and improved lower bounds;


1. feasibility: focus on finding improved feasible solutions in the first moments of the search process,
activates heuristics;
2. optimality: activates procedures that produced improved lower bounds, focusing in pruning the
search tree, even if the production of the first feasible solutions is delayed.
Changing this setting to 1 or 2 triggers the activation/deactivation of several algorithms that are pro-
cessed at each node of the search tree that impact the solver performance. Even though in average
these settings change the solver performance as described previously, depending on your formulation the
impact of these changes may be very different and it is usually worth to check the solver behavior with
these different settings in your application.

8 Chapter 3. Quick start


Chapter 4

Modeling Examples

This chapter includes commented examples on modeling and solving optimization problems with Python-
MIP.

4.1 The 0/1 Knapsack Problem

As a first example, consider the solution of the 0/1 knapsack problem: given a set 𝐼 of items, each one
with a weight 𝑤𝑖 and estimated profit 𝑝𝑖 , one wants to select a subset with maximum profit such that the
summation of the weights of the selected items is less or equal to the knapsack capacity 𝑐. Considering a
set of decision binary variables 𝑥𝑖 that receive value 1 if the 𝑖-th item is selected, or 0 if not, the resulting
mathematical programming formulation is:

Maximize:
∑︁
𝑝𝑖 · 𝑥𝑖
𝑖∈𝐼
Subject to:
∑︁
𝑤𝑖 · 𝑥𝑖 ≤ 𝑐
𝑖∈𝐼
𝑥𝑖 ∈ {0, 1} ∀𝑖 ∈ 𝐼

The following python code creates, optimizes and prints the optimal solution for the 0/1 knapsack
problem

1 from mip.model import *


2 p = [10, 13, 18, 31, 7, 15]
3 w = [11, 15, 20, 35, 10, 33]
4 c = 40
5 n = len(w)
6 m = Model('knapsack', MAXIMIZE)
7 x = [m.add_var(var_type='B') for i in range(n)]
8 m += xsum(p[i]*x[i] for i in range(n) )
9 m += xsum(w[i]*x[i] for i in range(n) ) <= c
10 m.optimize()
11 selected=[i for i in range(n) if x[i].x>=0.99]
12 print('selected items: {}'.format(selected))

Lines 1-5 load problem data. In line 6 an empty maximization model m with the (optional) name of
“knapsack” is created. Line 7 adds the binary decision variables to model m. Line 8 defines the objective
function of this model and Line 9 adds the capacity constraint. The model is optimized in line 10 and
the solution, a list of the selected items, is computed at line 11.

9
Mixed Integer Linear Programming with Python

4.2 The Traveling Salesman Problem

The traveling salesman problem (TSP) is one of the most studied combinatorial optimization problems.
To to illustrate this problem, consider that you will spend some time in Belgium and wish to visit some
of its main tourist attractions, depicted in the map bellow:

You want to find the shortest possible tour to visit all these places. More formally, considering 𝑛 points
𝐼 = {0, . . . , 𝑛 − 1} and a distance matrix 𝐷𝑛×𝑛 with elements 𝑑𝑖,𝑗 ∈ R+ , a solution consists in a set
of exactly 𝑛 (origin, destination) pairs indicating the itinerary of your trip, resulting in the following
formulation:
Minimize:
∑︁
𝑑𝑖,𝑗 . 𝑥𝑖,𝑗
𝑖∈𝐼,𝑗∈𝐼:𝑖̸=𝑗

Subject to:
∑︁
𝑥𝑖,𝑗 = 1 ∀𝑖 ∈ 𝐼
𝑗∈𝐼:𝑖̸=𝑗
∑︁
𝑥𝑖,𝑗 = 1 ∀𝑗 ∈ 𝐼
𝑖∈𝐼:𝑖̸=𝑗

𝑦𝑖 − (𝑛 + 1). 𝑥𝑖,𝑗 ≥ 𝑦𝑗 − 𝑛 ∀𝑖 ∈ 𝐼 ∖ {0}, 𝑗 ∈ 𝐼 ∖ {0, 𝑖}


𝑥𝑖,𝑗 ∈ {0, 1} ∀𝑖 ∈ 𝐽, 𝑗 ∈ 𝐼 ∖ {𝑗}
𝑦𝑖 ≥ 0 ∀𝑖 ∈ 𝐼

The first two sets of constraints enforce that we leave and arrive only once at each point. The optimal
solution for the problem including only these constraints could result in a solution with sub-tours, such
as the one bellow.

10 Chapter 4. Modeling Examples


Mixed Integer Linear Programming with Python

To enforce the production of connected routes, additional variables 𝑦𝑖 ≥ 0 are included in the model
indicating the sequential order of each point in the produced route. Point zero is arbitrarily selected as
the initial point and conditional constraints linking variables 𝑥𝑖,𝑗 , 𝑦𝑖 and 𝑦𝑗 ensure that the selection of
the arc 𝑥𝑖,𝑗 implies that 𝑦𝑗 ≥ 𝑦𝑖 + 1.
The Python code to create, optimize and print the optimal route for the TSP is included bellow:

1 from tspdata import TSPData


2 from sys import argv
3 from mip.model import *
4 from mip.constants import *
5 inst = TSPData(argv[1])
6 n = inst.n
7 d = inst.d
8 model = Model()
9 x = [ [ model.add_var(var_type=BINARY) for j in range(n) ] for i in range(n) ]
10 y = [ model.add_var() for i in range(n) ]
11 model += xsum( d[i][j]*x[i][j] for j in range(n) for i in range(n) )
12 for i in range(n):
13 model += xsum( x[j][i] for j in range(n) if j != i ) == 1
14 for i in range(n):
15 model += xsum( x[i][j] for j in range(n) if j != i ) == 1
16 for i in range(1, n):
17 for j in [x for x in range(1, n) if x!=i]:
18 model += y[i] - (n+1)*x[i][j] >= y[j] -n
19 model.optimize(max_seconds=30)
20 arcs = [(i,j) for i in range(n) for j in range(n) if x[i][j].x >= 0.99]
21 print('optimal route : {}'.format(arcs))

This example is included in the Python-MIP package in the example folder Additional code to load the
problem data (called from line 5) is included in tspdata.py. File belgium-tourism-14.tsp contains the
coordinates of the cities included in the example. To produce the optimal tourist tour for our Belgium
example just enter:

python tsp-compact.py belgium-tourism-14.tsp

In the command line. Follows an explanation of the tsp-compact code: line 10 creates the main binary
decision variables for the selection of arcs and line 11 creates the auxiliary continuous variables. Differ-
ently from the 𝑥 variables, 𝑦 variables are not required to be binary or integral, they can be declared just
as continuous variables, the default variable type. In this case, the parameter var_type can be omitted
from the add_var call. Line 11 sets the total traveled distance as objective function and lines 12-18
include the constraints. In line 19 we call the optimizer specifying a time limit of 30 seconds. This will
surely not be necessary for our Belgium example, which will be solved instantly, but may be important
for larger problems: even though high quality solutions may be found very quickly by the MIP solver,
the time required to prove that the current solution is optimal may be very large. With a time limit,

4.2. The Traveling Salesman Problem 11


Mixed Integer Linear Programming with Python

the search is truncated and the best solution found during the search is reported. Finally, the optimal
solution for our trip has length 547 and is depicted bellow:

12 Chapter 4. Modeling Examples


Chapter 5

Developing Customized Branch-&-Cut


algorithms

This chapter discusses some features of Python-MIP that allow the development of improved Branch-
&-Cut algorithms by linking application specific routines to the generic algorithm included in the solver
engine. We start providing an introduction to cutting planes and cut separation routines in the next
section, following with a section describing how these routines can be embedded in the Branch-&-Cut
solver engine using the generic cut callbacks of Python-MIP.

5.1 Cutting Planes

In many applications there are strong formulations that include an exponential number of constraints.
These formulations cannot be direct handled by the MIP Solver: entering all these constraints at once
is usually not practical. In the Cutting Planes method the LP relaxation is solved and only constraints
which are violated are inserted. The model is re-optimized and at each iteration a stronger formulation is
obtained until no more violated inequalities are found. The problem of discovering which are the missing
violated constraints is also an optimization problem (finding the most violated inequality) and it is called
the Separation Problem.
As an example, consider the Traveling Salesman Problem. The compact formulation (Section 4.2) is a
weak formulation: dual bounds produced at the root node of the search tree are distant from the optimal
solution cost and improving these bounds requires a potentially intractable number of branchings. In this
case, the culprit are the sub-tour elimination constraints involving variables 𝑥 and 𝑦. A much stronger
TSP formulation can be written as follows: consider a graph 𝐺 = (𝑁, 𝐴) where 𝑁 is the set of nodes
and 𝐴 is the set of directed edges with associated traveling costs 𝑐𝑎 ∈ 𝐴. Selection of arcs is done with
binary variables 𝑥𝑎 ∀𝑎 ∈ 𝐴. Consider also that edges arriving and leaving a node 𝑛 are indicated in 𝐴+ 𝑛
and 𝐴− 𝑛 , respectively. The complete formulation follows:

Minimize:
∑︁
𝑐𝑎 . 𝑥𝑎
𝑎∈𝐴
Subject to:
∑︁
𝑥𝑎 = 1 ∀𝑛 ∈ 𝑁
𝑎∈𝐴+
𝑛
∑︁
𝑥𝑎 = 1 ∀𝑛 ∈ 𝑁
𝑎∈𝐴−
𝑛
∑︁
𝑥(𝑖,𝑗) ≤ |𝑆| − 1 ∀𝑆 ⊂ 𝐼
(𝑖,𝑗)∈𝐴:𝑖∈𝑆∧𝑗∈𝑆

𝑥𝑎 ∈ {0, 1} ∀𝑎 ∈ 𝐴

13
Mixed Integer Linear Programming with Python

The third constraints are sub-tour elimination constraints. Since these constraints are stated for every
subset of nodes, the number of these constraints is 𝑂(2|𝑁 | ). These are the constraints that will be
separated by our cutting pane algorithm. As an example, consider the following graph:

The optimal LP relaxation of the previous formulation without the sub-tour elimination constraints has
cost 237:

As it can be seen, there are tree disconnected sub-tours. Two of these include only two nodes. Forbidding
sub-tours of size 2 is quite easy: in this case we only need to include the additional constraints: 𝑥(𝑑,𝑒) +
𝑥(𝑒,𝑑) ≤ 1 and 𝑥(𝑐,𝑓 ) + 𝑥(𝑓,𝑐) ≤ 1.
Optimizing with these two additional constraints the objective value increases to 244 and the following
new solution is generated:

14 Chapter 5. Developing Customized Branch-&-Cut algorithms


Mixed Integer Linear Programming with Python

Now there are sub-tours of size 3 and 4. Let’s consider the sub-tour defined by nodes 𝑆 = {𝑎, 𝑏, 𝑔}. The
valid inequality for 𝑆 is: 𝑥(𝑎,𝑔) + 𝑥(𝑔,𝑎) + 𝑥(𝑎,𝑏) + 𝑥(𝑏,𝑎) + 𝑥(𝑏,𝑔) + 𝑥(𝑔,𝑏) ≤ 2. Adding this cut to our model
increases the objective value to 261, s significant improvement. In our example, the visual identification
of the isolated subset is easy, but how to automatically identify these subsets efficiently in the general
case ? A subset is a cut in a Graph. To identify the most isolated subset we just have to solve the
Minimum cut problem in graphs. In python you can use the networkx min-cut module. The following
code implements a cutting plane algorithm for the asymmetric traveling salesman problem:

1 from mip.model import *


2 from itertools import product
3 from networkx import minimum_cut,DiGraph
4 N =['a', 'b', 'c', 'd', 'e', 'f', 'g']
5 A ={('a','d'):56,('d','a'):67,('a','b'):49,('b','a'):50,('d','b'):39,('b','d'):37,('c','f'):35,
6 ('f','c'):35,('g','b'):35,('b','g'):35,('g','b'):35,('b','g'):25,('a','c'):80,('c','a'):99,
7 ('e','f'):20,('f','e'):20,('g','e'):38,('e','g'):49,('g','f'):37,('f','g'):32,('b','e'):21,
8 ('e','b'):30,('a','g'):47,('g','a'):68,('d','c'):37,('c','d'):52,('d','e'):15,('e','d'):20}
9 Aout = {n:[a for a in A if a[0]==n] for n in N}
10 Ain = {n:[a for a in A if a[1]==n] for n in N}
11 m = Model()
12 x = {a:m.add_var(name='x({},{})'.format(a[0], a[1]), var_type=BINARY) for a in A}
13 m.objective = xsum(c*x[a] for a,c in A.items())
14 for n in N:
15 m += xsum(x[a] for a in Aout[n]) == 1, 'out({})'.format(n)
16 m += xsum(x[a] for a in Ain[n]) == 1, 'in({})'.format(n)
17 newConstraints=True
18 m.relax()
19 while newConstraints:
20 m.optimize()
21 print('objective value : {}'.format(m.objective_value))
22 G = DiGraph()
23 for a in A:
24 G.add_edge(a[0], a[1], capacity=x[a].x)
25 newConstraints=False
26 for (n1,n2) in [(i,j) for (i,j) in product(N,N) if i!=j]:
27 cut_value, (S,NS) = minimum_cut(G, n1, n2)
28 if (cut_value<=0.99):
29 m += xsum(x[a] for a in A if (a[0] in S and a[1] in S)) <= len(S)-1
30 newConstraints = True

Lines 5-8 are the input data. Nodes are labeled with letters in a list N and a dictionary A is used to store
the weighted directed graph. Lines 9 and 10 store output and input arcs per node. The mapping of
binary variables 𝑥𝑎 to arcs is made also using a dictionary in line 12. Line 13 sets the objective function
and the following tree lines include constraints enforcing one entering and one leaving arc to be selected
for each node. On line 18 we relax the integrality constraints of variables so that the optimization
performed in line 20 will only solve the LP relaxation and the separation routine can be executed. Our
separation routine is executed for each pair or nodes at line 28 and whenever a disconnected subset is
found the violated inequality is generated and included at line 29. The process repeats while new violated
inequalities are generated.

5.2 Cut Callback

The cutting plane method has some limitations: even though the first rounds of cuts improve significantly
the lower bound, the overall number of iterations needed to obtain the optimal integer solution may be too
large. Better results can be obtained with the Branch-&-Cut algorithm, where cut generation is combined
with branching. If you have an algorithm like the one included in the previous Section to separate
inequalities for your application you can combine it with the complete BC algorithm implemented in the
solver engine using callbacks. Cut generation callbacks (CGC) are called at each node of the search tree
where a fractional solution is found. Cuts are generated in the callback and returned to the MIP solver
engine which adds these cuts to the solver Cut Pool. These cuts are merged with the cuts generated

5.2. Cut Callback 15


Mixed Integer Linear Programming with Python

with the solver builtin cut generators and a subset of these cuts in included to the LP relaxation model.
Please note that in the Branch-&-Cut algorithm context cuts are optional components and only those
that are classified as good cuts by the solver engine will be accepted, i.e., cuts that are too dense and
have a small violation and could slow down too much the LP re-optimization can be discarded. Thus,
when using cut callbacks be sure that cuts are used only to improve the LP relaxation but not to define
feasible solutions, which need to be defined by the initial formulation. In other words, the initial model
without cuts may be weak but needs to be complete. In the case of TSP, we can include the weak
sub-tour elimination constraints presented in Section (Section 4.2) in the initial model and then add the
stronger sub-tour elimination constraints presented in the previous section as cuts.
In Python-MIP, CGC are implemented extending the CutsGenerator class.

1 from tspdata import TSPData


2 from sys import argv
3 from mip.model import *
4 from mip.constants import *
5 import networkx as nx
6 from itertools import product
7

8 class SubTourCutGenerator(CutsGenerator):
9 def __init__(self, model: Model):
10 super().__init__(model)
11

12 def generate_cuts(self, relax_solution: List[Tuple[Var, float]]) -> List[LinExpr]:


13 G = nx.DiGraph()
14 r = [(v,f) for (v,f) in relax_solution if 'x(' in v.name]
15 U = [int(v.name.split('(')[1].split(',')[0]) for v,f in r]
16 V = [int(v.name.split(')')[0].split(',')[1]) for v,f in r]
17 UV = {u for u in (U+V)}
18 for i in range(len(U)):
19 G.add_edge(U[i], V[i], capacity=r[i][1])
20 cp = CutPool()
21 for u in UV:
22 for v in [v for v in UV if v!=u]:
23 val, (S,NS) = nx.minimum_cut(G, u, v)
24 if val<=0.99:
25 arcsInS = [(v,f) for i,(v,f) in enumerate(r) if U[i] in S and V[i] in S]
26 if sum(f for v,f in arcsInS) >= (len(S)-1)+1e-4:
27 cut = xsum(1.0*v for v,fm in arcsInS) <= len(S)-1
28 cp.add(cut)
29 return cp.cuts
30

31 inst = TSPData(argv[1])
32 n,d = inst.n, inst.d
33 model = Model()
34 x = [[model.add_var(name='x({},{})'.format(i, j),
35 var_type=BINARY) for j in range(n)] for i in range(n)]
36 y = [model.add_var(name='y({})'.format(i),
37 lb=0.0, ub=n) for i in range(n)]
38 model.objective = xsum(d[i][j] * x[i][j] for j in range(n) for i in range(n))
39 for i in range(n):
40 model += xsum(x[j][i] for j in range(n) if j != i) == 1, 'enter({})'.format(i)
41 model += xsum(x[i][j] for j in range(n) if j != i) == 1, 'leave({})'.format(i)
42 for (i,j) in [(i,j) for (i,j) in product(range(1,n), range(1,n)) if i!=j]:
43 model += y[i] - (n + 1) * x[i][j] >= y[j] - n, 'noSub({},{})'.format(i, j)
44 model.cuts_generator = SubTourCutGenerator(model)
45 model.optimize()
46 arcs = [(i,j) for i in range(n) for j in range(n) if x[i][j].x >= 0.99]
47 print('optimal route : {}'.format(arcs))

16 Chapter 5. Developing Customized Branch-&-Cut algorithms


Mixed Integer Linear Programming with Python

5.3 Providing initial feasible solutions

The Branch-&-Cut algorithm usually executes faster with the availability of an integer feasible solution:
an upper bound for the solution cost improves its ability of pruning branches in the search tree and
this solution is also used in local search MIP heuristics. MIP solvers employ several heuristics for the
automatically production of these solutions but they do not always succeed.
If you have some problem specific heuristic which can produce an initial feasible solution for your ap-
plication then you can inform this solution to the MIP solver using the start model property. Let’s
consider our TSP application (Section 4.2). If the graph is complete, i.e. distances are available for
each pair of cities, then any permutation Π = (𝜋1 , . . . , 𝜋𝑛 ) of the cities 𝑁 can be used as an initial
feasible solution. This solution has exactly |𝑁 | 𝑥 variables equal to one indicating the selected arcs:
((𝜋1 , 𝜋2 ), (𝜋2 , 𝜋3 ), . . . , (𝜋𝑛−1 , 𝜋𝑛 ), (𝜋𝑛 , 𝜋1 )). Even though this solution is obvious for the modeler, which
knows that binary variables of this model refer to arcs in a TSP graph, this solution is not obvious for the
MIP solver, which only sees variables and a constraint matrix. The following example enters an initial
random permutation of cities as initial feasible solution for our TSP example, considering an instance
with n cities, and a model model with references to variables stored in a matrix x[0,...,n][0,..,n]:

1 from random import shuffle


2 S=[i for i in range(n)]
3 shuffle(S)
4 model.start = [(x[S[k-1]][S[k]], 1.0) for k in range(n)]

The previous example can be integrated in our TSP example (Section 4.2) by inserting these lines before
the model.optimize() call. Initial feasible solutions are informed in a list (line 4) of (var, value) pairs.
Please note that only the original non-zero problem variables need to be informed, i.e., the solver will
automatically compute the values of the auxiliary 𝑦 variables which are used only to eliminate sub-tours.

5.3. Providing initial feasible solutions 17


Mixed Integer Linear Programming with Python

18 Chapter 5. Developing Customized Branch-&-Cut algorithms


Chapter 6

Classes

6.1 Model

class Model(name=”, sense=’MIN’, solver_name=”)


Mixed Integer Programming Model
This is the main class, providing methods for building, optimizing, querying optimization results
and reoptimizing Mixed-Integer Programming Models.
To check how models are created please see the examples included.
add_constr(lin_expr, name=”)
Creates a new constraint (row)
Adds a new constraint to the model
Parameters
• lin_expr (LinExpr) – linear expression
• name (str ) – optional constraint name, used when saving model to lp or mps
files
Examples:
The following code adds the constraint 𝑥1 + 𝑥2 ≤ 1 (x1 and x2 should be created first using
add_var ):

m += x1 + x2 <= 1

Which is equivalent to:

m.add_constr( x1 + x2 <= 1 )

𝑛−1
∑︁
Summation expressions can be used also, to add the constraint 𝑥𝑖 = 𝑦 and name this
𝑖=0
constraint cons1:

m += xsum(x[i] for i in range(n)) == y, "cons1"

Return type Constr

add_var(name=”, lb=0.0, ub=inf, obj=0.0, var_type=’C’, column=None)


Creates a new variable in the model, returning its reference
Parameters

19
Mixed Integer Linear Programming with Python

• name (str ) – variable name (optional)


• lb (float ) – variable lower bound, default 0.0
• ub (float ) – variable upper bound, default infinity
• obj (float ) – coefficient of this variable in the objective function, default 0
• var_type (str ) – CONTINUOUS (“C”), BINARY (“B”) or INTEGER (“I”)
• column (Column) – constraints where this variable will appear, necessary only
when constraints are already created in the model and a new variable will be
created.

Examples

To add a variable x which is continuous and greater or equal to zero to model m:

x = m.add_var()

The following code creates a vector of binary variables x[0], ..., x[n-1] to model m:

x = [m.add_var(type=BINARY) for i in range(n)]

Return type Var

copy(solver_name=None)
Creates a copy of the current model
Parameters solver_name (str ) – solver name (optional)
Returns clone of current model
Return type Model
cutoff
upper limit for the solution cost, solutions with cost > cutoff will be removed from the search
space, a small cutoff value may significantly speedup the search, but if cutoff is set to a value
too low the model will become infeasible
Return type float
cuts
controls the generation of cutting planes, 0 disables completely, 1 (default) generates cutting
planes in a moderate way, 2 generates cutting planes aggressively and 3 generates even more
cutting planes. Cutting planes usually improve the LP relaxation bound but also make the
solution time of the LP relaxation larger, so the overall effect is hard to predict and it is
usually a good option to try different settings for this parameter.
Return type int
cuts_generator
Cut generator callback. Cut generators are called whenever a solution where one or more
integer variables appear with continuous values. A cut generator will try to produce one or
more inequalities to remove this fractional point.
Return type CutsGenerator
emphasis
defines the main objective of the search, if set to 1 (FEASIBILITY) then the search process will
focus on try to find quickly feasible solutions and improving them; if set to 2 (OPTIMALITY)
then the search process will try to find a provable optimal solution, procedures to further
improve the lower bounds will be activated in this setting, this may increase the time to
produce the first feasible solutions but will probably pay off in longer runs; the default option
if 0, where a balance between optimality and feasibility is sought.

20 Chapter 6. Classes
Mixed Integer Linear Programming with Python

Return type int


get_constr_by_name(name)
Queries a constraint by its name
Parameters name (str ) – constraint name
Returns constraint
Return type Constr
get_var_by_name(name)
Searchers a variable by its name
Returns a reference to the variable
Return type Var
max_mip_gap
value indicating the tolerance for the maximum percentage deviation from the optimal solution
cost, if a solution with cost 𝑐 and a lower bound 𝑙 are available and (𝑐 − 𝑙)/𝑙 < max_mip_gap
the search will be concluded.
Return type float
max_mip_gap_abs
tolerance for the quality of the optimal solution, if a solution with cost 𝑐 and a lower bound 𝑙
are available and 𝑐−𝑙 < mip_gap_abs, the search will be concluded, see mip_gap to determine
a percentage value
Return type float
max_nodes
maximum number of nodes to be explored in the search tree
Return type int
max_seconds
time limit in seconds for search
Return type float
max_solutions
solution limit, search will be stopped when max_solutions were found
Return type int
num_cols
number of columns (variables) in the model
Return type int
num_int
number of integer variables in the model
Return type int
num_nz
number of non-zeros in the constraint matrix
Return type int
num_rows
number of rows (constraints) in the model
Return type int
num_solutions
Number of solutions found during the MIP search
Returns number of solutions stored in the solution pool

6.1. Model 21
Mixed Integer Linear Programming with Python

Return type int


objective
The objective function of the problem as a linear expression.

Examples

The following code adds all x variables x[0], ..., x[n-1], to the objective function of model
m with the same cost w:

m.objective = xsum(w*x[i] for i in range(n))

A simpler way to define the objective function is the use of the model operator +=

m += xsum(w*x[i] for i in range(n))

Note that the only difference of adding a constraint is the lack of a sense and a rhs.
Return type LinExpr
objective_const
Returns the constant part of the objective function
Return type float
objective_value
Objective function value of the solution found
Return type float
objective_values
List of costs of all solutions in the solution pool
Returns costs of all solutions stored in the solution pool as an array from 0 (the
best solution) to num_solutions-1.
Return type List[float]
optimize(max_seconds=inf, max_nodes=inf, max_solutions=inf )
Optimizes current model
Optimizes current model, optionally specifying processing limits.
To optimize model m within a processing time limit of 300 seconds:

m.optimize(max_seconds=300)

Parameters
• max_seconds (float ) – Maximum runtime in seconds (default: inf)
• max_nodes (float ) – Maximum number of nodes (default: inf)
• max_solutions (float ) – Maximum number of solutions (default: inf)
Returns optimization status, which can be OPTIMAL(0), ERROR(-1), INFEASI-
BLE(1), UNBOUNDED(2). When optimizing problems with integer variables
some additional cases may happen, FEASIBLE(3) for the case when a feasible
solution was found but optimality was not proved, INT_INFEASIBLE(4) for
the case when the lp relaxation is feasible but no feasible integer solution exists
and NO_SOLUTION_FOUND(5) for the case when an integer solution was not
found in the optimization.
Return type int

22 Chapter 6. Classes
Mixed Integer Linear Programming with Python

read(path)
Reads a MIP model in .lp or .mps file format.
Parameters path (str ) – file name
relax()
Relax integrality constraints of variables
Changes the type of all integer and binary variables to continuous. Bounds are preserved.
sense
The optimization sense
Returns the objective function sense, MINIMIZE (default) or (MAXIMIZE)
Return type str
start
Initial feasible solution
Enters an initial feasible solution. Only the main binary/integer decision variables which
appear with non-zero values in the initial feasible solution need to be informed. Auxiliary or
continuous variables are automatically computed.
Return type List[Tuple[Var , float]]
status
optimization status, which can be OPTIMAL(0), ERROR(-1), INFEASIBLE(1), UN-
BOUNDED(2). When optimizing problems with integer variables some additional cases may
happen, FEASIBLE(3) for the case when a feasible solution was found but optimality was
not proved, INT_INFEASIBLE(4) for the case when the lp relaxation is feasible but no fea-
sible integer solution exists and NO_SOLUTION_FOUND(5) for the case when an integer
solution was not found in the optimization.
Return type int
threads
number of threads to be used when solving the problem. 0 uses solver default configuration,
-1 uses the number of available processing cores and ≥ 1 uses the specified number of threads.
An increased number of threads may improve the solution time but also increases the memory
consumption.
Return type int
verbose
0 to disable solver messages printed on the screen, 1 to enable
Return type int
write(path)
Saves the the MIP model, use the extension .lp or .mps in the file name to specify the file
format.
Parameters path (str ) – file name

6.2 LinExpr

class LinExpr(variables=None, coeffs=None, const=0, sense=”)


Linear expressions are used to enter the objective function and the model constraints. These
expressions are created using operators and variables.
Consider a model object m, the objective function of m can be specified as:

m.objective = 10*x1 + 7*x4

In the example bellow, a constraint is added to the model

6.2. LinExpr 23
Mixed Integer Linear Programming with Python

m += xsum(3*x[i] i in range(n)) - xsum(x[i] i in range(m))

A constraint is just a linear expression with the addition of a sense (==, <= or >=) and a right
hand side, e.g.:

m += x1 + x2 + x3 == 1

add_const(const)
adds a constant value to the linear expression, in the case of a constraint this correspond to
the right-hand-side
add_expr(expr, coeff=1 )
extends a linear expression with the contents of another
add_term(expr, coeff=1 )
extends a linear expression with another multiplied by a constant value coeff
add_var(var, coeff=1 )
adds a variable with a coefficient to the constraint
equals(other )
returns true if a linear expression equals to another, false otherwise
Return type bool

6.3 Var

class Var(model, idx, name=”)


Objects of class Var are decision variables of a model. The creation of variables is performed calling
the add_var() method of the Model class.
column
coefficients of variable in constraints
Return type Column
lb
the variable lower bound
Return type float
obj
coefficient of a variable in the objective function
Return type float
rc
reduced cost, only available after a linear programming model (no integer variables) is opti-
mized
Return type float
type
variable type (‘B’) BINARY, (‘C’) CONTINUOUS and (‘I’) INTEGER
Return type str
ub
the variable upper bound
Return type float
x
solution value
Return type float

24 Chapter 6. Classes
Mixed Integer Linear Programming with Python

xi(i )
solution value for this variable in the 𝑖-th solution from the solution pool
Return type float

6.4 Constr

class Constr(model, idx, name=”)


A row (constraint) in the constraint matrix
A constraint can be added to the model using the overloaded operator +=, e.g., if m is a model:

m += 3*x1 + 4*x2 <= 5

summation expressions are also supported:

m += xsum(x[i] for i in range(n)) == 1

6.5 Column

class Column(constrs=None, coeffs=None)


A column contains all the non-zero entries of a variable in the constraint matrix. To create a
variable see add_var()

6.6 CutsGenerator

class CutsGenerator(model )
abstract class for implementing cut generators
generate_cuts(relax_solution)
Method called by the solve engine to generate cuts
After analyzing the contents of the fractional solution in relax_solution, one or
mode cuts (LinExpr ) may be generated and returned. These cuts are added to the
relaxed model.

Parameters relax_solution (List [ Tuple [ Var, float ] ] ) – a list of tuples


(variable,value) indicating the values of variables in the current fractional so-
lution. Variables at zero are not included.

Note: take care not to query the value of the fractional solution in the cut generation
method using the x methods from original references to problem variables, use the contents
of relax_solution instead.
Return type List[LinExpr ]

6.7 CutPool
class CutPool

add(cut)
tries to add a cut to the pool, returns true if this is a new cut, false if it is a repeated one
Parameters cut (LinExpr) – a constraint

6.4. Constr 25
Mixed Integer Linear Programming with Python

Return type bool

26 Chapter 6. Classes
Python Module Index

m
mip.model, 19

27
Mixed Integer Linear Programming with Python

28 Python Module Index


Index

A num_nz (Model attribute), 21


add() (CutPool method ), 25 num_rows (Model attribute), 21
add_const() (LinExpr method ), 24 num_solutions (Model attribute), 21
add_constr() (Model method ), 19
add_expr() (LinExpr method ), 24 O
add_term() (LinExpr method ), 24 obj (Var attribute), 24
add_var() (LinExpr method ), 24 objective (Model attribute), 22
add_var() (Model method ), 19 objective_const (Model attribute), 22
objective_value (Model attribute), 22
C objective_values (Model attribute), 22
Column (class in mip.model ), 25 optimize() (Model method ), 22
column (Var attribute), 24
Constr (class in mip.model ), 25 R
copy() (Model method ), 20 rc (Var attribute), 24
cutoff (Model attribute), 20 read() (Model method ), 22
CutPool (class in mip.model ), 25 relax() (Model method ), 23
cuts (Model attribute), 20
cuts_generator (Model attribute), 20 S
CutsGenerator (class in mip.model ), 25 sense (Model attribute), 23
start (Model attribute), 23
E status (Model attribute), 23
emphasis (Model attribute), 20
equals() (LinExpr method ), 24 T
threads (Model attribute), 23
G type (Var attribute), 24
generate_cuts() (CutsGenerator method ), 25
get_constr_by_name() (Model method ), 21 U
get_var_by_name() (Model method ), 21 ub (Var attribute), 24

L V
lb (Var attribute), 24 Var (class in mip.model ), 24
LinExpr (class in mip.model ), 23 verbose (Model attribute), 23

M W
max_mip_gap (Model attribute), 21 write() (Model method ), 23
max_mip_gap_abs (Model attribute), 21
max_nodes (Model attribute), 21 X
max_seconds (Model attribute), 21 x (Var attribute), 24
max_solutions (Model attribute), 21 xi() (Var method ), 25
mip.model (module), 19
Model (class in mip.model ), 19

N
num_cols (Model attribute), 21
num_int (Model attribute), 21

29

Potrebbero piacerti anche