Initializing help system before first use

Inputting and solving a Linear Programming problem

In this chapter we take the example formulated in Chapter 2 and show how to implement the model with BCL. With some extensions to the initial formulation we also introduce input and output functionalities of BCL:

  • writing an LP model with BCL,
  • data input from file using index sets,
  • output facilities of BCL,
  • exporting a problem to a matrix file.

Chapter 3 shows how to formulate and solve the same example with Mosel and in Chapter 15 the problem is input and solved directly with Xpress Optimizer.

Implementation with BCL

All BCL examples in this book are written with C++. Due to the possibility of overloading arithmetic operators in the C++ programming language, this interface provides the most convenient way of stating models in a close to algebraic form. The same models can also be implemented using the C, Java, or C# interfaces of BCL.

The following BCL program implements the LP example introduced in Chapter 2:

#include <iostream>
#include "xprb_cpp.h"

using namespace std;
using namespace ::dashoptimization;

#define NSHARES 10                   // Number of shares
#define NRISK 5                      // Number of high-risk shares
#define NNA 4                        // Number of North-American shares

double RET[] = {5,17,26,12,8,9,7,6,31,21};  // Estimated return in investment
int RISK[] = {1,2,3,8,9};            // High-risk values among shares
int NA[] = {0,1,2,3};                // Shares issued in N.-America

int main(int argc, char **argv)
{
 int s;
 XPRBprob p("FolioLP");              // Initialize a new problem in BCL
 XPRBexpr Risk,Na,Return,Cap;
 XPRBvar frac[NSHARES];              // Fraction of capital used per share

// Create the decision variables
 for(s=0;s<NSHARES;s++) frac[s] = p.newVar("frac");

// Objective: total return
 for(s=0;s<NSHARES;s++) Return += RET[s]*frac[s];
 p.setObj(Return);                   // Set the objective function

// Limit the percentage of high-risk values
 for(s=0;s<NRISK;s++) Risk += frac[RISK[s]];
 p.newCtr("Risk", Risk <= 1.0/3);

// Minimum amount of North-American values
 for(s=0;s<NNA;s++) Na += frac[NA[s]];
 p.newCtr("NA", Na >= 0.5);

// Spend all the capital
 for(s=0;s<NSHARES;s++) Cap += frac[s];
 p.newCtr("Cap", Cap == 1);

// Upper bounds on the investment per share
 for(s=0;s<NSHARES;s++) frac[s].setUB(0.3);

// Solve the problem
 p.setSense(XPRB_MAXIM);
 p.lpOptimize("");

// Solution printing
 cout << "Total return: " << p.getObjVal() << endl;
 for(s=0;s<NSHARES;s++)
  cout << s << ": " << frac[s].getSol()*100 << "%" << endl;

 return 0;
}

Let us now have a closer look at what we have just written.

Initialization

To use the BCL C++ interface you need to include the header file xprb_cpp.h. We also define the namespace to which the BCL classes belong.

If the software has not been initialized previously, BCL is initialized automatically when the first problem is created, that is by the line

 XPRBprob p("FolioLP");

which creates a new problem with the name `FolioLP'.

General structure

The definition of the model itself starts with the creation of the decision variables (method newVar), followed by the definition of the objective function and the constraints. In C++ (and Java, C#) constraints may be created starting with linear expressions as shown in the example. Equivalently, they may be constructed termwise, for instance the constraint limiting the percentage of high-risk shares:

 XPRBctr CRisk;
 CRisk = p.newCtr("Risk");
 for(s=0;s<NRISK;s++) CRisk.addTerm(frac[RISK[s]], 1);
 CRisk.setType(XPRB_L);
 CRisk.addTerm(1.0/3);

This second type of constraint definition is common to all BCL interfaces and is the only method of defining constraints in C where overloading is not available.

Notice that in the definition of equality constraints (here the constraint stating that we wish to spend all the capital) we need to employ a double equality sign ==.

The method setUB is used to set the upper bounds on the decision variables frac. Alternatively to this separate function call, we may also specify the bounds directly at the creation of the variables, but in this case we need to provide the full information including the name, variable type (XPRB_PL for continuous), lower and upper bound values:

 for(s=0;s<NSHARES;s++) frac[s] = p.newVar("frac", XPRB_PL, 0, 0.3);

Giving string names to modeling objects (decision variables, constraints, etc.) as shown in our example program is optional. If the user does not specify any name, BCL will generate a default name. However, user-defined names may be helpful for debugging and for the interpretation of output produced by the Optimizer.

Solving

Prior to launching the solver, the optimization direction is set to maximization with a call to setSense. With the method lpOptimize we then call Xpress Optimizer to maximize the objective function (Return) set with the method setObj, subject to all constraints that have been defined. The empty string argument of lpOptimize indicates that the default LP algorithm is to be used. Other possible values are "p" for primal, "d" for dual Simplex, and "b" for Newton-Barrier.

Output printing

The last few lines print out the value of the optimal solution and the solution values for all variables.

Compilation and program execution

If you have followed the standard installation procedure of Xpress Optimizer and BCL, you may compile this file with the following command under Windows (note that it is important to use the flag /MD):

cl /MD /I%XPRESSDIR%\include %XPRESSDIR%\lib\xprb.lib foliolp.cpp

For Linux or Solaris use

cc -D_REENTRANT -I${XPRESSDIR}/include -L${XPRESSDIR}/lib foliolp.C -o foliolp -lxprb

For other systems please refer to the example makefile provided with the corresponding distribution.

Running the resulting program will generate the following output:

Reading Problem FolioLP
Problem Statistics
           3 (      0 spare) rows
          10 (      0 spare) structural columns
          19 (      0 spare) non-zero elements
Global Statistics
           0 entities        0 sets        0 set members
Maximizing LP FolioLP
Original problem has:
         3 rows           10 cols           19 elements
Presolved problem has:
         3 rows           10 cols           19 elements

   Its         Obj Value      S   Ninf  Nneg        Sum Inf  Time
     0         42.600000      D      2     0        .000000     0
     5         14.066667      D      0     0        .000000     0
Uncrunching matrix
Optimal solution found
Dual solved problem
  5 simplex iterations in 0s

Final objective                         : 1.406666666666667e+01
  Max primal violation      (abs / rel) :       0.0 /       0.0
  Max dual violation        (abs / rel) :       0.0 /       0.0
  Max complementarity viol. (abs / rel) :       0.0 /       0.0
All values within tolerances
Problem status: optimal
Total return: 14.0667
0: 30%
1: 0%
2: 20%
3: 0%
4: 6.66667%
5: 30%
6: 0%
7: 0%
8: 13.3333%
9: 0%

The upper half of this display is the log of Xpress Optimizer: the size of the matrix, 3 rows (i.e. constraints) and 10 columns (i.e. decision variables), and the log of the LP solution algorithm (here: `D' for dual Simplex). The lower half is the output produced by our program: the maximum return of 14.0667 is obtained with a portfolio consisting of shares 1, 3, 5, 6, and 9. 30% of the total amount are spent in shares 1 and 6 each, 20% in 3, 13.3333% in 9 and 6.6667% in 5. It is easily verified that all constraints are indeed satisfied: we have 50% of North-American shares (1 and 3) and 33.33% of high-risk shares (3 and 9).

It is possible to modify the amount of output printing by BCL and Xpress Optimizer by adding the following line before the start of the optimization:

 p.setMsgLevel(1);

This setting will disable all output (including warnings) from BCL and Xpress Optimizer, with the exception of error messages. The possible values for the printing level range from 0 to 4. In Chapter 13 we show how to access the Optimizer control parameters directly which, for instance, allows fine tuning the message display.

Data input from file

Instead of simply numbering the decision variables, we may wish to use more meaningful indices in our model. For instance, the problem data may be given in file(s) using string indices such as the file foliocpplp.dat we now wish to read:

! Return
"treasury" 	5
"hardware" 	17
"theater" 	26
"telecom" 	12
"brewery" 	8
"highways" 	9
"cars" 		7
"bank" 		6
"software" 	31
"electronics" 	21

We modify our previous model as follows to work with this data file:

#include <iostream>
#include "xprb_cpp.h"

using namespace std;
using namespace ::dashoptimization;

#define DATAFILE "foliocpplp.dat"

#define NSHARES 10                   // Number of shares
#define NRISK 5                      // Number of high-risk shares
#define NNA 4                        // Number of North-American shares

double RET[NSHARES];                 // Estimated return in investment
char RISK[][100] = {"hardware", "theater", "telecom", "software",
                    "electronics"};  // High-risk values among shares
char NA[][100] = {"treasury", "hardware", "theater", "telecom"};
                                     // Shares issued in N.-America

XPRBindexSet SHARES;                 // Set of shares

XPRBprob p("FolioLP");               // Initialize a new problem in BCL

void readData(void)
{
 double value;
 int s;
 FILE *datafile;
 char name[100];

 SHARES=p.newIndexSet("Shares",NSHARES);  // Create the `SHARES' index set

// Read `RET' data from file
 datafile=fopen(DATAFILE,"r");
 for(s=0;s<NSHARES;s++)
 {
  XPRBreadlinecb(XPRB_FGETS, datafile, 200, "T g", name, &value);
  RET[SHARES+=name]=value;
 }
 fclose(datafile);

 SHARES.print();                     // Print out the set contents
}

int main(int argc, char **argv)
{
 int s;
 XPRBexpr Risk,Na,Return,Cap;
 XPRBvar frac[NSHARES];              // Fraction of capital used per share

// Read data from file
 readData();

// Create the decision variables
 for(s=0;s<NSHARES;s++) frac[s] = p.newVar("frac");

// Objective: total return
 for(s=0;s<NSHARES;s++) Return += RET[s]*frac[s];
 p.setObj(Return);                   // Set the objective function

// Limit the percentage of high-risk values
 for(s=0;s<NRISK;s++) Risk += frac[SHARES[RISK[s]]];
 p.newCtr("Risk", Risk <= 1.0/3);

// Minimum amount of North-American values
 for(s=0;s<NNA;s++) Na += frac[SHARES[NA[s]]];
 p.newCtr("NA", Na >= 0.5);

// Spend all the capital
 for(s=0;s<NSHARES;s++) Cap += frac[s];
 p.newCtr("Cap", Cap == 1);

// Upper bounds on the investment per share
 for(s=0;s<NSHARES;s++) frac[s].setUB(0.3);

// Solve the problem
 p.setSense(XPRB_MAXIM);
 p.lpOptimize("");

// Solution printing
 cout << "Total return: " << p.getObjVal() << endl;
 for(s=0;s<NSHARES;s++)
  cout << SHARES[s] << ": " << frac[s].getSol()*100 << "%" << endl;

 return 0;
}

The arrays RISK and NA now store indices in the form of strings and we have added a new object, the index set SHARES that is defined while the return-on-investment values RET are read from the data file. In this example we have initialized the index set with exactly the right size. This is not really necessary since index sets may grow dynamically if more entries are added to them than the initially allocated space. The actual set size can be obtained with the method getSize.

For reading the data we use the function XPRBreadlinecb. It will skip comments preceded by ! and any empty lines in the data file. The format string "T g" indicates that we wish to read a text string (surrounded by single or double quotes if it contains blanks) followed by a real number, the two separated by spaces (including tabulation). If the data file used another separator sign such as `,' then the format string could be changed accordingly (e.g. "T,g").

In the model itself, the definition of the linear expressions Risk and Na has been adapted to the new indices.

Another modification concerns the solution printing: we print the name of every share, not simply its sequence number and hence the solution now gets displayed as follows:

Total return: 14.0667
treasury: 30%
hardware: 0%
theater: 20%
telecom: 0%
brewery: 6.66667%
highways: 30%
cars: 0%
bank: 0%
software: 13.3333%
electronics: 0%

Output functions and error handling

Most BCL modeling objects (XPRBprob, XPRBvar, XPRBctr, XPRBsos, and XPRBindexSet) have a method print. For variables, depending on where the method is invoked either their bounds or their solution value is printed: adding the line

 frac[2].print();

before the call to the optimization will print the name of the variable and its bounds,

 frac2: [0,0.3]

whereas after the problem has been solved its solution value gets displayed:

 frac2: 0.2

Whenever BCL detects an error it stops the program execution with an error message so that it will usually not be necessary to test the return value of every operation. If a BCL program is embedded into some larger application, it may be helpful to use the explicit initialization to check early on that the software can be accessed correctly, for instance:

 if(XPRB::init() != 0)
 {
   cout << "Initialization failed." << endl;
   return 1;
 }

The method getLPStat may be used to test the LP problem status. Only if the LP problem has been solved successfully BCL will return or print out meaningful solution values:

 char *LPSTATUS[] = {"not loaded", "optimal", "infeasible",
                     "worse than cutoff", "unfinished", "unbounded",
                     "cutoff in dual", "unsolved", "nonconvex"};

 cout << "Problem status: " << LPSTATUS[p.getLPStat()] << endl;

Exporting matrices

If the optimization process with Xpress Optimizer is started from within a BCL program (methods lpOptimize or mipOptimize of XPRBprob), then the problem matrix is loaded in memory into the solver without writing it out to a file (which would be expensive in terms of running time). However, in certain cases it may still be required to be able to produce a matrix. With Xpress, the user has the choice between two matrix formats: extended MPS and extended LP format, the latter being in general more easily human-readable since constraints are printed in algebraic form.

To export a matrix in MPS format add the following line to your BCL program, immediately before or instead of the optimization statement; this will create the file Folio.mat in your working directory:

 p.exportProb(XPRB_MPS, "Folio");

For an LP format matrix use the following:

 p.setSense(XPRB_MAXIM);
 p.exportProb(XPRB_LP, "Folio");

The LP format contains information about the sense of optimization. Since the default is to minimize, for maximization we first need to reset the sense. The resulting matrix file will have the name Folio.lp.