Initializing help system before first use

MIP model 1: limiting the number of different shares

To be able to count the number of different values we are investing in, we introduce a second set of variables buys in the LP model developed in Chapter 2. These variables are indicator variables or binary variables. A variable buys takes the value 1 if the share s is taken into the portfolio and 0 otherwise.

We introduce the following constraint to limit the total number of assets to a maximum of MAXNUM. It expresses the constraint that at most MAXNUM of the variables buys may take the value 1 at the same time.

s ∈ SHARES buys ≤ MAXNUM

We now still need to link the new binary variables buys with the variables fracs, the quantity of every share selected into the portfolio. The relation that we wish to express is `if a share is selected into the portfolio, then it is counted in the total number of values' or `if fracs > 0 then buys = 1'. The following inequality formulates this implication:

∀ s ∈ SHARES: fracs ≤ buys

If, for some s, fracs is non-zero, then buys must be greater than 0 and hence 1. Conversely, if buys is at 0, then fracs is also 0, meaning that no fraction of share s is taken into the portfolio. Notice that these constraints do not prevent the possibility that buys is at 1 and fracs at 0. However, this does not matter in our case, since any solution in which this is the case is also valid with both variables, buys and fracs, at 0.

Implementation with BCL

We extend the LP model developed in Chapter 10 with the new variables and constraints. The fact that the new variables are binary variables (i.e. they only take the values 0 and 1) is expressed through the type XPRB_BV at their creation.

Another common type of discrete variable is an integer variable, that is, a variable that can only take on integer values between given lower and upper bounds. These variables are defined in BCL with the type XPRB_UI. In the following section (MIP model 2) we shall see yet another example of discrete variables, namely semi-continuous variables.

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

using namespace std;
using namespace ::dashoptimization;

#define MAXNUM 4                   // Max. number of shares to be selected

#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("FolioMIP1");          // Initialize a new problem in BCL
 XPRBexpr Risk,Na,Return,Cap,Num;
 XPRBvar frac[NSHARES];            // Fraction of capital used per share
 XPRBvar buy[NSHARES];             // 1 if asset is in portfolio, 0 otherwise

// Create the decision variables (including upper bounds for `frac')
 for(s=0;s<NSHARES;s++)
 {
  frac[s] = p.newVar("frac", XPRB_PL, 0, 0.3);
  buy[s] = p.newVar("buy", XPRB_BV);
 }

// 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 <= 1.0/3);

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

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

// Limit the total number of assets
 for(s=0;s<NSHARES;s++) Num += buy[s];
 p.newCtr(Num <= MAXNUM);

// Linking the variables
 for(s=0;s<NSHARES;s++) p.newCtr(frac[s] <= buy[s]);

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

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

 return 0;
}

Besides the additional variables and constraints, the choice of optimization algorithm needs to be adapted to the problem type: we now wish to solve a MIP problem via Branch-and-Bound, and we therefore use the method mipOptimize.

Just as with the LP problem in the previous chapter, it is usually helpful to check the solution status before accessing the MIP solution—only if the MIP status is `unfinished (solution found)' or `optimal' will BCL print out a meaningful solution:

 char *MIPSTATUS[] = {"not loaded", "not optimized", "LP optimized",
                      "unfinished (no solution)",
                      "unfinished (solution found)", "infeasible",
                      "optimal", "unbounded"};

 cout << "Problem status: " << MIPSTATUS[p.getMIPStat()] << endl;

Analyzing the solution

As the result of the execution of our program we obtain the following output:

Reading Problem FolioMIP1
Problem Statistics
          14 (    514 spare) rows
          20 (      0 spare) structural columns
          49 (   5056 spare) non-zero elements
Global Statistics
          10 entities        0 sets        0 set members
Maximizing MILP FolioMIP1
Original problem has:
        14 rows           20 cols           49 elements        10 globals
Presolved problem has:
        13 rows           19 cols           46 elements         9 globals
LP relaxation tightened
Will try to keep branch and bound tree memory usage below 14.8Gb
Starting concurrent solve with dual

 Concurrent-Solve,   0s
            Dual
    objective   dual inf
 D  14.066667   .0000000
------- optimal --------
Concurrent statistics:
      Dual: 4 simplex iterations, 0.00s
Optimal solution found

   Its         Obj Value      S   Ninf  Nneg   Sum Dual Inf  Time
     4         14.066667      D      0     0        .000000     0
Dual solved problem
  4 simplex iterations in 0s

Final objective                         : 1.406666666666667e+01
  Max primal violation      (abs / rel) : 5.551e-17 / 5.551e-17
  Max dual violation        (abs / rel) :       0.0 /       0.0
  Max complementarity viol. (abs / rel) :       0.0 /       0.0
All values within tolerances

Starting root cutting & heuristics

 Its Type    BestSoln    BestBound   Sols    Add    Del     Gap     GInf   Time
c           13.100000    14.066667      1                  6.87
   1  K     13.100000    13.908571      1      1      0    5.81      0
   2  K     13.100000    13.580000      1     12      0    3.53      0
 *** Search completed ***     Time:     0 Nodes:          1
Number of integer feasible solutions found is 1
Best integer solution found is    13.100000
Best bound is    13.100014
Uncrunching matrix
Problem status: optimal
Total return: 13.1
0: 20% (1)
1: 0% (0)
2: 30% (1)
3: 0% (0)
4: 20% (1)
5: 30% (1)
6: 0% (0)
7: 0% (0)
8: 0% (0)
9: 0% (0)

At the beginning we see the log of the execution of Xpress Optimizer: the problem statistics (we now have 14 constraints and 20 variables, out of which 10 are MIP variables, refered to as `entities'), the log of the execution of the LP algorithm (concurrently solving with primal and dual simplex on a multi-core processor), the log of the built-in MIP heuristics (a solution with the value 13.1 has been found) and the automated cut generation (a total of 13 cuts of type `K' = knapsack have been generated). Since this problem is very small, it is solved by the MIP heuristics and the addition of cuts (additional constraints that cut off parts of the LP solution space, but no MIP solution) tightens the LP formulation in such a way that the solution to the LP relaxation becomes integer feasible. The Branch-and-Bound search therefore stops at the first node and no log of the Branch-and-Bound search gets displayed.

The output printed by our program tells us that the problem has been solved to optimality (i.e. the MIP search has been completed and at least one integer feasible solution has been found). The maximum return is now lower than in the original LP problem due to the additional constraint. As required, only four different shares are selected to form the portfolio.

© 2001-2019 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.