A Monte Carlo Linear Programming Implementation

Abstract

Most Linear Programming (LP) models include exogenous parameters whose values cannot be influenced by the models’ users.  Commodity prices, interest rates and return on investments are examples of exogenous parameters.  Monte Carlo Linear Programming (MCLP) is a set of instances for a particular model in which the values for the exogenous parameters are generated randomly.  The results of an MCLP is a statistical report summarizing the behavior of the runs.

This paper presents the architecture and implementation for extending a linear programming optimizer to implement a Monte Carlo linear programming optimizer.  An example of an operational MCLP is given.

Introduction

A conventional Linear Programming (LP) model provides a single answer for a given set of parameters.  Varying those parameters for a set of solutions is called sensitivity analysis and gives a picture of how the model behaves as the parameters change.  This is a very useful tool for the real world practitioner.

Some of the model’s parameters are exogenous variables in that they are determined by environmental factors outside of the practitioner’s control.  Investment returns, interest rates and crude oil prices are examples of exogenous variables.  MCLP solves many instances of the model, each with its exogenous variable randomly generated. Whereas a conventional LP reports a solution view of the model’s variables,  MCLP’s output presents a statistical view of the behavior of the model over the range of these randomly generated variables.  Generating these random values is an art form unto itself, peculiar to each application.

This paper presents a generalized architecture for embedding the code for a MCLP into a conventional LP system.  Included is an example of a successfully application of this technique in the form of an Internet based retirement decision support system, the Optimal Retirement Planner (ORP, http://i-orp.com). 

ORP is a linear programming (LP) implementation of a retirement planner.  It has been available, at no charge, on the Internet free since 1996. The purpose of ORP is demonstrate how a computationally intensive process can be made accessible to the public. The Monte Carlo option is a recent addition to ORP.

The MCLP is an extension to the Bosc optimizer used to solve the ORP LP.  Bosc is William Orchard-Hays’ [3] last optimizer.  Bosc was originally implemented to solve feed blending models for Precision Soya Inc in New Castle, Indiana.

Architecture

Bosc is comprised of a main program and a library of functions.  The main program is available in source form so that it can be modified.  Several commercial LP systems are available in a form similar to this

 

 

Abstractly the main program looks something like this:

 

CALL INPUT( … )           !Input and set up the problem, from a standard MPS input file.

CALL SOLVE(…)            !Get an optimal or failed solution

CALL OUTPUT(…)         !Generate the standard MPS solution file

EXIT

 

Embedding MCLP into Bosc requires adding 3 new functions to the Bosc library and adjusting the main program.  The three new functions are:

  1. MCBuild:  analyze the LP matrix and set up the infrastructure for inserting the random  values into the matrix.
  2. MCFill:  generate the random values and insert them into the matrix.
  3. MCDecide:  evaluate the most recent solution to decide whether to go on for another Monte Carlo iteration or stop.  If the decision is to stop, write a file containing the summary of all runs.

 

These three functions are called from the main program as shown below.  This assumes that a control value came into the main program in its command line.  The settings for the control value are:

  1. Normal LP, no Monte Carlo processing.
  2. Debug mode, make one Monte Carlo iteration and stop.
  3. Monte Carlo mode, make multiple LP runs each with different randomly generated values for certain exogenesis values.

 

The pseudo code logic for the revised main program is as follows:

 

CALL INPUT(…)            !Input and set up the problem, from a standard MPS input file.

IF( control != 1 ) CALL MCBuild(…)           !Create Monte Carlo structures

  100   CONTINUE                                   !Loop back here on each Monte Carlo iteration

            IF( control != 1 ) CALL MCFill(…)                          !Generate the random values   

CALL SOLVE(…)                                              !Get an optimal or failed solution

IF( problem not optimal ) GOTO 100             !Generate new values and try again

IF( control = 3 and MCDecide( ) =0 )  GOTO 100                           !Not done yet

CALL OUTPUT(…)                               !Generate the standard MPS solution file

EXIT

 

When SOLVE is called the basis from the previous run is the starting basis for the next solution.  Only a few LP iterations are required to reach the next optimal solution.

 

The three new functions assume that the in-memory LP matrix, its descriptive structures and the optimizer’s solution vector are accessible. All of the processing done by the functions is done from the optimizer’s structures or from supporting data loaded by MCBuild.  Beyond these changes to the main program there are no other modifications to the Bosc LP system.

 

Monte Carlo Function Descriptions

 

This section describes the three functions that implement Monte Carlo LP in Bosc.  Each is described generally as a generic MCLP application and then specifically as the ORP implementation.

 

MCBuild:

MCBuild scans through the LP matrix, finds the coefficients to be modified and sets up a structure for MCFill to use during Monte Carlo iterations.  MCBuild is called only once.

 

The LP matrix is described in accessible values and arrays:

 

m is the number of rows in the model.

n is the number of columns in the model

mn is the number of non null coefficients in the matrix.

 

RowNames (m) contains an 8 character name for each row in the matrix.

 

ColNames(n) contains an 8 character name for each column in the matrix.

 

ORP is a MathPro generated matrix.  MathPro views an LP matrix as row strips, that are collections of equations of identical form, and column strips, that are collections of columns of identical structure.  The first character of each row name identifies its row strip with an alphabetic character, A, B, C, ….  Similarly for column strips.

 

ColDir(n+1) contains the integer index of the column’s elements in the matrix array.  ColDir(n+1) points the matrix cell after the last matrix element.  Thus the matrix entries for column j can be found in the elements ColDir(j) through ColDir(j+1)-1.  The number of elements in the column is computed as ColDir(j+1) – ColDir(j).

 

Matrix(2,mn) contains two values for each matrix element:

  1. Matrix(1,k) is the row index for element k.
  2. Matrix(2,k) is the index into the coefficient pool for element k.

 

Pool(nc) is a single precision, floating point array of unique model coefficients.  Most of the coefficients in ORP are 1, -1 or two digit fractions.  Bosc uses WHIZARD’s [2] coefficient pool implementation. While there are nc coefficients in Pool the actual array is much bigger and it had better be because MCBuild is going to need it.   

 

MCLP has a few variables and arrays that are private and not of interest to the LP side of things.

 

y is the number of years that are being modeled by ORP. For a 55 year old retiree expecting to live until 90 y = 45, 10 years of work left and 35 years in retirement.

 

InvRet(y) contains the investment returns specified by the user in the ORP input form and put into the matrix by MathPro.  These values are used by MCFill while calculating the randomized return values

 

MCBuild is interested in columns in the column strips B, L and Q as well as the rows in row strips C, F and M.  The coefficients in blocks (B, C), (L, F) and (Q, M) are to be randomized.  ORP is a time dynamic model and the structure of these blocks is:

 

            1

            -v      1

                      -v     1

                              -v  …

 

The v’s are the coefficients that are the average return on investment that are to be replaced by randomized values.

 

MCBuild iterates through ColName looking for names from the target column strips.  Columns for a strip are all grouped together which makes things easier. When MCBuild finds the column strip of interest it scans through the matrix elements, pointed to by ColDir, until it finds the row strip of interest.  The row index of the matrix element points to the row name in RowNames.  The rows of interest are identified by the first character of the row name

 

MCBuild moves the pool coefficient to InvRet(q).  Then the matrix pool index (Matrix(2,h) is changed to point to the next available pool position Pool(nc+q).  Then increment q, the count of new coefficients added to the pool.  Because of the structure of the block of coefficients, i.e. there is only one coefficient of interest for each column; once a coefficient has been processed we can move to the next matrix column.

 

y is the number of years being modeled.  During the processing of the last block only – (Q,M)- MCBuild counts the number of intersections processed, which is also the number of years being modeled.  This count is of great interest to MCFill.  ORP models the IRA and Roth IRA separately for the retiree and spouse.  The block (Q,M) represents the couples after-tax account which is combined account.  The column strips B and L contains two matrix columns (retiree and spouse) for each year which is not too helpful.

 

InvRet, the last value of q and y are all kept in COMMON for MCFill’s use.  q = 5*y for a married couple and q = 3*y for a single retiree.

 

MCBuild finishes by initializing a hand full of variables used by MCDecide.  For example the count of Monte Carlo iterations is set to 0.

 

MCFill

 

MCFill computes randomly generated coefficients and inserts them into the matrix.  MCFill has two steps.  The first generates the random returns for each year being modeled.  The second scales that return by the user’s volatility desires and stores it in the matrix pool.

 

AnnualRet(y) is local to MCFill.  A random asset return is generated for each year and stored in AnnualRet.  The details of the generation process can be found in Appendix B and Appendix C.

 

Next the InvRet array is scanned using index x running from 1 to q.  The year being processed is:

z = mod(x-1, y)+1.

 

The new coefficient stored is

 

Pool(nc+x ) = InvRet(x)*(AnnualRet(z)/0.08)

 

The justification for this is to be found in Appendix D.

 

MCDecide

 

MCDecide keeps statistics on the Monte Carlo process is going and makes the decision as to when to stop.

 

S is amount of money, after taxes, that is available for spending in the first year of retirement.  (It is inflated annually thereafter.)  It is found in the last position of Bosc’s solution vector.  (A back up position is to scan the row names looking for its row index but since the row is basic from the get go searching for it is usually not needed.)  S is ORP’s most important computed value, followed by the schedule in the Withdrawal Report.

 

MCDecide keeps a running sum of S and the sum of the squares of S.   (The initial values of zero are set by MCBuild).

 

The average of S for all prior iterations is computed.

 

i, the Monte Carlo iteration count is incremented. The sum and sum of the squares of S are incremented by S and S**2.

 

The new average is computed.

 

If the new average and the old average disagree by more than the tolerance 0.1 and fewer than 500 iterations have been run then MCDecide signals the main program to carry on and exits.  0.1 is actually $1,000 so that’s not a very tight tolerance

 

Otherwise the process terminates.  MCDecide writes a file in the form of an html document containing the Monte Carlo statistics and exits.  The summary shows the number of Monte Carlo iterations, the mean value for S and the first and second standard deviations below the mean and the worst trial encountered.  The html file is picked up by client and displayed to the user.

 

Conclusion

 

The process described here is specific to ORP.  The architecture is general and can be applied to any Monte Carlo LP implementation.  All that has to be done is to define the communicating common and program the three functions.

Appendix B:  Annual Return Domain

To create ORP's asset return domain the list of annual returns of the S&P 500 stocks, sorted by year, is directed into two other lists.

List Pos contains returns where the previous year's return was positive. i.e. Pos list contains r(j) if r(j-1) > 0.

Likewise the Neg list contains returns where the previous years return was negative: i.e. Net list contains r(j) if r(j-1) <= 0.

These two lists are used by MCFill to create the randomized annual returns as described in Appendix c.

Appendix C: Computational Algorithm

The idea is to compute a random annual investment return for each year j.   ORP does random sampling of the historical data stored in Pos and Neg.  The selected value is random, historically accurate and follows the trend set by the previous coefficient.

Compute model's return m for year j as:

For j=1, randomly select m(1) from the combined list.
Set m(j) = Pos(k) if m(j-1) > 0 where k is a random integer 1<= k <= 501, j > 2.
Set m(j) = Neg(k) if m(j-1) < 0 where k is a random integer 1 <= k <.= 195, j > 2.

Appendix D: Incorporating the User's Investment Policy

From 1955 to 2009 the average annual return for the S&P 500 is 8%, or .08. The user has specified her expected annual return on the ORP parameter form is available in the LP matrix..

A specified return R < .08 is a conservative investment policy with a low volatility. Expected returns R >= .08 are for an aggressive portfolio with a high volatility.

Each m(j) is adjusted to reflect the users expectations (hopes). m(j) is scaled to reflect this using the scale R/.08, where .08 is the average return of the combine list of returns and R is the user specified expected asset returns. Note that if R < .08 then R/.08 is less than 1.0 and return volatility is reduced.  Similarly for R >.08 the return volatility is increased. Therefore the generated value is m`(j) = (R/.08) * m(j).

 

References:

 

  1. Monte Carlo working paper;  i-orp.com/ModelDescription/MonteCarlo.html.)
  2. Denis Rarick,  WHIZARD User Manual, Management Science Systems, Inc. 1977

 

 

###

 

 

 

 

October 14, 2009

© 2009, James S. Welch, Jr