Abstract
This is a demonstration of Dirk Schumacher’s ompr package (https://dirkschumacher.github.io/ompr/). We will exercise it on the MIP discriminant model from an old paper of mine [1], applied to the Anderson’s iris data. Specifically, we’ll try to discriminate between each individual species (setosa, versicolor or virginica) and the other two, using a linear discriminant function that minimizes the number of misclassifications on the overall sample.
For general amusement, we’ll also look at how a support vector machine does on the same three classification problems. This is not intended to be a test of MIP classification v. other methods, though, and we won’t get into separate training and testing sample or k-fold cross-validation. The focus of this notebook is to exercise the ompr package (using CPLEX 12.6.3 as the solver) on a (somewhat) real-world statistical optimization problem.
Before starting, I’ll just note one minor glitch. As of this writing, you cannot name a variable “c” in your optimization model. Dirk has been very responsive in checking into issues with ompr
, but this one issue traces back to a library used by ompr
, so while Dirk has reported it, he cannot fix it himself. So far, “c” seems to be the only variable name that causes any trouble.
Preparation
We will use the 1-norm of the observations to set some model parameters, so we start by defining that.
norm.1 <- function(x) sum(abs(x))
We will arbitrarily set a minimum absolute discriminant value (delta) of 0.01 for a correct classification (so that discriminant values between -0.01 and +0.01 will be considered inconclusive).
delta <- 0.01
Before creating any models, we need to load the required libraries.
library(ROI) # general solver interface
ROI.plugin.cplex: 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
library(ompr.roi) # connects OMPR to ROI
library(magrittr) # for the pipe operator
library(kernlab) # for SVMs
Function definitions
Partitioning the data into positive/negative samples
We will be partitioning the original data set into positive and negative samples more than once, so we create a function to do that. The function takes as its sole argument the name (“pos”) of the species in the positive sample and returns a named list of samples. The (now redundant) “Species” column is removed from each subsample.
partition <- function(pos) {
list(positive = subset(iris, subset = (Species == pos), select = -Species),
negative = subset(iris, subset = (Species != pos), select = -Species)
)
}
Building and running MIP discriminant models
Since we want to generate several different discriminant functions, we will create a function that takes our list of subsamples, uses it to generate a MIP model, solves the model and returns the result in a list.
Variable names generally conform to the 1990 paper. The variables in the model have “.var” appended to distinguish them from the R variables containing their values (e.g., w.var
is the discriminant coefficient vector in the model, while w
is the R variable receiving the values of w.var
in the solution).
mipDiscriminant <- function(data) {
positive <- data$positive
negative <- data$negative
#
# We need the maximum 1-norm of any observation in each sample.
#
Delta1 <- max(apply(negative, 1, norm.1))
Delta2 <- max(apply(positive, 1, norm.1))
Deltabar <- (Delta1 + Delta2) / 2
#
# Those values are used to compute a "big M" coefficient for each sample.
#
M1 <- 2 * Delta1 + Deltabar # big M for negative observations
M2 <- 2 * Delta2 + Deltabar # big M for positive observations
#
# We also need a coefficient for a term in the objective function
# that rewards greater separation between the two groups.
#
epsilon <- 1 / (2 * Deltabar)
#
# Next, we compute the dimensions of the data.
#
npos <- nrow(positive) # size of positive sample
nneg <- nrow(negative) # size of negative sample
nvar <- ncol(positive) # number of features
#
# Now we can build the model.
#
model <-
MIPModel() %>%
add_variable(y.var[i], i = 1:nneg, type = "binary") %>% # indicators for negative misclassifications
add_variable(z.var[i], i = 1:npos, type = "binary") %>% # indicators for positive misclassifications
add_variable(w.var[i], i = 1:nvar, type = "continuous", lb = -1, ub = 1) %>% # coefficients of discriminant function
add_variable(c.var, type = "continuous") %>% # constant term of discriminant function (unbounded)
add_variable(d.var, type = "continuous", lb = delta) %>% # minimum absolute score of any successfully classified observation (bounded away from zero)
set_objective(sum_expr(y.var[i], i = 1:nneg) + sum_expr(z.var[i], i = 1:npos) - epsilon * d.var, sense = "min") %>% # minimize number of misclassifications with credit for higher spacing from the classification boundary
add_constraint(sum_expr(negative[i, j] * w.var[j], j = 1:nvar) + c.var + d.var - M1 * y.var[i] <= 0, i = 1:nneg) %>% # determine negative misclassifications
add_constraint(sum_expr(positive[i, j] * w.var[j], j = 1:nvar) + c.var - d.var + M2 * z.var[i] >= 0, i = 1:npos) # determine positive misclassifications
#
# Finally, we solve the model and return the result.
#
modelObject <<- as_ROI_model(model)
model %>% solve_model(with_ROI(solver = "cplex"))
}
Creating discriminant functions
We will also want a function that takes the solution to our MIP model and turns it into a linear discriminant function, to facilitate testing the function.
extractFunction <- function(mipResult) {
a <- (mipResult %>% get_solution(w.var[j]))[, "value"]
a0 <- mipResult %>% get_solution(c.var)
f <- function(x) as.numeric(a %*% as.numeric(x) + a0)
f
}
Testing the MIP discriminant model
Setosa v. Versicolor/Virginica
Let’s start by trying to discriminate between setosa (“positive”) and versicolor/virginica (“negative”). The sink()
function is used to avoid cluttering the page with a rather lengthy progress report from the solver.
samples <- partition("setosa")
sink("/dev/null")
result <- mipDiscriminant(samples)
CPLEX environment opened
Closed CPLEX environment
sink()
The first thing to do is check whether the model solved successfully.
cat("Status = ", result$status)
Status = optimal
Next, we can take a look at the objective value.
cat("Objective value = ", result$objective_value)
Objective value = -0.04166667
Values for scalar model variables are returned as numeric vectors of length 1.
c <- result %>% get_solution(c.var)
c
c.var
3.65
d <- result %>% get_solution(d.var)
d
d.var
1.35
Values for indexed model variables are returned in data frames.
w <- result %>% get_solution(w.var[j])
y <- result %>% get_solution(y.var[i])
z <- result %>% get_solution(z.var[i])
w
Let’s see, based on the y and z indicators, how many observations the model thinks it misclassified.
cat(sprintf("Negative misclassifications: %d\n", nrow(y[y$value >= 0.1, ])))
Negative misclassifications: 0
cat(sprintf("Positive misclassifications: %d\n", nrow(z[z$value >= 0.1, ])))
Positive misclassifications: 0
We allegedly have no errors (100% correct classification for this split). Just to be sure, let’s extract the discriminant function and apply it directly to the data, finding its smallest score on the positive sample (hopefully > 0) and its largest score on the negative sample (hopefully < 0).
dFunc <- extractFunction(result)
cat("Mininum clearance for positive sample: ", min(apply(samples$positive, 1, dFunc)))
Mininum clearance for positive sample: 1.35
cat("\nMinimum clearance for negative sample: ", max(apply(samples$negative, 1, dFunc)))
Minimum clearance for negative sample: -1.35
So all scores are correct (as expected), and the closest any observation comes to the “boundary” score of 0 is 1.35 (which is the value of the d variable in the model).
For completeness, since we will be doing it below, we create a “confusion matrix” comparing predictions to actual values.
predicted <- as.factor(ifelse(apply(iris[, 1:4], 1, dFunc) > 0, "setosa", "other")) # predicted class
table(predicted, iris$Species)
predicted setosa versicolor virginica
other 0 50 50
setosa 50 0 0
Vericolor v. Setosa/Virginica
Next, we repeat the experiment, trying to distinguish versicolor from setosa and virginica.
samples <- partition("versicolor")
sink("/dev/null")
result2 <- mipDiscriminant(samples)
CPLEX environment opened
Closed CPLEX environment
sink()
cat("Status = ", result2$status)
Status = optimal
cat("\nObjective value = ", result2$objective_value)
Objective value = 25.99967
This takes considerably longer to solve, because 100% accuracy is not attainable (so the MIP solver has to do a fair bit of branching). The good news is that we have an optimal solution; the bad news is that we apparently misclassified 26 observations. We can break that down by group.
dFunc <- extractFunction(result2)
predicted <- as.factor(ifelse(apply(iris[, 1:4], 1, dFunc) > 0, "versicolor", "other")) # predicted class
table(predicted, iris$Species)
predicted setosa versicolor virginica
other 50 8 32
versicolor 0 42 18
The best solution misclassifies 8 versicolor (positive) specimens and 18 specimens from the other two species (negative). We can easily check which they are.
y <- result2 %>% get_solution(y.var[i])
z <- result2 %>% get_solution(z.var[i])
e1 <- which(y$value > 0.5)
e2 <- which(z$value > 0.5)
cat("Negative:\n")
Negative:
e1
[1] 53 54 56 58 59 62 67 68 69 70 73 76 80 81 82 84 85
[18] 88
cat("Positive:\n")
Positive:
e2
[1] 2 7 10 12 15 21 36 49
We can take a look at the discriminant values for those observations, just to confirm what we are seeing.
cat("Negative:\n")
Negative:
apply(samples$negative[e1, ], 1, dFunc)
103 104 106 108 109
0.03900046 0.24529895 0.35739388 0.55365130 0.45506618
112 117 118 119 120
0.06451392 0.16337289 0.05438156 0.42010497 0.48674121
123 126 130 131 132
0.56428571 0.32193063 0.49007303 0.38879507 0.10960749
134 135 138
0.34169329 0.74228663 0.13439069
cat("\nPositive:\n")
Positive:
apply(samples$positive[e2, ], 1, dFunc)
52 57 60 62
-0.06579188 -0.10093565 -0.06825650 -0.13452761
65 71 86 99
-0.17902784 -0.21499315 -0.21973984 -0.12654039
Virginica v. Setosa/Versicolor
Finally, let’s see if we can discriminate between virginica and setosa/versicolor.
samples <- partition("virginica")
sink("/dev/null")
result3 <- mipDiscriminant(samples)
CPLEX environment opened
Closed CPLEX environment
sink()
cat("Status = ", result3$status)
Status = optimal
cat("\nObjective value = ", result3$objective_value)
Objective value = 0.9991571
It looks as though we got away with just one misclassification.
dFunc <- extractFunction(result3)
predicted <- as.factor(ifelse(apply(iris[, 1:4], 1, dFunc) > 0, "virginica", "other")) # predicted class
table(predicted, iris$Species)
predicted setosa versicolor virginica
other 50 49 0
virginica 0 1 50
# one of the negative samples was classified positive - which?
y <- result3 %>% get_solution(y.var[i])
e1 <- which(y$value > 0.5)
cat("Error in observation ", e1)
Error in observation 84
So observation 84 from the negative sample, which happens to be a versicolor specimen, seems to be the only one misclassified. Let’s check its discriminant score.
dFunc <- extractFunction(result3)
apply(samples$negative[e1, ], 1, dFunc)
84
0.2365741
It is indeed a positive (incorrect) score.
Classification using an SVM
Just for fun, let’s try using a classification support vector machine (SVMs), via the kernlab package. To keep things simple, we will leave all options at default settings.
Setosa v. Versicolor/Virginica
First, let’s try to discriminate setosa from the others.
class <- factor(iris$Species == "setosa", labels = c("negative", "positive"))
svm <- ksvm(class ~ . - Species, data = iris)
table(predict(svm, iris), iris$Species)
setosa versicolor virginica
negative 0 50 50
positive 50 0 0
The SVM matches the 100% accuracy of the MIP model on setosa.
Versicolor v. Setosa/Virginica
class <- factor(iris$Species == "versicolor", labels = c("negative", "positive"))
svm <- ksvm(class ~ . - Species, data = iris)
table(predict(svm, iris), iris$Species)
setosa versicolor virginica
negative 50 1 48
positive 0 49 2
Attempting to distinguish versicolor from the other two species, the SVM model misclassifies one or two veriscolor instances and one or two virginica instances, much better than the MIP model did. (This is due to the SVM not being restricted to a linear discriminant function.) The reason I say “one or two” and not specifically what you see in the confusion matrix is that the SVM fitting algorithm appears to use a somewhat randomized algorithm. If you keep refitting the model, the results periodically change. You can stabilize the results by setting a random seed before calling ksvm()
, but which results you get will depend on how lucky you are picking the seed.
Virginica v. Setosa/Versicolor
class <- factor(iris$Species == "virginica", labels = c("negative", "positive"))
svm <- ksvm(class ~ . - Species, data = iris)
table(predict(svm, iris), iris$Species)
setosa versicolor virginica
negative 50 48 2
positive 0 2 48
The SVM has a total of three or four errors when trying to discriminate between virginica and the other two species, worse than the single error the MIP model achieved. Again, the total error count varies depending on the random seed used.
LS0tCnRpdGxlOiAiT01QUiBEZW1vIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBQYXVsIEEuIFJ1YmluCmRhdGU6IE5vdmVtYmVyIDksIDIwMTYKLS0tCgojIEFic3RyYWN0CgpUaGlzIGlzIGEgZGVtb25zdHJhdGlvbiBvZiBEaXJrIFNjaHVtYWNoZXIncyAqKm9tcHIqKiBwYWNrYWdlIChodHRwczovL2RpcmtzY2h1bWFjaGVyLmdpdGh1Yi5pby9vbXByLykuIFdlIHdpbGwgZXhlcmNpc2UgaXQgb24gdGhlIE1JUCBkaXNjcmltaW5hbnQgbW9kZWwgZnJvbSBhbiBvbGQgcGFwZXIgb2YgbWluZSBbMV0sIGFwcGxpZWQgdG8gdGhlIEFuZGVyc29uJ3MgaXJpcyBkYXRhLiBTcGVjaWZpY2FsbHksIHdlJ2xsIHRyeSB0byBkaXNjcmltaW5hdGUgYmV0d2VlbiBlYWNoIGluZGl2aWR1YWwgc3BlY2llcyAoc2V0b3NhLCB2ZXJzaWNvbG9yIG9yIHZpcmdpbmljYSkgYW5kIHRoZSBvdGhlciB0d28sIHVzaW5nIGEgbGluZWFyIGRpc2NyaW1pbmFudCBmdW5jdGlvbiB0aGF0IG1pbmltaXplcyB0aGUgbnVtYmVyIG9mIG1pc2NsYXNzaWZpY2F0aW9ucyBvbiB0aGUgb3ZlcmFsbCBzYW1wbGUuCgpGb3IgZ2VuZXJhbCBhbXVzZW1lbnQsIHdlJ2xsIGFsc28gbG9vayBhdCBob3cgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIGRvZXMgb24gdGhlIHNhbWUgdGhyZWUgY2xhc3NpZmljYXRpb24gcHJvYmxlbXMuIFRoaXMgaXMgbm90IGludGVuZGVkIHRvIGJlIGEgdGVzdCBvZiBNSVAgY2xhc3NpZmljYXRpb24gdi4gb3RoZXIgbWV0aG9kcywgdGhvdWdoLCBhbmQgd2Ugd29uJ3QgZ2V0IGludG8gc2VwYXJhdGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2FtcGxlIG9yIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uLiBUaGUgZm9jdXMgb2YgdGhpcyBub3RlYm9vayBpcyB0byBleGVyY2lzZSB0aGUgb21wciBwYWNrYWdlICh1c2luZyBDUExFWCAxMi42LjMgYXMgdGhlIHNvbHZlcikgb24gYSAoc29tZXdoYXQpIHJlYWwtd29ybGQgc3RhdGlzdGljYWwgb3B0aW1pemF0aW9uIHByb2JsZW0uCgpCZWZvcmUgc3RhcnRpbmcsIEknbGwganVzdCBub3RlIG9uZSBtaW5vciBnbGl0Y2guIEFzIG9mIHRoaXMgd3JpdGluZywgeW91IGNhbm5vdCBuYW1lIGEgdmFyaWFibGUgImMiIGluIHlvdXIgb3B0aW1pemF0aW9uIG1vZGVsLiBEaXJrIGhhcyBiZWVuIHZlcnkgcmVzcG9uc2l2ZSBpbiBjaGVja2luZyBpbnRvIGlzc3VlcyB3aXRoIGBvbXByYCwgYnV0IHRoaXMgb25lIGlzc3VlIHRyYWNlcyBiYWNrIHRvIGEgbGlicmFyeSB1c2VkIGJ5IGBvbXByYCwgc28gd2hpbGUgRGlyayBoYXMgcmVwb3J0ZWQgaXQsIGhlIGNhbm5vdCBmaXggaXQgaGltc2VsZi4gU28gZmFyLCAiYyIgc2VlbXMgdG8gYmUgdGhlIG9ubHkgdmFyaWFibGUgbmFtZSB0aGF0IGNhdXNlcyBhbnkgdHJvdWJsZS4KCiMgQWJvdXQgdGhpcyBkb2N1bWVudAoKVGhpcyBkb2N1bWVudCBpcyBhbiB0aGUgSFRNTCBvdXRwdXQgZnJvbSBhbiBbUiBub3RlYm9va10oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS9yX25vdGVib29rcy5odG1sKS4gVGhlIGNvbnRyb2wgaW4gdGhlIHVwcGVyIHJpZ2h0IHdpbGwgbGV0IHlvdSBzaG93IG9yIGhpZGUgY29kZSAoYWx0aG91Z2gsIGluIHRoaXMgY2FzZSwgdGhlIGNvZGUgaXMgcHJldHR5IG11Y2ggdGhlIHBvaW50IG9mIHRoZSBub3RlYm9vaykuIEl0IHdpbGwgYWxzbyBhbGxvdyB5b3UgdG8gZXh0cmFjdCBhbmQgc2F2ZSB0aGUgbm90ZWJvb2sgKFIgc291cmNlIGZpbGUpIGl0c2VsZi4KCiMgU29sdmVyCgpBcyBtZW50aW9uZWQsIHRoZSBzb2x2ZXIgdXNlZCBoZXJlIGlzIENQTEVYIDEyLjYuMy4gT25lIG9mIHRoZSB2aXJ0dWVzIG9mIG9tcHIgKGFuZCB0aGUgYFJPSWAgcGFja2FnZSBpdCBkZXBlbmRzIG9uKSBpcyB0aGF0LCBhcm1lZCB3aXRoIGFwcHJvcHJpYXRlIHBsdWctaW5zLCB5b3UgY2FuIHN3aXRjaCB0byBhbHRlcm5hdGl2ZSBzb2x2ZXJzLiBUaGUgY29ubmVjdGlvbiB0byBDUExFWCBpcyBoYW5kbGVkIGJ5IHRoZSBgUk9JLnBsdWdpbi5jcGxleGAgcGFja2FnZSwgbG9hZGVkIGJlbG93LiBXaGF0IGlzIG5vdCBpbW1lZGlhdGVseSB2aXNpYmxlIGlzIHRoYXQsIGluIGFkZGl0aW9uIHRvIENQTEVYIGl0c2VsZiwgdGhpcyBwYWNrYWdlIGFsc28gZGVwZW5kcyBvbiB0aGUgYFJjcGxleGAgbGlicmFyeS4KCiMgUHJlcGFyYXRpb24KCldlIHdpbGwgdXNlIHRoZSAxLW5vcm0gb2YgdGhlIG9ic2VydmF0aW9ucyB0byBzZXQgc29tZSBtb2RlbCBwYXJhbWV0ZXJzLCBzbyB3ZSBzdGFydCBieSBkZWZpbmluZyB0aGF0LgoKYGBge3J9Cm5vcm0uMSA8LSBmdW5jdGlvbih4KSBzdW0oYWJzKHgpKQpgYGAKCldlIHdpbGwgYXJiaXRyYXJpbHkgc2V0IGEgbWluaW11bSBhYnNvbHV0ZSBkaXNjcmltaW5hbnQgdmFsdWUgKGRlbHRhKSBvZiAwLjAxIGZvciBhIGNvcnJlY3QgY2xhc3NpZmljYXRpb24gKHNvIHRoYXQgZGlzY3JpbWluYW50IHZhbHVlcyBiZXR3ZWVuIC0wLjAxIGFuZCArMC4wMSB3aWxsIGJlIGNvbnNpZGVyZWQgaW5jb25jbHVzaXZlKS4KCmBgYHtyfQpkZWx0YSA8LSAwLjAxCmBgYAoKQmVmb3JlIGNyZWF0aW5nIGFueSBtb2RlbHMsIHdlIG5lZWQgdG8gbG9hZCB0aGUgcmVxdWlyZWQgbGlicmFyaWVzLgoKYGBge3J9CmxpYnJhcnkoUk9JKSAgICAgICAgICAgICAgICMgZ2VuZXJhbCBzb2x2ZXIgaW50ZXJmYWNlCmxpYnJhcnkoUk9JLnBsdWdpbi5jcGxleCkgICMgY29ubmVjdHMgdGhlIFJPSSBwYWNrYWdlIHRvIENQTEVYCmxpYnJhcnkob21wcikgICAgICAgICAgICAgICMgTUlQIG1vZGVsaW5nIHBhY2thZ2UKbGlicmFyeShvbXByLnJvaSkgICAgICAgICAgIyBjb25uZWN0cyBPTVBSIHRvIFJPSQpsaWJyYXJ5KG1hZ3JpdHRyKSAgICAgICAgICAjIGZvciB0aGUgcGlwZSBvcGVyYXRvcgpsaWJyYXJ5KGtlcm5sYWIpICAgICAgICAgICAjIGZvciBTVk1zCmBgYAoKIyBGdW5jdGlvbiBkZWZpbml0aW9ucwoKIyMjIFBhcnRpdGlvbmluZyB0aGUgZGF0YSBpbnRvIHBvc2l0aXZlL25lZ2F0aXZlIHNhbXBsZXMKCldlIHdpbGwgYmUgcGFydGl0aW9uaW5nIHRoZSBvcmlnaW5hbCBkYXRhIHNldCBpbnRvIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzYW1wbGVzIG1vcmUgdGhhbiBvbmNlLCBzbyB3ZSBjcmVhdGUgYSBmdW5jdGlvbiB0byBkbyB0aGF0LiBUaGUgZnVuY3Rpb24gdGFrZXMgYXMgaXRzIHNvbGUgYXJndW1lbnQgdGhlIG5hbWUgKCJwb3MiKSBvZiB0aGUgc3BlY2llcyBpbiB0aGUgcG9zaXRpdmUgc2FtcGxlIGFuZCByZXR1cm5zIGEgbmFtZWQgbGlzdCBvZiBzYW1wbGVzLiBUaGUgKG5vdyByZWR1bmRhbnQpICJTcGVjaWVzIiBjb2x1bW4gaXMgcmVtb3ZlZCBmcm9tIGVhY2ggc3Vic2FtcGxlLgoKYGBge3J9CnBhcnRpdGlvbiA8LSBmdW5jdGlvbihwb3MpIHsKICBsaXN0KHBvc2l0aXZlID0gc3Vic2V0KGlyaXMsIHN1YnNldCA9IChTcGVjaWVzID09IHBvcyksIHNlbGVjdCA9IC1TcGVjaWVzKSwKICAgICAgIG5lZ2F0aXZlID0gc3Vic2V0KGlyaXMsIHN1YnNldCA9IChTcGVjaWVzICE9IHBvcyksIHNlbGVjdCA9IC1TcGVjaWVzKQogICkKfQpgYGAKCiMjIyBCdWlsZGluZyBhbmQgcnVubmluZyBNSVAgZGlzY3JpbWluYW50IG1vZGVscwoKU2luY2Ugd2Ugd2FudCB0byBnZW5lcmF0ZSBzZXZlcmFsIGRpZmZlcmVudCBkaXNjcmltaW5hbnQgZnVuY3Rpb25zLCB3ZSB3aWxsIGNyZWF0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgb3VyIGxpc3Qgb2Ygc3Vic2FtcGxlcywgdXNlcyBpdCB0byBnZW5lcmF0ZSBhIE1JUCBtb2RlbCwgc29sdmVzIHRoZSBtb2RlbCBhbmQgcmV0dXJucyB0aGUgcmVzdWx0IGluIGEgbGlzdC4KClZhcmlhYmxlIG5hbWVzIGdlbmVyYWxseSBjb25mb3JtIHRvIHRoZSAxOTkwIHBhcGVyLiBUaGUgdmFyaWFibGVzIGluIHRoZSBtb2RlbCBoYXZlICIudmFyIiBhcHBlbmRlZCB0byBkaXN0aW5ndWlzaCB0aGVtIGZyb20gdGhlIFIgdmFyaWFibGVzIGNvbnRhaW5pbmcgdGhlaXIgdmFsdWVzIChlLmcuLCBgdy52YXJgIGlzIHRoZSBkaXNjcmltaW5hbnQgY29lZmZpY2llbnQgdmVjdG9yIGluIHRoZSBtb2RlbCwgd2hpbGUgYHdgIGlzIHRoZSBSIHZhcmlhYmxlIHJlY2VpdmluZyB0aGUgdmFsdWVzIG9mIGB3LnZhcmAgaW4gdGhlIHNvbHV0aW9uKS4KCmBgYHtyfQptaXBEaXNjcmltaW5hbnQgPC0gZnVuY3Rpb24oZGF0YSkgewogIHBvc2l0aXZlIDwtIGRhdGEkcG9zaXRpdmUKICBuZWdhdGl2ZSA8LSBkYXRhJG5lZ2F0aXZlCiAgIwogICMgV2UgbmVlZCB0aGUgbWF4aW11bSAxLW5vcm0gb2YgYW55IG9ic2VydmF0aW9uIGluIGVhY2ggc2FtcGxlLgogICMKICBEZWx0YTEgPC0gbWF4KGFwcGx5KG5lZ2F0aXZlLCAxLCBub3JtLjEpKQogIERlbHRhMiA8LSBtYXgoYXBwbHkocG9zaXRpdmUsIDEsIG5vcm0uMSkpCiAgRGVsdGFiYXIgPC0gKERlbHRhMSArIERlbHRhMikgLyAyCiAgIwogICMgVGhvc2UgdmFsdWVzIGFyZSB1c2VkIHRvIGNvbXB1dGUgYSAiYmlnIE0iIGNvZWZmaWNpZW50IGZvciBlYWNoIHNhbXBsZS4KICAjCiAgTTEgPC0gMiAqIERlbHRhMSArIERlbHRhYmFyICAjIGJpZyBNIGZvciBuZWdhdGl2ZSBvYnNlcnZhdGlvbnMKICBNMiA8LSAyICogRGVsdGEyICsgRGVsdGFiYXIgICMgYmlnIE0gZm9yIHBvc2l0aXZlIG9ic2VydmF0aW9ucwogICMKICAjIFdlIGFsc28gbmVlZCBhIGNvZWZmaWNpZW50IGZvciBhIHRlcm0gaW4gdGhlIG9iamVjdGl2ZSBmdW5jdGlvbgogICMgdGhhdCByZXdhcmRzIGdyZWF0ZXIgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSB0d28gZ3JvdXBzLgogICMKICBlcHNpbG9uIDwtIDEgLyAoMiAqIERlbHRhYmFyKQogICMKICAjIE5leHQsIHdlIGNvbXB1dGUgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIGRhdGEuCiAgIwogIG5wb3MgPC0gbnJvdyhwb3NpdGl2ZSkgICMgc2l6ZSBvZiBwb3NpdGl2ZSBzYW1wbGUKICBubmVnIDwtIG5yb3cobmVnYXRpdmUpICAjIHNpemUgb2YgbmVnYXRpdmUgc2FtcGxlCiAgbnZhciA8LSBuY29sKHBvc2l0aXZlKSAgIyBudW1iZXIgb2YgZmVhdHVyZXMKICAjCiAgIyBOb3cgd2UgY2FuIGJ1aWxkIHRoZSBtb2RlbC4KICAjCiAgbW9kZWwgPC0KICAgIE1JUE1vZGVsKCkgJT4lIAogICAgYWRkX3ZhcmlhYmxlKHkudmFyW2ldLCBpID0gMTpubmVnLCB0eXBlID0gImJpbmFyeSIpICU+JSAgICMgaW5kaWNhdG9ycyBmb3IgbmVnYXRpdmUgbWlzY2xhc3NpZmljYXRpb25zCiAgICBhZGRfdmFyaWFibGUoei52YXJbaV0sIGkgPSAxOm5wb3MsIHR5cGUgPSAiYmluYXJ5IikgJT4lICAgIyBpbmRpY2F0b3JzIGZvciBwb3NpdGl2ZSBtaXNjbGFzc2lmaWNhdGlvbnMKICAgIGFkZF92YXJpYWJsZSh3LnZhcltpXSwgaSA9IDE6bnZhciwgdHlwZSA9ICJjb250aW51b3VzIiwgbGIgPSAtMSwgdWIgPSAxKSAlPiUgICMgY29lZmZpY2llbnRzIG9mIGRpc2NyaW1pbmFudCBmdW5jdGlvbgogICAgYWRkX3ZhcmlhYmxlKGMudmFyLCB0eXBlID0gImNvbnRpbnVvdXMiKSAlPiUgICAjIGNvbnN0YW50IHRlcm0gb2YgZGlzY3JpbWluYW50IGZ1bmN0aW9uICh1bmJvdW5kZWQpCiAgICBhZGRfdmFyaWFibGUoZC52YXIsIHR5cGUgPSAiY29udGludW91cyIsIGxiID0gZGVsdGEpICU+JSAgICMgbWluaW11bSBhYnNvbHV0ZSBzY29yZSBvZiBhbnkgc3VjY2Vzc2Z1bGx5IGNsYXNzaWZpZWQgb2JzZXJ2YXRpb24gKGJvdW5kZWQgYXdheSBmcm9tIHplcm8pCiAgICBzZXRfb2JqZWN0aXZlKHN1bV9leHByKHkudmFyW2ldLCBpID0gMTpubmVnKSArIHN1bV9leHByKHoudmFyW2ldLCBpID0gMTpucG9zKSAtIGVwc2lsb24gKiBkLnZhciwgc2Vuc2UgPSAibWluIikgJT4lICAgIyBtaW5pbWl6ZSBudW1iZXIgb2YgbWlzY2xhc3NpZmljYXRpb25zIHdpdGggY3JlZGl0IGZvciBoaWdoZXIgc3BhY2luZyBmcm9tIHRoZSBjbGFzc2lmaWNhdGlvbiBib3VuZGFyeQogICAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIobmVnYXRpdmVbaSwgal0gKiB3LnZhcltqXSwgaiA9IDE6bnZhcikgKyBjLnZhciArIGQudmFyIC0gTTEgKiB5LnZhcltpXSA8PSAwLCBpID0gMTpubmVnKSAlPiUgICAjIGRldGVybWluZSBuZWdhdGl2ZSBtaXNjbGFzc2lmaWNhdGlvbnMKICAgIGFkZF9jb25zdHJhaW50KHN1bV9leHByKHBvc2l0aXZlW2ksIGpdICogdy52YXJbal0sIGogPSAxOm52YXIpICsgYy52YXIgLSBkLnZhciArIE0yICogei52YXJbaV0gPj0gMCwgaSA9IDE6bnBvcykgICMgZGV0ZXJtaW5lIHBvc2l0aXZlIG1pc2NsYXNzaWZpY2F0aW9ucwogICMKICAjIEZpbmFsbHksIHdlIHNvbHZlIHRoZSBtb2RlbCBhbmQgcmV0dXJuIHRoZSByZXN1bHQuCiAgIwogIG1vZGVsICU+JSBzb2x2ZV9tb2RlbCh3aXRoX1JPSShzb2x2ZXIgPSAiY3BsZXgiKSkKfQpgYGAKCiMjIyBDcmVhdGluZyBkaXNjcmltaW5hbnQgZnVuY3Rpb25zCgpXZSB3aWxsIGFsc28gd2FudCBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIHNvbHV0aW9uIHRvIG91ciBNSVAgbW9kZWwgYW5kIHR1cm5zIGl0IGludG8gYSBsaW5lYXIgZGlzY3JpbWluYW50IGZ1bmN0aW9uLCB0byBmYWNpbGl0YXRlIHRlc3RpbmcgdGhlIGZ1bmN0aW9uLgoKYGBge3J9CmV4dHJhY3RGdW5jdGlvbiA8LSBmdW5jdGlvbihtaXBSZXN1bHQpIHsKICBhIDwtIChtaXBSZXN1bHQgJT4lIGdldF9zb2x1dGlvbih3LnZhcltqXSkpWywgInZhbHVlIl0KICBhMCA8LSBtaXBSZXN1bHQgJT4lIGdldF9zb2x1dGlvbihjLnZhcikKICBmIDwtIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoYSAlKiUgYXMubnVtZXJpYyh4KSArIGEwKQogIGYKfQpgYGAKCiMgVGVzdGluZyB0aGUgTUlQIGRpc2NyaW1pbmFudCBtb2RlbAoKIyMjIFNldG9zYSB2LiBWZXJzaWNvbG9yL1ZpcmdpbmljYQoKTGV0J3Mgc3RhcnQgYnkgdHJ5aW5nIHRvIGRpc2NyaW1pbmF0ZSBiZXR3ZWVuIHNldG9zYSAoInBvc2l0aXZlIikgYW5kIHZlcnNpY29sb3IvdmlyZ2luaWNhICgibmVnYXRpdmUiKS4gVGhlIGBzaW5rKClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gYXZvaWQgY2x1dHRlcmluZyB0aGUgcGFnZSB3aXRoIGEgcmF0aGVyIGxlbmd0aHkgcHJvZ3Jlc3MgcmVwb3J0IGZyb20gdGhlIHNvbHZlci4KCmBgYHtyfQpzYW1wbGVzIDwtIHBhcnRpdGlvbigic2V0b3NhIikKc2luaygiL2Rldi9udWxsIikKcmVzdWx0IDwtIG1pcERpc2NyaW1pbmFudChzYW1wbGVzKQpzaW5rKCkKYGBgCgoKVGhlIGZpcnN0IHRoaW5nIHRvIGRvIGlzIGNoZWNrIHdoZXRoZXIgdGhlIG1vZGVsIHNvbHZlZCBzdWNjZXNzZnVsbHkuCgpgYGB7cn0KY2F0KCJTdGF0dXMgPSAiLCByZXN1bHQkc3RhdHVzKQpgYGAKCk5leHQsIHdlIGNhbiB0YWtlIGEgbG9vayBhdCB0aGUgb2JqZWN0aXZlIHZhbHVlLgoKYGBge3J9CmNhdCgiT2JqZWN0aXZlIHZhbHVlID0gIiwgcmVzdWx0JG9iamVjdGl2ZV92YWx1ZSkKYGBgCgpWYWx1ZXMgZm9yIHNjYWxhciBtb2RlbCB2YXJpYWJsZXMgYXJlIHJldHVybmVkIGFzIG51bWVyaWMgdmVjdG9ycyBvZiBsZW5ndGggMS4KCmBgYHtyfQpjIDwtIHJlc3VsdCAlPiUgZ2V0X3NvbHV0aW9uKGMudmFyKQpjCmQgPC0gcmVzdWx0ICU+JSBnZXRfc29sdXRpb24oZC52YXIpCmQKYGBgCgpWYWx1ZXMgZm9yIGluZGV4ZWQgbW9kZWwgdmFyaWFibGVzIGFyZSByZXR1cm5lZCBpbiBkYXRhIGZyYW1lcy4KCmBgYHtyfQp3IDwtIHJlc3VsdCAlPiUgZ2V0X3NvbHV0aW9uKHcudmFyW2pdKQp5IDwtIHJlc3VsdCAlPiUgZ2V0X3NvbHV0aW9uKHkudmFyW2ldKQp6IDwtIHJlc3VsdCAlPiUgZ2V0X3NvbHV0aW9uKHoudmFyW2ldKQp3CmBgYAoKTGV0J3Mgc2VlLCBiYXNlZCBvbiB0aGUgeSBhbmQgeiBpbmRpY2F0b3JzLCBob3cgbWFueSBvYnNlcnZhdGlvbnMgdGhlIG1vZGVsIHRoaW5rcyBpdCBtaXNjbGFzc2lmaWVkLgoKYGBge3J9CmNhdChzcHJpbnRmKCJOZWdhdGl2ZSBtaXNjbGFzc2lmaWNhdGlvbnM6ICVkXG4iLCBucm93KHlbeSR2YWx1ZSA+PSAwLjEsIF0pKSkKY2F0KHNwcmludGYoIlBvc2l0aXZlIG1pc2NsYXNzaWZpY2F0aW9uczogJWRcbiIsIG5yb3coelt6JHZhbHVlID49IDAuMSwgXSkpKQpgYGAKCldlIGFsbGVnZWRseSBoYXZlIG5vIGVycm9ycyAoMTAwJSBjb3JyZWN0IGNsYXNzaWZpY2F0aW9uIGZvciB0aGlzIHNwbGl0KS4gSnVzdCB0byBiZSBzdXJlLCBsZXQncyBleHRyYWN0IHRoZSBkaXNjcmltaW5hbnQgZnVuY3Rpb24gYW5kIGFwcGx5IGl0IGRpcmVjdGx5IHRvIHRoZSBkYXRhLCBmaW5kaW5nIGl0cyBzbWFsbGVzdCBzY29yZSBvbiB0aGUgcG9zaXRpdmUgc2FtcGxlIChob3BlZnVsbHkgPiAwKSBhbmQgaXRzIGxhcmdlc3Qgc2NvcmUgb24gdGhlIG5lZ2F0aXZlIHNhbXBsZSAoaG9wZWZ1bGx5IDwgMCkuCgpgYGB7cn0KZEZ1bmMgPC0gZXh0cmFjdEZ1bmN0aW9uKHJlc3VsdCkKY2F0KCJNaW5pbnVtIGNsZWFyYW5jZSBmb3IgcG9zaXRpdmUgc2FtcGxlOiAiLCBtaW4oYXBwbHkoc2FtcGxlcyRwb3NpdGl2ZSwgMSwgZEZ1bmMpKSkKY2F0KCJcbk1pbmltdW0gY2xlYXJhbmNlIGZvciBuZWdhdGl2ZSBzYW1wbGU6ICIsIG1heChhcHBseShzYW1wbGVzJG5lZ2F0aXZlLCAxLCBkRnVuYykpKQpgYGAKClNvIGFsbCBzY29yZXMgYXJlIGNvcnJlY3QgKGFzIGV4cGVjdGVkKSwgYW5kIHRoZSBjbG9zZXN0IGFueSBvYnNlcnZhdGlvbiBjb21lcyB0byB0aGUgImJvdW5kYXJ5IiBzY29yZSBvZiAwIGlzIDEuMzUgKHdoaWNoIGlzIHRoZSB2YWx1ZSBvZiB0aGUgZCB2YXJpYWJsZSBpbiB0aGUgbW9kZWwpLgoKRm9yIGNvbXBsZXRlbmVzcywgc2luY2Ugd2Ugd2lsbCBiZSBkb2luZyBpdCBiZWxvdywgd2UgY3JlYXRlIGEgImNvbmZ1c2lvbiBtYXRyaXgiIGNvbXBhcmluZyBwcmVkaWN0aW9ucyB0byBhY3R1YWwgdmFsdWVzLgoKYGBge3J9CnByZWRpY3RlZCA8LSBhcy5mYWN0b3IoaWZlbHNlKGFwcGx5KGlyaXNbLCAxOjRdLCAxLCBkRnVuYykgPiAwLCAic2V0b3NhIiwgIm90aGVyIikpICAjIHByZWRpY3RlZCBjbGFzcwp0YWJsZShwcmVkaWN0ZWQsIGlyaXMkU3BlY2llcykKYGBgCgojIyMgVmVyaWNvbG9yIHYuIFNldG9zYS9WaXJnaW5pY2EKCk5leHQsIHdlIHJlcGVhdCB0aGUgZXhwZXJpbWVudCwgdHJ5aW5nIHRvIGRpc3Rpbmd1aXNoIHZlcnNpY29sb3IgZnJvbSBzZXRvc2EgYW5kIHZpcmdpbmljYS4KCmBgYHtyfQpzYW1wbGVzIDwtIHBhcnRpdGlvbigidmVyc2ljb2xvciIpCnNpbmsoIi9kZXYvbnVsbCIpCnJlc3VsdDIgPC0gbWlwRGlzY3JpbWluYW50KHNhbXBsZXMpCnNpbmsoKQpjYXQoIlN0YXR1cyA9ICIsIHJlc3VsdDIkc3RhdHVzKQpjYXQoIlxuT2JqZWN0aXZlIHZhbHVlID0gIiwgcmVzdWx0MiRvYmplY3RpdmVfdmFsdWUpCmBgYAoKVGhpcyB0YWtlcyBjb25zaWRlcmFibHkgbG9uZ2VyIHRvIHNvbHZlLCBiZWNhdXNlIDEwMCUgYWNjdXJhY3kgaXMgbm90IGF0dGFpbmFibGUgKHNvIHRoZSBNSVAgc29sdmVyIGhhcyB0byBkbyBhIGZhaXIgYml0IG9mIGJyYW5jaGluZykuIFRoZSBnb29kIG5ld3MgaXMgdGhhdCB3ZSBoYXZlIGFuIG9wdGltYWwgc29sdXRpb247IHRoZSBiYWQgbmV3cyBpcyB0aGF0IHdlIGFwcGFyZW50bHkgbWlzY2xhc3NpZmllZCAyNiBvYnNlcnZhdGlvbnMuIFdlIGNhbiBicmVhayB0aGF0IGRvd24gYnkgZ3JvdXAuCgpgYGB7cn0KZEZ1bmMgPC0gZXh0cmFjdEZ1bmN0aW9uKHJlc3VsdDIpCnByZWRpY3RlZCA8LSBhcy5mYWN0b3IoaWZlbHNlKGFwcGx5KGlyaXNbLCAxOjRdLCAxLCBkRnVuYykgPiAwLCAidmVyc2ljb2xvciIsICJvdGhlciIpKSAgIyBwcmVkaWN0ZWQgY2xhc3MKdGFibGUocHJlZGljdGVkLCBpcmlzJFNwZWNpZXMpCmBgYAoKVGhlIGJlc3Qgc29sdXRpb24gbWlzY2xhc3NpZmllcyA4IHZlcnNpY29sb3IgKHBvc2l0aXZlKSBzcGVjaW1lbnMgYW5kIDE4IHNwZWNpbWVucyBmcm9tIHRoZSBvdGhlciB0d28gc3BlY2llcyAobmVnYXRpdmUpLiBXZSBjYW4gZWFzaWx5IGNoZWNrIHdoaWNoIHRoZXkgYXJlLgoKYGBge3J9CnkgPC0gcmVzdWx0MiAlPiUgZ2V0X3NvbHV0aW9uKHkudmFyW2ldKQp6IDwtIHJlc3VsdDIgJT4lIGdldF9zb2x1dGlvbih6LnZhcltpXSkKZTEgPC0gd2hpY2goeSR2YWx1ZSA+IDAuNSkKZTIgPC0gd2hpY2goeiR2YWx1ZSA+IDAuNSkKY2F0KCJOZWdhdGl2ZTpcbiIpCmUxCmNhdCgiUG9zaXRpdmU6XG4iKQplMgpgYGAKCldlIGNhbiB0YWtlIGEgbG9vayBhdCB0aGUgZGlzY3JpbWluYW50IHZhbHVlcyBmb3IgdGhvc2Ugb2JzZXJ2YXRpb25zLCBqdXN0IHRvIGNvbmZpcm0gd2hhdCB3ZSBhcmUgc2VlaW5nLgoKYGBge3J9CmNhdCgiTmVnYXRpdmU6XG4iKQphcHBseShzYW1wbGVzJG5lZ2F0aXZlW2UxLCBdLCAxLCBkRnVuYykKY2F0KCJcblBvc2l0aXZlOlxuIikKYXBwbHkoc2FtcGxlcyRwb3NpdGl2ZVtlMiwgXSwgMSwgZEZ1bmMpCmBgYAoKIyMjIFZpcmdpbmljYSB2LiBTZXRvc2EvVmVyc2ljb2xvcgoKRmluYWxseSwgbGV0J3Mgc2VlIGlmIHdlIGNhbiBkaXNjcmltaW5hdGUgYmV0d2VlbiB2aXJnaW5pY2EgYW5kIHNldG9zYS92ZXJzaWNvbG9yLgoKYGBge3J9CnNhbXBsZXMgPC0gcGFydGl0aW9uKCJ2aXJnaW5pY2EiKQpzaW5rKCIvZGV2L251bGwiKQpyZXN1bHQzIDwtIG1pcERpc2NyaW1pbmFudChzYW1wbGVzKQpzaW5rKCkKY2F0KCJTdGF0dXMgPSAiLCByZXN1bHQzJHN0YXR1cykKY2F0KCJcbk9iamVjdGl2ZSB2YWx1ZSA9ICIsIHJlc3VsdDMkb2JqZWN0aXZlX3ZhbHVlKQpgYGAKCkl0IGxvb2tzIGFzIHRob3VnaCB3ZSBnb3QgYXdheSB3aXRoIGp1c3Qgb25lIG1pc2NsYXNzaWZpY2F0aW9uLgoKYGBge3J9CmRGdW5jIDwtIGV4dHJhY3RGdW5jdGlvbihyZXN1bHQzKQpwcmVkaWN0ZWQgPC0gYXMuZmFjdG9yKGlmZWxzZShhcHBseShpcmlzWywgMTo0XSwgMSwgZEZ1bmMpID4gMCwgInZpcmdpbmljYSIsICJvdGhlciIpKSAgIyBwcmVkaWN0ZWQgY2xhc3MKdGFibGUocHJlZGljdGVkLCBpcmlzJFNwZWNpZXMpCiMgb25lIG9mIHRoZSBuZWdhdGl2ZSBzYW1wbGVzIHdhcyBjbGFzc2lmaWVkIHBvc2l0aXZlIC0gd2hpY2g/CnkgPC0gcmVzdWx0MyAlPiUgZ2V0X3NvbHV0aW9uKHkudmFyW2ldKQplMSA8LSB3aGljaCh5JHZhbHVlID4gMC41KQpjYXQoIkVycm9yIGluIG9ic2VydmF0aW9uICIsIGUxKQpgYGAKClNvIG9ic2VydmF0aW9uIDg0IGZyb20gdGhlIG5lZ2F0aXZlIHNhbXBsZSwgd2hpY2ggaGFwcGVucyB0byBiZSBhIHZlcnNpY29sb3Igc3BlY2ltZW4sIHNlZW1zIHRvIGJlIHRoZSBvbmx5IG9uZSBtaXNjbGFzc2lmaWVkLiBMZXQncyBjaGVjayBpdHMgZGlzY3JpbWluYW50IHNjb3JlLgoKYGBge3J9CmRGdW5jIDwtIGV4dHJhY3RGdW5jdGlvbihyZXN1bHQzKQphcHBseShzYW1wbGVzJG5lZ2F0aXZlW2UxLCBdLCAxLCBkRnVuYykKYGBgCgpJdCBpcyBpbmRlZWQgYSBwb3NpdGl2ZSAoaW5jb3JyZWN0KSBzY29yZS4KCiMgQ2xhc3NpZmljYXRpb24gdXNpbmcgYW4gU1ZNCgpKdXN0IGZvciBmdW4sIGxldCdzIHRyeSB1c2luZyBhIGNsYXNzaWZpY2F0aW9uIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgKFNWTXMpLCB2aWEgdGhlIGtlcm5sYWIgcGFja2FnZS4gVG8ga2VlcCB0aGluZ3Mgc2ltcGxlLCB3ZSB3aWxsIGxlYXZlIGFsbCBvcHRpb25zIGF0IGRlZmF1bHQgc2V0dGluZ3MuCgojIyMgU2V0b3NhIHYuIFZlcnNpY29sb3IvVmlyZ2luaWNhCgpGaXJzdCwgbGV0J3MgdHJ5IHRvIGRpc2NyaW1pbmF0ZSBzZXRvc2EgZnJvbSB0aGUgb3RoZXJzLgoKYGBge3J9CmNsYXNzIDwtIGZhY3RvcihpcmlzJFNwZWNpZXMgPT0gInNldG9zYSIsIGxhYmVscyA9IGMoIm5lZ2F0aXZlIiwgInBvc2l0aXZlIikpCnN2bSA8LSBrc3ZtKGNsYXNzIH4gLiAtIFNwZWNpZXMsIGRhdGEgPSBpcmlzKQp0YWJsZShwcmVkaWN0KHN2bSwgaXJpcyksIGlyaXMkU3BlY2llcykKYGBgCgpUaGUgU1ZNIG1hdGNoZXMgdGhlIDEwMCUgYWNjdXJhY3kgb2YgdGhlIE1JUCBtb2RlbCBvbiBzZXRvc2EuCgojIyMgVmVyc2ljb2xvciB2LiBTZXRvc2EvVmlyZ2luaWNhCgpgYGB7cn0KY2xhc3MgPC0gZmFjdG9yKGlyaXMkU3BlY2llcyA9PSAidmVyc2ljb2xvciIsIGxhYmVscyA9IGMoIm5lZ2F0aXZlIiwgInBvc2l0aXZlIikpCnN2bSA8LSBrc3ZtKGNsYXNzIH4gLiAtIFNwZWNpZXMsIGRhdGEgPSBpcmlzKQp0YWJsZShwcmVkaWN0KHN2bSwgaXJpcyksIGlyaXMkU3BlY2llcykKYGBgCgpBdHRlbXB0aW5nIHRvIGRpc3Rpbmd1aXNoIHZlcnNpY29sb3IgZnJvbSB0aGUgb3RoZXIgdHdvIHNwZWNpZXMsIHRoZSBTVk0gbW9kZWwgbWlzY2xhc3NpZmllcyBvbmUgb3IgdHdvIHZlcmlzY29sb3IgaW5zdGFuY2VzIGFuZCBvbmUgb3IgdHdvIHZpcmdpbmljYSBpbnN0YW5jZXMsIG11Y2ggYmV0dGVyIHRoYW4gdGhlIE1JUCBtb2RlbCBkaWQuIChUaGlzIGlzIGR1ZSB0byB0aGUgU1ZNIG5vdCBiZWluZyByZXN0cmljdGVkIHRvIGEgbGluZWFyIGRpc2NyaW1pbmFudCBmdW5jdGlvbi4pIFRoZSByZWFzb24gSSBzYXkgIm9uZSBvciB0d28iIGFuZCBub3Qgc3BlY2lmaWNhbGx5IHdoYXQgeW91IHNlZSBpbiB0aGUgY29uZnVzaW9uIG1hdHJpeCBpcyB0aGF0IHRoZSBTVk0gZml0dGluZyBhbGdvcml0aG0gYXBwZWFycyB0byB1c2UgYSBzb21ld2hhdCByYW5kb21pemVkIGFsZ29yaXRobS4gSWYgeW91IGtlZXAgcmVmaXR0aW5nIHRoZSBtb2RlbCwgdGhlIHJlc3VsdHMgcGVyaW9kaWNhbGx5IGNoYW5nZS4gWW91IGNhbiBzdGFiaWxpemUgdGhlIHJlc3VsdHMgYnkgc2V0dGluZyBhIHJhbmRvbSBzZWVkIGJlZm9yZSBjYWxsaW5nIGBrc3ZtKClgLCBidXQgd2hpY2ggcmVzdWx0cyB5b3UgZ2V0IHdpbGwgZGVwZW5kIG9uIGhvdyBsdWNreSB5b3UgYXJlIHBpY2tpbmcgdGhlIHNlZWQuCgojIyMgVmlyZ2luaWNhIHYuIFNldG9zYS9WZXJzaWNvbG9yCgpgYGB7cn0KY2xhc3MgPC0gZmFjdG9yKGlyaXMkU3BlY2llcyA9PSAidmlyZ2luaWNhIiwgbGFiZWxzID0gYygibmVnYXRpdmUiLCAicG9zaXRpdmUiKSkKc3ZtIDwtIGtzdm0oY2xhc3MgfiAuIC0gU3BlY2llcywgZGF0YSA9IGlyaXMpCnRhYmxlKHByZWRpY3Qoc3ZtLCBpcmlzKSwgaXJpcyRTcGVjaWVzKQpgYGAKClRoZSBTVk0gaGFzIGEgdG90YWwgb2YgdGhyZWUgb3IgZm91ciBlcnJvcnMgd2hlbiB0cnlpbmcgdG8gZGlzY3JpbWluYXRlIGJldHdlZW4gdmlyZ2luaWNhIGFuZCB0aGUgb3RoZXIgdHdvIHNwZWNpZXMsIHdvcnNlIHRoYW4gdGhlIHNpbmdsZSBlcnJvciB0aGUgTUlQIG1vZGVsIGFjaGlldmVkLiBBZ2FpbiwgdGhlIHRvdGFsIGVycm9yIGNvdW50IHZhcmllcyBkZXBlbmRpbmcgb24gdGhlIHJhbmRvbSBzZWVkIHVzZWQuCgojIFJlZmVyZW5jZQoKWzFdIFJ1YmluLCBQLiBBLiBIZXVyaXN0aWMgc29sdXRpb24gcHJvY2VkdXJlcyBmb3IgYSBtaXhlZC1pbnRlZ2VyIHByb2dyYW1taW5nIGRpc2NyaW1pbmFudCBtb2RlbC4gKk1hbmFnZXJpYWwgYW5kIERlY2lzaW9uIEVjb25vbWljcyosIFZvbC4gMTEsIE5vLiA0LCBPY3RvYmVyIDE5OTAu