Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Lecture 8: Integer Optimization

Open In Colab

An integer linear optimization (ILO) problem is an LO problem with the extra requirement that some or all decision variables take integer values, xi∈{0,1,2,… }x_i \in \{0, 1, 2, \dots\}, or are binary, xi∈{0,1}x_i \in \{0, 1\}. (A binary variable is just an integer one with the added constraint xi≤1x_i \le 1.)

In pulp this is a one-word change: set a variable’s cat to "Integer" or "Binary" instead of the default "Continuous". Everything else about building and solving the model is the same. Solving it, however, is a different matter: in general ILO is much harder than LO, as this notebook and Complexity and Heuristics explain.

Learning outcomes

On completion of this notebook, you will be able to:

  • recognize when a problem needs integer or binary decision variables, and formulate and solve it in pulp;

  • explain why ILO is generally harder to solve than LO;

  • explain how branch and bound finds the optimum of an ILO problem;

  • write any LO problem in the general matrix form;

  • explain why linearity (but not integrality) is essential for efficient solvability, and how integer variables let you model many other nonlinearities without giving that up.

Why Integer Problems Are Harder

Take the bookcase/desk product-mix problem from Linear Optimization and require whole units of xx and yy. Its LO optimum was (x,y)=(3.6,2.8)(x, y) = (3.6, 2.8) with profit 24.8, not integer. Two things go wrong compared to LO:

  • the optimal corner is no longer feasible, so the simplex reasoning (“the optimum is at a corner”) does not directly help;

  • rounding the LO optimum is not enough: rounding both up to (4,3)(4, 3) violates the oak-panel constraint (4+9=13>124 + 9 = 13 > 12), and rounding both down to (3,2)(3, 2) is feasible but only reaches a profit of 19, well below the true integer optimum. Evaluating nearby integer points tells us little, because finding the true optimum in general needs a systematic search.

Let pulp solve the integer version:

integer optimum: {'bookcase': 3.0, 'desk': 3.0}
optimal profit: 24.0

The picture below shows why rounding is unreliable: the LP-relaxation optimum (the star) does not sit on the integer grid, and the nearest lattice points are not necessarily feasible or optimal. The ILO optimum (the diamond) is the best feasible grid point, which can be several steps away from the naive rounding of the relaxation.

Loading...

Figure 1:Feasible region of the integer product-mix problem with its integer points, the optimum of the LO relaxation (star) and the ILO optimum (diamond). The dashed line contains all points with the optimal integer profit 24: no integer feasible point lies above it, while the LO optimum does. Hover over a point to see its coordinates and profit.

Branch and Bound

A common method for solving integer linear optimization (ILO) problems is branch and bound. The basic idea is to start by just ignoring the integer restrictions. More precisely, we allow integer variables to be continuous while retaining all their other bounds and constraints. For example, instead of requiring a binary variable to satisfy xi∈{0,1}x_i \in \{0,1\}, we allow it to take any value between zero and one:

0≤xi≤1.0 \leq x_i \leq 1.

Likewise, instead of requiring xi∈{0,1,2,…}x_i \in \{0,1,2,\ldots\}, we only require:

xi≥0.x_i \geq 0.

This produces the LO relaxation: the same optimization problem, but without the requirement that certain variables must be integers.

The optimal solution to the LO relaxation usually contains non-integer values that should be integers. Such a solution is therefore infeasible for the original ILO problem, but it is still useful in two ways. First, for a maximization problem, it provides an upper bound (UB): the best possible integer solution cannot have a higher objective value than the relaxed solution. Second, it provides a starting point for further search by branching on non-integer variables one by one.

Branching on an LO relaxation that yields an infeasible ILO solution works as follows. Suppose the relaxed solution contains xj=2.5x_j = 2.5, while xjx_j should be an integer. We then branch the original root problem into two subproblems based on the LO relaxation of the original problem:

  • First subproblem: original problem but with extra constraint xj≤2x_j \leq 2;

  • Second subproblem: original problem but with extra constraint xj≥3x_j \geq 3;

Together, these two subproblems cover all possible integer values of xjx_j, but neither allows xj=2.5x_j = 2.5 anymore. We then solve the LO relaxation of each subproblem (independently) in the same manner. If either relaxation again yields a non-integer variable that must be integer, we apply the same strategy and branch again, et cetera. To illustrate this, suppose that solving the relaxation for the subproblem with xj≤2x_j \leq 2 leads to a solution with xk=5.6x_k = 5.6. We then branch this subproblem again to get one subproblem with constraints xj≤2x_j \leq 2 and xk≤5x_k \leq 5, and one subproblem with xj≤2x_j \leq 2 and xk≥6x_k \geq 6. Indeed, both subproblems still have the previous constraint xj≤2x_j \leq 2.

At first sight, this may seem like enumerating all possible integer values. The key difference is that branch and bound uses bounds to avoid exploring subproblems that cannot improve the best integer solution found so far. Hence the name branch and bound.

Any feasible integer solution to the original ILO problem provides a lower bound (LB) for a maximization problem. Along the way, we keep track of the best original ILO solution, called the incumbent. Its objective value is the current best/largest LB. On the other hand, as mentioned before, the LO relaxation of a subproblem is always a UB for the best integer-feasible solution at the subproblem. So if the UB at a subproblem is smaller than the current best LB, we don’t need to branch further on that subproblem, as it will never lead to a better ILO solution. This saves computation time.

Let us formalize this method further. The branch and bound method keeps track of (1) a pool of (sub)problems whose LO relaxation is not solved yet and which may still lead to an optimal solution, and (2) the current best solution with the largest/best LB so far. Initially, this pool contains only the original problem, and the LB is set to −∞-\infty. As long as the pool is not empty, choose a (sub)problem from the pool, remove it, and solve its LO relaxation. This solve leads to one of three outcomes:

  1. The found solution is infeasible for the ILO problem, and its objective value is a UB:

    1. If UB ≤\leq largest LB: We eliminate this subproblem (we will not find a better solution here; this can by default not happen for the original problem).

    2. If UB >> largest LB: Pick a variable from the solution that is non-integer but should be. Branch the (sub)problem on this variable into two new (sub)problems and add them to the pool.

  2. The found solution is feasible for the ILO problem and gives a new LB:

    1. If LB >> largest LB: Update the new best LB and see whether we can eliminate subproblems that were not eliminated yet. In particular, for all non-eliminated subproblems for which we obtained a UB, check whether their UB << largest LB; if so, eliminate the subproblem and remove all its descendants still in the pool.

    2. If LB ≤\leq largest LB: Eliminate this subproblem (nothing to branch further here).

  3. There is no feasible solution for the specific (sub)problem: Eliminate this subproblem as it cannot have an ILO feasible solution.

Keep repeating this procedure until the pool is empty. Once the pool is empty, we are guaranteed that the solution with the current best LB is optimal.

The process can be visualized as a tree. The original problem is the root. Each time we branch, we split one (sub)problem into two child subproblems. The pool contains the subproblems that have not yet been eliminated or solved.

We did not discuss how to choose two things: which decision variable to branch on (since an LO relaxation solution typically has many non-integer variables that should be integer) and which (sub)problem from the pool to pick first. The choice affects performance, and what works best is a research topic on its own and outside the scope of this course. In this course, just make a choice; we always know that in the end we will find the optimal solution.

Let us apply this idea to the integer product-mix problem to illustrate its workings. We number the steps in the order in which we take them:

  • Step 1: root problem. The LO relaxation has optimum (3.6,2.8)\left(3.6, 2.8\right) with objective value 24.8. This is a UB, but the solution is not integer in both decision variables and thus infeasible. Just pick a decision variable to branch on. We branch on y=2.8y = 2.8, which adds the subproblems with y≤2y \leq 2 and y≥3y \geq 3 to the pool.

  • Step 2: left subproblem, the root problem with y≤2y \leq 2. Its relaxation has optimum (4,2)\left(4, 2\right) with value 22. This solution is integer, so it is feasible for the ILO problem and thus holds the current best LB of 22. No need to branch further from this subproblem.

  • Step 3: right subproblem, the root problem with y≥3y \geq 3. Its relaxation has optimum (3,3)\left(3, 3\right) with value 24. This is also feasible for the ILO problem, so it improves the best LB to 24, and we can replace the previous best solution with the solution (3,3)\left(3, 3\right). No need to branch further from this subproblem.

  • Step 4: left subproblem is suboptimal. Its value of 22 is below the new best LB of 24, so the solution (4,2)\left(4, 2\right) is no longer the best one.

  • Step 5: optimum. The pool is now empty (no promising (sub)problems left to explore), so we are done, and the current best solution of (3,3)\left(3,3\right) with profit 24 must be the optimal ILO solution.

The figure below shows these steps as a branch-and-bound tree. Each node is a (sub)problem: its header gives the step in which its LO relaxation is solved, the objective value, and whether that value is a UB or a LB, and the line below gives the relaxation’s optimal solution (x,y)(x, y). The labels on the edges are the constraints added by branching.

Source
<Figure size 1200x585 with 1 Axes>

Figure 2:Branch-and-bound tree for the integer product-mix problem (UB = upper bound from the LO relaxation, LB = lower bound from a feasible integer solution, subopt = suboptimal solution).

In this example, we needed only three LO relaxations, rather than checking every possible integer combination. Modern integer-optimization solvers use this basic branch-and-bound idea, often enhanced with additional techniques. More on this later.

The Knapsack Problem

Let us now illustrate branch and bound on an archetypical ILO problem called the knapsack problem. We will first give a practical motivation for this problem, then we will model the problem as an ILO problem. The ILO problem will be made concrete for a small example, which is then solved in pulp. We finish with some extensions.

Practical Motivation

A transport company has one truck left for tomorrow and more shipment requests than the truck can carry. Each shipment earns a known revenue and has a known weight, and the truck has a maximum load. Which shipments should it accept to earn as much as possible? The same question comes up when choosing which projects to fund from a fixed budget, which pieces to cut from a steel plate, or which items to pack for a trip.

Modeling

Problem Definition and Example

There are nn items. Item ii has a reward rir_i and a weight wiw_i, and the knapsack has a capacity CC. The problem is to choose a subset of the items with maximum total reward whose total weight does not exceed the capacity.

As an example, take n=4n = 4 items and capacity C=8C = 8:

item ii1234
reward rir_i159105
weight wiw_i1354

For example, taking items 2 and 3 gives a total reward of 9+10=199 + 10 = 19 and a total weight of 3+5=83 + 5 = 8, which fits exactly. Adding item 1 as well would give a weight of 9, which does not fit.

Decision Variables

For every item we decide whether to take it or not. An item cannot be taken in part, so we use a binary decision variable per item:

xi={1if item i is taken,0otherwise,i=1,…,n.x_i = \begin{cases} 1 & \text{if item } i \text{ is taken,} \\ 0 & \text{otherwise,} \end{cases} \quad i = 1, \dots, n.

Objective

Item ii adds rir_i to the reward if it is taken (xi=1x_i = 1) and nothing if it is not (xi=0x_i = 0), so the total reward is rixir_i x_i summed over all items. We maximize

∑i=1nrixi.\sum_{i=1}^{n} r_i x_i.

Constraints

In the same way, the total weight of the items taken is ∑i=1nwixi\sum_{i=1}^{n} w_i x_i, which may not exceed the capacity:

∑i=1nwixi≤C.\sum_{i=1}^{n} w_i x_i \le C.

Finally, xi∈{0,1}x_i \in \{0, 1\} for all ii.

Complete ILO Model

max⁡∑i=1nrixis.t.∑i=1nwixi≤Cxi∈{0,1},i=1,…,n.\begin{aligned} \max \quad & \sum_{i=1}^{n} r_i x_i \\ \text{s.t.} \quad & \sum_{i=1}^{n} w_i x_i \le C \\ & x_i \in \{0, 1\}, \quad i = 1, \dots, n. \end{aligned}

Modeling the Example

max⁡15x1+9x2+10x3+5x4s.t.x1+3x2+5x3+4x4≤8x1,x2,x3,x4∈{0,1}.\begin{aligned} \max \quad & 15x_1 + 9x_2 + 10x_3 + 5x_4 \\ \text{s.t.} \quad & x_1 + 3x_2 + 5x_3 + 4x_4 \le 8 \\ & x_1, x_2, x_3, x_4 \in \{0, 1\}. \end{aligned}

Solving the Example in pulp

The only change from an LO model in pulp is cat="Binary" for the decision variables. The items are numbered, so we store the data in lists and the variables in a list take, where take[i] is xi+1x_{i+1} (Python counts from 0). Printing the model is a quick way to check that it matches the model above.

knapsack:
MAXIMIZE
15*x_1 + 9*x_2 + 10*x_3 + 5*x_4 + 0.0
SUBJECT TO
capacity: x_1 + 3 x_2 + 5 x_3 + 4 x_4 <= 8

VARIABLES
0 <= x_1 <= 1 Integer
0 <= x_2 <= 1 Integer
0 <= x_3 <= 1 Integer
0 <= x_4 <= 1 Integer

take items: [1, 2, 4]
total reward: 29.0

The optimal choice is items 1, 2 and 4, with a total reward of 29 and a total weight of 8.

Extensions

Several Copies per Item

If up to uiu_i copies of item ii are available, xix_i becomes the number of copies taken: replace xi∈{0,1}x_i \in \{0, 1\} by xi∈{0,1,…,ui}x_i \in \{0, 1, \dots, u_i\}, an integer variable with upper bound uiu_i. The objective and the capacity constraint stay the same.

Several Knapsacks

With several knapsacks k=1,…,Kk = 1, \dots, K (for example, several trucks) with capacities CkC_k, the decision is which item goes into which knapsack. Use a binary xikx_{ik} that is 1 if item ii goes into knapsack kk, require ∑kxik≤1\sum_k x_{ik} \le 1 so that every item is used at most once, and give every knapsack its own capacity constraint ∑iwixik≤Ck\sum_i w_i x_{ik} \le C_k.

Branch and Bound for the Knapsack Example

We now solve the same example by hand with branch and bound.

Solving the LO Relaxation

Every step of branch and bound solves an LO relaxation, so we first need to know how to do that. For the knapsack problem this turns out to be easy, and no LO solver is needed.

In the LO relaxation, xi∈{0,1}x_i \in \{0, 1\} becomes 0≤xi≤10 \leq x_i \leq 1: we may take any fraction of an item, and a fraction xix_i of item ii gives reward rixir_i x_i and uses capacity wixiw_i x_i. Each unit of capacity should then go to the item that gives the most reward per unit of weight. So we sort the items in decreasing order of their reward-to-weight ratio ri/wir_i / w_i and fill the knapsack in that order, taking each item completely as long as it fits. The first item that no longer fits completely is taken for the fraction that fills the remaining capacity, and all later items are left out. As a result, at most one variable in the optimal solution of the LO relaxation is fractional.

For our example, the ratios are 15/1=1515/1 = 15, 9/3=39/3 = 3, 10/5=210/5 = 2 and 5/4=1.255/4 = 1.25, so the items are already in decreasing order of ratio. Filling the knapsack in that order goes as follows:

item iiratio ri/wir_i / w_iweight wiw_ifraction taken xix_icapacity left
115117
23314
3254/50
41.25400

Items 1 and 2 fit completely. Item 3 does not: only 4 of its weight of 5 fits, so we take a fraction 4/54/5 of it, and there is no capacity left for item 4. The optimal solution of the LO relaxation is therefore (1,1,0.8,0)(1, 1, 0.8, 0) with objective value 15+9+0.8⋅10=3215 + 9 + 0.8 \cdot 10 = 32.

In branch and bound, some variables are fixed to 0 or 1 in a subproblem. The same rule then still applies: items fixed to 1 are packed first, items fixed to 0 are left out, and the remaining items fill the remaining capacity in decreasing order of ratio. If the items fixed to 1 already exceed the capacity, the subproblem is infeasible.

Branch and Bound Steps

With the LO relaxation in hand, we solve our example with branch and bound. Since the LO relaxation has at most one fractional variable, there is only one variable to branch on in each step, and branching on a binary variable means fixing it to 0 in one subproblem and to 1 in the other. We use the same step numbering as before:

  • Step 1: root problem. The LO relaxation has optimum (1,1,0.8,0)(1, 1, 0.8, 0) with value 32, a UB. We branch on x3=0.8x_3 = 0.8.

  • Step 2: subproblem x3=0x_3 = 0. Without item 3, items 1, 2 and 4 fit exactly: the relaxation has optimum (1,1,0,1)(1, 1, 0, 1) with value 29. This solution is integer, so it is feasible for the ILO problem and gives the current best LB of 29. No need to branch further from this subproblem.

  • Step 3: subproblem x3=1x_3 = 1. Item 3 uses 5 of the capacity, item 1 then fits, and 2/32/3 of item 2 fills the rest: optimum (1,2/3,1,0)(1, 2/3, 1, 0) with value 31. This UB is larger than the best LB of 29, so this subproblem may still contain a better solution. We branch on x2x_2.

  • Step 4: subproblem x3=1x_3 = 1, x2=0x_2 = 0. Items 3 and 1 fit, and half of item 4 fills the rest: optimum (1,0,1,0.5)(1, 0, 1, 0.5) with value 27.5, a UB.

  • Step 5: eliminate. The UB of 27.5 is below the best LB of 29, so this subproblem cannot contain a better solution and we eliminate it without branching.

  • Step 6: subproblem x3=1x_3 = 1, x2=1x_2 = 1. Items 3 and 2 use the full capacity, so item 1 no longer fits: optimum (0,1,1,0)(0, 1, 1, 0) with value 19. This solution is integer and thus a LB.

  • Step 7: suboptimal. The LB of 19 is below the best LB of 29.

  • Step 8: optimum. The pool is now empty, so the best solution found, (1,1,0,1)(1, 1, 0, 1) with reward 29 from step 2, is optimal.

The figure below shows the branch-and-bound tree.

<Figure size 1650x750 with 1 Axes>

Figure 3:Branch-and-bound tree for the knapsack example (UB = upper bound from the LO relaxation, LB = lower bound from a feasible integer solution, subopt = suboptimal solution).

General Formulation

Having now seen both LO and ILO in action, we can step back and write down the general form both fit into. An LO problem with nn decision variables and mm constraints is

maximize∑j=1npjxjsubject to∑j=1naijxj≤bi,i=1,…,mx1,…,xn≥0,\begin{aligned} \text{maximize} \quad & \sum_{j=1}^{n} p_j x_j \\ \text{subject to} \quad & \sum_{j=1}^{n} a_{ij} x_j \le b_i, \quad i = 1, \dots, m \\ & x_1, \dots, x_n \ge 0, \end{aligned}

or in matrix notation max⁡{pTx∣Ax≤b, x≥0}\max\{p^T x \mid A x \le b,\ x \ge 0\}, with p,xp, x column vectors of length nn, bb a column vector of length mm, and AA an m×nm \times n matrix.

This one form covers more than it seems. Every other case can be rewritten into it:

  • minimization: min⁡pTx=−max⁡(−pTx)\min p^T x = -\max (-p^T x);

  • “≥\ge” constraints: Ax≥b⇔−Ax≤−bAx \ge b \Leftrightarrow -Ax \le -b;

  • “==” constraints: Ax=b⇔Ax≤bAx = b \Leftrightarrow Ax \le b and Ax≥bAx \ge b;

  • free (unrestricted) variables: replace xx by x+−x−x^+ - x^- with x+,x−≥0x^+, x^- \ge 0.

Why Linearity Matters, and What Integrality Buys Back

Linearity is what makes LO efficiently solvable: written in the general form above, the feasible region is a convex polyhedron, an optimum sits at a corner, and a local optimum is automatically global. As soon as the objective or a constraint is nonlinear, both of those break:

  • Nonlinear objective. Maximize x1x2x_1 x_2 subject to x1+x2≤1x_1 + x_2 \le 1, x1,x2≥0x_1, x_2 \ge 0. The optimum is (0.5,0.5)(0.5, 0.5), in the interior of an edge, not at a corner.

  • Nonlinear constraint. Maximize x1+x2x_1 + x_2 subject to min⁡(x1,x2)=0\min(x_1, x_2) = 0, x1≤2x_1 \le 2, x2≤1x_2 \le 1. The feasible set is two line segments meeting at the origin (either x1=0x_1 = 0 or x2=0x_2 = 0). It has two local optima, (2,0)(2, 0) and (0,1)(0, 1); you cannot be sure which is global without checking both. More generally, the simplex reasoning from Linear Optimization breaks down whenever the feasible region is not a convex polyhedron: you are not sure you have found the best solution until you have checked every local optimum, which is usually intractable.

Nonlinear optimization therefore needs slower, less reliable algorithms. But there is a large and useful middle ground: integer constraints. On the one hand, requiring xi∈{0,1,2,… }x_i \in \{0, 1, 2, \dots\} is itself a nonlinear constraint, and it does make a problem harder to solve, as branch and bound’s extra work above shows. On the other hand, unlike general nonlinearities, it is not intractably so, and many other nonlinearities (an either/or choice, a fixed cost that applies only when an activity is used, a “this constraint holds only if...” condition) can be expressed with integer (usually binary) variables and otherwise-linear constraints, and then solved with branch and bound. That is why so much modeling effort goes into casting a problem as ILO, and why it is worth treating as its own class rather than lumping it in with general nonlinear optimization. Set Covering and Shift Scheduling and Machine Scheduling show many of those tricks in practice.

References

  • Koole, G. (2019). An Introduction to Business Analytics. §6.4 “Integer Problems.”