Case Study 1

Suppose a farmer has 75 acres on which to plant two crops: wheat and barley. To produce these crops, it costs the farmer (for seed, fertilizer, etc.) $120 per acre for the wheat and $210 per acre for the barley. The farmer has $15000 available for expenses. But after the harvest, the farmer must store the crops while awaiting avourable market conditions. The farmer has storage space for 4000 bushels. Each acre yields an average of 110 bushels of wheat or 30 bushels of barley. If the net profit per bushel of wheat (after all expenses have been subtracted) is $1.30 and for barley is $2.00, how should the farmer plant the 75 acres to maximize profit?

Problem definition

First, we need to translate the problem in a mathematical way. Let’s define the following variables

  • \(w\) is the number of acres allotted to wheat.
  • \(b\) is the number of acres allotted to barley.

Now we can define \(\hat X = \begin{pmatrix} w \\ b \end{pmatrix}\) as the decision variable vector. Note that it must be \(\hat X \geq 0\).

We would like to maximize the profit, so we must set our objective function as follows

\[ P(w, b) = (110*1.30) w + (30*2.00) b = MAX(profit) \]

Then the expression to be maximized, that is the profit, is clearly

\[P(w, b) = 143 w + 60 b = MAX(profit) \]

which means that \[\hat P = \begin{pmatrix} 143 \\ 60 \end{pmatrix}\].

The constraints can be set in the following ways:

  1. Cost for seed, fertilizer, etc. \[ 120w + 210b \leq 15000 \]
  2. Storage space \[ 110w + 30b \leq 4000 \]
  3. Area of farmland \[ w + b \leq 75 \]
  4. Acres allotted to wheat \[ w \geq 0 \]
  5. Acres allotted to barley \[ b \geq 0 \]

We can now define the coefficient matrix \[A = \begin{pmatrix} 120 & 210 \\ 110 & 30 \\ 1 & 1 \\ -1 & 0 \\ 0 & -1 \end{pmatrix}\], and

\[B = \begin{pmatrix} 15000 \\ 4000 \\ 75 \\ 0 \\ 0 \end{pmatrix}\].

Solution with lpSolve

lpsolve: is callable from R via an extension or module. As such, it looks like lpsolve is fully integrated with R. Matrices can directly be transferred between R and lpsolve in both directions. The complete interface is written in P so it has maximum performance.

Objective Function

Here are the coefficients of the decision variables:

  • The net profit per bushel of wheat \(w\) is \(\$1.30\)
  • The net profit per bushel of barley \(b\) is \(\$2.00\)

Therefore, the obj function is:

\[ P(w, b) = (110*1.30) w + (30*2.00) b \] \[P(w, b) = 143 w + 60 b \]

## [1] 143  60

Constraint Matrix

  • First row is for the cost for seed, fertilizer, etc.
  • Second row is for the storage space
  • Third row is for the area of farmland
  • Fourth row is for acres allotted to wheat
  • Fifth row is for acres allotted to barley
##      [,1] [,2]
## [1,]  120  210
## [2,]  110   30
## [3,]    1    1
## [4,]    1    0
## [5,]    0    1
## [1] 15000  4000    75     0     0
## [1] "<=" "<=" "<=" ">=" ">="

The Optimum Result

## List of 28
##  $ direction       : int 1
##  $ x.count         : int 2
##  $ objective       : num [1:2] 143 60
##  $ const.count     : int 5
##  $ constraints     : num [1:4, 1:5] 120 210 1 15000 110 30 1 4000 1 1 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:4] "" "" "const.dir.num" "const.rhs"
##   .. ..$ : NULL
##  $ int.count       : int 2
##  $ int.vec         : int [1:2] 1 2
##  $ bin.count       : int 0
##  $ binary.vec      : int 0
##  $ num.bin.solns   : int 1
##  $ objval          : num 6266
##  $ solution        : num [1:2] 22 52
##  $ presolve        : int 0
##  $ compute.sens    : int 0
##  $ sens.coef.from  : num 0
##  $ sens.coef.to    : num 0
##  $ duals           : num 0
##  $ duals.from      : num 0
##  $ duals.to        : num 0
##  $ scale           : int 196
##  $ use.dense       : int 0
##  $ dense.col       : int 0
##  $ dense.val       : num 0
##  $ dense.const.nrow: int 0
##  $ dense.ctr       : num 0
##  $ use.rw          : int 0
##  $ tmp             : chr "Nobody will ever look at this"
##  $ status          : int 0
##  - attr(*, "class")= chr "lp"
## [1] 0
##  w  b 
## 22 52
## [1] "Maksimum profit: 6266"

Solution with lpSolveAPI

The lpSolveAPI R package is a second implementation of an interface of lpsolve to R. It provides an R API mirroring the lp_solve C API and hence provides a great deal more functionality but has a steeper learning curve.

Let’s try to solve the problem again using lpSolveAPI:

## $anti.degen
## [1] "fixedvars" "stalling" 
## 
## $basis.crash
## [1] "none"
## 
## $bb.depthlimit
## [1] -50
## 
## $bb.floorfirst
## [1] "automatic"
## 
## $bb.rule
## [1] "pseudononint" "greedy"       "dynamic"      "rcostfixing" 
## 
## $break.at.first
## [1] FALSE
## 
## $break.at.value
## [1] 1e+30
## 
## $epsilon
##       epsb       epsd      epsel     epsint epsperturb   epspivot 
##      1e-10      1e-09      1e-12      1e-07      1e-05      2e-07 
## 
## $improve
## [1] "dualfeas" "thetagap"
## 
## $infinite
## [1] 1e+30
## 
## $maxpivot
## [1] 250
## 
## $mip.gap
## absolute relative 
##    1e-11    1e-11 
## 
## $negrange
## [1] -1e+06
## 
## $obj.in.basis
## [1] TRUE
## 
## $pivoting
## [1] "devex"    "adaptive"
## 
## $presolve
## [1] "none"
## 
## $scalelimit
## [1] 5
## 
## $scaling
## [1] "geometric"   "equilibrate" "integers"   
## 
## $sense
## [1] "maximize"
## 
## $simplextype
## [1] "dual"   "primal"
## 
## $timeout
## [1] 0
## 
## $verbose
## [1] "neutral"
## Model name: 
##            C1   C2             
## Maximize  143   60             
## R1          0    0  free      0
## R2          0    0  free      0
## R3          0    0  free      0
## R4          0    0  free      0
## R5          0    0  free      0
## R6        120  210    <=  15000
## R7        110   30    <=   4000
## R8          1    1    <=     75
## R9          1    0    >=      0
## R10         0    1    >=      0
## Kind      Std  Std             
## Type      Int  Int             
## Upper     Inf  Inf             
## Lower       0    0
## [1] 0
## [1] 22 52
## [1] 6266
## $lower
## [1] 0 0
## 
## $upper
## [1] Inf Inf
LS0tDQp0aXRsZTogJ0xhYjM6IExpbmVhciBQcm9ncmFtbWluZyBNb2RlbGluZycNCmF1dGhvcjogIkltZWxkYSBTaWFudHVyaSINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDogb3BlbmludHJvOjpsYWJfcmVwb3J0DQotLS0NCg0KYGBge3IgTG9nbywgZWNobz1GQUxTRSxmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aCA9ICc0MCUnfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vZ2l0aHViLmNvbS9CYWt0aS1TaXJlZ2FyL2ltYWdlcy9ibG9iL21hc3Rlci9sb2dvLnBuZz9yYXc9dHJ1ZSIpDQpgYGANCg0KIyBDYXNlIFN0dWR5IDENCg0KYGBgDQpTdXBwb3NlIGEgZmFybWVyIGhhcyA3NSBhY3JlcyBvbiB3aGljaCB0byBwbGFudCB0d28gY3JvcHM6IHdoZWF0IGFuZCBiYXJsZXkuIFRvIHByb2R1Y2UgdGhlc2UgY3JvcHMsIGl0IGNvc3RzIHRoZSBmYXJtZXIgKGZvciBzZWVkLCBmZXJ0aWxpemVyLCBldGMuKSAkMTIwIHBlciBhY3JlIGZvciB0aGUgd2hlYXQgYW5kICQyMTAgcGVyIGFjcmUgZm9yIHRoZSBiYXJsZXkuIFRoZSBmYXJtZXIgaGFzICQxNTAwMCBhdmFpbGFibGUgZm9yIGV4cGVuc2VzLiBCdXQgYWZ0ZXIgdGhlIGhhcnZlc3QsIHRoZSBmYXJtZXIgbXVzdCBzdG9yZSB0aGUgY3JvcHMgd2hpbGUgYXdhaXRpbmcgYXZvdXJhYmxlIG1hcmtldCBjb25kaXRpb25zLiBUaGUgZmFybWVyIGhhcyBzdG9yYWdlIHNwYWNlIGZvciA0MDAwIGJ1c2hlbHMuIEVhY2ggYWNyZSB5aWVsZHMgYW4gYXZlcmFnZSBvZiAxMTAgYnVzaGVscyBvZiB3aGVhdCBvciAzMCBidXNoZWxzIG9mIGJhcmxleS4gSWYgdGhlIG5ldCBwcm9maXQgcGVyIGJ1c2hlbCBvZiB3aGVhdCAoYWZ0ZXIgYWxsIGV4cGVuc2VzIGhhdmUgYmVlbiBzdWJ0cmFjdGVkKSBpcyAkMS4zMCBhbmQgZm9yIGJhcmxleSBpcyAkMi4wMCwgaG93IHNob3VsZCB0aGUgZmFybWVyIHBsYW50IHRoZSA3NSBhY3JlcyB0byBtYXhpbWl6ZSBwcm9maXQ/DQpgYGANCiMjIFByb2JsZW0gZGVmaW5pdGlvbg0KDQpGaXJzdCwgd2UgbmVlZCB0byB0cmFuc2xhdGUgdGhlIHByb2JsZW0gaW4gYSBtYXRoZW1hdGljYWwgd2F5LiBMZXTigJlzIGRlZmluZSB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcw0KDQoqICR3JCBpcyB0aGUgbnVtYmVyIG9mIGFjcmVzIGFsbG90dGVkIHRvIHdoZWF0Lg0KKiAkYiQgaXMgdGhlIG51bWJlciBvZiBhY3JlcyBhbGxvdHRlZCB0byBiYXJsZXkuIA0KDQpOb3cgd2UgY2FuIGRlZmluZSAkXGhhdCBYID0gXGJlZ2lue3BtYXRyaXh9IHcgXFwgYiBcZW5ke3BtYXRyaXh9JCBhcyB0aGUgZGVjaXNpb24gdmFyaWFibGUgdmVjdG9yLiBOb3RlIHRoYXQgaXQgbXVzdCBiZSAkXGhhdCBYIFxnZXEgMCQuIA0KDQpXZSB3b3VsZCBsaWtlIHRvIG1heGltaXplIHRoZSBwcm9maXQsIHNvIHdlIG11c3Qgc2V0IG91ciBvYmplY3RpdmUgZnVuY3Rpb24gYXMgZm9sbG93cw0KDQokJCBQKHcsIGIpID0gKDExMCoxLjMwKSB3ICsgKDMwKjIuMDApIGIgPSBNQVgocHJvZml0KSAkJA0KDQpUaGVuIHRoZSBleHByZXNzaW9uIHRvIGJlIG1heGltaXplZCwgdGhhdCBpcyB0aGUgcHJvZml0LCBpcyBjbGVhcmx5DQoNCiQkUCh3LCBiKSA9IDE0MyB3ICsgNjAgYiA9IE1BWChwcm9maXQpICQkDQoNCndoaWNoIG1lYW5zIHRoYXQgXFtcaGF0IFAgPSBcYmVnaW57cG1hdHJpeH0gMTQzIFxcIDYwIFxlbmR7cG1hdHJpeH1cXS4NCg0KVGhlIGNvbnN0cmFpbnRzIGNhbiBiZSBzZXQgaW4gdGhlIGZvbGxvd2luZyB3YXlzOg0KDQoxLiBDb3N0IGZvciBzZWVkLCBmZXJ0aWxpemVyLCBldGMuICQkIDEyMHcgKyAyMTBiIFxsZXEgMTUwMDAgJCQNCjIuIFN0b3JhZ2Ugc3BhY2UgJCQgMTEwdyArIDMwYiBcbGVxIDQwMDAgJCQNCjMuIEFyZWEgb2YgZmFybWxhbmQgJCQgdyArIGIgIFxsZXEgNzUgJCQNCjQuIEFjcmVzIGFsbG90dGVkIHRvIHdoZWF0ICQkIHcgXGdlcSAwICQkDQo1LiBBY3JlcyBhbGxvdHRlZCB0byBiYXJsZXkgJCQgYiBcZ2VxIDAgJCQNCg0KV2UgY2FuIG5vdyBkZWZpbmUgdGhlIGNvZWZmaWNpZW50IG1hdHJpeCANClxbQSA9IFxiZWdpbntwbWF0cml4fSAxMjAgJiAyMTAgXFwNCiAgICAgICAgICAgICAgICAgICAgICAxMTAgJiAzMCBcXCANCiAgICAgICAgICAgICAgICAgICAgICAxICYgMSBcXCAgDQogICAgICAgICAgICAgICAgICAgICAtMSAmIDAgXFwNCiAgICAgICAgICAgICAgICAgICAgICAwICYgLTENCiAgICAgICBcZW5ke3BtYXRyaXh9XF0sIGFuZCANCg0KXFtCID0gXGJlZ2lue3BtYXRyaXh9IDE1MDAwIFxcIA0KICAgICAgICAgICAgICAgICAgICAgIDQwMDAgXFwgDQogICAgICAgICAgICAgICAgICAgICAgNzUgXFwgDQogICAgICAgICAgICAgICAgICAgICAgMCBcXA0KICAgICAgICAgICAgICAgICAgICAgIDANCiAgICAgIFxlbmR7cG1hdHJpeH1cXS4NCg0KDQojIyBTb2x1dGlvbiB3aXRoIGBscFNvbHZlYA0KDQpgbHBzb2x2ZTpgIGlzIGNhbGxhYmxlIGZyb20gUiB2aWEgYW4gZXh0ZW5zaW9uIG9yIG1vZHVsZS4gQXMgc3VjaCwgaXQgbG9va3MgbGlrZSBscHNvbHZlIGlzIGZ1bGx5IGludGVncmF0ZWQgd2l0aCBSLiBNYXRyaWNlcyBjYW4gZGlyZWN0bHkgYmUgdHJhbnNmZXJyZWQgYmV0d2VlbiBSIGFuZCBscHNvbHZlIGluIGJvdGggZGlyZWN0aW9ucy4gVGhlIGNvbXBsZXRlIGludGVyZmFjZSBpcyB3cml0dGVuIGluIFAgc28gaXQgaGFzIG1heGltdW0gcGVyZm9ybWFuY2UuDQoNCmBgYHtyfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJscFNvbHZlIikNCmxpYnJhcnkobHBTb2x2ZSkNCmBgYA0KDQojIyMgT2JqZWN0aXZlIEZ1bmN0aW9uDQoNCkhlcmUgYXJlIHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIGRlY2lzaW9uIHZhcmlhYmxlczoNCg0KKiBUaGUgbmV0IHByb2ZpdCBwZXIgYnVzaGVsIG9mIHdoZWF0ICR3JCBpcyAkXCQxLjMwJA0KKiBUaGUgbmV0IHByb2ZpdCBwZXIgYnVzaGVsIG9mIGJhcmxleSAkYiQgaXMgJFwkMi4wMCQNCg0KVGhlcmVmb3JlLCB0aGUgb2JqIGZ1bmN0aW9uIGlzOg0KDQokJCBQKHcsIGIpID0gKDExMCoxLjMwKSB3ICsgKDMwKjIuMDApIGIgJCQNCiQkUCh3LCBiKSA9IDE0MyB3ICsgNjAgYiAkJA0KDQpgYGB7cn0NCiMgU2V0IHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIGRlY2lzaW9uIHZhcmlhYmxlcyAtPiBQDQpQIDwtIGMoMTQzLCA2MCkgICAgICAgIA0KUA0KYGBgDQoNCiMjIyBDb25zdHJhaW50IE1hdHJpeA0KDQoqIEZpcnN0IHJvdyBpcyBmb3IgdGhlIGNvc3QgZm9yIHNlZWQsIGZlcnRpbGl6ZXIsIGV0Yy4gDQoqIFNlY29uZCByb3cgaXMgZm9yIHRoZSBzdG9yYWdlIHNwYWNlDQoqIFRoaXJkIHJvdyBpcyBmb3IgdGhlIGFyZWEgb2YgZmFybWxhbmQNCiogRm91cnRoIHJvdyBpcyBmb3IgYWNyZXMgYWxsb3R0ZWQgdG8gd2hlYXQNCiogRmlmdGggcm93IGlzIGZvciBhY3JlcyBhbGxvdHRlZCB0byBiYXJsZXkNCg0KYGBge3J9DQojIENyZWF0ZSBjb25zdHJhaW50IG1hdHJpeCANCkEgPC0gbWF0cml4KGMoMTIwLCAyMTAsDQogICAgICAgICAgICAgIDExMCwgMzAsDQogICAgICAgICAgICAgIDEsIDEsDQogICAgICAgICAgICAgIDEsIDAsDQogICAgICAgICAgICAgIDAsIDEpLCBucm93PTUsIGJ5cm93PVRSVUUpDQpBDQoNCiMgUmlnaHQgaGFuZCBzaWRlIGZvciB0aGUgY29uc3RyYWludHMNCkIgPC0gYygxNTAwMCwgNDAwMCwgNzUsIDAsIDApDQpCDQoNCiMgRGlyZWN0aW9uIG9mIHRoZSBjb25zdHJhaW50cw0KY29uc3RyYW5pbnRzX2RpcmVjdGlvbiAgPC0gYygiPD0iLCAiPD0iLCAiPD0iLCAiPj0iLCAiPj0iKQ0KY29uc3RyYW5pbnRzX2RpcmVjdGlvbg0KYGBgDQoNCiMjIyBUaGUgT3B0aW11bSBSZXN1bHQNCg0KYGBge3J9DQojIEZpbmQgdGhlIG9wdGltYWwgc29sdXRpb24NCm9wdGltdW0gPC0gIGxwKGRpcmVjdGlvbj0ibWF4IiwNCiAgICAgICAgICAgICAgIG9iamVjdGl2ZS5pbiA9IFAsDQogICAgICAgICAgICAgICBjb25zdC5tYXQgPSBBLA0KICAgICAgICAgICAgICAgY29uc3QuZGlyID0gY29uc3RyYW5pbnRzX2RpcmVjdGlvbiwNCiAgICAgICAgICAgICAgIGNvbnN0LnJocyA9IEIsDQogICAgICAgICAgICAgICBhbGwuaW50ID0gVCkNCg0Kc3RyKG9wdGltdW0pDQpgYGANCg0KYGBge3J9DQojIFByaW50IHN0YXR1czogMCA9IHN1Y2Nlc3MsIDIgPSBubyBmZWFzaWJsZSBzb2x1dGlvbg0KcHJpbnQob3B0aW11bSRzdGF0dXMpDQpgYGANCg0KYGBge3J9DQojIERpc3BsYXkgdGhlIG9wdGltdW0gdmFsdWVzIGZvciB3LCBiDQpiZXN0X3NvbCA8LSBvcHRpbXVtJHNvbHV0aW9uDQpuYW1lcyhiZXN0X3NvbCkgPC0gYygidyIsICJiIikgDQpwcmludChiZXN0X3NvbCkNCmBgYA0KDQpgYGB7cn0NCiMgQ2hlY2sgdGhlIHZhbHVlIG9mIG9iamVjdGl2ZSBmdW5jdGlvbiBhdCBvcHRpbWFsIHBvaW50DQpwcmludChwYXN0ZSgiTWFrc2ltdW0gcHJvZml0OiAiLCBvcHRpbXVtJG9ianZhbCwgc2VwPSIiKSkNCmBgYA0KDQpgYGB7cn0NCiMgRGlzY29ubmVjdCBmcm9tIHRoZSBtb2RlbCBhbmQgdGhlIG9wdGltdW0gc29sdXRpb24NCnJtKG9wdGltdW0sIGNvbnN0cmFuaW50c19kaXJlY3Rpb24sIGJlc3Rfc29sKQ0KYGBgDQoNCiMjIFNvbHV0aW9uIHdpdGggYGxwU29sdmVBUElgDQoNClRoZSBgbHBTb2x2ZUFQSWAgUiBwYWNrYWdlIGlzIGEgc2Vjb25kIGltcGxlbWVudGF0aW9uIG9mIGFuIGludGVyZmFjZSBvZiBgbHBzb2x2ZWAgdG8gUi4gSXQgcHJvdmlkZXMgYW4gUiBBUEkgbWlycm9yaW5nIHRoZSBgbHBfc29sdmVgIEMgQVBJIGFuZCBoZW5jZSBwcm92aWRlcyBhIGdyZWF0IGRlYWwgbW9yZSBmdW5jdGlvbmFsaXR5IGJ1dCBoYXMgYSBzdGVlcGVyIGxlYXJuaW5nIGN1cnZlLg0KDQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygibHBTb2x2ZUFQSSIpDQpsaWJyYXJ5KGxwU29sdmVBUEkpDQpgYGANCg0KTGV0J3MgdHJ5IHRvIHNvbHZlIHRoZSBwcm9ibGVtIGFnYWluIHVzaW5nIGBscFNvbHZlQVBJYDoNCg0KYGBge3J9DQojIFNldCA1IGNvbnN0cmFpbnRzIGFuZCAyIGRlY2lzaW9uIHZhcmlhYmxlcw0KbHByZWMgPC0gbWFrZS5scChucm93ID0gNSwgbmNvbCA9IDIpDQojIFNldCB0aGUgdHlwZSBvZiBwcm9ibGVtIHdlIGFyZSB0cnlpbmcgdG8gc29sdmUNCmxwLmNvbnRyb2wobHByZWMsIHNlbnNlPSJtYXgiKQ0KIyBTZXQgdHlwZSBvZiBkZWNpc2lvbiB2YXJpYWJsZXMNCnNldC50eXBlKGxwcmVjLCAxOjIsIHR5cGU9YygiaW50ZWdlciIpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBTZXQgb2JqZWN0aXZlIGZ1bmN0aW9uIGNvZWZmaWNpZW50cyB2ZWN0b3IgUA0Kc2V0Lm9iamZuKGxwcmVjLCBQKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBZGQgY29uc3RyYWludHMNCmFkZC5jb25zdHJhaW50KGxwcmVjLCBBWzEsIF0sICI8PSIsIEJbMV0pDQphZGQuY29uc3RyYWludChscHJlYywgQVsyLCBdLCAiPD0iLCBCWzJdKQ0KYWRkLmNvbnN0cmFpbnQobHByZWMsIEFbMywgXSwgIjw9IiwgQlszXSkNCmFkZC5jb25zdHJhaW50KGxwcmVjLCBBWzQsIF0sICI+PSIsIEJbNF0pDQphZGQuY29uc3RyYWludChscHJlYywgQVs1LCBdLCAiPj0iLCBCWzVdKQ0KYGBgDQoNCmBgYHtyfQ0KIyBEaXNwbGF5IHRoZSBMUHNvbHZlIG1hdHJpeA0KbHByZWMNCmBgYA0KDQpgYGB7cn0NCiMgU29sdmUgcHJvYmxlbQ0Kc29sdmUobHByZWMpDQpgYGANCmBgYHtyfQ0KIyBHZXQgdGhlIGRlY2lzaW9uIHZhcmlhYmxlcyB2YWx1ZXMNCmdldC52YXJpYWJsZXMobHByZWMpDQpgYGANCg0KYGBge3J9DQojIEdldCB0aGUgdmFsdWUgb2YgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbg0KZ2V0Lm9iamVjdGl2ZShscHJlYykNCmBgYA0KDQpgYGB7cn0NCiMgTm90ZSB0aGF0IHRoZSBkZWZhdWx0IGJvdW5kYXJpZXMgb24gdGhlIGRlY2lzaW9uIHZhcmlhYmxlIGFyZSBjKDAsIDAsIDApIGFuZCBjKEluZiwgSW5mLCBJbmYpDQpnZXQuYm91bmRzKGxwcmVjKQ0KYGBgDQoNCmBgYHtyfQ0KIyBCb3VuZGFyaWVzIGNhbiBiZSBzZXQgd2l0aCBmb2xsb3dpbmcgZnVuY3Rpb24NCmxwU29sdmVBUEk6OnNldC5ib3VuZHMobHByZWMpDQpgYGA=