Opl Tutorial
Opl Tutorial
Opl Tutorial
Truls Flatberg
January 17, 2009
Introduction
This note is a short tutorial to the modeling language OPL. The tutorial is by
no means complete with regard to all features of OPL. More detailed information can be found in the Language Users Manual and the Language Reference
Manual provided with the OPL installation. The motivation of this note is to
show a few examples to get people quickly started using OPL.
OPL is a modeling language for describing (and solving) optimization problems.
The motivation for using modeling languages to model optimization problems
is primarily due to two reasons:
It provides a syntax that is close to the mathematical formulation, thus
making it easier to make the transition from the mathematical formulation
to something that can be solved by the computer.
It enables a clean separation between the model and the accompanying
data. The same model can then be solved with different input data with
little extra effort.
OPL is just one example of a modeling language. Other examples include
AMPL, Mosel and GAMS. Modeling languages are typically used for linear and
integer optimization problems, but it is also possible to formulate quadratic
problems in OPL.
Before starting on the examples, we give a short overview of the structure of an
OPL model. A model typically includes these four parts:
1. Data declarations. This part declares the data parameters used in the
model, typically coefficients and index sets for the decision variables. Individual data parameters can be declared by giving their type and name,
e.g.
int a = 4;
Sets of a specific type are declared by enclosing the type within curly
braces
A first model
We will start out with a minimal OPL model to solve the following linear optimization problem:
minimize 4x 2y
xy 1
subject to
x0
The OPL model is as follows:
Listing 1: simple.mod
1
2
dvar float x ;
dvar float y ;
3
4
5
minimize
4 x 2 y ;
6
7
8
9
10
subject to {
x y >= 1 ;
x >= 0 ;
}
Note how similar the two formulations are. The constraint in line 9 can be
removed and replaced with an explicit declaration of the variable x as type
float+.
Production planning
program:
maximize
rd xd + rw xw
subject to
td,1 xd + tw,1 xw c1
td,2 xd + tw,2 xw c2
td,3 xd + tw,3 xw c3
x 0, y 0
where rp is the revenue for product p, tp,i is the time needed at factory i to
produce a series of product p and ci is the time available at factory i. The series
produced is given by xp for each product p.
The following is a formulation of the problem in OPL:
Listing 2: prod.mod
1
2
{ string } Products = . . . ;
{ string } Factories = . . . ;
3
4
5
6
float r [ Products ] = . . . ;
float t [ Products ] [ Factories ] =
float c [ Factories ] = . . . ;
...;
7
8
9
10
11
maximize
sum ( p in Products ) r [ p ] x [ p ] ;
12
13
14
15
16
subject to {
forall ( i in Factories )
sum ( p in Products ) t [ p ] [ i ] x [ p ] <= c [ i ] ;
}
Lines 1 and 2 declare two arrays of strings defining the set of products and the
set of factories. Note the use of ... to indicate that the value will be provided
in a separate data file. Lines 4-6 define the input data as real numbers: revenues,
time required for each product and the capacity of each factory. Line 8 declares a
non-negative decision variable for each product, lines 10-11 declare the objective
and lines 13-16 declare the constraints, one for each factory by using the forall
keyword.
Suppose now that we want to solve the problem with the following data:
Factory 1
Factory 2
Factory 3
Revenue/series
Hours/series
Door Window
1
0
0
3
3
2
3000
5000
4
Hours at disposal
4
12
18
3
4
5
6
r = [3000 5000];
t = [ [ 1 0 3] [0 3 2 ] ] ;
c = [ 4 12 1 8 ] ;
Note that the separation between model and data facilitates easy updating if
part of the problem is changed. For instance, adding a new product to the
problem, will only affect the data file and the model can be left unchanged.
Linear programming
This example shows how to solve a general linear programming problem, and
illustrates how scripting can be combined with the model to display results and
obtain details on the solution. The problem we want to solve can be written in
matrix form as
max cT x
s.t. Ax b,
x0
The OPL model is as follows
Listing 4: lp.mod
1
2
int m = . . . ;
int n = . . . ;
3
4
5
range Rows = 1 . . m ;
range Cols = 1 . . n ;
6
7
8
9
10
11
12
13
14
maximize
sum ( j in Cols ) c [ j ] x [ j ] ;
15
16
17
18
19
subject to
{
forall ( i in Rows )
ct :
20
21
In lines 4-5 we use a special kind of set, range, that can be used to represent a
sequence of integer values. Note also that we give a name to the constraint in
line 19. This name will be used as an identifier when we later want to obtain
information about the constraint.
The problem instance can be specified in a separate data file. The following is
the input for an example with four constraints and four variables.
Listing 5: lp.dat
1
2
m = 4;
n = 4;
3
4
5
6
7
8
A = [[3
[2
[0
[1
];
2
3
1
3
2
2
4
2
1]
1]
3]
4]
9
10
b = [3 4 5 7 ] ;
11
12
c = [2 2 2 1 ] ;
In addition to the modeling features, OPL has a scripting language that can be
used for several purposes. In the following example we show how to print both
primal and dual solution information for the LP problem.
Listing 6: lp.mod
22
23
24
25
26
27
28
29
30
execute {
writeln ( Optimal value : , cplex . getObjValue ( ) ) ;
writeln ( Primal solution : ) ;
write ( x = [ ) ;
for ( var j in Cols )
{
write ( x [ j ] , ) ;
}
writeln ( ] ) ;
31
32
33
34
35
36
37
38
39
Note the slightly different syntax used in the scripting language, e.g. variables
are declared using the var keyword while iteration over a set is done using
the for keyword. If an execute block is placed after the objective and the
constraints, it will be performed as a postprocessing step. If placed beforehand,
it can also be used as a preprocessing step.
Integer programming
In this section we show how to model and solve a problem with integer variables,
in this case 0-1 variables. Consider the following problem: We want to construct
a sequence of n numbers. The ith position should give the number of times
the number i occurs in the sequence, where the indexing starts at zero. The
following is an example of a legal sequence for n = 4:
pos 0
value 1
1
2
2
1
3
0
subject to
(i)
Pn1
xij = 1,
(ii)
Pn1
(iii)
Pn1
(iv)
xij {0, 1}
j=0
k=0
k=0
for i = 0, . . . , n 1
for i, j = 0, . . . , n 1
int n = 1 0 ;
range N = 0 . . n 1;
3
4
dvar boolean x [ N ] [ N ] ;
5
6
minimize 0 ;
7
8
9
10
11
subject to
{
forall ( i in N )
sum ( j in N ) x [ i ] [ j ] == 1 ;
12
forall ( i in N , j in N )
sum ( k in N ) x [ k ] [ i]j <= n(1x [ i ] [ j ] ) ;
13
14
15
forall ( i in N , j in N )
sum ( k in N ) x [ k ] [ i]j >= n ( x [ i ] [ j ] 1);
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
execute {
for ( var i in N )
{
for ( var j in N )
{
if ( x [ i ] [ j]==1)
{
write ( j , ) ;
}
}
}
writeln ( ) ;
}
Note that 0-1 variables are declared using the keyword boolean, while general
integer variables are declared using the int keyword.
We end this tutorial with a slightly more complex model. In the minimum
spanning tree problem we want to find a spanning tree of minimum weight in an
undirected graph G = (V, E) with weight function w : E R. This problem can
be solved efficiently as a combinatorial problem using e.g. Kruskals algorithm.
Here we will show how to formulate and solve the problem as an integer problem.
The following is a valid formulation of the problem as an integer problem:
P
min
eE we xe
s.t.
(i)
xe = |V | 1
(ii)
xe 1,
(iii)
xe {0, 1},
eE
e(U )
for all U V
for all e E
Constraint (ii) is a cut constraint that ensures that we do not get a solution
with subcycles. Note that there is an exponential number of these constraints,
clearly limiting the size of the problems solvable in OPL.
int n = . . . ;
range Nodes = 1 . . n ;
{ int } NodeSet = asSet ( Nodes ) ;
4
5
6
7
8
9
10
11
tuple Edge
{
int u ;
int v ;
}
{ Edge } Edges with u in Nodes , v in Nodes = . . . ;
{ int } Nbs [ i in Nodes ] = { j | <i , j> in Edges } ;
12
13
14
15
16
17
18
19
20
21
22
constraint ctCut [ S ] ;
23
24
25
minimize
sum ( e in Edges ) Cost [ e ] x [ e ] ;
26
27
28
29
30
31
32
subject to {
forall ( s in S : 0 < card ( Subset [ s ] ) < n )
ctCut [ s ] :
sum ( i in Sub [ s ] , j in Nbs [ i ] inter Compl [ s ] ) x[<i , j >]
+ sum ( i in Compl [ s ] , j in Nbs [ i ] inter Sub [ s ] ) x[<i , j >]
>= 1 ;
33
ctAll :
sum ( e in Edges ) x [ e ] == n 1;
34
35
36
37
38
39
40
41
42
43
execute
{
writeln ( Used edges = , Used ) ;
}
The graph is represented as a list of edges where each edge is an ordered tuple
9
of two nodes. These are declared in line 1-10. For each node we record the set of
neighbouring nodes in line 11. Lines 13-16 declares all possible subsets of nodes
and the complement of these subsets. Since we have an ordered tuple for each
edge we need to check for cutting edges in both directions when implmenting
the cut constraints in lines 30-31.
The following is data for an example with 12 nodes:
Listing 9: mst.mod
1
n = 12;
2
3
4
5
6
7
Cost = [ 5 1 3 1 2 4 2 3 2 5 1 6 2 2 1 1 2 1 ] ;
10