Optimal Resource Allocation Using Python

A brief introduction to modeling and solving resource allocation and scheduling problems using Python and SCIP.

Ryan J. O'Neil

George Mason University

Talk Outline

What is Integer Programming?

A few major points in the history of Mathematical Optimization

LP Example: Production Planning

SpamCo manufactures two types of potted meat. Type 1 sells for $3 per pot and requires 2 units of animal byproducts and 3 hours to cure. Type 2 sells for $4 per pot and requires 3 units of byproduct and 1 hour to cure. There are 10 units of byproduct and 6 hours for curing available. How can SpamCo maximize its profit?

maximize:   z = 3x1 + 4x2          profit
subject to:     2x1 + 3x2 <= 10    animal byproducts
                3x1 +  x2 <=  6    time for curing
                   x1, x2 >=  0

LP Example: Production Planning

The feasible set has extreme points (0, 0), (0, 10/3), (8/7, 18/7) and (2, 0).

If we move the objective line up and to the right, the last point it touches, (8/7, 18/7), is the optimal solution with objective value 96/7.

LP Problem Dimensions

Simplex: An Algorithm for Solving LP

In general LP can be solved in polynomial time. Assuming an LP is feasible and bounded, Simplex, the most common method, looks roughly like:

Current software solves LP with millions of variables.

Disclaimer: this is only a very small part of a large story.

The Combinatorial Explosion

IP Example: Knapsack Problems

We have n items, each with value vi and weight wi. We can carry b in total weight. What combination of items should we pack to maximize value?

maximize:   z = Σ vi * xi
subject to:     Σ wi * xi <= b 
                xi in {0, 1} for all i

In General, IP Problems are Hard

Disclaimer: IANACT (I am not a complexity theorist)

But That's OK!

"NP-Hard is a term we use to scare graduate students."

Overheard at INFORMS Conference 2008

Consider the following instance of the Travelling Salesman Problem:

Add 995 points along the perimeter of the pentagon Now you have an easy, 1,000-city TSP.

Solving IPs: It's all about bounding

  • Assume we're maximizing (we could also minimize)
  • Dual bound: a value we can't do better than (optimal <= this)
  • Primal solution: any feasible solution (optimal >= this)
  • If dual bound = primal bound: we're optimal

Generating Primal Bounds

  • Any feasible solution will do.
  • One common method: greedy algorithm
  • Greedy for knapsack problem:
    • order the list by descending value
    • put in as many items as will fit
  • Sometimes finding a feasible solution is as hard finding the optimal solution...

Generating Dual Bounds

  • Typically this means relaxing the problem.
  • The LP relaxation of an IP assumes the variables can be fractional. We can do no better than this.
  • Important: If the optimal solution of an LP relaxation has integer variables, then that is the optimal solution of the IP!

Cutting Planes

Add constraints that make the LP have (more) integral extreme points.

If all extreme points are integral, then Simplex will return an optimal integer point.

Branch & Bound

If the LP relaxation returns fractional variables, divide it into two LPs.

Discard the fractional area.

Now we have two sub-LPs, each with a dual bound. We can use this to guide our search.

Preprocessing

Sometimes we can make logical implications about integer variables just by looking at the model. Consider:

x1 + x2 >= 1
x1 + x2 + x3 <= 1
xi in {0, 1} for all i

We can set x3 to zero and remove it from our problem entirely. (Why?)

OK, but how do I really do it?

All these techniques (and many more) exist in commercial and open source optimization solvers.

The ZIB Optimization Suite is a set of non-commercial tools for solving MIPs. It contains:

  • ZIMPL: algebraic modeling language like AMPL
  • SoPlex: linear programming solver
  • SCIP: mixed integer programming solver

ZIMPL Example: SpamCo

The ZIMPL version of our model is a lot like the algebraic one:

var x1 >= 0; # pots of type 1
var x2 >= 0; # pots of type 2

maximize profit:  3*x1 + 4*x2;
subto byproducts: 2*x1 + 3*x2 <= 10;
subto curing:     3*x1 +  x2  <=  6;

Algebraic Modeling Languages

  • The quickest way to get started
  • Produce models that closely resemble mathematical descriptions of problemsi
  • Separate data from model
  • Aren't full programming languages. It is difficult to:
    • Change the model depending on solver output
    • Combine data from disparate sources
      (CSV, RDBMS, arbitrarily structured text...)
    • Base parts of the model on logic or user input

Solver APIs

  • Give all the existing power of a solver
  • Allow you to build your own problem-specific optimization components
  • Are usually in C...

SpamCo in Python

from zibopt import scip
solver = scip.solver()

x1 = solver.variable() # pots of type 1
x2 = solver.variable() # pots of type 2

solver += 2*x1 + 3*x2 <= 10 # animal byproducts
solver += 3*x1 +   x2 <=  6 # curing time

solution = solver.maximize(objective=3*x1 + 4*x2)
if solution:
    print 'profit:', solution.objective
    print 'pots of type 1:', solution[x1]
    print 'pots of type 2:', solution[x2]
else:
    print 'invalid problem'

A Larger Example: Sudoku

We can model Sudoku as a Binary Integer Program (BIP) with 9*9*9 = 729 variables.

# 0 indicates a cell value is not given
problem = [
    [0, 0, 0,   6, 9, 2,   0, 4, 0],
    [7, 0, 0,   0, 0, 0,   8, 9, 0],
    [0, 0, 0,   0, 0, 0,   0, 0, 6],

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

    [5, 0, 0,   0, 0, 0,   0, 0, 0],
    [0, 8, 6,   0, 0, 0,   0, 0, 1],
    [0, 3, 0,   7, 2, 8,   0, 0, 0]
]

Sudoku: Data & Indexes

Indexes for rows, columns, 3x3 groups, and values cells can take on:

# Indexes for creating variables and constraints
rows = range(len(problem))
cols = rows
vals = range(1, 10)

9x9x9 structure for storing our binary variables. .

x = dict((i, dict((j, {}) for j in cols)) for i in rows)

Sudoku: Creating the Variables

Create a variable for each cell and value. If row i, column j requires value k, then x[i][j][k] = 1.

from itertools import product
from zibopt import scip

# Initialize one binary variable per cell value
solver = scip.solver()
for i, j, k in product(rows, cols, vals):
    x[i][j][k] = solver.variable(
        vartype = scip.BINARY,
        lower   = 1 if problem[i][j] == k else 0
    )

Sudoku: Cell Values, Columns, & Rows

Each cell takes exactly one value:

for i, j in product(rows, cols):
    solver += sum(x[i][j][k] for k in vals) == 1

Each value occurs in each row once:

for i, k in product(rows, vals):
    solver += sum(x[i][j][k] for j in cols) == 1

Each value occurs in each column once:

for j, k in product(cols, vals):
    solver += sum(x[i][j][k] for i in rows) == 1

Sudoku: 3x3 Groups

Each 3x3 group contains each value once:

groups = (0, 3, 6)

for m, n, k in product(groups, groups, vals):
    solver += sum(
        x[i][j][k] for i, j in product(
            range(m,m+3), range(n,n+3)
        )
    ) == 1

Sudoku: Finding the Solution

Find the solution and fill in our problem array with appropriate values.

solution = solver.maximize()
if solution:
    for i, j in product(rows, cols):
        for k in vals:
            if solution[x[i][j][k]]:
                problem[i][j] = k
                break

    for row in problem:
        print row

else:
    print 'invalid'

Sudoku: Output

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

Conclusions

  • Just because a problem is intractable doesn't mean you can't solve it (or at least know bounds for the solution you have)
  • ZIB has an excellent suite of open source tools for just this purpose
  • Optimization is for Python programmers too!

Questions



?