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.
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:
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-2020 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.