Reference Manual Example

In this section we solve how to define and solve the example given at the beginning of the GLPK C API reference manual.

maximize Z= 10 x0+6 x1+4 x2
subject to p= x0+ x1+ x2
q= 10x0+ 4x1+ 5x2
r= 2x0+ 2x1+ 6x2
and bounds of variables
−∞ < p ≤ 100   0 ≤ x0 < ∞
−∞ < q ≤ 600   0 ≤ x1 < ∞
−∞ < r ≤ 300   0 ≤ x2 < ∞

The Implementation

Here is the implementation of that linear program within PyGLPK:

import glpk            # Import the GLPK module

lp = glpk.LPX()        # Create empty problem instance
lp.name = 'sample'     # Assign symbolic name to problem
lp.obj.maximize = True # Set this as a maximization problem
lp.rows.add(3)         # Append three rows to this instance
for r in lp.rows:      # Iterate over all rows
	r.name = chr(ord('p')+r.index) # Name them p, q, and r
lp.rows[0].bounds = None, 100.0  # Set bound -inf < p <= 100
lp.rows[1].bounds = None, 600.0  # Set bound -inf < q <= 600
lp.rows[2].bounds = None, 300.0  # Set bound -inf < r <= 300
lp.cols.add(3)         # Append three columns to this instance
for c in lp.cols:      # Iterate over all columns
	c.name = 'x%d' % c.index # Name them x0, x1, and x2
	c.bounds = 0.0, None     # Set bound 0 <= xi < inf
lp.obj[:] = [ 10.0, 6.0, 4.0 ]   # Set objective coefficients
lp.matrix = [ 1.0, 1.0, 1.0,     # Set nonzero entries of the
             10.0, 4.0, 5.0,     #   constraint matrix.  (In this
              2.0, 2.0, 6.0 ]    #   case, all are non-zero.)
lp.simplex()           # Solve this LP with the simplex method
print 'Z = %g;' % lp.obj.value,  # Retrieve and print obj func value
print '; '.join('%s = %g' % (c.name, c.primal) for c in lp.cols)
                       # Print struct variable names and primal values

The Explanation

We shall now go over this implementation section by section.

import glpk

First we need the module of course.

lp = glpk.LPX()        # Create empty problem instance

Here we construct a new linear program object with a call to the LPX constructor.

lp.name = 'sample'     # Assign symbolic name to problem

Linear program objects have various attributes. One of them is name, which holds a symbolic name for the program. Initially a program has no name, and name correspondingly has the value None. Here we name the program 'sample'. Programs do not necessarily require names, but a user may wish to give a program a name nonetheless.

lp.obj.maximize = True # Set this as a maximization problem

Linear program objects contain other less trivial attributes. One of the most important is obj, an object representing the linear program's objective function. In this case, we are assigning lp.obj's maximize attribute the value True, informing out linear program that we want to maximize our objective function.

lp.rows.add(3)         # Append three rows to this instance

Another very important component of lp is the rows attribute, holding an object which indexes over the rows of this linear program. In this case, we call the lp.rows method add, telling it to add three rows to the linear program.

for r in lp.rows:      # Iterate over all rows
	r.name = chr(ord('p')+r.index) # Name them p, q, and r

The lp.rows object is also used for accessing particular rows. In this case, we are iterating over each row. In the course of this iteration, r holds the first, second, and third row. We want to name these rows 'p', 'q', and 'r', in order.

Note that an individual row is an object in itself. It also has a name attribute, to which we assign the character with ASCII value of p plus whatever the index of this row is. The first row has index of 0, the next 1, the next and last 2. So, this will give us the desired names.

lp.rows[0].bounds = None, 100.0  # Set bound -inf < p <= 100
lp.rows[1].bounds = None, 600.0  # Set bound -inf < q <= 600
lp.rows[2].bounds = None, 300.0  # Set bound -inf < r <= 300

In addition to iterating over all rows, we can access a particular row by indexing the lp.rows object. In this case we index by the numeric row index. (Now that we have set their names, we could alternatively index them by their names!)

In this case, we are using the row's bounds attribute to set the bounds for the corresponding auxiliary variable. Bounds consist of a lower and upper bound. In this case, we are specifying that we always want the lower end unbounded (by assigning None, indicating no bound in that direction), and otherwise setting an appropriate upper bound.

lp.cols.add(3)         # Append three columns to this instance

In addition to the rows object, there is also a cols object for creating and accessing columns. Indeed, the two objects have the same type. In this case, we see we are adding three columns to the linear program.

for c in lp.cols:      # Iterate over all columns
	c.name = 'x%d' % c.index # Name them x0, x1, and x2
	c.bounds = 0.0, None     # Set bound 0 <= xi < inf

Similar to how we iterated over and assigned names to the rows, in this case we assign appropriate names to our columns. We also assign bounds to each column's associated structural variable, though in this case we want each structural variable to be greater than 0, and have no upper bound.

lp.obj[:] = [ 10.0, 6.0, 4.0 ]   # Set objective coefficients

There is one objective coefficient for every column. In this, we set all the coefficients at once to their desired values. Note that these lp.obj objects act like sequences over the objective coefficient values, just as the row and column collections do over rows and the columns.

lp.matrix = [ 1.0, 1.0, 1.0,     # Set nonzero entries of the
             10.0, 4.0, 5.0,     #   constraint matrix.  (In this
              2.0, 2.0, 6.0 ]    #   case, all are non-zero.)

We are setting the non-zero entries of the coefficient constraint matrix by assigning to the linear program's matrix attribute. Matrix entries are either (1) values, or (2) tuples specifying the row index, column index, and value. In the first case, if it is just a value with the indices omitted, it assumes that the value specified is for the next value in the constraint matrix, read top to bottom, left to right. We could also have explicitly defined the indices with this equivalent statement:

lp.matrix = [ (0,0, 1.0), (0,1,1.0), (0,2,1.0),
              (1,0,10.0), (1,1,4.0), (1,2,5.0),
              (2,0, 2.0), (2,1,2.0), (2,2,6.0) ]

But we did not. Let's move on.

lp.simplex()           # Solve this LP with the simplex method

Here we are calling a simplex solver to solve the defined linear program!

print 'Z = %g;' % lp.obj.value,  # Retrieve and print obj func value
print '; '.join('%s = %g' % (c.name, c.primal) for c in lp.cols)
                       # Print struct variable names and primal values

After optimization, we want to print out the value of the objective function (as stored in lp.obj.value), and the value of the primal variable for each of the columns (as stored in each column's primal attribute).

This all results in this output.

Z = 733.333; x0 = 33.3333; x1 = 66.6667; x2 = 0

Thomas Finley, 2007, 2008