This notebook demonstrates the solution I posted to a regression problem posted on OR Stack Exchange: “Linearizing a program with multinomial logit in the objective”. Given \(K\) observations \(y_1,\dots,y_K\) of the dependent variable, the mission is to choose nonnegative values for \(x_{i,k}\) (\(i=1,\dots,N\), \(k=1,\dots,K\)) so as to minimize \[\sum_{k=1}^K \left|y_k - \sum_{i=1}^N \frac{e^{x_{i,k}}}{\sum_{j=1}^K e^{x_{i,j}}}\right|.\]

Libraries

We will generate a test problem and solve it, using OMPR + ROI + CPLEX to solve the modified model (a linear program).

library(ROI)               # general solver interface
ROI: R Optimization Infrastructure
Registered solver plugins: nlminb, cplex.
Default solver: auto.
library(ROI.plugin.cplex)  # connects the ROI package to CPLEX
library(ompr)              # MIP modeling package
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(ompr.roi)          # connects OMPR to ROI
library(magrittr)          # for the pipe operator
library(GA)                # to compare a genetic algorithm with the LP
Loading required package: foreach
Loading required package: iterators
  ____    _    
 / ___|  / \     Genetic 
| |  _  / _ \    Algorithms
| |_| |/ ___ \   
 \____/_/   \_\  version 3.2
Type 'citation("GA")' for citing this R package in publications.

Attaching package: ‘GA’

The following object is masked from ‘package:utils’:

    de

Test data

We will pick some arbitrary dimensions and generate “dependent variable observations” using a uniform distribution. (The purpose here is to illustrate the computations, not to fit actual data.)

N <- 10        # number of "agents"
K <- 100       # number of sample points
set.seed(123)  # for reproducibility
y <- runif(K)  # sample data

We will also code the objective function as specified in the original question, so that we can check any proposed solution.

obj <- function(x) {
  # The input x will be an N x K matrix.
  # We convert each x[i, j] to e^x[i, j].
  xx <- exp(x)
  # Next, we scale by row sums.
  xx <- xx / apply(xx, 1, sum)
  # Finally, we subtract column sums from y, take absolute values, and sum.
  (y - apply(xx, 2, sum)) %>% abs() %>% sum()
}

LP model

The proposed model involves a change of variables to \[z_{i,k}=\frac{e^{x_{i,k}}}{\sum_{j=1}^K e^{x_{i,j}}}.\] The \(z\) variables are nonnegative and must satisfy one constraint: \[\sum_{k=1}^{K}z_{i, k}=1\quad\forall i=1,\dots,N.\]The objective becomes \[\min \sum_{k=1}^K \left|y_k - \sum_{i=1}^N z_{i,k}\right|.\] We linearize this by adding new variables \(w_k\ge 0\), whose sum we minimize, together with the constraints \[-w_k \le y_k - \sum_{i=1}^N z_{i,k} \le w_k \quad \forall k=1,\dots,K.\]

Since the matrix of \(z\) variables has to be flattened into a vector for OMPR, we need indexing functions to convert between one- and two-dimensional representations.

ix1 <- function(i, k) {
  # Convert i in 1...N and k in 1...K to a single index in 1...N*K.
  (i - 1) * K + k
}
ix2 <- function(t) {
  # Convert t in 1...N*K to vector (i,k) with i in 1...N and k in 1...K
  c((t - 1) %/% K + 1, (t - 1) %% K + 1)
}

Now we build the LP model. It requires a strictly positive lower bound on \(z\), which I will arbitrarily set.

zlo <- 1e-7
model <- 
  # Create an empty model.
  MIPModel() %>% 
  # Add the z and w variables. z.var[t] is z[ix2(t)].
  add_variable(z.var[j], j = 1:(N * K), type = "continuous", lb = zlo) %>% 
  add_variable(w.var[k], k = 1:K, type = "continuous", lb = 0) %>% 
  # Set the objective to minimize the sum of the w variables.
  set_objective(sum_expr(w.var[k], k = 1:K), "min") %>% 
  # Constrain each row of the z matrix to sum to 1.
  add_constraint(sum_expr(z.var[j], j = ix1(i, 1):ix1(i, K)) == 1, i = 1:N) %>% 
  # Constrain w[k] to be at least the absolute error in observation k.
  add_constraint(sum_expr(z.var[j], j = 1:(N * K), (j - 1) %% K + 1 == k) - w.var[k] <= y[k], k = 1:K) %>% 
  add_constraint(sum_expr(z.var[j], j = 1:(N * K), (j - 1) %% K + 1 == k) + w.var[k] >= y[k], k = 1:K)

  adding constraints [================================>--------]  80% eta  0s
  adding constraints [====================================>----]  90% eta  0s
  adding constraints [=========================================] 100% eta  0s
                                                                             

  adding constraints [============>----------------------------]  31% eta  0s
  adding constraints [============>----------------------------]  32% eta  0s
  adding constraints [=============>---------------------------]  33% eta  0s
  adding constraints [=============>---------------------------]  34% eta  0s
  adding constraints [=============>---------------------------]  35% eta  0s
  adding constraints [==============>--------------------------]  36% eta  0s
  adding constraints [==============>--------------------------]  37% eta  0s
  adding constraints [===============>-------------------------]  38% eta  0s
  adding constraints [===============>-------------------------]  39% eta  0s
  adding constraints [===============>-------------------------]  40% eta  0s
  adding constraints [================>------------------------]  41% eta  0s
  adding constraints [================>------------------------]  42% eta  0s
  adding constraints [=================>-----------------------]  43% eta  0s
  adding constraints [=================>-----------------------]  44% eta  0s
  adding constraints [=================>-----------------------]  45% eta  0s
  adding constraints [==================>----------------------]  46% eta  0s
  adding constraints [==================>----------------------]  47% eta  0s
  adding constraints [===================>---------------------]  48% eta  0s
  adding constraints [===================>---------------------]  49% eta  0s
  adding constraints [===================>---------------------]  50% eta  0s
  adding constraints [====================>--------------------]  51% eta  0s
  adding constraints [====================>--------------------]  52% eta  0s
  adding constraints [=====================>-------------------]  53% eta  0s
  adding constraints [=====================>-------------------]  54% eta  0s
  adding constraints [======================>------------------]  55% eta  0s
  adding constraints [======================>------------------]  56% eta  0s
  adding constraints [======================>------------------]  57% eta  0s
  adding constraints [=======================>-----------------]  58% eta  0s
  adding constraints [=======================>-----------------]  59% eta  0s
  adding constraints [========================>----------------]  60% eta  0s
  adding constraints [========================>----------------]  61% eta  0s
  adding constraints [========================>----------------]  62% eta  0s
  adding constraints [=========================>---------------]  63% eta  0s
  adding constraints [=========================>---------------]  64% eta  0s
  adding constraints [==========================>--------------]  65% eta  0s
  adding constraints [==========================>--------------]  66% eta  0s
  adding constraints [==========================>--------------]  67% eta  0s
  adding constraints [===========================>-------------]  68% eta  0s
  adding constraints [===========================>-------------]  69% eta  0s
  adding constraints [============================>------------]  70% eta  0s
  adding constraints [============================>------------]  71% eta  0s
  adding constraints [=============================>-----------]  72% eta  0s
  adding constraints [=============================>-----------]  73% eta  0s
  adding constraints [=============================>-----------]  74% eta  0s
  adding constraints [==============================>----------]  75% eta  0s
  adding constraints [==============================>----------]  76% eta  0s
  adding constraints [===============================>---------]  77% eta  0s
  adding constraints [===============================>---------]  78% eta  0s
  adding constraints [===============================>---------]  79% eta  0s
  adding constraints [================================>--------]  80% eta  0s
  adding constraints [================================>--------]  81% eta  0s
  adding constraints [=================================>-------]  82% eta  0s
  adding constraints [=================================>-------]  83% eta  0s
  adding constraints [=================================>-------]  84% eta  0s
  adding constraints [==================================>------]  85% eta  0s
  adding constraints [==================================>------]  86% eta  0s
  adding constraints [===================================>-----]  87% eta  0s
  adding constraints [===================================>-----]  88% eta  0s
  adding constraints [===================================>-----]  89% eta  0s
  adding constraints [====================================>----]  90% eta  0s
  adding constraints [====================================>----]  91% eta  0s
  adding constraints [=====================================>---]  92% eta  0s
  adding constraints [=====================================>---]  93% eta  0s
  adding constraints [======================================>--]  94% eta  0s
  adding constraints [======================================>--]  95% eta  0s
  adding constraints [======================================>--]  96% eta  0s
  adding constraints [=======================================>-]  97% eta  0s
  adding constraints [=======================================>-]  98% eta  0s
  adding constraints [========================================>]  99% eta  0s
  adding constraints [=========================================] 100% eta  0s
                                                                             

  adding constraints [=============>---------------------------]  33% eta  0s
  adding constraints [=============>---------------------------]  34% eta  0s
  adding constraints [=============>---------------------------]  35% eta  0s
  adding constraints [==============>--------------------------]  36% eta  0s
  adding constraints [==============>--------------------------]  37% eta  0s
  adding constraints [===============>-------------------------]  38% eta  0s
  adding constraints [===============>-------------------------]  39% eta  0s
  adding constraints [===============>-------------------------]  40% eta  0s
  adding constraints [================>------------------------]  41% eta  0s
  adding constraints [================>------------------------]  42% eta  0s
  adding constraints [=================>-----------------------]  43% eta  0s
  adding constraints [=================>-----------------------]  44% eta  0s
  adding constraints [=================>-----------------------]  45% eta  0s
  adding constraints [==================>----------------------]  46% eta  0s
  adding constraints [==================>----------------------]  47% eta  0s
  adding constraints [===================>---------------------]  48% eta  0s
  adding constraints [===================>---------------------]  49% eta  0s
  adding constraints [===================>---------------------]  50% eta  0s
  adding constraints [====================>--------------------]  51% eta  0s
  adding constraints [====================>--------------------]  52% eta  0s
  adding constraints [=====================>-------------------]  53% eta  0s
  adding constraints [=====================>-------------------]  54% eta  0s
  adding constraints [======================>------------------]  55% eta  0s
  adding constraints [======================>------------------]  56% eta  0s
  adding constraints [======================>------------------]  57% eta  0s
  adding constraints [=======================>-----------------]  58% eta  0s
  adding constraints [=======================>-----------------]  59% eta  0s
  adding constraints [========================>----------------]  60% eta  0s
  adding constraints [========================>----------------]  61% eta  0s
  adding constraints [========================>----------------]  62% eta  0s
  adding constraints [=========================>---------------]  63% eta  0s
  adding constraints [=========================>---------------]  64% eta  0s
  adding constraints [==========================>--------------]  65% eta  0s
  adding constraints [==========================>--------------]  66% eta  0s
  adding constraints [==========================>--------------]  67% eta  0s
  adding constraints [===========================>-------------]  68% eta  0s
  adding constraints [===========================>-------------]  69% eta  0s
  adding constraints [============================>------------]  70% eta  0s
  adding constraints [============================>------------]  71% eta  0s
  adding constraints [=============================>-----------]  72% eta  0s
  adding constraints [=============================>-----------]  73% eta  0s
  adding constraints [=============================>-----------]  74% eta  0s
  adding constraints [==============================>----------]  75% eta  0s
  adding constraints [==============================>----------]  76% eta  0s
  adding constraints [===============================>---------]  77% eta  0s
  adding constraints [===============================>---------]  78% eta  0s
  adding constraints [===============================>---------]  79% eta  0s
  adding constraints [================================>--------]  80% eta  0s
  adding constraints [================================>--------]  81% eta  0s
  adding constraints [=================================>-------]  82% eta  0s
  adding constraints [=================================>-------]  83% eta  0s
  adding constraints [=================================>-------]  84% eta  0s
  adding constraints [==================================>------]  85% eta  0s
  adding constraints [==================================>------]  86% eta  0s
  adding constraints [===================================>-----]  87% eta  0s
  adding constraints [===================================>-----]  88% eta  0s
  adding constraints [===================================>-----]  89% eta  0s
  adding constraints [====================================>----]  90% eta  0s
  adding constraints [====================================>----]  91% eta  0s
  adding constraints [=====================================>---]  92% eta  0s
  adding constraints [=====================================>---]  93% eta  0s
  adding constraints [======================================>--]  94% eta  0s
  adding constraints [======================================>--]  95% eta  0s
  adding constraints [======================================>--]  96% eta  0s
  adding constraints [=======================================>-]  97% eta  0s
  adding constraints [=======================================>-]  98% eta  0s
  adding constraints [========================================>]  99% eta  0s
  adding constraints [=========================================] 100% eta  0s
                                                                             

We solve the LP model and extract the solution.

# Solve the LP.
solution <- model %>% solve_model(with_ROI(solver = "cplex"))
CPLEX environment opened
Closed CPLEX environment
# Get and print the objective value.
objval <- solution$objective_value
cat("Optimal objective value = ", objval, "\n")
Optimal objective value =  39.8559 

We can extract \(w\) and \(z\) from the solver output.

# The solution contains the values of w first, followed by the values of z.
w <- solution$solution[grepl("w.var*", names(solution$solution))]
z <- matrix(solution$solution[grepl("z.var*", names(solution$solution))], nrow = N, byrow = T)

The process to recover \(x\) from \(z\) is explained in the solution I posted to the original question. For each \(i=1,\dots,N\), we find the index \(j\) of the smallest entry \(z_{i,k}\) in that row, then set \(x_{i,k} = \log(z_{i,k} / z_{i,j})\) for \(k=1,\dots,K\).

# This function converts a row of the z matrix to a row of the x matrix.
z.to.x <- function(zz) {
  # zz is one row of z.
  log(zz / min(zz))
}
# We apply the function to each row of z to get x.
x <- t(apply(z, 1, z.to.x))

As a sanity check, we confirm the objective value of the proposed solution.

obj(x)
[1] 39.8559

Genetic algorithm

As a further sanity check, we try to minimize the original objective function using a genetic algorithm. A GA solution with a larger objective value than that of the LP will not prove that the LP solution is optimal, but a GA solution with a smaller objective value than that of the LP will definitely indicate a problem.

The “chromosome” in the GA will just be the \(x\) matrix flattened to a double precision vector, the same way that we flattened \(z\) in the LP model. The only constraint in the original formulation was the requirement that \(x\ge 0\), which we enforce as a lower bound. To compute the fitness of a chromosome, we convert the vector of values to a matrix and apply the obj() function to it. Because the GA package maximizes fitness and we are minimizing, we negate the original objective function.

fitness.fun <- function(chromosome) {
  temp <- 
    chromosome %>% 
      # Reshape it to an N x K matrix.
      matrix(nrow = N, byrow = T) %>% 
      # Apply the objective function to the matrix.
      obj()
  # Return the negated value.
  -temp
}

We build and run the genetic algorithm (using mostly default settings). The ga() function requires upper bounds for the \(x\) variables, which we set rather arbitrarily.

genalg <- ga(
  type = "real-valued",
  fitness = fitness.fun,
  lower = rep.int(0, N * K),
  upper = rep.int(100, N * K),
  monitor = FALSE,
  maxiter = 1000,
  run = 100,
  seed = 123
)
cat("The best fit from the GA has error ", -genalg@fitnessValue, " compared to the LP result ", obj(x), ".\n")
The best fit from the GA has error  39.8559  compared to the LP result  39.8559 .

It appears that the GA was able to match the LP result but not improve on it.

Density

Let’s define a function to calculate the density (fraction of elements that are nonzero) in a matrix or vector.

fractionDense <- function(x) {
  temp <- 
    x %>%
    # Flatten the argument to a vector.
    as.vector() %>% 
    # Truncate small entries to zero.
    zapsmall()
  sum(temp != 0) / length(temp)
}

We can compare the density of the LP and GA solutions.

cat("The LP solution has density ", fractionDense(x), ".\n")
The LP solution has density  0.034 .
cat("The GA solution has density ", fractionDense(genalg@solution[1,]), ".\n")
The GA solution has density  1 .

If we want to use the LP model but desire a denser solution, then (using the invariance property mentioned in my solution online) we can add an arbitrary positive amount \(\lambda_i\) to each \(x_{i,k}\). We do that next, confirm the density, and check the objective value to confirm that it is unchanged.

lambda <- runif(N)
x2 <- apply(x, 2, function(t) t + lambda)
cat("The adjusted solution x has density ", fractionDense(x2), " and objective value ", obj(x2), ".\n")
The adjusted solution x has density  1  and objective value  39.8559 .

Out of curiosity, let’s see whether a similar transformation can make the GA solution very sparse.

# Get the GA solution and convert it to a matrix.
x3 <- genalg@solution[1,] %>% matrix(nrow = N, ncol = K, byrow = T)
# Find the smallest $x$ value for each $i$ (which we can subtract and still preserve nonnegativity).
x4 <- apply(x3, 1, min)
# Subtract each row min from that row.
x3 <- apply(x3, 2, function(t) t - x4)
# Confirm that the revised solution is nonnegative.
cat("The minimum of the modified solution is ", min(x3), ".\n")
The minimum of the modified solution is  0 .
# Confirm that the objective value is unchanged.
cat("The modified solution has objective value ", obj(x3), ".\n")
The modified solution has objective value  39.8559 .
# Check its density.
cat("The density of the modified solution is ", fractionDense(x3), ".\n")
The density of the modified solution is  0.99 .

So we reduced the density, but only slightly.

LS0tCnRpdGxlOiAiTXVsdGlub21pYWwgTG9naXQgRml0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBQYXVsIEEuIFJ1YmluCi0tLQoKVGhpcyBub3RlYm9vayBkZW1vbnN0cmF0ZXMgdGhlIHNvbHV0aW9uIEkgcG9zdGVkIHRvIGEgcmVncmVzc2lvbiBwcm9ibGVtIHBvc3RlZCBvbiBPUiBTdGFjayBFeGNoYW5nZTogWyJMaW5lYXJpemluZyBhIHByb2dyYW0gd2l0aCBtdWx0aW5vbWlhbCBsb2dpdCBpbiB0aGUgb2JqZWN0aXZlIl0oaHR0cHM6Ly9vci5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvNTA0MC9saW5lYXJpemluZy1hLXByb2dyYW0td2l0aC1tdWx0aW5vbWlhbC1sb2dpdC1pbi10aGUtb2JqZWN0aXZlKS4gR2l2ZW4gJEskIG9ic2VydmF0aW9ucyAkeV8xLFxkb3RzLHlfSyQgb2YgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSwgdGhlIG1pc3Npb24gaXMgdG8gY2hvb3NlIG5vbm5lZ2F0aXZlIHZhbHVlcyBmb3IgJHhfe2ksa30kICgkaT0xLFxkb3RzLE4kLCAkaz0xLFxkb3RzLEskKSBzbyBhcyB0byBtaW5pbWl6ZSAkJFxzdW1fe2s9MX1eSyBcbGVmdHx5X2sgLSBcc3VtX3tpPTF9Xk4gXGZyYWN7ZV57eF97aSxrfX19e1xzdW1fe2o9MX1eSyBlXnt4X3tpLGp9fX1ccmlnaHR8LiQkCgoKIyBMaWJyYXJpZXMKCldlIHdpbGwgZ2VuZXJhdGUgYSB0ZXN0IHByb2JsZW0gYW5kIHNvbHZlIGl0LCB1c2luZyBPTVBSICsgUk9JICsgQ1BMRVggdG8gc29sdmUgdGhlIG1vZGlmaWVkIG1vZGVsIChhIGxpbmVhciBwcm9ncmFtKS4KCmBgYHtyfQpsaWJyYXJ5KFJPSSkgICAgICAgICAgICAgICAjIGdlbmVyYWwgc29sdmVyIGludGVyZmFjZQpsaWJyYXJ5KFJPSS5wbHVnaW4uY3BsZXgpICAjIGNvbm5lY3RzIHRoZSBST0kgcGFja2FnZSB0byBDUExFWApsaWJyYXJ5KG9tcHIpICAgICAgICAgICAgICAjIE1JUCBtb2RlbGluZyBwYWNrYWdlCmxpYnJhcnkob21wci5yb2kpICAgICAgICAgICMgY29ubmVjdHMgT01QUiB0byBST0kKbGlicmFyeShtYWdyaXR0cikgICAgICAgICAgIyBmb3IgdGhlIHBpcGUgb3BlcmF0b3IKbGlicmFyeShHQSkgICAgICAgICAgICAgICAgIyB0byBjb21wYXJlIGEgZ2VuZXRpYyBhbGdvcml0aG0gd2l0aCB0aGUgTFAKYGBgCgojIFRlc3QgZGF0YQoKV2Ugd2lsbCBwaWNrIHNvbWUgYXJiaXRyYXJ5IGRpbWVuc2lvbnMgYW5kIGdlbmVyYXRlICJkZXBlbmRlbnQgdmFyaWFibGUgb2JzZXJ2YXRpb25zIiB1c2luZyBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uLiAoVGhlIHB1cnBvc2UgaGVyZSBpcyB0byBpbGx1c3RyYXRlIHRoZSBjb21wdXRhdGlvbnMsIG5vdCB0byBmaXQgYWN0dWFsIGRhdGEuKQoKYGBge3J9Ck4gPC0gMTAgICAgICAgICMgbnVtYmVyIG9mICJhZ2VudHMiCksgPC0gMTAwICAgICAgICMgbnVtYmVyIG9mIHNhbXBsZSBwb2ludHMKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnkgPC0gcnVuaWYoSykgICMgc2FtcGxlIGRhdGEKYGBgCgpXZSB3aWxsIGFsc28gY29kZSB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGFzIHNwZWNpZmllZCBpbiB0aGUgb3JpZ2luYWwgcXVlc3Rpb24sIHNvIHRoYXQgd2UgY2FuIGNoZWNrIGFueSBwcm9wb3NlZCBzb2x1dGlvbi4KCmBgYHtyfQpvYmogPC0gZnVuY3Rpb24oeCkgewogICMgVGhlIGlucHV0IHggd2lsbCBiZSBhbiBOIHggSyBtYXRyaXguCiAgIyBXZSBjb252ZXJ0IGVhY2ggeFtpLCBqXSB0byBlXnhbaSwgal0uCiAgeHggPC0gZXhwKHgpCiAgIyBOZXh0LCB3ZSBzY2FsZSBieSByb3cgc3Vtcy4KICB4eCA8LSB4eCAvIGFwcGx5KHh4LCAxLCBzdW0pCiAgIyBGaW5hbGx5LCB3ZSBzdWJ0cmFjdCBjb2x1bW4gc3VtcyBmcm9tIHksIHRha2UgYWJzb2x1dGUgdmFsdWVzLCBhbmQgc3VtLgogICh5IC0gYXBwbHkoeHgsIDIsIHN1bSkpICU+JSBhYnMoKSAlPiUgc3VtKCkKfQpgYGAKCiMgTFAgbW9kZWwKClRoZSBwcm9wb3NlZCBtb2RlbCBpbnZvbHZlcyBhIGNoYW5nZSBvZiB2YXJpYWJsZXMgdG8gJCR6X3tpLGt9PVxmcmFje2Vee3hfe2ksa319fXtcc3VtX3tqPTF9XksgZV57eF97aSxqfX19LiQkIFRoZSAkeiQgdmFyaWFibGVzIGFyZSBub25uZWdhdGl2ZSBhbmQgbXVzdCBzYXRpc2Z5IG9uZSBjb25zdHJhaW50OiAkJFxzdW1fe2s9MX1ee0t9el97aSwga309MVxxdWFkXGZvcmFsbCBpPTEsXGRvdHMsTi4kJFRoZSBvYmplY3RpdmUgYmVjb21lcyAkJFxtaW4gXHN1bV97az0xfV5LIFxsZWZ0fHlfayAtIFxzdW1fe2k9MX1eTiB6X3tpLGt9XHJpZ2h0fC4kJCBXZSBsaW5lYXJpemUgdGhpcyBieSBhZGRpbmcgbmV3IHZhcmlhYmxlcyAkd19rXGdlIDAkLCB3aG9zZSBzdW0gd2UgbWluaW1pemUsIHRvZ2V0aGVyIHdpdGggdGhlIGNvbnN0cmFpbnRzICQkLXdfayBcbGUgeV9rIC0gXHN1bV97aT0xfV5OIHpfe2ksa30gXGxlIHdfayBccXVhZCBcZm9yYWxsIGs9MSxcZG90cyxLLiQkCgpTaW5jZSB0aGUgbWF0cml4IG9mICR6JCB2YXJpYWJsZXMgaGFzIHRvIGJlIGZsYXR0ZW5lZCBpbnRvIGEgdmVjdG9yIGZvciBPTVBSLCB3ZSBuZWVkIGluZGV4aW5nIGZ1bmN0aW9ucyB0byBjb252ZXJ0IGJldHdlZW4gb25lLSBhbmQgdHdvLWRpbWVuc2lvbmFsIHJlcHJlc2VudGF0aW9ucy4KCmBgYHtyfQppeDEgPC0gZnVuY3Rpb24oaSwgaykgewogICMgQ29udmVydCBpIGluIDEuLi5OIGFuZCBrIGluIDEuLi5LIHRvIGEgc2luZ2xlIGluZGV4IGluIDEuLi5OKksuCiAgKGkgLSAxKSAqIEsgKyBrCn0KaXgyIDwtIGZ1bmN0aW9uKHQpIHsKICAjIENvbnZlcnQgdCBpbiAxLi4uTipLIHRvIHZlY3RvciAoaSxrKSB3aXRoIGkgaW4gMS4uLk4gYW5kIGsgaW4gMS4uLksKICBjKCh0IC0gMSkgJS8lIEsgKyAxLCAodCAtIDEpICUlIEsgKyAxKQp9CmBgYAoKTm93IHdlIGJ1aWxkIHRoZSBMUCBtb2RlbC4gSXQgcmVxdWlyZXMgYSBzdHJpY3RseSBwb3NpdGl2ZSBsb3dlciBib3VuZCBvbiAkeiQsIHdoaWNoIEkgd2lsbCBhcmJpdHJhcmlseSBzZXQuCgpgYGB7cn0KemxvIDwtIDFlLTcKYGBgCgoKYGBge3J9Cm1vZGVsIDwtIAogICMgQ3JlYXRlIGFuIGVtcHR5IG1vZGVsLgogIE1JUE1vZGVsKCkgJT4lIAogICMgQWRkIHRoZSB6IGFuZCB3IHZhcmlhYmxlcy4gei52YXJbdF0gaXMgeltpeDIodCldLgogIGFkZF92YXJpYWJsZSh6LnZhcltqXSwgaiA9IDE6KE4gKiBLKSwgdHlwZSA9ICJjb250aW51b3VzIiwgbGIgPSB6bG8pICU+JSAKICBhZGRfdmFyaWFibGUody52YXJba10sIGsgPSAxOkssIHR5cGUgPSAiY29udGludW91cyIsIGxiID0gMCkgJT4lIAogICMgU2V0IHRoZSBvYmplY3RpdmUgdG8gbWluaW1pemUgdGhlIHN1bSBvZiB0aGUgdyB2YXJpYWJsZXMuCiAgc2V0X29iamVjdGl2ZShzdW1fZXhwcih3LnZhcltrXSwgayA9IDE6SyksICJtaW4iKSAlPiUgCiAgIyBDb25zdHJhaW4gZWFjaCByb3cgb2YgdGhlIHogbWF0cml4IHRvIHN1bSB0byAxLgogIGFkZF9jb25zdHJhaW50KHN1bV9leHByKHoudmFyW2pdLCBqID0gaXgxKGksIDEpOml4MShpLCBLKSkgPT0gMSwgaSA9IDE6TikgJT4lIAogICMgQ29uc3RyYWluIHdba10gdG8gYmUgYXQgbGVhc3QgdGhlIGFic29sdXRlIGVycm9yIGluIG9ic2VydmF0aW9uIGsuCiAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIoei52YXJbal0sIGogPSAxOihOICogSyksIChqIC0gMSkgJSUgSyArIDEgPT0gaykgLSB3LnZhcltrXSA8PSB5W2tdLCBrID0gMTpLKSAlPiUgCiAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIoei52YXJbal0sIGogPSAxOihOICogSyksIChqIC0gMSkgJSUgSyArIDEgPT0gaykgKyB3LnZhcltrXSA+PSB5W2tdLCBrID0gMTpLKQpgYGAKV2Ugc29sdmUgdGhlIExQIG1vZGVsIGFuZCBleHRyYWN0IHRoZSBzb2x1dGlvbi4KCmBgYHtyfQojIFNvbHZlIHRoZSBMUC4Kc29sdXRpb24gPC0gbW9kZWwgJT4lIHNvbHZlX21vZGVsKHdpdGhfUk9JKHNvbHZlciA9ICJjcGxleCIpKQojIEdldCBhbmQgcHJpbnQgdGhlIG9iamVjdGl2ZSB2YWx1ZS4Kb2JqdmFsIDwtIHNvbHV0aW9uJG9iamVjdGl2ZV92YWx1ZQpjYXQoIk9wdGltYWwgb2JqZWN0aXZlIHZhbHVlID0gIiwgb2JqdmFsLCAiXG4iKQpgYGAKCldlIGNhbiBleHRyYWN0ICR3JCBhbmQgJHokIGZyb20gdGhlIHNvbHZlciBvdXRwdXQuCgpgYGB7cn0KIyBUaGUgc29sdXRpb24gY29udGFpbnMgdGhlIHZhbHVlcyBvZiB3IGZpcnN0LCBmb2xsb3dlZCBieSB0aGUgdmFsdWVzIG9mIHouCncgPC0gc29sdXRpb24kc29sdXRpb25bZ3JlcGwoIncudmFyKiIsIG5hbWVzKHNvbHV0aW9uJHNvbHV0aW9uKSldCnogPC0gbWF0cml4KHNvbHV0aW9uJHNvbHV0aW9uW2dyZXBsKCJ6LnZhcioiLCBuYW1lcyhzb2x1dGlvbiRzb2x1dGlvbikpXSwgbnJvdyA9IE4sIGJ5cm93ID0gVCkKCmBgYAoKVGhlIHByb2Nlc3MgdG8gcmVjb3ZlciAkeCQgZnJvbSAkeiQgaXMgZXhwbGFpbmVkIGluIHRoZSBzb2x1dGlvbiBJIHBvc3RlZCB0byB0aGUgb3JpZ2luYWwgcXVlc3Rpb24uIEZvciBlYWNoICRpPTEsXGRvdHMsTiQsIHdlIGZpbmQgdGhlIGluZGV4ICRqJCBvZiB0aGUgc21hbGxlc3QgZW50cnkgJHpfe2ksa30kIGluIHRoYXQgcm93LCB0aGVuIHNldCAkeF97aSxrfSA9IFxsb2coel97aSxrfSAvIHpfe2ksan0pJCBmb3IgJGs9MSxcZG90cyxLJC4KCmBgYHtyfQojIFRoaXMgZnVuY3Rpb24gY29udmVydHMgYSByb3cgb2YgdGhlIHogbWF0cml4IHRvIGEgcm93IG9mIHRoZSB4IG1hdHJpeC4Kei50by54IDwtIGZ1bmN0aW9uKHp6KSB7CiAgIyB6eiBpcyBvbmUgcm93IG9mIHouCiAgbG9nKHp6IC8gbWluKHp6KSkKfQojIFdlIGFwcGx5IHRoZSBmdW5jdGlvbiB0byBlYWNoIHJvdyBvZiB6IHRvIGdldCB4Lgp4IDwtIHQoYXBwbHkoeiwgMSwgei50by54KSkKYGBgCgpBcyBhIHNhbml0eSBjaGVjaywgd2UgY29uZmlybSB0aGUgb2JqZWN0aXZlIHZhbHVlIG9mIHRoZSBwcm9wb3NlZCBzb2x1dGlvbi4KCmBgYHtyfQpvYmooeCkKYGBgCiMgR2VuZXRpYyBhbGdvcml0aG0KCkFzIGEgZnVydGhlciBzYW5pdHkgY2hlY2ssIHdlIHRyeSB0byBtaW5pbWl6ZSB0aGUgb3JpZ2luYWwgb2JqZWN0aXZlIGZ1bmN0aW9uIHVzaW5nIGEgZ2VuZXRpYyBhbGdvcml0aG0uIEEgR0Egc29sdXRpb24gd2l0aCBhIGxhcmdlciBvYmplY3RpdmUgdmFsdWUgdGhhbiB0aGF0IG9mIHRoZSBMUCB3aWxsIG5vdCBwcm92ZSB0aGF0IHRoZSBMUCBzb2x1dGlvbiBpcyBvcHRpbWFsLCBidXQgYSBHQSBzb2x1dGlvbiB3aXRoIGEgc21hbGxlciBvYmplY3RpdmUgdmFsdWUgdGhhbiB0aGF0IG9mIHRoZSBMUCB3aWxsIGRlZmluaXRlbHkgaW5kaWNhdGUgYSBwcm9ibGVtLgoKVGhlICJjaHJvbW9zb21lIiBpbiB0aGUgR0Egd2lsbCBqdXN0IGJlIHRoZSAkeCQgbWF0cml4IGZsYXR0ZW5lZCB0byBhIGRvdWJsZSBwcmVjaXNpb24gdmVjdG9yLCB0aGUgc2FtZSB3YXkgdGhhdCB3ZSBmbGF0dGVuZWQgJHokIGluIHRoZSBMUCBtb2RlbC4gVGhlIG9ubHkgY29uc3RyYWludCBpbiB0aGUgb3JpZ2luYWwgZm9ybXVsYXRpb24gd2FzIHRoZSByZXF1aXJlbWVudCB0aGF0ICR4XGdlIDAkLCB3aGljaCB3ZSBlbmZvcmNlIGFzIGEgbG93ZXIgYm91bmQuIFRvIGNvbXB1dGUgdGhlIGZpdG5lc3Mgb2YgYSBjaHJvbW9zb21lLCB3ZSBjb252ZXJ0IHRoZSB2ZWN0b3Igb2YgdmFsdWVzIHRvIGEgbWF0cml4IGFuZCBhcHBseSB0aGUgb2JqKCkgZnVuY3Rpb24gdG8gaXQuIEJlY2F1c2UgdGhlIEdBIHBhY2thZ2UgbWF4aW1pemVzIGZpdG5lc3MgYW5kIHdlIGFyZSBtaW5pbWl6aW5nLCB3ZSBuZWdhdGUgdGhlIG9yaWdpbmFsIG9iamVjdGl2ZSBmdW5jdGlvbi4KCmBgYHtyfQpmaXRuZXNzLmZ1biA8LSBmdW5jdGlvbihjaHJvbW9zb21lKSB7CiAgdGVtcCA8LSAKICAgIGNocm9tb3NvbWUgJT4lIAogICAgICAjIFJlc2hhcGUgaXQgdG8gYW4gTiB4IEsgbWF0cml4LgogICAgICBtYXRyaXgobnJvdyA9IE4sIGJ5cm93ID0gVCkgJT4lIAogICAgICAjIEFwcGx5IHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gdG8gdGhlIG1hdHJpeC4KICAgICAgb2JqKCkKICAjIFJldHVybiB0aGUgbmVnYXRlZCB2YWx1ZS4KICAtdGVtcAp9CmBgYAoKV2UgYnVpbGQgYW5kIHJ1biB0aGUgZ2VuZXRpYyBhbGdvcml0aG0gKHVzaW5nIG1vc3RseSBkZWZhdWx0IHNldHRpbmdzKS4gVGhlIGdhKCkgZnVuY3Rpb24gcmVxdWlyZXMgdXBwZXIgYm91bmRzIGZvciB0aGUgJHgkIHZhcmlhYmxlcywgd2hpY2ggd2Ugc2V0IHJhdGhlciBhcmJpdHJhcmlseS4KCmBgYHtyfQpnZW5hbGcgPC0gZ2EoCiAgdHlwZSA9ICJyZWFsLXZhbHVlZCIsCiAgZml0bmVzcyA9IGZpdG5lc3MuZnVuLAogIGxvd2VyID0gcmVwLmludCgwLCBOICogSyksCiAgdXBwZXIgPSByZXAuaW50KDEwMCwgTiAqIEspLAogIG1vbml0b3IgPSBGQUxTRSwKICBtYXhpdGVyID0gMTAwMCwKICBydW4gPSAxMDAsCiAgc2VlZCA9IDEyMwopCmNhdCgiVGhlIGJlc3QgZml0IGZyb20gdGhlIEdBIGhhcyBlcnJvciAiLCAtZ2VuYWxnQGZpdG5lc3NWYWx1ZSwgIiBjb21wYXJlZCB0byB0aGUgTFAgcmVzdWx0ICIsIG9iaih4KSwgIi5cbiIpCmBgYApJdCBhcHBlYXJzIHRoYXQgdGhlIEdBIHdhcyBhYmxlIHRvIG1hdGNoIHRoZSBMUCByZXN1bHQgYnV0IG5vdCBpbXByb3ZlIG9uIGl0LgoKIyBEZW5zaXR5CgpMZXQncyBkZWZpbmUgYSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgdGhlIGRlbnNpdHkgKGZyYWN0aW9uIG9mIGVsZW1lbnRzIHRoYXQgYXJlIG5vbnplcm8pIGluIGEgbWF0cml4IG9yIHZlY3Rvci4KCmBgYHtyfQpmcmFjdGlvbkRlbnNlIDwtIGZ1bmN0aW9uKHgpIHsKICB0ZW1wIDwtIAogICAgeCAlPiUKICAgICMgRmxhdHRlbiB0aGUgYXJndW1lbnQgdG8gYSB2ZWN0b3IuCiAgICBhcy52ZWN0b3IoKSAlPiUgCiAgICAjIFRydW5jYXRlIHNtYWxsIGVudHJpZXMgdG8gemVyby4KICAgIHphcHNtYWxsKCkKICBzdW0odGVtcCAhPSAwKSAvIGxlbmd0aCh0ZW1wKQp9CmBgYAoKV2UgY2FuIGNvbXBhcmUgdGhlIGRlbnNpdHkgb2YgdGhlIExQIGFuZCBHQSBzb2x1dGlvbnMuCgpgYGB7cn0KY2F0KCJUaGUgTFAgc29sdXRpb24gaGFzIGRlbnNpdHkgIiwgZnJhY3Rpb25EZW5zZSh4KSwgIi5cbiIpCmNhdCgiVGhlIEdBIHNvbHV0aW9uIGhhcyBkZW5zaXR5ICIsIGZyYWN0aW9uRGVuc2UoZ2VuYWxnQHNvbHV0aW9uWzEsXSksICIuXG4iKQpgYGAKSWYgd2Ugd2FudCB0byB1c2UgdGhlIExQIG1vZGVsIGJ1dCBkZXNpcmUgYSBkZW5zZXIgc29sdXRpb24sIHRoZW4gKHVzaW5nIHRoZSBpbnZhcmlhbmNlIHByb3BlcnR5IG1lbnRpb25lZCBpbiBteSBzb2x1dGlvbiBvbmxpbmUpIHdlIGNhbiBhZGQgYW4gYXJiaXRyYXJ5IHBvc2l0aXZlIGFtb3VudCAkXGxhbWJkYV9pJCB0byBlYWNoICR4X3tpLGt9JC4gV2UgZG8gdGhhdCBuZXh0LCBjb25maXJtIHRoZSBkZW5zaXR5LCBhbmQgY2hlY2sgdGhlIG9iamVjdGl2ZSB2YWx1ZSB0byBjb25maXJtIHRoYXQgaXQgaXMgdW5jaGFuZ2VkLgoKYGBge3J9CmxhbWJkYSA8LSBydW5pZihOKQp4MiA8LSBhcHBseSh4LCAyLCBmdW5jdGlvbih0KSB0ICsgbGFtYmRhKQpjYXQoIlRoZSBhZGp1c3RlZCBzb2x1dGlvbiB4IGhhcyBkZW5zaXR5ICIsIGZyYWN0aW9uRGVuc2UoeDIpLCAiIGFuZCBvYmplY3RpdmUgdmFsdWUgIiwgb2JqKHgyKSwgIi5cbiIpCmBgYApPdXQgb2YgY3VyaW9zaXR5LCBsZXQncyBzZWUgd2hldGhlciBhIHNpbWlsYXIgdHJhbnNmb3JtYXRpb24gY2FuIG1ha2UgdGhlIEdBIHNvbHV0aW9uIHZlcnkgc3BhcnNlLgoKYGBge3J9CiMgR2V0IHRoZSBHQSBzb2x1dGlvbiBhbmQgY29udmVydCBpdCB0byBhIG1hdHJpeC4KeDMgPC0gZ2VuYWxnQHNvbHV0aW9uWzEsXSAlPiUgbWF0cml4KG5yb3cgPSBOLCBuY29sID0gSywgYnlyb3cgPSBUKQojIEZpbmQgdGhlIHNtYWxsZXN0ICR4JCB2YWx1ZSBmb3IgZWFjaCAkaSQgKHdoaWNoIHdlIGNhbiBzdWJ0cmFjdCBhbmQgc3RpbGwgcHJlc2VydmUgbm9ubmVnYXRpdml0eSkuCng0IDwtIGFwcGx5KHgzLCAxLCBtaW4pCiMgU3VidHJhY3QgZWFjaCByb3cgbWluIGZyb20gdGhhdCByb3cuCngzIDwtIGFwcGx5KHgzLCAyLCBmdW5jdGlvbih0KSB0IC0geDQpCiMgQ29uZmlybSB0aGF0IHRoZSByZXZpc2VkIHNvbHV0aW9uIGlzIG5vbm5lZ2F0aXZlLgpjYXQoIlRoZSBtaW5pbXVtIG9mIHRoZSBtb2RpZmllZCBzb2x1dGlvbiBpcyAiLCBtaW4oeDMpLCAiLlxuIikKIyBDb25maXJtIHRoYXQgdGhlIG9iamVjdGl2ZSB2YWx1ZSBpcyB1bmNoYW5nZWQuCmNhdCgiVGhlIG1vZGlmaWVkIHNvbHV0aW9uIGhhcyBvYmplY3RpdmUgdmFsdWUgIiwgb2JqKHgzKSwgIi5cbiIpCiMgQ2hlY2sgaXRzIGRlbnNpdHkuCmNhdCgiVGhlIGRlbnNpdHkgb2YgdGhlIG1vZGlmaWVkIHNvbHV0aW9uIGlzICIsIGZyYWN0aW9uRGVuc2UoeDMpLCAiLlxuIikKYGBgClNvIHdlIHJlZHVjZWQgdGhlIGRlbnNpdHksIGJ1dCBvbmx5IHNsaWdodGx5Lgo=