This notebook contains a mixed integer linear programming (MILP) model for a problem posed on Stack Overflow. The problem involves 52 binary variables in 13 groups of four each. Exactly one variable from each group must be set to 1, with the objective of minimizing a cubic function of the variables. In a blog post, Erwin Kalvelagen compared MINLP and MIQCP models, using GAMS for the modeling and four different solvers. Here I will linearize the problem and solve it using CPLEX.

Set up

We will need the Rcplex package to interface with CPLEX and the Matrix package for sparse matrices (given the size of the resulting MILP model).

library(Matrix)
library(Rcplex)

Next, we load coefficient vectors a, b and c from the original SO post.

a <- c(251, 179, 215, 251, 63, 45, 54, 63, 47, 34, 40, 47, 141, 101, 121, 141, 47, 34, 40, 47, 94, 67, 81, 94, 47, 34, 40, 47, 157, 108, 133, 157, 126, 85, 106, 126, 126, 85, 106, 126, 110, 74, 92, 110, 110, 74, 92, 110, 63, 40, 52, 63)
b <- c(179, 251, 215, 0, 45, 63, 54, 0, 34, 47, 40, 0, 101, 141, 121, 0, 34, 47, 40, 0, 67, 94, 81, 0, 34, 47, 40, 0, 108, 157, 133, 0, 85, 126, 106, 0, 85, 126, 106, 0, 74, 110, 92, 0, 74, 110, 92, 0, 40, 63, 52, 0)
c <- c(179, 179, 118, 179, 45, 45, 30, 45, 34, 34, 22, 34, 101, 101, 67, 101, 34, 34, 22, 34, 67, 67, 44, 67, 34, 34, 22, 34, 108, 108, 71, 108, 85, 85, 56, 85, 85, 85, 56, 85, 74, 74, 49, 74, 74, 74, 49, 74, 40, 40, 27, 40)

Variables

Let \(y_{ij} = x_i * x_j\) and \(z_{ijk} = x_i * x_j * x_k = x_i * y_{jk}\) for all combinations of \(i,j,k\). Although \(y_{ij}\) and \(z_{ijk}\) can only be 0 or 1, they need not be declared binary. The constraints that define them will restrict them to being 0 or 1.

Note that, due to \(x\) being binary, \(y_{ii} = x_i^2 = x_i\) and \(z_{iii} = x_i^3 = x_i\). Also, \(y_{ij} = y_{ji}\) and \(z_{ijk} = z_{ikj} = \dots = z_{kji}\) since multiplication is commutative. Moreover, \(z_{iij} = z_{iji} = z_{jii} = y_{ij}\) for all \(i\neq j\). To exploit all that requires getting a bit “creative” about indexing variables.

Since Rcplex works with matrix representation, we need to “flatten” the variable matrices in a single vector. The variables will be listed in the order \(x\), \(y\), \(z\) with \(y\) and \(z\) sequenced so that the last index increases fastest and the first index slowest. To facilitate moving between algebraic and matrix representations, we will use a set of arrays that maps tuples of \(i\), \(j\) and \(k\) to 1-based indices in the master variable vector.

For convenience, we assign a label to the number of binary variables.

nx <- length(a)      # number of x variables (52)
# Index array for subscripts of x (which do not need modification).
ix <- 1:nx
# Index array for subscripts of y (which need to be mapped after the x entries).
t <- nx + 1  # next variable index
iy <- matrix(0, nrow = nx, ncol = nx)
for (i in 1:nx) {
  for (j in i:nx) {
    if (i == j) {
      iy[i, j] <- ix[i]
    } else {
      iy[i, j] <- t
      iy[j, i] <- t
      t <- t + 1
    }
  }
}
# Index array for subscripts of z (which need to be mapped after the y entries).
iz <- array(0, dim = c(nx, nx, nx))
for (i in 1:nx) {
  for (j in i:nx) {
    for (k in j: nx) {
      if (i == j) {
        if (j == k) {
          # All indices the same: reduces to an x variable.
          q <- ix[i]
        } else {
          # First two indices the same: reduces to a y variable.
          q <- iy[i, k]
        }
      } else if (j == k || i == k) {
        # Third index matches one of the others: reduces to a y variable.
        q <- iy[i, j]
      } else {
        # All indices are distinct.
        q <- t
        t <- t + 1
      }
      # All permutations of the indices get the same index.
      iz[i, j, k] <- q
      iz[i, k, j] <- q
      iz[j, i, k] <- q
      iz[j, k, i] <- q
      iz[k, i, j] <- q
      iz[k, j, i] <- q
    }
  }
}

For convenience, we’ll assign a name to the total number of distinct variables.

nvar <- nx + choose(nx, 2) + choose(nx, 3)

One of the arguments to Rcplex will be a vector indicating the type (continuous, integer, binary) of each variable. We can use scalars for the lower and upper bounds of the variables, since all variables are between 0 and 1.

# Start with all continuous variables.
vtype <- rep.int("C", nvar)
# The x variables (only) are binary.
vtype[1:nx] <- "B"
lb <- 0  # lower bound of all variables
ub <- 1  # upper bound of all variables

Objective function

The original post minimized the negative of a product. For a bit more simplicity, we will maximize the product without negating it. The original objective function has the following form (where the \(x_i\) are the binary variables): \[ \left(\alpha\sum_{i}a_{i}x_{i}\right)\times\left(\beta\sum_{j}b_{j}x_{j}+\beta_{0}\right)\times\left(\gamma\sum_{k}c_{k}x_{k}+\gamma_{0}\right) \] where \(\alpha = 1166/2000\), \(\beta = 1/2100\), \(\beta_0 = 0.05\), \(\gamma = 1/1500\) and \(\gamma_0 = 1.5\). We store those values here.

alpha <- 1166/2000
beta <- 1/2100
beta0 <- 0.05
gamma <- 1/1500
gamma0 <- 1.5

We can rewrite the objective function in linear form as follows: \[ \sum_{i,j,k}\alpha\beta\gamma a_{i}b_{j}c_{k}z_{ijk}+\sum_{i,j}\left(\alpha\beta\gamma_{0}a_{i}b_{j}+\alpha\beta_{0}\gamma a_{i}c_{j}\right)y_{ij}+\sum_{i}\alpha\beta_{0}\gamma_{0}a_{i}x_{i}. \]

To cut down on repetitive multiplications, we can assign some of the products to variables.

abg <- alpha * beta * gamma
abg0 <- alpha * beta * gamma0
ab0g <- alpha * beta0 * gamma
ab0g0 <- alpha * beta0 * gamma0

Now we build the objective coefficient vector.

# Start with a vector of zeros.
obj <- rep.int(0, nvar)
# Fill in the x coefficients.
obj[ix[1]:ix[nx]] <- ab0g0 * a
# Fill in the y coefficients using a loop. Note that we add to the existing values because the coefficient of y_{ii} adds to that of x_i and the coefficient of y_{ij} with j < i adds to that of y_{ji}.
for (i in 1:nx) {
  for (j in 1:nx) {
    obj[iy[i, j]] <- obj[iy[i, j]] + abg0 * a[i] * b[j] + ab0g * a[i] * c[j]
  }
}
# Fill in the z coefficients similarly.
for (i in 1:nx) {
  for (j in 1:nx) {
    for (k in 1:nx) {
      obj[iz[i, j, k]] <- obj[iz[i, j, k]] + abg * a[i] * b[j] * c[k]
    }
  }
}

Constraints

We will use a sparse matrix for the constraint coefficient matrix. To do that, we create three vectors of equal length, one (rindex) to hold row indices of nonzero entries, one (cindex) to hold column indices, and one (entry) to hold the entries themselves.

rindex <- c()
cindex <- c()
entry <- c()

We will use vector rhs for the right hand sides and vector sense for the constraint directions.

rhs <- c()
sense <- c()

The constraint on \(x\) is that in each consecutive group of four entries, exactly one must be 1 and the other three 0. So \(x_i + x_{i+1} + x_{i+2} + x_{i+3} = 1\) for \(i=1,5,9,\dots,49\).

r <- 0 # last row index used
for (i in seq(from = 1, to = nx, by = 4)) {
  r <- r + 1 # row index for the constraint
  # Create four entries (same row, consecutive columns) with value 1.
  rindex <- c(rindex, rep.int(r, 4))
  cindex <- c(cindex, ix[i]:ix[i + 3])
  entry <- c(entry, rep.int(1, 4))
  # The right hand side is 1.
  rhs <- c(rhs, 1)
  # The constraint is an equation.
  sense <- c(sense, "E")
}

To define the \(y\) variables, we assert the following constraints for each \(i\) and \(j\) with \(i<j\): \[ y_{ij} \le x_{i}\\ y_{ij} \le x_{j}\\ y_{ij} \ge x_{i}+x_{j}-1. \]

# Create vectors to hold the indices and coefficients. Each (i,j) pair with i < j generates three constraints with a total of seven coefficients.
ncoef <- 7 * choose(nx, 2)
rtemp <- rep.int(0, ncoef)
ctemp <- rep.int(0, ncoef)
etemp <- rep.int(0, ncoef)
t <- 1  # index for next entry
for (i in 1:(nx - 1)) {
  for (j in (i + 1):nx) {
    r <- r + 1
    rtemp[t:(t + 1)] <- r
    ctemp[t] <- iy[i, j]
    ctemp[t + 1] <- ix[i]
    etemp[t] <- 1
    etemp[t + 1] <- -1
    t <- t + 2
    r <- r + 1
    rtemp[t:(t + 1)] <- r
    ctemp[t] <- iy[i, j]
    ctemp[t + 1] <- ix[j]
    etemp[t] <- 1
    etemp[t + 1] <- -1
    t <- t + 2
    r <- r + 1
    rtemp[t:(t + 2)] <- r
    ctemp[t] <- iy[i, j]
    ctemp[t + 1] <- ix[i]
    ctemp[t + 2] <- ix[j]
    etemp[t] <- 1
    etemp[t + 1] <- -1
    etemp[t + 2] <- -1
    t <- t + 3
  }
}
# Merge the coefficients into the existing arrays.
rindex <- c(rindex, rtemp)
cindex <- c(cindex, ctemp)
entry <- c(entry, etemp)
# Each (i, j) entry has three constraints with the pattern "L", "L", "G".
npair <- choose(nx, 2)  # number of i, j pairs with i < j
sense <- c(sense, rep.int(c("L", "L", "G"), npair))
# Right hand sides for the three constraints are 0, 0, -1.
rhs <- c(rhs, rep.int(c(0, 0, -1), npair))

To define the \(z\) variables, we assert the following constraints for all combinations of \(i < j < k\): \[ z_{ijk} \le x_i \\ z_{ijk} \le y_{jk} \\ z_{ijk} \ge x_i + y_{jk} - 1. \]

# Create vectors to hold the indices and coefficients. Each (i,j,k) triplet generates three constraints with a total of seven coefficients.
ncoef <- 7 * nx^2
rtemp <- rep.int(0, ncoef)
ctemp <- rep.int(0, ncoef)
etemp <- rep.int(0, ncoef)
t <- 1  # index for next entry
for (i in 1:(nx- 2)) {
  for (j in (i + 1):(nx - 1)) {
    for (k in (j + 1):nx) {
      r <- r + 1
      rtemp[t:(t + 1)] <- r
      ctemp[t] <- iz[i, j, k]
      ctemp[t + 1] <- ix[i]
      etemp[t] <- 1
      etemp[t + 1] <- -1
      t <- t + 2
      r <- r + 1
      rtemp[t:(t + 1)] <- r
      ctemp[t] <- iz[i, j, k]
      ctemp[t + 1] <- iy[j, k]
      etemp[t] <- 1
      etemp[t + 1] <- -1
      t <- t + 2
      r <- r + 1
      rtemp[t:(t + 2)] <- r
      ctemp[t] <- iz[i, j, k]
      ctemp[t + 1] <- ix[i]
      ctemp[t + 2] <- iy[j, k]
      etemp[t] <- 1
      etemp[t + 1] <- -1
      etemp[t + 2] <- -1
      t <- t + 3
    }
  }
}
# Merge the coefficients into the existing arrays.
rindex <- c(rindex, rtemp)
cindex <- c(cindex, ctemp)
entry <- c(entry, etemp)
# Each (i, j, k) entry has three constraints with the pattern "L", "L", "G".
ntriples <- choose(nx, 3)
sense <- c(sense, rep.int(c("L", "L", "G"), ntriples))
# Right hand sides for the three constraints are 0, 0, -1.
rhs <- c(rhs, rep.int(c(0, 0, -1), ntriples))

Solution

We need to convert the constraint coefficients into a sparse matrix.

A <- sparseMatrix(i = rindex, j = cindex, x = entry)

We also need to set a time limit, since it is difficult to stop the CPLEX solver once it starts.

tl <- 300 # time limit in seconds

Now we call Rcplex to solve (hopefully) the model.

solution <- Rcplex(obj, A, rhs, lb = 0, ub = 1, objsense = "max", sense = sense, vtype = vtype, control = list(tilim = tl))

Once the solver is done, we can release it.

Rcplex.close()

We can inspect the results from CPLEX.

cat("The objective value of the incumbent solution is ", solution$obj, ".\n", "Incumbent solution:\n")
which(solution$xopt[1:nx] == 1)

Finally, we can use the original poster’s objective function (with the leading minus sign removed) to compare results and confirm that we have the correct objective value for the MIP solution.

objective_function <- function(x) {
  (1166 *  sum(x[1:52] * a) / 2000) *
  (((sum(x[1:52] * b)) / 2100) + .05) *
  (((sum(x[1:52] * c))/1500) + 1.5)
}
objective_function(solution$xopt)
LS0tCnRpdGxlOiAiR3JvdXAgU2VsZWN0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBQYXVsIEEuIFJ1YmluCi0tLQoKVGhpcyBub3RlYm9vayBjb250YWlucyBhIG1peGVkIGludGVnZXIgbGluZWFyIHByb2dyYW1taW5nIChNSUxQKSBtb2RlbCBmb3IgYSBwcm9ibGVtIFtwb3NlZCBvbiBTdGFjayBPdmVyZmxvd10oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNjM1ODQ3MDcvci1iaW5hcnktaW50ZWdlci1vcHRpbWl6YXRpb24td2l0aC1ncm91cHMpLiBUaGUgcHJvYmxlbSBpbnZvbHZlcyA1MiBiaW5hcnkgdmFyaWFibGVzIGluIDEzIGdyb3VwcyBvZiBmb3VyIGVhY2guIEV4YWN0bHkgb25lIHZhcmlhYmxlIGZyb20gZWFjaCBncm91cCBtdXN0IGJlIHNldCB0byAxLCB3aXRoIHRoZSBvYmplY3RpdmUgb2YgbWluaW1pemluZyBhIGN1YmljIGZ1bmN0aW9uIG9mIHRoZSB2YXJpYWJsZXMuIEluIGEgW2Jsb2cgcG9zdF0oaHR0cHM6Ly95ZXRhbm90aGVybWF0aHByb2dyYW1taW5nY29uc3VsdGFudC5ibG9nc3BvdC5jb20vMjAyMC8wOC9taW5scC12cy1taXFjcC5odG1sKSwgRXJ3aW4gS2FsdmVsYWdlbiBjb21wYXJlZCBNSU5MUCBhbmQgTUlRQ1AgbW9kZWxzLCB1c2luZyBHQU1TIGZvciB0aGUgbW9kZWxpbmcgYW5kIGZvdXIgZGlmZmVyZW50IHNvbHZlcnMuIEhlcmUgSSB3aWxsIGxpbmVhcml6ZSB0aGUgcHJvYmxlbSBhbmQgc29sdmUgaXQgdXNpbmcgQ1BMRVguCgojIFNldCB1cAoKV2Ugd2lsbCBuZWVkIHRoZSBgUmNwbGV4YCBwYWNrYWdlIHRvIGludGVyZmFjZSB3aXRoIENQTEVYIGFuZCB0aGUgYE1hdHJpeGAgcGFja2FnZSBmb3Igc3BhcnNlIG1hdHJpY2VzIChnaXZlbiB0aGUgc2l6ZSBvZiB0aGUgcmVzdWx0aW5nIE1JTFAgbW9kZWwpLgoKYGBge3J9CmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KFJjcGxleCkKYGBgCgpOZXh0LCB3ZSBsb2FkIGNvZWZmaWNpZW50IHZlY3RvcnMgYSwgYiBhbmQgYyBmcm9tIHRoZSBvcmlnaW5hbCBTTyBwb3N0LgoKYGBge3J9CmEgPC0gYygyNTEsIDE3OSwgMjE1LCAyNTEsIDYzLCA0NSwgNTQsIDYzLCA0NywgMzQsIDQwLCA0NywgMTQxLCAxMDEsIDEyMSwgMTQxLCA0NywgMzQsIDQwLCA0NywgOTQsIDY3LCA4MSwgOTQsIDQ3LCAzNCwgNDAsIDQ3LCAxNTcsIDEwOCwgMTMzLCAxNTcsIDEyNiwgODUsIDEwNiwgMTI2LCAxMjYsIDg1LCAxMDYsIDEyNiwgMTEwLCA3NCwgOTIsIDExMCwgMTEwLCA3NCwgOTIsIDExMCwgNjMsIDQwLCA1MiwgNjMpCmIgPC0gYygxNzksIDI1MSwgMjE1LCAwLCA0NSwgNjMsIDU0LCAwLCAzNCwgNDcsIDQwLCAwLCAxMDEsIDE0MSwgMTIxLCAwLCAzNCwgNDcsIDQwLCAwLCA2NywgOTQsIDgxLCAwLCAzNCwgNDcsIDQwLCAwLCAxMDgsIDE1NywgMTMzLCAwLCA4NSwgMTI2LCAxMDYsIDAsIDg1LCAxMjYsIDEwNiwgMCwgNzQsIDExMCwgOTIsIDAsIDc0LCAxMTAsIDkyLCAwLCA0MCwgNjMsIDUyLCAwKQpjIDwtIGMoMTc5LCAxNzksIDExOCwgMTc5LCA0NSwgNDUsIDMwLCA0NSwgMzQsIDM0LCAyMiwgMzQsIDEwMSwgMTAxLCA2NywgMTAxLCAzNCwgMzQsIDIyLCAzNCwgNjcsIDY3LCA0NCwgNjcsIDM0LCAzNCwgMjIsIDM0LCAxMDgsIDEwOCwgNzEsIDEwOCwgODUsIDg1LCA1NiwgODUsIDg1LCA4NSwgNTYsIDg1LCA3NCwgNzQsIDQ5LCA3NCwgNzQsIDc0LCA0OSwgNzQsIDQwLCA0MCwgMjcsIDQwKQpgYGAKCiMgVmFyaWFibGVzCgpMZXQgJHlfe2lqfSA9IHhfaSAqIHhfaiQgYW5kICR6X3tpamt9ID0geF9pICogeF9qICogeF9rID0geF9pICogeV97amt9JCBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiAkaSxqLGskLiBBbHRob3VnaCAkeV97aWp9JCBhbmQgJHpfe2lqa30kIGNhbiBvbmx5IGJlIDAgb3IgMSwgdGhleSBuZWVkIG5vdCBiZSBkZWNsYXJlZCBiaW5hcnkuIFRoZSBjb25zdHJhaW50cyB0aGF0IGRlZmluZSB0aGVtIHdpbGwgcmVzdHJpY3QgdGhlbSB0byBiZWluZyAwIG9yIDEuCgpOb3RlIHRoYXQsIGR1ZSB0byAkeCQgYmVpbmcgYmluYXJ5LCAkeV97aWl9ID0geF9pXjIgPSB4X2kkIGFuZCAkel97aWlpfSA9IHhfaV4zID0geF9pJC4gQWxzbywgJHlfe2lqfSA9IHlfe2ppfSQgYW5kICR6X3tpamt9ID0gel97aWtqfSA9IFxkb3RzID0gel97a2ppfSQgc2luY2UgbXVsdGlwbGljYXRpb24gaXMgY29tbXV0YXRpdmUuIE1vcmVvdmVyLCAkel97aWlqfSA9IHpfe2lqaX0gPSB6X3tqaWl9ID0geV97aWp9JCBmb3IgYWxsICRpXG5lcSBqJC4gVG8gZXhwbG9pdCBhbGwgdGhhdCByZXF1aXJlcyBnZXR0aW5nIGEgYml0ICJjcmVhdGl2ZSIgYWJvdXQgaW5kZXhpbmcgdmFyaWFibGVzLgoKU2luY2UgYFJjcGxleGAgd29ya3Mgd2l0aCBtYXRyaXggcmVwcmVzZW50YXRpb24sIHdlIG5lZWQgdG8gImZsYXR0ZW4iIHRoZSB2YXJpYWJsZSBtYXRyaWNlcyBpbiBhIHNpbmdsZSB2ZWN0b3IuIFRoZSB2YXJpYWJsZXMgd2lsbCBiZSBsaXN0ZWQgaW4gdGhlIG9yZGVyICR4JCwgJHkkLCAkeiQgd2l0aCAkeSQgYW5kICR6JCBzZXF1ZW5jZWQgc28gdGhhdCB0aGUgbGFzdCBpbmRleCBpbmNyZWFzZXMgZmFzdGVzdCBhbmQgdGhlIGZpcnN0IGluZGV4IHNsb3dlc3QuIFRvIGZhY2lsaXRhdGUgbW92aW5nIGJldHdlZW4gYWxnZWJyYWljIGFuZCBtYXRyaXggcmVwcmVzZW50YXRpb25zLCB3ZSB3aWxsIHVzZSBhIHNldCBvZiBhcnJheXMgdGhhdCBtYXBzIHR1cGxlcyBvZiAkaSQsICRqJCBhbmQgJGskIHRvIDEtYmFzZWQgaW5kaWNlcyBpbiB0aGUgbWFzdGVyIHZhcmlhYmxlIHZlY3Rvci4KCkZvciBjb252ZW5pZW5jZSwgd2UgYXNzaWduIGEgbGFiZWwgdG8gdGhlIG51bWJlciBvZiBiaW5hcnkgdmFyaWFibGVzLgoKYGBge3J9Cm54IDwtIGxlbmd0aChhKSAgICAgICMgbnVtYmVyIG9mIHggdmFyaWFibGVzICg1MikKYGBgCgoKYGBge3J9CiMgSW5kZXggYXJyYXkgZm9yIHN1YnNjcmlwdHMgb2YgeCAod2hpY2ggZG8gbm90IG5lZWQgbW9kaWZpY2F0aW9uKS4KaXggPC0gMTpueAojIEluZGV4IGFycmF5IGZvciBzdWJzY3JpcHRzIG9mIHkgKHdoaWNoIG5lZWQgdG8gYmUgbWFwcGVkIGFmdGVyIHRoZSB4IGVudHJpZXMpLgp0IDwtIG54ICsgMSAgIyBuZXh0IHZhcmlhYmxlIGluZGV4Cml5IDwtIG1hdHJpeCgwLCBucm93ID0gbngsIG5jb2wgPSBueCkKZm9yIChpIGluIDE6bngpIHsKICBmb3IgKGogaW4gaTpueCkgewogICAgaWYgKGkgPT0gaikgewogICAgICBpeVtpLCBqXSA8LSBpeFtpXQogICAgfSBlbHNlIHsKICAgICAgaXlbaSwgal0gPC0gdAogICAgICBpeVtqLCBpXSA8LSB0CiAgICAgIHQgPC0gdCArIDEKICAgIH0KICB9Cn0KIyBJbmRleCBhcnJheSBmb3Igc3Vic2NyaXB0cyBvZiB6ICh3aGljaCBuZWVkIHRvIGJlIG1hcHBlZCBhZnRlciB0aGUgeSBlbnRyaWVzKS4KaXogPC0gYXJyYXkoMCwgZGltID0gYyhueCwgbngsIG54KSkKZm9yIChpIGluIDE6bngpIHsKICBmb3IgKGogaW4gaTpueCkgewogICAgZm9yIChrIGluIGo6IG54KSB7CiAgICAgIGlmIChpID09IGopIHsKICAgICAgICBpZiAoaiA9PSBrKSB7CiAgICAgICAgICAjIEFsbCBpbmRpY2VzIHRoZSBzYW1lOiByZWR1Y2VzIHRvIGFuIHggdmFyaWFibGUuCiAgICAgICAgICBxIDwtIGl4W2ldCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICMgRmlyc3QgdHdvIGluZGljZXMgdGhlIHNhbWU6IHJlZHVjZXMgdG8gYSB5IHZhcmlhYmxlLgogICAgICAgICAgcSA8LSBpeVtpLCBrXQogICAgICAgIH0KICAgICAgfSBlbHNlIGlmIChqID09IGsgfHwgaSA9PSBrKSB7CiAgICAgICAgIyBUaGlyZCBpbmRleCBtYXRjaGVzIG9uZSBvZiB0aGUgb3RoZXJzOiByZWR1Y2VzIHRvIGEgeSB2YXJpYWJsZS4KICAgICAgICBxIDwtIGl5W2ksIGpdCiAgICAgIH0gZWxzZSB7CiAgICAgICAgIyBBbGwgaW5kaWNlcyBhcmUgZGlzdGluY3QuCiAgICAgICAgcSA8LSB0CiAgICAgICAgdCA8LSB0ICsgMQogICAgICB9CiAgICAgICMgQWxsIHBlcm11dGF0aW9ucyBvZiB0aGUgaW5kaWNlcyBnZXQgdGhlIHNhbWUgaW5kZXguCiAgICAgIGl6W2ksIGosIGtdIDwtIHEKICAgICAgaXpbaSwgaywgal0gPC0gcQogICAgICBpeltqLCBpLCBrXSA8LSBxCiAgICAgIGl6W2osIGssIGldIDwtIHEKICAgICAgaXpbaywgaSwgal0gPC0gcQogICAgICBpeltrLCBqLCBpXSA8LSBxCiAgICB9CiAgfQp9CmBgYAoKRm9yIGNvbnZlbmllbmNlLCB3ZSdsbCBhc3NpZ24gYSBuYW1lIHRvIHRoZSB0b3RhbCBudW1iZXIgb2YgZGlzdGluY3QgdmFyaWFibGVzLgoKYGBge3J9Cm52YXIgPC0gbnggKyBjaG9vc2UobngsIDIpICsgY2hvb3NlKG54LCAzKQpgYGAKCk9uZSBvZiB0aGUgYXJndW1lbnRzIHRvIGBSY3BsZXhgIHdpbGwgYmUgYSB2ZWN0b3IgaW5kaWNhdGluZyB0aGUgdHlwZSAoY29udGludW91cywgaW50ZWdlciwgYmluYXJ5KSBvZiBlYWNoIHZhcmlhYmxlLiBXZSBjYW4gdXNlIHNjYWxhcnMgZm9yIHRoZSBsb3dlciBhbmQgdXBwZXIgYm91bmRzIG9mIHRoZSB2YXJpYWJsZXMsIHNpbmNlIGFsbCB2YXJpYWJsZXMgYXJlIGJldHdlZW4gMCBhbmQgMS4KCmBgYHtyfQojIFN0YXJ0IHdpdGggYWxsIGNvbnRpbnVvdXMgdmFyaWFibGVzLgp2dHlwZSA8LSByZXAuaW50KCJDIiwgbnZhcikKIyBUaGUgeCB2YXJpYWJsZXMgKG9ubHkpIGFyZSBiaW5hcnkuCnZ0eXBlWzE6bnhdIDwtICJCIgpsYiA8LSAwICAjIGxvd2VyIGJvdW5kIG9mIGFsbCB2YXJpYWJsZXMKdWIgPC0gMSAgIyB1cHBlciBib3VuZCBvZiBhbGwgdmFyaWFibGVzCmBgYAoKIyBPYmplY3RpdmUgZnVuY3Rpb24KClRoZSBvcmlnaW5hbCBwb3N0IG1pbmltaXplZCB0aGUgbmVnYXRpdmUgb2YgYSBwcm9kdWN0LiBGb3IgYSBiaXQgbW9yZSBzaW1wbGljaXR5LCB3ZSB3aWxsIG1heGltaXplIHRoZSBwcm9kdWN0IHdpdGhvdXQgbmVnYXRpbmcgaXQuIFRoZSBvcmlnaW5hbCBvYmplY3RpdmUgZnVuY3Rpb24gaGFzIHRoZSBmb2xsb3dpbmcgZm9ybSAod2hlcmUgdGhlICR4X2kkIGFyZSB0aGUgYmluYXJ5IHZhcmlhYmxlcyk6ClxbClxsZWZ0KFxhbHBoYVxzdW1fe2l9YV97aX14X3tpfVxyaWdodClcdGltZXNcbGVmdChcYmV0YVxzdW1fe2p9Yl97an14X3tqfStcYmV0YV97MH1ccmlnaHQpXHRpbWVzXGxlZnQoXGdhbW1hXHN1bV97a31jX3trfXhfe2t9K1xnYW1tYV97MH1ccmlnaHQpClxdCndoZXJlICRcYWxwaGEgPSAxMTY2LzIwMDAkLCAkXGJldGEgPSAxLzIxMDAkLCAkXGJldGFfMCA9IDAuMDUkLCAkXGdhbW1hID0gMS8xNTAwJCBhbmQgJFxnYW1tYV8wID0gMS41JC4gV2Ugc3RvcmUgdGhvc2UgdmFsdWVzIGhlcmUuCgpgYGB7cn0KYWxwaGEgPC0gMTE2Ni8yMDAwCmJldGEgPC0gMS8yMTAwCmJldGEwIDwtIDAuMDUKZ2FtbWEgPC0gMS8xNTAwCmdhbW1hMCA8LSAxLjUKYGBgCgpXZSBjYW4gcmV3cml0ZSB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGluIGxpbmVhciBmb3JtIGFzIGZvbGxvd3M6ClxbClxzdW1fe2ksaixrfVxhbHBoYVxiZXRhXGdhbW1hIGFfe2l9Yl97an1jX3trfXpfe2lqa30rXHN1bV97aSxqfVxsZWZ0KFxhbHBoYVxiZXRhXGdhbW1hX3swfWFfe2l9Yl97an0rXGFscGhhXGJldGFfezB9XGdhbW1hIGFfe2l9Y197an1ccmlnaHQpeV97aWp9K1xzdW1fe2l9XGFscGhhXGJldGFfezB9XGdhbW1hX3swfWFfe2l9eF97aX0uClxdCgpUbyBjdXQgZG93biBvbiByZXBldGl0aXZlIG11bHRpcGxpY2F0aW9ucywgd2UgY2FuIGFzc2lnbiBzb21lIG9mIHRoZSBwcm9kdWN0cyB0byB2YXJpYWJsZXMuCgpgYGB7cn0KYWJnIDwtIGFscGhhICogYmV0YSAqIGdhbW1hCmFiZzAgPC0gYWxwaGEgKiBiZXRhICogZ2FtbWEwCmFiMGcgPC0gYWxwaGEgKiBiZXRhMCAqIGdhbW1hCmFiMGcwIDwtIGFscGhhICogYmV0YTAgKiBnYW1tYTAKYGBgCgpOb3cgd2UgYnVpbGQgdGhlIG9iamVjdGl2ZSBjb2VmZmljaWVudCB2ZWN0b3IuCgpgYGB7cn0KIyBTdGFydCB3aXRoIGEgdmVjdG9yIG9mIHplcm9zLgpvYmogPC0gcmVwLmludCgwLCBudmFyKQojIEZpbGwgaW4gdGhlIHggY29lZmZpY2llbnRzLgpvYmpbaXhbMV06aXhbbnhdXSA8LSBhYjBnMCAqIGEKIyBGaWxsIGluIHRoZSB5IGNvZWZmaWNpZW50cyB1c2luZyBhIGxvb3AuIE5vdGUgdGhhdCB3ZSBhZGQgdG8gdGhlIGV4aXN0aW5nIHZhbHVlcyBiZWNhdXNlIHRoZSBjb2VmZmljaWVudCBvZiB5X3tpaX0gYWRkcyB0byB0aGF0IG9mIHhfaSBhbmQgdGhlIGNvZWZmaWNpZW50IG9mIHlfe2lqfSB3aXRoIGogPCBpIGFkZHMgdG8gdGhhdCBvZiB5X3tqaX0uCmZvciAoaSBpbiAxOm54KSB7CiAgZm9yIChqIGluIDE6bngpIHsKICAgIG9ialtpeVtpLCBqXV0gPC0gb2JqW2l5W2ksIGpdXSArIGFiZzAgKiBhW2ldICogYltqXSArIGFiMGcgKiBhW2ldICogY1tqXQogIH0KfQojIEZpbGwgaW4gdGhlIHogY29lZmZpY2llbnRzIHNpbWlsYXJseS4KZm9yIChpIGluIDE6bngpIHsKICBmb3IgKGogaW4gMTpueCkgewogICAgZm9yIChrIGluIDE6bngpIHsKICAgICAgb2JqW2l6W2ksIGosIGtdXSA8LSBvYmpbaXpbaSwgaiwga11dICsgYWJnICogYVtpXSAqIGJbal0gKiBjW2tdCiAgICB9CiAgfQp9CmBgYAoKIyBDb25zdHJhaW50cwoKV2Ugd2lsbCB1c2UgYSBzcGFyc2UgbWF0cml4IGZvciB0aGUgY29uc3RyYWludCBjb2VmZmljaWVudCBtYXRyaXguIFRvIGRvIHRoYXQsIHdlIGNyZWF0ZSB0aHJlZSB2ZWN0b3JzIG9mIGVxdWFsIGxlbmd0aCwgb25lIChgcmluZGV4YCkgdG8gaG9sZCByb3cgaW5kaWNlcyBvZiBub256ZXJvIGVudHJpZXMsIG9uZSAoYGNpbmRleGApIHRvIGhvbGQgY29sdW1uIGluZGljZXMsIGFuZCBvbmUgKGBlbnRyeWApIHRvIGhvbGQgdGhlIGVudHJpZXMgdGhlbXNlbHZlcy4KCmBgYHtyfQpyaW5kZXggPC0gYygpCmNpbmRleCA8LSBjKCkKZW50cnkgPC0gYygpCmBgYAoKV2Ugd2lsbCB1c2UgdmVjdG9yIGByaHNgIGZvciB0aGUgcmlnaHQgaGFuZCBzaWRlcyBhbmQgdmVjdG9yIGBzZW5zZWAgZm9yIHRoZSBjb25zdHJhaW50IGRpcmVjdGlvbnMuCgpgYGB7cn0KcmhzIDwtIGMoKQpzZW5zZSA8LSBjKCkKYGBgCgpUaGUgY29uc3RyYWludCBvbiAkeCQgaXMgdGhhdCBpbiBlYWNoIGNvbnNlY3V0aXZlIGdyb3VwIG9mIGZvdXIgZW50cmllcywgZXhhY3RseSBvbmUgbXVzdCBiZSAxIGFuZCB0aGUgb3RoZXIgdGhyZWUgMC4gU28gJHhfaSArIHhfe2krMX0gKyB4X3tpKzJ9ICsgeF97aSszfSA9IDEkIGZvciAkaT0xLDUsOSxcZG90cyw0OSQuCgpgYGB7cn0KciA8LSAwICMgbGFzdCByb3cgaW5kZXggdXNlZApmb3IgKGkgaW4gc2VxKGZyb20gPSAxLCB0byA9IG54LCBieSA9IDQpKSB7CiAgciA8LSByICsgMSAjIHJvdyBpbmRleCBmb3IgdGhlIGNvbnN0cmFpbnQKICAjIENyZWF0ZSBmb3VyIGVudHJpZXMgKHNhbWUgcm93LCBjb25zZWN1dGl2ZSBjb2x1bW5zKSB3aXRoIHZhbHVlIDEuCiAgcmluZGV4IDwtIGMocmluZGV4LCByZXAuaW50KHIsIDQpKQogIGNpbmRleCA8LSBjKGNpbmRleCwgaXhbaV06aXhbaSArIDNdKQogIGVudHJ5IDwtIGMoZW50cnksIHJlcC5pbnQoMSwgNCkpCiAgIyBUaGUgcmlnaHQgaGFuZCBzaWRlIGlzIDEuCiAgcmhzIDwtIGMocmhzLCAxKQogICMgVGhlIGNvbnN0cmFpbnQgaXMgYW4gZXF1YXRpb24uCiAgc2Vuc2UgPC0gYyhzZW5zZSwgIkUiKQp9CmBgYAoKVG8gZGVmaW5lIHRoZSAkeSQgdmFyaWFibGVzLCB3ZSBhc3NlcnQgdGhlIGZvbGxvd2luZyBjb25zdHJhaW50cyBmb3IgZWFjaCAkaSQgYW5kICRqJCB3aXRoICRpPGokOgpcWwp5X3tpan0gXGxlIHhfe2l9XFwKeV97aWp9IFxsZSB4X3tqfVxcCnlfe2lqfSBcZ2UgeF97aX0reF97an0tMS4KXF0KCmBgYHtyfQojIENyZWF0ZSB2ZWN0b3JzIHRvIGhvbGQgdGhlIGluZGljZXMgYW5kIGNvZWZmaWNpZW50cy4gRWFjaCAoaSxqKSBwYWlyIHdpdGggaSA8IGogZ2VuZXJhdGVzIHRocmVlIGNvbnN0cmFpbnRzIHdpdGggYSB0b3RhbCBvZiBzZXZlbiBjb2VmZmljaWVudHMuCm5jb2VmIDwtIDcgKiBjaG9vc2UobngsIDIpCnJ0ZW1wIDwtIHJlcC5pbnQoMCwgbmNvZWYpCmN0ZW1wIDwtIHJlcC5pbnQoMCwgbmNvZWYpCmV0ZW1wIDwtIHJlcC5pbnQoMCwgbmNvZWYpCnQgPC0gMSAgIyBpbmRleCBmb3IgbmV4dCBlbnRyeQpmb3IgKGkgaW4gMToobnggLSAxKSkgewogIGZvciAoaiBpbiAoaSArIDEpOm54KSB7CiAgICByIDwtIHIgKyAxCiAgICBydGVtcFt0Oih0ICsgMSldIDwtIHIKICAgIGN0ZW1wW3RdIDwtIGl5W2ksIGpdCiAgICBjdGVtcFt0ICsgMV0gPC0gaXhbaV0KICAgIGV0ZW1wW3RdIDwtIDEKICAgIGV0ZW1wW3QgKyAxXSA8LSAtMQogICAgdCA8LSB0ICsgMgogICAgciA8LSByICsgMQogICAgcnRlbXBbdDoodCArIDEpXSA8LSByCiAgICBjdGVtcFt0XSA8LSBpeVtpLCBqXQogICAgY3RlbXBbdCArIDFdIDwtIGl4W2pdCiAgICBldGVtcFt0XSA8LSAxCiAgICBldGVtcFt0ICsgMV0gPC0gLTEKICAgIHQgPC0gdCArIDIKICAgIHIgPC0gciArIDEKICAgIHJ0ZW1wW3Q6KHQgKyAyKV0gPC0gcgogICAgY3RlbXBbdF0gPC0gaXlbaSwgal0KICAgIGN0ZW1wW3QgKyAxXSA8LSBpeFtpXQogICAgY3RlbXBbdCArIDJdIDwtIGl4W2pdCiAgICBldGVtcFt0XSA8LSAxCiAgICBldGVtcFt0ICsgMV0gPC0gLTEKICAgIGV0ZW1wW3QgKyAyXSA8LSAtMQogICAgdCA8LSB0ICsgMwogIH0KfQojIE1lcmdlIHRoZSBjb2VmZmljaWVudHMgaW50byB0aGUgZXhpc3RpbmcgYXJyYXlzLgpyaW5kZXggPC0gYyhyaW5kZXgsIHJ0ZW1wKQpjaW5kZXggPC0gYyhjaW5kZXgsIGN0ZW1wKQplbnRyeSA8LSBjKGVudHJ5LCBldGVtcCkKIyBFYWNoIChpLCBqKSBlbnRyeSBoYXMgdGhyZWUgY29uc3RyYWludHMgd2l0aCB0aGUgcGF0dGVybiAiTCIsICJMIiwgIkciLgpucGFpciA8LSBjaG9vc2UobngsIDIpICAjIG51bWJlciBvZiBpLCBqIHBhaXJzIHdpdGggaSA8IGoKc2Vuc2UgPC0gYyhzZW5zZSwgcmVwLmludChjKCJMIiwgIkwiLCAiRyIpLCBucGFpcikpCiMgUmlnaHQgaGFuZCBzaWRlcyBmb3IgdGhlIHRocmVlIGNvbnN0cmFpbnRzIGFyZSAwLCAwLCAtMS4KcmhzIDwtIGMocmhzLCByZXAuaW50KGMoMCwgMCwgLTEpLCBucGFpcikpCmBgYAoKVG8gZGVmaW5lIHRoZSAkeiQgdmFyaWFibGVzLCB3ZSBhc3NlcnQgdGhlIGZvbGxvd2luZyBjb25zdHJhaW50cyBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiAkaSA8IGogPCBrJDoKXFsKel97aWprfSBcbGUgeF9pIFxcCnpfe2lqa30gXGxlIHlfe2prfSBcXAp6X3tpamt9IFxnZSB4X2kgKyB5X3tqa30gLSAxLgpcXQoKYGBge3J9CiMgQ3JlYXRlIHZlY3RvcnMgdG8gaG9sZCB0aGUgaW5kaWNlcyBhbmQgY29lZmZpY2llbnRzLiBFYWNoIChpLGosaykgdHJpcGxldCBnZW5lcmF0ZXMgdGhyZWUgY29uc3RyYWludHMgd2l0aCBhIHRvdGFsIG9mIHNldmVuIGNvZWZmaWNpZW50cy4KbmNvZWYgPC0gNyAqIG54XjIKcnRlbXAgPC0gcmVwLmludCgwLCBuY29lZikKY3RlbXAgPC0gcmVwLmludCgwLCBuY29lZikKZXRlbXAgPC0gcmVwLmludCgwLCBuY29lZikKdCA8LSAxICAjIGluZGV4IGZvciBuZXh0IGVudHJ5CmZvciAoaSBpbiAxOihueC0gMikpIHsKICBmb3IgKGogaW4gKGkgKyAxKToobnggLSAxKSkgewogICAgZm9yIChrIGluIChqICsgMSk6bngpIHsKICAgICAgciA8LSByICsgMQogICAgICBydGVtcFt0Oih0ICsgMSldIDwtIHIKICAgICAgY3RlbXBbdF0gPC0gaXpbaSwgaiwga10KICAgICAgY3RlbXBbdCArIDFdIDwtIGl4W2ldCiAgICAgIGV0ZW1wW3RdIDwtIDEKICAgICAgZXRlbXBbdCArIDFdIDwtIC0xCiAgICAgIHQgPC0gdCArIDIKICAgICAgciA8LSByICsgMQogICAgICBydGVtcFt0Oih0ICsgMSldIDwtIHIKICAgICAgY3RlbXBbdF0gPC0gaXpbaSwgaiwga10KICAgICAgY3RlbXBbdCArIDFdIDwtIGl5W2osIGtdCiAgICAgIGV0ZW1wW3RdIDwtIDEKICAgICAgZXRlbXBbdCArIDFdIDwtIC0xCiAgICAgIHQgPC0gdCArIDIKICAgICAgciA8LSByICsgMQogICAgICBydGVtcFt0Oih0ICsgMildIDwtIHIKICAgICAgY3RlbXBbdF0gPC0gaXpbaSwgaiwga10KICAgICAgY3RlbXBbdCArIDFdIDwtIGl4W2ldCiAgICAgIGN0ZW1wW3QgKyAyXSA8LSBpeVtqLCBrXQogICAgICBldGVtcFt0XSA8LSAxCiAgICAgIGV0ZW1wW3QgKyAxXSA8LSAtMQogICAgICBldGVtcFt0ICsgMl0gPC0gLTEKICAgICAgdCA8LSB0ICsgMwogICAgfQogIH0KfQojIE1lcmdlIHRoZSBjb2VmZmljaWVudHMgaW50byB0aGUgZXhpc3RpbmcgYXJyYXlzLgpyaW5kZXggPC0gYyhyaW5kZXgsIHJ0ZW1wKQpjaW5kZXggPC0gYyhjaW5kZXgsIGN0ZW1wKQplbnRyeSA8LSBjKGVudHJ5LCBldGVtcCkKIyBFYWNoIChpLCBqLCBrKSBlbnRyeSBoYXMgdGhyZWUgY29uc3RyYWludHMgd2l0aCB0aGUgcGF0dGVybiAiTCIsICJMIiwgIkciLgpudHJpcGxlcyA8LSBjaG9vc2UobngsIDMpCnNlbnNlIDwtIGMoc2Vuc2UsIHJlcC5pbnQoYygiTCIsICJMIiwgIkciKSwgbnRyaXBsZXMpKQojIFJpZ2h0IGhhbmQgc2lkZXMgZm9yIHRoZSB0aHJlZSBjb25zdHJhaW50cyBhcmUgMCwgMCwgLTEuCnJocyA8LSBjKHJocywgcmVwLmludChjKDAsIDAsIC0xKSwgbnRyaXBsZXMpKQpgYGAKCgojIFNvbHV0aW9uCgpXZSBuZWVkIHRvIGNvbnZlcnQgdGhlIGNvbnN0cmFpbnQgY29lZmZpY2llbnRzIGludG8gYSBzcGFyc2UgbWF0cml4LgoKYGBge3J9CkEgPC0gc3BhcnNlTWF0cml4KGkgPSByaW5kZXgsIGogPSBjaW5kZXgsIHggPSBlbnRyeSkKYGBgCgpXZSBhbHNvIG5lZWQgdG8gc2V0IGEgdGltZSBsaW1pdCwgc2luY2UgaXQgaXMgZGlmZmljdWx0IHRvIHN0b3AgdGhlIENQTEVYIHNvbHZlciBvbmNlIGl0IHN0YXJ0cy4KCmBgYHtyfQp0bCA8LSAzMDAgIyB0aW1lIGxpbWl0IGluIHNlY29uZHMKYGBgCgpOb3cgd2UgY2FsbCBgUmNwbGV4YCB0byBzb2x2ZSAoaG9wZWZ1bGx5KSB0aGUgbW9kZWwuCgpgYGB7cn0Kc29sdXRpb24gPC0gUmNwbGV4KG9iaiwgQSwgcmhzLCBsYiA9IDAsIHViID0gMSwgb2Jqc2Vuc2UgPSAibWF4Iiwgc2Vuc2UgPSBzZW5zZSwgdnR5cGUgPSB2dHlwZSwgY29udHJvbCA9IGxpc3QodGlsaW0gPSB0bCkpCmBgYAoKT25jZSB0aGUgc29sdmVyIGlzIGRvbmUsIHdlIGNhbiByZWxlYXNlIGl0LgoKYGBge3J9ClJjcGxleC5jbG9zZSgpCmBgYAoKV2UgY2FuIGluc3BlY3QgdGhlIHJlc3VsdHMgZnJvbSBDUExFWC4KCmBgYHtyfQpjYXQoIlRoZSBvYmplY3RpdmUgdmFsdWUgb2YgdGhlIGluY3VtYmVudCBzb2x1dGlvbiBpcyAiLCBzb2x1dGlvbiRvYmosICIuXG4iLCAiSW5jdW1iZW50IHNvbHV0aW9uOlxuIikKd2hpY2goc29sdXRpb24keG9wdFsxOm54XSA9PSAxKQpgYGAKCkZpbmFsbHksIHdlIGNhbiB1c2UgdGhlIG9yaWdpbmFsIHBvc3RlcidzIG9iamVjdGl2ZSBmdW5jdGlvbiAod2l0aCB0aGUgbGVhZGluZyBtaW51cyBzaWduIHJlbW92ZWQpIHRvIGNvbXBhcmUgcmVzdWx0cyBhbmQgY29uZmlybSB0aGF0IHdlIGhhdmUgdGhlIGNvcnJlY3Qgb2JqZWN0aXZlIHZhbHVlIGZvciB0aGUgTUlQIHNvbHV0aW9uLgoKYGBge3J9Cm9iamVjdGl2ZV9mdW5jdGlvbiA8LSBmdW5jdGlvbih4KSB7CiAgKDExNjYgKiAgc3VtKHhbMTo1Ml0gKiBhKSAvIDIwMDApICoKICAoKChzdW0oeFsxOjUyXSAqIGIpKSAvIDIxMDApICsgLjA1KSAqCiAgKCgoc3VtKHhbMTo1Ml0gKiBjKSkvMTUwMCkgKyAxLjUpCn0Kb2JqZWN0aXZlX2Z1bmN0aW9uKHNvbHV0aW9uJHhvcHQpCmBgYAoK