In this notebook we try to solve a nonlinear optimization model posted on OR Stack Exchange. We deal only with the case where the \(x\) variables are continuous rather than binary. The problem and solution process are discussed in a post on my blog.

The solution approach combines one dimensional line search with solving a linear program. We use OMPR to model the LP and CPLEX (via the ROI plugin) to solve the LPs.

First, we load the required packages.

library(ompr)
library(ompr.roi)
library(ROI)
library(ROI.plugin.cplex)
library(magrittr)

Preparation

Data

The problem author provided an example. We set up the example data here.

p <- c(99.9, 200.2, 300.0)
f <- c(100, 120, 320, 80, 45)
b <- t(matrix(c(10, 100, 20, 39, 4,
                70, 10, 200, 49, 40,
                60, 50, 300, 50, 60), nrow = 3, ncol = 5, byrow = TRUE))
beta <- 1  # placeholder value

Next, we define problem dimensions.

tmax <- 4 # $T$ in the original post
n <- 3

Lastly, we need upper bounds for the nonnegative variables \(q_1\) and \(q_2\).

q1max <- 100
q2max <- 100

Functions

Unless noted otherwise, all functions will take as argument a list containing scalar elements q1 and q2 and vector element x.

We need to define functions for the right-hand sides of the three constraints and their common left-hand side.

lhs <- function(v) drop(p %*% v$x) # LHS of (1)-(3)
rhs1 <- function(v) sum(f / (1 + v$q1)^(0:tmax))  # RHS of (1)
rhs2 <- function(v) sum(drop(b %*% v$x) / (1 + v$q2)^(0:tmax)) # RHS of (2)

Before proceeding, we need to find a value for \(\beta\) that makes the model feasible. To that end, we will pick arbitrary values for \(x\), use equation (1) to find \(q_1\), use equation (2) to find \(q_2\), and then choose \(\beta\) to balance equation (3).

temp <- list(x = c(0.2, 0.1, 0.3)) # arbitrary
junk <- function(q) rhs1(list(q1 = q)) - lhs(temp)
temp$q1 <- uniroot(junk, lower = 0, upper = q1max, tol = 1e-9)$root
junk <- function(q) {
  v <- temp
  v$q2 <- q
  rhs2(v) - lhs(temp)
}
temp$q2 <- uniroot(junk, lower = 0, upper = q2max, tol = 1e-9)$root
beta <- lhs(temp) / rhs1(list(q1 = temp$q1 + temp$q2))
cat("Known feasible solution: q1 = ", temp$q1, ", q2 = ", temp$q2, ", x = ", temp$x, ".\nUsing beta = ", beta, ".\n")
Known feasible solution: q1 =  4.893879 , q2 =  0.3784701 , x =  0.2 0.1 0.3 .
Using beta =  1.01866 .
rm(temp, junk)

Armed with a reasonable value for \(\beta\), we can define the function that computes the right-hand side of equation (3).

rhs3 <- function(v) beta * rhs1(list(q1 = v$q1 + v$q2, q2 = NA, x = NA)) # RHS of (3)

We also need a function to compute the coefficients of \(x\) in the right-hand side of constraint (2), given \(q_2\).

coefs2 <- function(v) drop((1/(1 + v$q2)^(0:tmax)) %*% b)

Given values of \(q_1\) and \(q_2\), we find a suitable vector \(x\in [0,1]^n\) by solving a linear program, using the following function. The return value is a list of all variable values \(q_1\), \(q_2\), \(x\) or NA if no feasible solution for \(x\) can be found.

findX <- function(v) {
  a <- coefs2(v)
  zz <- file("/tmp/junk", open="wt")
  # Divert annoying CPLEX messages to a junk file.
  sink(zz, type = "message")
  sink(zz)
  # Set up and solve the LP.
  sol <- MIPModel() %>% 
           add_variable(x[i], i=1:n, type = "continuous", lb = 0, ub = 1) %>% 
           set_objective(0) %>% 
           add_constraint(sum_expr(p[i] * x[i], i=1:n) == rhs1(v)) %>% 
           add_constraint(sum_expr((p[i] - a[i]) * x[i], i=1:n) == 0) %>% 
           solve_model(with_ROI(solver = "cplex"))
  # Stop diversion and close the temp file.
  sink()
  sink(type = "message")
  close(zz)
  if (sol$status == "infeasible") {
    return(NA)
  } else {
    v$x <- sol %>% get_solution(x[i])
    return(v)
  }
}

Given a value for \(q_1\), we need a function that finds the corresponding value of \(q_2\) using the equality of the right-hand sides of constraints 1 and 3. The return value is a list containing all three of \(q_1\), \(q_2\) and \(x\). If no value of \(q_2\) can be found, the function returns NA.

findQ2 <- function(v) {
  # Define a function of one variable (q2) that computes the difference between
  # the right-hand sides of constraints (1) and (3), given q1.
  r1 = rhs1(v)
  f <- function(q) { 
    v0 <- v
    v0$q2 <- q
    rhs3(v0) - r1
  }
  # Solve for a root of f. If a root is found, add it to the input list v.
  # If not, return NA.
  result <- tryCatch(uniroot(f, c(0, q2max), tol = 1e-5),
                     error = function(e) NA)
  if (is.list(result)) {
    v$q2 <- result$root
  } else {
    return(NA)
  }
  # We now have values for q1 and q2. Solve for x. If a solution is found, add it
  # to v and return the completed list. If not, return NA.
  result <- findX(v)
  if (is.list(result)) {
    # Add the objective value.
    result$obj <- result$q1 + result$q2
    return(result)
  } else {
    return(NA)
  }
}

Solution process

Throughout the process, we will maintain an incumbent solution.

incumbent <- list(obj = -1)  # dummy starting value

For convenience, we will define a function to check for a new incumbent.

checkIncumbent <- function(v) {
  if (v$obj > incumbent$obj) {
    incumbent <<- v
  }
}

Lower limit for \(q_1\)

The first step in the solution process is to establish a lower bound for \(q_1\), using constraint (1). The maximum value of the left-hand side occurs when \(x_i = 1\) for all \(i\). We solve for the corresponding value of \(q_1\) and set that as the domain minimum (q1min).

temp <- function(q) rhs1(list(q1 = q)) - sum(p)
lo <- tryCatch(uniroot(temp, c(0, q1max), tol = 1e-7), error = function(e) NA)
if (is.list(lo)) {
  q1min <- lo$root
} else {
  cat("Unable to find a valid lower bound for q_1.\n")
}
# Clean up.
rm(lo)

The lower limit may not be feasible. We can try a line search with fixed step size until we find a feasible value for \(q_1\).

stepSize <- 0.1   # step size for search
v <- list(q1 = q1min)
temp <- findQ2(v)
while (!is.list(temp)) {
  v$q1 <- v$q1 + stepSize
  if (v$q1 > q1max) {
    break
  } else {
    temp <- findQ2(v)
  }
}
if (is.list(temp)) {
  # We have a feasible solution. Check if it is a new incumbent.
  v <- temp
  checkIncumbent(v)
  # Make it the new lower limit on q1.
  q1min <- v$q1
} else {
  # We failed to find a feasible solution, so give up.
  cat("Cannot find a feasible solution!\n")
}
# Clean up.
rm(v, temp, stepSize)

Before continuing, we need to set a stopping criterion. We will stop when the gap (the width of the interval of uncertainty for the value of \(q_1\)) drops below a specified limit.

gaptol <- 1e-5

Upper limit for \(q_1\)

If the upper limit we arbitrarily set for \(q_1\) is not feasible, we can perform a bisection search to find the largest feasible value for \(q_1\).

v <- findQ2(list(q1 = q1max))
if (!is.list(v)) {
  # The upper limit is not feasible. We need to do a bisection search.
  uncertainty <- c(q1min, q1max)
  while (uncertainty[2] - uncertainty[1] > gaptol) {
    # Try the midpoint.
    mid <- (uncertainty[1] + uncertainty[2]) / 2
    v <- findQ2(list(q1 = mid))
    if (is.list(v)) {
      # The midpoint is feasible and becomes the lower end of the uncertainty interval.
      uncertainty[1] <- mid
      # Check if we stumbled on a new incumbent.
      checkIncumbent(v)
    } else {
      # The midpoint is infeasible and becomes the upper end of the uncertainty interval.
      uncertainty[2] <- mid
    }
  }
  # Set the lower limit of the interval as the upper limit for q1.
  q1max <- uncertainty[1]
  # Clean up
  rm(uncertainty, mid, v)
}
cat("The interval of uncertainty for q1 is [", q1min, ", ", q1max, "].\n")
The interval of uncertainty for q1 is [ 4.760744 ,  5.450373 ].
cat("The current incumbent solution has q1 = ", incumbent$q1, " and objective value ", incumbent$obj, ".\n")
The current incumbent solution has q1 =  5.450373  and objective value  5.916804 .
LS0tCnRpdGxlOiAiTkxQIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpJbiB0aGlzIG5vdGVib29rIHdlIHRyeSB0byBzb2x2ZSBhIFtub25saW5lYXIgb3B0aW1pemF0aW9uIG1vZGVsXShodHRwczovL29yLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy81NjI4L3BpZWNld2lzZS1saW5lYXItYW5kLWdsb2JhbC1vcHRpbWl6YXRpb24pIHBvc3RlZCBvbiBPUiBTdGFjayBFeGNoYW5nZS4gV2UgZGVhbCBvbmx5IHdpdGggdGhlIGNhc2Ugd2hlcmUgdGhlICR4JCB2YXJpYWJsZXMgYXJlIGNvbnRpbnVvdXMgcmF0aGVyIHRoYW4gYmluYXJ5LiBUaGUgcHJvYmxlbSBhbmQgc29sdXRpb24gcHJvY2VzcyBhcmUgZGlzY3Vzc2VkIGluIGEgW3Bvc3Qgb24gbXkgYmxvZ10oaHR0cHM6Ly9vcmluYW5vYndvcmxkLmJsb2dzcG90LmNvbS8yMDIxLzAxL3NvbHZpbmctbXVsdGlkaW1lbnNpb25hbC1ubHAtdmlhLWxpbmUuaHRtbCkuCgpUaGUgc29sdXRpb24gYXBwcm9hY2ggY29tYmluZXMgb25lIGRpbWVuc2lvbmFsIGxpbmUgc2VhcmNoIHdpdGggc29sdmluZyBhIGxpbmVhciBwcm9ncmFtLiBXZSB1c2UgT01QUiB0byBtb2RlbCB0aGUgTFAgYW5kIENQTEVYICh2aWEgdGhlIFJPSSBwbHVnaW4pIHRvIHNvbHZlIHRoZSBMUHMuIAoKRmlyc3QsIHdlIGxvYWQgdGhlIHJlcXVpcmVkIHBhY2thZ2VzLgoKYGBge3J9CmxpYnJhcnkob21wcikKbGlicmFyeShvbXByLnJvaSkKbGlicmFyeShST0kpCmxpYnJhcnkoUk9JLnBsdWdpbi5jcGxleCkKbGlicmFyeShtYWdyaXR0cikKYGBgCiMgUHJlcGFyYXRpb24KCiMjIERhdGEKClRoZSBwcm9ibGVtIGF1dGhvciBwcm92aWRlZCBhbiBleGFtcGxlLiBXZSBzZXQgdXAgdGhlIGV4YW1wbGUgZGF0YSBoZXJlLgoKYGBge3J9CnAgPC0gYyg5OS45LCAyMDAuMiwgMzAwLjApCmYgPC0gYygxMDAsIDEyMCwgMzIwLCA4MCwgNDUpCmIgPC0gdChtYXRyaXgoYygxMCwgMTAwLCAyMCwgMzksIDQsCiAgICAgICAgICAgICAgICA3MCwgMTAsIDIwMCwgNDksIDQwLAogICAgICAgICAgICAgICAgNjAsIDUwLCAzMDAsIDUwLCA2MCksIG5yb3cgPSAzLCBuY29sID0gNSwgYnlyb3cgPSBUUlVFKSkKYmV0YSA8LSAxICAjIHBsYWNlaG9sZGVyIHZhbHVlCmBgYAoKTmV4dCwgd2UgZGVmaW5lIHByb2JsZW0gZGltZW5zaW9ucy4KCmBgYHtyfQp0bWF4IDwtIDQgIyAkVCQgaW4gdGhlIG9yaWdpbmFsIHBvc3QKbiA8LSAzCmBgYAoKTGFzdGx5LCB3ZSBuZWVkIHVwcGVyIGJvdW5kcyBmb3IgdGhlIG5vbm5lZ2F0aXZlIHZhcmlhYmxlcyAkcV8xJCBhbmQgJHFfMiQuCgpgYGB7cn0KcTFtYXggPC0gMTAwCnEybWF4IDwtIDEwMApgYGAKCiMjIEZ1bmN0aW9ucwoKVW5sZXNzIG5vdGVkIG90aGVyd2lzZSwgYWxsIGZ1bmN0aW9ucyB3aWxsIHRha2UgYXMgYXJndW1lbnQgYSBsaXN0IGNvbnRhaW5pbmcgc2NhbGFyIGVsZW1lbnRzIGBxMWAgYW5kIGBxMmAgYW5kIHZlY3RvciBlbGVtZW50IGB4YC4KCldlIG5lZWQgdG8gZGVmaW5lIGZ1bmN0aW9ucyBmb3IgdGhlIHJpZ2h0LWhhbmQgc2lkZXMgb2YgdGhlIHRocmVlIGNvbnN0cmFpbnRzIGFuZCB0aGVpciBjb21tb24gbGVmdC1oYW5kIHNpZGUuCgpgYGB7cn0KbGhzIDwtIGZ1bmN0aW9uKHYpIGRyb3AocCAlKiUgdiR4KSAjIExIUyBvZiAoMSktKDMpCmBgYAoKYGBge3J9CnJoczEgPC0gZnVuY3Rpb24odikgc3VtKGYgLyAoMSArIHYkcTEpXigwOnRtYXgpKSAgIyBSSFMgb2YgKDEpCmBgYAoKYGBge3J9CnJoczIgPC0gZnVuY3Rpb24odikgc3VtKGRyb3AoYiAlKiUgdiR4KSAvICgxICsgdiRxMileKDA6dG1heCkpICMgUkhTIG9mICgyKQpgYGAKCkJlZm9yZSBwcm9jZWVkaW5nLCB3ZSBuZWVkIHRvIGZpbmQgYSB2YWx1ZSBmb3IgJFxiZXRhJCB0aGF0IG1ha2VzIHRoZSBtb2RlbCBmZWFzaWJsZS4gVG8gdGhhdCBlbmQsIHdlIHdpbGwgcGljayBhcmJpdHJhcnkgdmFsdWVzIGZvciAkeCQsIHVzZSBlcXVhdGlvbiAoMSkgdG8gZmluZCAkcV8xJCwgdXNlIGVxdWF0aW9uICgyKSB0byBmaW5kICRxXzIkLCBhbmQgdGhlbiBjaG9vc2UgJFxiZXRhJCB0byBiYWxhbmNlIGVxdWF0aW9uICgzKS4KCmBgYHtyfQp0ZW1wIDwtIGxpc3QoeCA9IGMoMC4yLCAwLjEsIDAuMykpICMgYXJiaXRyYXJ5Cmp1bmsgPC0gZnVuY3Rpb24ocSkgcmhzMShsaXN0KHExID0gcSkpIC0gbGhzKHRlbXApCnRlbXAkcTEgPC0gdW5pcm9vdChqdW5rLCBsb3dlciA9IDAsIHVwcGVyID0gcTFtYXgsIHRvbCA9IDFlLTkpJHJvb3QKanVuayA8LSBmdW5jdGlvbihxKSB7CiAgdiA8LSB0ZW1wCiAgdiRxMiA8LSBxCiAgcmhzMih2KSAtIGxocyh0ZW1wKQp9CnRlbXAkcTIgPC0gdW5pcm9vdChqdW5rLCBsb3dlciA9IDAsIHVwcGVyID0gcTJtYXgsIHRvbCA9IDFlLTkpJHJvb3QKYmV0YSA8LSBsaHModGVtcCkgLyByaHMxKGxpc3QocTEgPSB0ZW1wJHExICsgdGVtcCRxMikpCmNhdCgiS25vd24gZmVhc2libGUgc29sdXRpb246IHExID0gIiwgdGVtcCRxMSwgIiwgcTIgPSAiLCB0ZW1wJHEyLCAiLCB4ID0gIiwgdGVtcCR4LCAiLlxuVXNpbmcgYmV0YSA9ICIsIGJldGEsICIuXG4iKQpybSh0ZW1wLCBqdW5rKQpgYGAKQXJtZWQgd2l0aCBhIHJlYXNvbmFibGUgdmFsdWUgZm9yICRcYmV0YSQsIHdlIGNhbiBkZWZpbmUgdGhlIGZ1bmN0aW9uIHRoYXQgY29tcHV0ZXMgdGhlIHJpZ2h0LWhhbmQgc2lkZSBvZiBlcXVhdGlvbiAoMykuCgpgYGB7cn0KcmhzMyA8LSBmdW5jdGlvbih2KSBiZXRhICogcmhzMShsaXN0KHExID0gdiRxMSArIHYkcTIsIHEyID0gTkEsIHggPSBOQSkpICMgUkhTIG9mICgzKQpgYGAKCldlIGFsc28gbmVlZCBhIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGNvZWZmaWNpZW50cyBvZiAkeCQgaW4gdGhlIHJpZ2h0LWhhbmQgc2lkZSBvZiBjb25zdHJhaW50ICgyKSwgZ2l2ZW4gJHFfMiQuCgpgYGB7cn0KY29lZnMyIDwtIGZ1bmN0aW9uKHYpIGRyb3AoKDEvKDEgKyB2JHEyKV4oMDp0bWF4KSkgJSolIGIpCmBgYAoKR2l2ZW4gdmFsdWVzIG9mICRxXzEkIGFuZCAkcV8yJCwgd2UgZmluZCBhIHN1aXRhYmxlIHZlY3RvciAkeFxpbiBbMCwxXV5uJCBieSBzb2x2aW5nIGEgbGluZWFyIHByb2dyYW0sIHVzaW5nIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb24uIFRoZSByZXR1cm4gdmFsdWUgaXMgYSBsaXN0IG9mIGFsbCB2YXJpYWJsZSB2YWx1ZXMgJHFfMSQsICRxXzIkLCAkeCQgb3IgTkEgaWYgbm8gZmVhc2libGUgc29sdXRpb24gZm9yICR4JCBjYW4gYmUgZm91bmQuCgpgYGB7cn0KZmluZFggPC0gZnVuY3Rpb24odikgewogIGEgPC0gY29lZnMyKHYpCiAgenogPC0gZmlsZSgiL3RtcC9qdW5rIiwgb3Blbj0id3QiKQogICMgRGl2ZXJ0IGFubm95aW5nIENQTEVYIG1lc3NhZ2VzIHRvIGEganVuayBmaWxlLgogIHNpbmsoenosIHR5cGUgPSAibWVzc2FnZSIpCiAgc2luayh6eikKICAjIFNldCB1cCBhbmQgc29sdmUgdGhlIExQLgogIHNvbCA8LSBNSVBNb2RlbCgpICU+JSAKICAgICAgICAgICBhZGRfdmFyaWFibGUoeFtpXSwgaT0xOm4sIHR5cGUgPSAiY29udGludW91cyIsIGxiID0gMCwgdWIgPSAxKSAlPiUgCiAgICAgICAgICAgc2V0X29iamVjdGl2ZSgwKSAlPiUgCiAgICAgICAgICAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIocFtpXSAqIHhbaV0sIGk9MTpuKSA9PSByaHMxKHYpKSAlPiUgCiAgICAgICAgICAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIoKHBbaV0gLSBhW2ldKSAqIHhbaV0sIGk9MTpuKSA9PSAwKSAlPiUgCiAgICAgICAgICAgc29sdmVfbW9kZWwod2l0aF9ST0koc29sdmVyID0gImNwbGV4IikpCiAgIyBTdG9wIGRpdmVyc2lvbiBhbmQgY2xvc2UgdGhlIHRlbXAgZmlsZS4KICBzaW5rKCkKICBzaW5rKHR5cGUgPSAibWVzc2FnZSIpCiAgY2xvc2UoenopCiAgaWYgKHNvbCRzdGF0dXMgPT0gImluZmVhc2libGUiKSB7CiAgICByZXR1cm4oTkEpCiAgfSBlbHNlIHsKICAgIHYkeCA8LSBzb2wgJT4lIGdldF9zb2x1dGlvbih4W2ldKQogICAgcmV0dXJuKHYpCiAgfQp9CmBgYAoKR2l2ZW4gYSB2YWx1ZSBmb3IgJHFfMSQsIHdlIG5lZWQgYSBmdW5jdGlvbiB0aGF0IGZpbmRzIHRoZSBjb3JyZXNwb25kaW5nIHZhbHVlIG9mICRxXzIkIHVzaW5nIHRoZSBlcXVhbGl0eSBvZiB0aGUgcmlnaHQtaGFuZCBzaWRlcyBvZiBjb25zdHJhaW50cyAxIGFuZCAzLiBUaGUgcmV0dXJuIHZhbHVlIGlzIGEgbGlzdCBjb250YWluaW5nIGFsbCB0aHJlZSBvZiAkcV8xJCwgJHFfMiQgYW5kICR4JC4gSWYgbm8gdmFsdWUgb2YgJHFfMiQgY2FuIGJlIGZvdW5kLCB0aGUgZnVuY3Rpb24gcmV0dXJucyBgTkFgLgoKYGBge3J9CmZpbmRRMiA8LSBmdW5jdGlvbih2KSB7CiAgIyBEZWZpbmUgYSBmdW5jdGlvbiBvZiBvbmUgdmFyaWFibGUgKHEyKSB0aGF0IGNvbXB1dGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4KICAjIHRoZSByaWdodC1oYW5kIHNpZGVzIG9mIGNvbnN0cmFpbnRzICgxKSBhbmQgKDMpLCBnaXZlbiBxMS4KICByMSA9IHJoczEodikKICBmIDwtIGZ1bmN0aW9uKHEpIHsgCiAgICB2MCA8LSB2CiAgICB2MCRxMiA8LSBxCiAgICByaHMzKHYwKSAtIHIxCiAgfQogICMgU29sdmUgZm9yIGEgcm9vdCBvZiBmLiBJZiBhIHJvb3QgaXMgZm91bmQsIGFkZCBpdCB0byB0aGUgaW5wdXQgbGlzdCB2LgogICMgSWYgbm90LCByZXR1cm4gTkEuCiAgcmVzdWx0IDwtIHRyeUNhdGNoKHVuaXJvb3QoZiwgYygwLCBxMm1heCksIHRvbCA9IDFlLTUpLAogICAgICAgICAgICAgICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIE5BKQogIGlmIChpcy5saXN0KHJlc3VsdCkpIHsKICAgIHYkcTIgPC0gcmVzdWx0JHJvb3QKICB9IGVsc2UgewogICAgcmV0dXJuKE5BKQogIH0KICAjIFdlIG5vdyBoYXZlIHZhbHVlcyBmb3IgcTEgYW5kIHEyLiBTb2x2ZSBmb3IgeC4gSWYgYSBzb2x1dGlvbiBpcyBmb3VuZCwgYWRkIGl0CiAgIyB0byB2IGFuZCByZXR1cm4gdGhlIGNvbXBsZXRlZCBsaXN0LiBJZiBub3QsIHJldHVybiBOQS4KICByZXN1bHQgPC0gZmluZFgodikKICBpZiAoaXMubGlzdChyZXN1bHQpKSB7CiAgICAjIEFkZCB0aGUgb2JqZWN0aXZlIHZhbHVlLgogICAgcmVzdWx0JG9iaiA8LSByZXN1bHQkcTEgKyByZXN1bHQkcTIKICAgIHJldHVybihyZXN1bHQpCiAgfSBlbHNlIHsKICAgIHJldHVybihOQSkKICB9Cn0KYGBgCgojIyBTb2x1dGlvbiBwcm9jZXNzCgpUaHJvdWdob3V0IHRoZSBwcm9jZXNzLCB3ZSB3aWxsIG1haW50YWluIGFuIGluY3VtYmVudCBzb2x1dGlvbi4KCmBgYHtyfQppbmN1bWJlbnQgPC0gbGlzdChvYmogPSAtMSkgICMgZHVtbXkgc3RhcnRpbmcgdmFsdWUKYGBgCgpGb3IgY29udmVuaWVuY2UsIHdlIHdpbGwgZGVmaW5lIGEgZnVuY3Rpb24gdG8gY2hlY2sgZm9yIGEgbmV3IGluY3VtYmVudC4KCmBgYHtyfQpjaGVja0luY3VtYmVudCA8LSBmdW5jdGlvbih2KSB7CiAgaWYgKHYkb2JqID4gaW5jdW1iZW50JG9iaikgewogICAgaW5jdW1iZW50IDw8LSB2CiAgfQp9CmBgYAoKIyMjIExvd2VyIGxpbWl0IGZvciAkcV8xJAoKVGhlIGZpcnN0IHN0ZXAgaW4gdGhlIHNvbHV0aW9uIHByb2Nlc3MgaXMgdG8gZXN0YWJsaXNoIGEgbG93ZXIgYm91bmQgZm9yICRxXzEkLCB1c2luZyBjb25zdHJhaW50ICgxKS4gVGhlIG1heGltdW0gdmFsdWUgb2YgdGhlIGxlZnQtaGFuZCBzaWRlIG9jY3VycyB3aGVuICR4X2kgPSAxJCBmb3IgYWxsICRpJC4gV2Ugc29sdmUgZm9yIHRoZSBjb3JyZXNwb25kaW5nIHZhbHVlIG9mICRxXzEkIGFuZCBzZXQgdGhhdCBhcyB0aGUgZG9tYWluIG1pbmltdW0gKHExbWluKS4KCmBgYHtyfQp0ZW1wIDwtIGZ1bmN0aW9uKHEpIHJoczEobGlzdChxMSA9IHEpKSAtIHN1bShwKQpsbyA8LSB0cnlDYXRjaCh1bmlyb290KHRlbXAsIGMoMCwgcTFtYXgpLCB0b2wgPSAxZS03KSwgZXJyb3IgPSBmdW5jdGlvbihlKSBOQSkKaWYgKGlzLmxpc3QobG8pKSB7CiAgcTFtaW4gPC0gbG8kcm9vdAp9IGVsc2UgewogIGNhdCgiVW5hYmxlIHRvIGZpbmQgYSB2YWxpZCBsb3dlciBib3VuZCBmb3IgcV8xLlxuIikKfQojIENsZWFuIHVwLgpybShsbykKYGBgCgpUaGUgbG93ZXIgbGltaXQgbWF5IG5vdCBiZSBmZWFzaWJsZS4gV2UgY2FuIHRyeSBhIGxpbmUgc2VhcmNoIHdpdGggZml4ZWQgc3RlcCBzaXplIHVudGlsIHdlIGZpbmQgYSBmZWFzaWJsZSB2YWx1ZSBmb3IgJHFfMSQuCgpgYGB7cn0Kc3RlcFNpemUgPC0gMC4xICAgIyBzdGVwIHNpemUgZm9yIHNlYXJjaAp2IDwtIGxpc3QocTEgPSBxMW1pbikKdGVtcCA8LSBmaW5kUTIodikKd2hpbGUgKCFpcy5saXN0KHRlbXApKSB7CiAgdiRxMSA8LSB2JHExICsgc3RlcFNpemUKICBpZiAodiRxMSA+IHExbWF4KSB7CiAgICBicmVhawogIH0gZWxzZSB7CiAgICB0ZW1wIDwtIGZpbmRRMih2KQogIH0KfQppZiAoaXMubGlzdCh0ZW1wKSkgewogICMgV2UgaGF2ZSBhIGZlYXNpYmxlIHNvbHV0aW9uLiBDaGVjayBpZiBpdCBpcyBhIG5ldyBpbmN1bWJlbnQuCiAgdiA8LSB0ZW1wCiAgY2hlY2tJbmN1bWJlbnQodikKICAjIE1ha2UgaXQgdGhlIG5ldyBsb3dlciBsaW1pdCBvbiBxMS4KICBxMW1pbiA8LSB2JHExCn0gZWxzZSB7CiAgIyBXZSBmYWlsZWQgdG8gZmluZCBhIGZlYXNpYmxlIHNvbHV0aW9uLCBzbyBnaXZlIHVwLgogIGNhdCgiQ2Fubm90IGZpbmQgYSBmZWFzaWJsZSBzb2x1dGlvbiFcbiIpCn0KIyBDbGVhbiB1cC4Kcm0odiwgdGVtcCwgc3RlcFNpemUpCmBgYAoKQmVmb3JlIGNvbnRpbnVpbmcsIHdlIG5lZWQgdG8gc2V0IGEgc3RvcHBpbmcgY3JpdGVyaW9uLiBXZSB3aWxsIHN0b3Agd2hlbiB0aGUgZ2FwICh0aGUgd2lkdGggb2YgdGhlIGludGVydmFsIG9mIHVuY2VydGFpbnR5IGZvciB0aGUgdmFsdWUgb2YgJHFfMSQpIGRyb3BzIGJlbG93IGEgc3BlY2lmaWVkIGxpbWl0LgoKYGBge3J9CmdhcHRvbCA8LSAxZS01CmBgYAoKIyMjIFVwcGVyIGxpbWl0IGZvciAkcV8xJAoKSWYgdGhlIHVwcGVyIGxpbWl0IHdlIGFyYml0cmFyaWx5IHNldCBmb3IgJHFfMSQgaXMgbm90IGZlYXNpYmxlLCB3ZSBjYW4gcGVyZm9ybSBhIGJpc2VjdGlvbiBzZWFyY2ggdG8gZmluZCB0aGUgbGFyZ2VzdCBmZWFzaWJsZSB2YWx1ZSBmb3IgJHFfMSQuCgpgYGB7cn0KdiA8LSBmaW5kUTIobGlzdChxMSA9IHExbWF4KSkKaWYgKCFpcy5saXN0KHYpKSB7CiAgIyBUaGUgdXBwZXIgbGltaXQgaXMgbm90IGZlYXNpYmxlLiBXZSBuZWVkIHRvIGRvIGEgYmlzZWN0aW9uIHNlYXJjaC4KICB1bmNlcnRhaW50eSA8LSBjKHExbWluLCBxMW1heCkKICB3aGlsZSAodW5jZXJ0YWludHlbMl0gLSB1bmNlcnRhaW50eVsxXSA+IGdhcHRvbCkgewogICAgIyBUcnkgdGhlIG1pZHBvaW50LgogICAgbWlkIDwtICh1bmNlcnRhaW50eVsxXSArIHVuY2VydGFpbnR5WzJdKSAvIDIKICAgIHYgPC0gZmluZFEyKGxpc3QocTEgPSBtaWQpKQogICAgaWYgKGlzLmxpc3QodikpIHsKICAgICAgIyBUaGUgbWlkcG9pbnQgaXMgZmVhc2libGUgYW5kIGJlY29tZXMgdGhlIGxvd2VyIGVuZCBvZiB0aGUgdW5jZXJ0YWludHkgaW50ZXJ2YWwuCiAgICAgIHVuY2VydGFpbnR5WzFdIDwtIG1pZAogICAgICAjIENoZWNrIGlmIHdlIHN0dW1ibGVkIG9uIGEgbmV3IGluY3VtYmVudC4KICAgICAgY2hlY2tJbmN1bWJlbnQodikKICAgIH0gZWxzZSB7CiAgICAgICMgVGhlIG1pZHBvaW50IGlzIGluZmVhc2libGUgYW5kIGJlY29tZXMgdGhlIHVwcGVyIGVuZCBvZiB0aGUgdW5jZXJ0YWludHkgaW50ZXJ2YWwuCiAgICAgIHVuY2VydGFpbnR5WzJdIDwtIG1pZAogICAgfQogIH0KICAjIFNldCB0aGUgbG93ZXIgbGltaXQgb2YgdGhlIGludGVydmFsIGFzIHRoZSB1cHBlciBsaW1pdCBmb3IgcTEuCiAgcTFtYXggPC0gdW5jZXJ0YWludHlbMV0KICAjIENsZWFuIHVwCiAgcm0odW5jZXJ0YWludHksIG1pZCwgdikKfQpjYXQoIlRoZSBpbnRlcnZhbCBvZiB1bmNlcnRhaW50eSBmb3IgcTEgaXMgWyIsIHExbWluLCAiLCAiLCBxMW1heCwgIl0uXG4iKQpjYXQoIlRoZSBjdXJyZW50IGluY3VtYmVudCBzb2x1dGlvbiBoYXMgcTEgPSAiLCBpbmN1bWJlbnQkcTEsICIgYW5kIG9iamVjdGl2ZSB2YWx1ZSAiLCBpbmN1bWJlbnQkb2JqLCAiLlxuIikKYGBgCgojIyMgR29sZGVuIHNlY3Rpb24gc2VhcmNoCgpUbyBmaW5kIHRoZSBvcHRpbXVtLCB3ZSBwZXJmb3JtIGdvbGRlbiBzZWN0aW9uIHNlYXJjaCBvbiB0aGUgaW50ZXJ2YWwgZnJvbSBxMW1pbiB0byBxMW1heC4gR1Mgc2VhcmNoIGluc2VydHMgdHdvIHN5bW1ldHJpY2FsbHkgcG9zaXRpb25lZCBwb2ludHMgaW4gdGhlIGludGVydmFsLCBnaXZpbmcgdXMgYSB0b3RhbCBvZiBmb3VyIHBvaW50cyBpbmNsdWRpbmcgdGhlIGVuZHBvaW50cy4gU2luY2Ugd2UgYXJlIG1heGltaXppbmcsIGlmIHRoZSBtYXhpbXVtIG9jY3VycyBhdCBlaXRoZXIgZW5kLCB3ZSBkcm9wIHRoZSBvcHBvc2l0ZSBlbmRwb2ludCAobGVhdmluZyB0aHJlZSBwb2ludHMpIGFuZCBpbnNlcnQgYSBuZXcgcG9pbnQgaW4gdGhlIHN5bW1ldHJpYyBwb3NpdGlvbiB0byB0aGUgcmVtYWluaW5nIGludGVyaW9yIHBvaW50LiBJZiB0aGUgbWF4aW11bSBvY2N1cnMgYXQgYW4gaW50ZXJpb3IgcG9pbnQsIHdlIGRyb3AgdGhlIGVuZHBvaW50IHRoYXQgaXMgZnVydGhlciBhd2F5IGZyb20gdGhlIG1heGltdW0gYW5kIGFnYWluIGFkZCBhIHN5bW1ldHJpYyBwb2ludC4KCmBgYHtyfQojIFNldCB1cCB0aGUgZm91ciBwb2ludHMgaW4gdGhlIGluaXRpYWwgaW50ZXJ2YWwgYW5kIGdldCB0aGVpciBvYmplY3RpdmUgdmFsdWVzLgpncyA8LSAwLjYxOCAjIGFwcHJveGltYXRlIGdvbGRlbiBzZWN0aW9uIGZhY3RvcgpnczEgPC0gMSAtIGdzCmludGVydmFsIDwtIGMocTFtaW4sIGdzICogcTFtaW4gKyBnczEgKiBxMW1heCwgZ3MgKiBxMW1heCArIGdzMSAqIHExbWluLCBxMW1heCkKb2JqIDwtIGMoZmluZFEyKGxpc3QocTE9cTFtaW4pKSRvYmosIDAsIDAsIGZpbmRRMihsaXN0KHExPXExbWF4KSkkb2JqKQp0ZW1wIDwtIGZpbmRRMihsaXN0KHExPWludGVydmFsWzJdKSkKb2JqWzJdIDwtIHRlbXAkb2JqCmNoZWNrSW5jdW1iZW50KHRlbXApCnRlbXAgPC0gZmluZFEyKGxpc3QocTE9aW50ZXJ2YWxbM10pKQpvYmpbM10gPC0gdGVtcCRvYmoKY2hlY2tJbmN1bWJlbnQodGVtcCkKIyBXaGlsZSB0aGUgaW50ZXJ2YWwgd2lkdGggZXhjZWVkcyB0aGUgZ2FwIGxpbWl0LCBhcHBseSBHUyBzZWFyY2guCndoaWxlKGludGVydmFsWzRdIC0gaW50ZXJ2YWxbMV0gPiBnYXB0b2wpIHsKICAjIEZpbmQgdGhlIGluZGV4IG9mIHRoZSBwb2ludCB3aXRoIGdyZWF0ZXN0IG9iamVjdGl2ZSB2YWx1ZS4KICBpIDwtIHdoaWNoLm1heChvYmopCiAgIyBSZXBsYWNlIHRoZSBvcHBvc2l0ZSBlbmRwb2ludC4KICBpZiAoaSA+PSAzKSB7CiAgICAjIFJlcGxhY2UgdGhlIGxvd2VyIGVuZHBvaW50LgogICAgaW50ZXJ2YWxbMV0gPC0gaW50ZXJ2YWxbMl0KICAgIG9ialsxXSA8LSBvYmpbMl0KICAgICMgVGhlIGhpZ2hlciBpbnRlcmlvciBwb2ludCBiZWNvbWVzIHRoZSBsb3dlciBpbnRlcmlvciBwb2ludC4KICAgIGludGVydmFsWzJdIDwtIGludGVydmFsWzNdCiAgICBvYmpbMl0gPC0gb2JqWzNdCiAgICAjIEluc2VydCBhIG5ldyB1cHBlciBpbnRlcmlvciBwb2ludC4KICAgIGludGVydmFsWzNdIDwtIGdzMSAqIGludGVydmFsWzFdICsgZ3MgKiBpbnRlcnZhbFs0XQogICAgdGVtcCA8LSBmaW5kUTIobGlzdChxMSA9IGludGVydmFsWzNdKSkKICAgIGNoZWNrSW5jdW1iZW50KHRlbXApCiAgICBvYmpbM10gPC0gdGVtcCRvYmoKICB9IGVsc2UgewogICAgIyBSZXBsYWNlIHRoZSB1cHBlciBlbmRwb2ludC4KICAgIGludGVydmFsWzRdIDwtIGludGVydmFsWzNdCiAgICBvYmpbNF0gPC0gb2JqWzNdCiAgICAjIFRoZSBsb3dlciBpbnRlcmlvciBwb2ludCBiZWNvbWVzIHRoZSBoaWdoZXIgaW50ZXJpb3IgcG9pbnQuCiAgICBpbnRlcnZhbFszXSA8LSBpbnRlcnZhbFsyXQogICAgb2JqWzNdIDwtIG9ialsyXQogICAgIyBJbnNlcnQgYSBuZXcgbG93ZXIgaW50ZXJpb3IgcG9pbnQuCiAgICBpbnRlcnZhbFsyXSA8LSBncyAqIGludGVydmFsWzFdICsgZ3MxICogaW50ZXJ2YWxbNF0KICAgIHRlbXAgPC0gZmluZFEyKGxpc3QocTEgPSBpbnRlcnZhbFsyXSkpCiAgICBjaGVja0luY3VtYmVudCh0ZW1wKQogICAgb2JqWzJdIDwtIHRlbXAkb2JqCiAgfQp9CiMgQ2xlYW4gdXAuCnJtKGdzLCBnczEsIHRlbXApCmBgYAoKRGlzcGxheSB0aGUgaW5jdW1iZW50IHNvbHV0aW9uLgoKYGBge3J9CmNhdCgiSW5jdW1iZW50IHNvbHV0aW9uOlxucTEgPSAiLCBpbmN1bWJlbnQkcTEsICJcbnEyID0gIiwgaW5jdW1iZW50JHEyLCAiXG54ID0gIiwgaW5jdW1iZW50JHgkdmFsdWUsICJcbk9iamVjdGl2ZSB2YWx1ZSA9ICIsIGluY3VtYmVudCRvYmosICIuXG4iKQpgYGAKYGBge3J9CiMgQ2xlYW4gdXAgeCBhbmQgc2VlIGhvdyBiYWRseSBjb25zdHJhaW50cyBhcmUgdmlvbGF0ZWQuCnYgPC0gaW5jdW1iZW50CnYkeCA8LSBpbmN1bWJlbnQkeCR2YWx1ZSAlPiUgcG1pbiguLCAxKSAlPiUgcG1heCguLCAwKQpjYXQoIkFkanVzdGVkIHggdmVjdG9yID0gIiwgdiR4LCAiXG4iKQpjYXQoIkNvbW1vbiBMSFMgb2YgYWxsIHRocmVlIGNvbnN0cmFpbnRzID0gIiwgbGhzKHYpLCAiXG4iKQpjYXQoIlJIUyBvZiAoMSkgPSAiLCByaHMxKHYpLCAiXG4iKQpjYXQoIlJIUyBvZiAoMikgPSAiLCByaHMyKHYpLCAiXG4iKQpjYXQoIlJIUyBvZiAoMykgPSAiLCByaHMzKHYpLCAiXG4iKQpgYGAKCg==