Problem 5

We have seen that we can fit an SVM with a non-linear kernel in order to perform classification using a non-linear decision boundary. We will now see that we can also obtain a non-linear decision boundary by performing logistic regression using non-linear transformations of the features.

(a) Generate a data set with n = 500 and p = 2, such that the observations belong to two classes with a quadratic decision boundary between them.

set.seed(1)
x1 <- runif(500) - 0.5
x2 <- runif(500) - 0.5
y  <- 1 * (x1^2 - x2^2 > 0)
data <- data.frame(x1 = x1, x2 = x2, y = as.factor(y))

(b) Plot the observations, colored according to their class labels. Your plot should display X1 on the x-axis, and X2 on the y-axis.

plot(x1, x2, col = ifelse(y == 1, "red", "blue"), pch = 19,
     xlab = "X1", ylab = "X2", main = "True Class Labels")

(c) Fit a logistic regression model to the data, using X1 and X2 as predictors.

glm_fit <- glm(y ~ x1 + x2, data = data, family = binomial)
summary(glm_fit)

Call:
glm(formula = y ~ x1 + x2, family = binomial, data = data)

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.087260   0.089579  -0.974    0.330
x1           0.196199   0.316864   0.619    0.536
x2          -0.002854   0.305712  -0.009    0.993

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 692.18  on 499  degrees of freedom
Residual deviance: 691.79  on 497  degrees of freedom
AIC: 697.79

Number of Fisher Scoring iterations: 3

(d) Apply this model to the training data in order to obtain a predicted class label for each training observation. Plot the observations, colored according to the predicted class labels. The decision boundary should be linear.

glm_probs <- predict(glm_fit, data, type = "response")
glm_pred  <- ifelse(glm_probs > 0.5, 1, 0)

plot(x1, x2, col = ifelse(glm_pred == 1, "red", "blue"), pch = 19,
     xlab = "X1", ylab = "X2", main = "Logistic Regression (Linear Terms)")

(e) Now fit a logistic regression model to the data using non-linear functions of X1 and X2 as predictors (e.g. X1², X1×X2, log(X2), and so forth).

glm_fit2 <- glm(y ~ poly(x1, 2) + poly(x2, 2) + x1:x2, data = data, family = binomial)
summary(glm_fit2)

Call:
glm(formula = y ~ poly(x1, 2) + poly(x2, 2) + x1:x2, family = binomial, 
    data = data)

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept)    -102.2     4302.0  -0.024    0.981
poly(x1, 2)1   2715.3   141109.5   0.019    0.985
poly(x1, 2)2  27218.5   842987.2   0.032    0.974
poly(x2, 2)1   -279.7    97160.4  -0.003    0.998
poly(x2, 2)2 -28693.0   875451.3  -0.033    0.974
x1:x2          -206.4    41802.8  -0.005    0.996

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 6.9218e+02  on 499  degrees of freedom
Residual deviance: 3.5810e-06  on 494  degrees of freedom
AIC: 12

Number of Fisher Scoring iterations: 25

(f) Apply this model to the training data in order to obtain a predicted class label for each training observation. Plot the observations, colored according to the predicted class labels. The decision boundary should be obviously non-linear.

glm_probs2 <- predict(glm_fit2, data, type = "response")
glm_pred2  <- ifelse(glm_probs2 > 0.5, 1, 0)

plot(x1, x2, col = ifelse(glm_pred2 == 1, "red", "blue"), pch = 19,
     xlab = "X1", ylab = "X2", main = "Logistic Regression (Quadratic Terms)")

(g) Fit a support vector classifier to the data with X1 and X2 as predictors. Obtain a class prediction for each training observation. Plot the observations, colored according to the predicted class labels.

library(caret)

train_control <- trainControl(method = "cv", number = 10)
svm_linear <- train(y~x1+x2, data = data, method = "svmLinear", trControl = train_control, 
                    preProcess =   c("center","scale"))
svm_linear_pred <- predict(svm_linear, data)

plot(x1, x2, col = ifelse(svm_linear_pred == 1, "red", "blue"), pch = 19,
     xlab = "X1", ylab = "X2", main = "Support Vector Classifier (Linear)")

(h) Fit a SVM using a non-linear kernel to the data. Obtain a class prediction for each training observation. Plot the observations, colored according to the predicted class labels.

train_control <- trainControl(method = "cv", number = 10)
svm_radial <- train(y~x1+x2, data = data, method = "svmRadial", trControl = train_control, 
                    preProcess = c("center", "scale"))
svm_radial_pred <- predict(svm_radial, data)

plot(x1, x2, col = ifelse(svm_radial_pred == 1, "red", "blue"), pch = 19,
     xlab = "X1", ylab = "X2", main = "Support Vector Machine (Radial Kernel)")

(i) Comment on your results.


Problem 7

In this problem, you will use support vector approaches in order to predict whether a given car gets high or low gas mileage based on the Auto data set.

(a) Create a binary variable that takes on a 1 for cars with gas mileage above the median, and a 0 for cars with gas mileage below the median.

library(ISLR2)

Auto$mpg01 <- as.factor(ifelse(Auto$mpg > median(Auto$mpg), 1, 0))

(b) Fit a support vector classifier to the data with various values of cost, in order to predict whether a car gets high or low gas mileage. Report the cross-validation errors associated with different values of this parameter. Comment on your results. Note you will need to fit the classifier without the gas mileage variable to produce sensible results.

library(e1071)

set.seed(1)
Auto_svm <- Auto[, !(names(Auto) %in% c("mpg", "name"))]

train_control = trainControl(method = "cv", number = 10)
tune_linear <- train(mpg01~.,data=Auto_svm,method='svmLinear',trControl=train_control
                     ,preProcess=c('center','scale'),tuneGrid=expand.grid(C=c(0.01, 0.1, 1, 5, 10, 100)))
tune_linear$results

(c) Now repeat (b), this time using SVMs with radial and polynomial basis kernels, with different values of gamma and degree and cost. Comment on your results.

set.seed(1)
train_control = trainControl(method = "cv", number = 10)
tune_radial <- train(mpg01~.,data=Auto_svm,method='svmRadial',trControl=train_control
                     ,preProcess=c('center','scale'),tuneGrid=
                       expand.grid(C = c(0.1, 1, 5, 10, 100),sigma = c(0.01, 0.1, 1, 5)))

tune_radial$results
set.seed(1)
train_control = trainControl(method = "cv", number = 10)
tune_poly <- train(mpg01~.,data=Auto_svm,method='svmPoly',trControl=train_control
                     ,preProcess=c('center','scale'),tuneGrid=
                      expand.grid(C = c(0.1, 1, 5, 10, 100), degree = c(2, 3, 4), scale = c(0.1,.1)))
tune_poly$results

(d) Make some plots to back up your assertions in (b) and (c).

svm_best_linear <- svm(mpg01 ~ ., data = Auto_svm, kernel = "linear",
                       cost = tune_linear$bestTune)
svm_best_radial <- svm(mpg01 ~ ., data = Auto_svm, kernel = "radial",
                       cost = tune_radial$bestTune,
                       sigma = tune_radial$bestTune)

plot(svm_best_linear, Auto_svm, horsepower ~ weight)

plot(svm_best_radial, Auto_svm, horsepower ~ weight)

plot(svm_best_linear, Auto_svm, displacement ~ acceleration)


Problem 8

This problem involves the OJ data set which is part of the ISLR2 package.

(a) Create a training set containing a random sample of 800 observations, and a test set containing the remaining observations.

set.seed(1)
train_oj <- sample(1:nrow(OJ), 800)
oj_train <- OJ[train_oj, ]
oj_test  <- OJ[-train_oj, ]

(b) Fit a support vector classifier to the training data using cost = 0.01, with Purchase as the response and the other variables as predictors. Use the summary() function to produce summary statistics, and describe the results obtained.

svm_oj_linear <- train(Purchase~.,data = oj_train, method = "svmLinear", 
                       trControl = train_control, preProcess = c("center", "scale"), tuneGrid = expand.grid(C=0.01))
svm_oj_linear$results

(c) What are the training and test error rates?

train_pred_linear <- predict(svm_oj_linear, oj_train)
test_pred_linear   <- predict(svm_oj_linear, oj_test)

mean(train_pred_linear != oj_train$Purchase)
[1] 0.17625
mean(test_pred_linear != oj_test$Purchase)
[1] 0.1851852

(d) Use the tune() function to select an optimal cost. Consider values in the range 0.01 to 10.

set.seed(1)
tune_oj_linear <- train(Purchase~., data = oj_train, method = "svmLinear", trControl = train_control,
                        preProcess = c("center", "scale"), 
                        tuneGrid = expand.grid(C = seq(0.01, 10, length.out = 10)))
tune_oj_linear$results
tune_oj_linear$bestTune

(e) Compute the training and test error rates using this new value for cost.

train_pred_linear <- predict(tune_oj_linear, oj_train)
test_pred_linear <- predict(tune_oj_linear, oj_test)

mean(train_pred != oj_train$Purchase)
[1] 0.16625
mean(test_pred != oj_test$Purchase)
[1] 0.1518519

(f) Repeat parts (b) through (e) using a support vector machine with a radial kernel. Use the default value for gamma.

#Ploy results and best tune before tune
svm_oj_radial <- train(Purchase~.,data = oj_train, method = "svmRadial", 
                       trControl = train_control, preProcess = c("center", "scale"), 
                       tuneGrid = expand.grid(C=0.01,sigma = 0.01))
svm_oj_radial$results
NA
#radial test train errors before tune
train_pred_radial <- predict(svm_oj_radial, oj_train)
test_pred_radial <- predict(svm_oj_radial, oj_test)

mean(train_pred_radial != oj_train$Purchase)
[1] 0.39375
mean(test_pred_radial != oj_test$Purchase)
[1] 0.3777778
#Radial results and best tune
set.seed(1)
tune_oj_radial <- train(Purchase~., data = oj_train, method = "svmRadial", trControl = train_control,
                        preProcess = c("center", "scale"), 
                        tuneLength = 10)
tune_oj_radial$results
tune_oj_radial$bestTune
NA
#radial test train errors
train_pred_radial <- predict(tune_oj_radial, oj_train)
test_pred_radial <- predict(tune_oj_radial, oj_test)

mean(train_pred_radial != oj_train$Purchase)
[1] 0.14875
mean(test_pred_radial != oj_test$Purchase)
[1] 0.1814815

(g) Repeat parts (b) through (e) using a support vector machine with a polynomial kernel. Set degree = 2.

#Ploy results and best tune
svm_oj_poly <- train(Purchase~.,data = oj_train, method = "svmPoly", 
                       trControl = train_control, preProcess = c("center", "scale"), 
                       tuneGrid = expand.grid(C=0.01,scale = 0.01, degree = 2))
svm_oj_poly$results
#Poly test train errors before tune
train_pred_poly <- predict(svm_oj_poly, oj_train)
test_pred_poly <- predict(svm_oj_poly, oj_test)

mean(train_pred_poly != oj_train$Purchase)
[1] 0.39375
mean(test_pred_poly != oj_test$Purchase)
[1] 0.3777778
#Ploy results and best tune
set.seed(1)
tune_oj_poly <- train(Purchase~., data = oj_train, method = "svmPoly", trControl = train_control,
                        preProcess = c("center", "scale"), 
                        tuneGrid = expand.grid(degree = c(2, 3, 4),scale = 0.01,C = c(0.01, 0.1, 1, 10)))
tune_oj_poly$results
tune_oj_poly$bestTune
#Poly test train errors
train_pred_poly <- predict(tune_oj_poly, oj_train)
test_pred_poly <- predict(tune_oj_poly, oj_test)

mean(train_pred_poly != oj_train$Purchase)
[1] 0.155
mean(test_pred_poly != oj_test$Purchase)
[1] 0.1777778

(h) Overall, which approach seems to give the best results on this data?

results_oj <- data.frame(
  Kernel = c("Linear", "Radial", "Polynomial"),
  Test_Error = c(
    mean(test_pred_linear != oj_test$Purchase),
    mean(test_pred_radial != oj_test$Purchase),
    mean(test_pred_poly != oj_test$Purchase)
  ))
results_oj
LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQ4Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMjIFByb2JsZW0gNQ0KDQpfX1dlIGhhdmUgc2VlbiB0aGF0IHdlIGNhbiBmaXQgYW4gU1ZNIHdpdGggYSBub24tbGluZWFyIGtlcm5lbCBpbiBvcmRlciB0byBwZXJmb3JtIGNsYXNzaWZpY2F0aW9uIHVzaW5nIGEgbm9uLWxpbmVhciBkZWNpc2lvbiBib3VuZGFyeS4gV2Ugd2lsbCBub3cgc2VlIHRoYXQgd2UgY2FuIGFsc28gb2J0YWluIGEgbm9uLWxpbmVhciBkZWNpc2lvbiBib3VuZGFyeSBieSBwZXJmb3JtaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdXNpbmcgbm9uLWxpbmVhciB0cmFuc2Zvcm1hdGlvbnMgb2YgdGhlIGZlYXR1cmVzLl9fDQoNCl9fKGEpIEdlbmVyYXRlIGEgZGF0YSBzZXQgd2l0aCBuID0gNTAwIGFuZCBwID0gMiwgc3VjaCB0aGF0IHRoZSBvYnNlcnZhdGlvbnMgYmVsb25nIHRvIHR3byBjbGFzc2VzIHdpdGggYSBxdWFkcmF0aWMgZGVjaXNpb24gYm91bmRhcnkgYmV0d2VlbiB0aGVtLl9fDQpgYGB7cn0NCnNldC5zZWVkKDEpDQp4MSA8LSBydW5pZig1MDApIC0gMC41DQp4MiA8LSBydW5pZig1MDApIC0gMC41DQp5ICA8LSAxICogKHgxXjIgLSB4Ml4yID4gMCkNCmRhdGEgPC0gZGF0YS5mcmFtZSh4MSA9IHgxLCB4MiA9IHgyLCB5ID0gYXMuZmFjdG9yKHkpKQ0KYGBgDQoNCl9fKGIpIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlaXIgY2xhc3MgbGFiZWxzLiBZb3VyIHBsb3Qgc2hvdWxkIGRpc3BsYXkgWDEgb24gdGhlIHgtYXhpcywgYW5kIFgyIG9uIHRoZSB5LWF4aXMuX18NCmBgYHtyfQ0KcGxvdCh4MSwgeDIsIGNvbCA9IGlmZWxzZSh5ID09IDEsICJyZWQiLCAiYmx1ZSIpLCBwY2ggPSAxOSwNCiAgICAgeGxhYiA9ICJYMSIsIHlsYWIgPSAiWDIiLCBtYWluID0gIlRydWUgQ2xhc3MgTGFiZWxzIikNCmBgYA0KDQoNCl9fKGMpIEZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdG8gdGhlIGRhdGEsIHVzaW5nIFgxIGFuZCBYMiBhcyBwcmVkaWN0b3JzLl9fDQpgYGB7cn0NCmdsbV9maXQgPC0gZ2xtKHkgfiB4MSArIHgyLCBkYXRhID0gZGF0YSwgZmFtaWx5ID0gYmlub21pYWwpDQpzdW1tYXJ5KGdsbV9maXQpDQpgYGANCg0KX18oZCkgQXBwbHkgdGhpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBvYnRhaW4gYSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWwgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbHMuIFRoZSBkZWNpc2lvbiBib3VuZGFyeSBzaG91bGQgYmUgbGluZWFyLl9fDQpgYGB7cn0NCmdsbV9wcm9icyA8LSBwcmVkaWN0KGdsbV9maXQsIGRhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KZ2xtX3ByZWQgIDwtIGlmZWxzZShnbG1fcHJvYnMgPiAwLjUsIDEsIDApDQoNCnBsb3QoeDEsIHgyLCBjb2wgPSBpZmVsc2UoZ2xtX3ByZWQgPT0gMSwgInJlZCIsICJibHVlIiksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiTG9naXN0aWMgUmVncmVzc2lvbiAoTGluZWFyIFRlcm1zKSIpDQpgYGANCg0KDQpfXyhlKSBOb3cgZml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byB0aGUgZGF0YSB1c2luZyBub24tbGluZWFyIGZ1bmN0aW9ucyBvZiBYMSBhbmQgWDIgYXMgcHJlZGljdG9ycyAoZS5nLiBYMcKyLCBYMcOXWDIsIGxvZyhYMiksIGFuZCBzbyBmb3J0aCkuX18NCmBgYHtyfQ0KZ2xtX2ZpdDIgPC0gZ2xtKHkgfiBwb2x5KHgxLCAyKSArIHBvbHkoeDIsIDIpICsgeDE6eDIsIGRhdGEgPSBkYXRhLCBmYW1pbHkgPSBiaW5vbWlhbCkNCnN1bW1hcnkoZ2xtX2ZpdDIpDQpgYGANCg0KX18oZikgQXBwbHkgdGhpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBvYnRhaW4gYSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWwgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbHMuIFRoZSBkZWNpc2lvbiBib3VuZGFyeSBzaG91bGQgYmUgb2J2aW91c2x5IG5vbi1saW5lYXIuX18NCmBgYHtyfQ0KZ2xtX3Byb2JzMiA8LSBwcmVkaWN0KGdsbV9maXQyLCBkYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCmdsbV9wcmVkMiAgPC0gaWZlbHNlKGdsbV9wcm9iczIgPiAwLjUsIDEsIDApDQoNCnBsb3QoeDEsIHgyLCBjb2wgPSBpZmVsc2UoZ2xtX3ByZWQyID09IDEsICJyZWQiLCAiYmx1ZSIpLCBwY2ggPSAxOSwNCiAgICAgeGxhYiA9ICJYMSIsIHlsYWIgPSAiWDIiLCBtYWluID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24gKFF1YWRyYXRpYyBUZXJtcykiKQ0KYGBgDQoNCg0KX18oZykgRml0IGEgc3VwcG9ydCB2ZWN0b3IgY2xhc3NpZmllciB0byB0aGUgZGF0YSB3aXRoIFgxIGFuZCBYMiBhcyBwcmVkaWN0b3JzLiBPYnRhaW4gYSBjbGFzcyBwcmVkaWN0aW9uIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9uLiBQbG90IHRoZSBvYnNlcnZhdGlvbnMsIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzLl9fDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQoNCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0Kc3ZtX2xpbmVhciA8LSB0cmFpbih5fngxK3gyLCBkYXRhID0gZGF0YSwgbWV0aG9kID0gInN2bUxpbmVhciIsIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsIA0KICAgICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gICBjKCJjZW50ZXIiLCJzY2FsZSIpKQ0Kc3ZtX2xpbmVhcl9wcmVkIDwtIHByZWRpY3Qoc3ZtX2xpbmVhciwgZGF0YSkNCg0KcGxvdCh4MSwgeDIsIGNvbCA9IGlmZWxzZShzdm1fbGluZWFyX3ByZWQgPT0gMSwgInJlZCIsICJibHVlIiksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciAoTGluZWFyKSIpDQpgYGANCg0KICAtIFRoZSBsaW5lYXIgc3VwcG9ydCB2ZWN0b3IgY2xhc3NpZmllciBwcmVkaWN0ZWQgZXZlcnkgb2JzZXJ2YXRpb24gYXMgdGhlIHNhbWUgY2xhc3MuIFRoaXMgc3VnZ2VzdHMgdGhhdCBhIGxpbmVhciBkZWNpc2lvbiBib3VuZGFyeSBpcyBub3QgZmxleGlibGUgZW5vdWdoIHRvIGNhcHR1cmUgdGhlIG5vbi1saW5lYXIgcmVsYXRpb25zaGlwIGluIHRoZSBkYXRhLg0KDQpfXyhoKSBGaXQgYSBTVk0gdXNpbmcgYSBub24tbGluZWFyIGtlcm5lbCB0byB0aGUgZGF0YS4gT2J0YWluIGEgY2xhc3MgcHJlZGljdGlvbiBmb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbi4gUGxvdCB0aGUgb2JzZXJ2YXRpb25zLCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgcHJlZGljdGVkIGNsYXNzIGxhYmVscy5fXw0KYGBge3J9DQp0cmFpbl9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCnN2bV9yYWRpYWwgPC0gdHJhaW4oeX54MSt4MiwgZGF0YSA9IGRhdGEsIG1ldGhvZCA9ICJzdm1SYWRpYWwiLCB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCANCiAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpKQ0Kc3ZtX3JhZGlhbF9wcmVkIDwtIHByZWRpY3Qoc3ZtX3JhZGlhbCwgZGF0YSkNCg0KcGxvdCh4MSwgeDIsIGNvbCA9IGlmZWxzZShzdm1fcmFkaWFsX3ByZWQgPT0gMSwgInJlZCIsICJibHVlIiksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoUmFkaWFsKSIpDQpgYGANCg0KICAtIFRoZSBTVk0gd2l0aCBhIHJhZGlhbCBrZXJuZWwgY29ycmVjdGx5IGNsYXNzaWZpZWQgbmVhcmx5IGFsbCBvZiB0aGUgb2JzZXJ2YXRpb25zLCBpbmRpY2F0aW5nIHRoYXQgdGhlIG5vbi1saW5lYXIga2VybmVsIHdhcyBhYmxlIHRvIG1vZGVsIHRoZSB1bmRlcmx5aW5nIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHgxLCB4MiwgYW5kIHkuDQoNCl9fKGkpIENvbW1lbnQgb24geW91ciByZXN1bHRzLl9fDQoNCiAgLSBUaGlzIGV4ZXJjaXNlIHNob3dzIHRoYXQgYm90aCBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBTVk1zIGNhbiBtb2RlbCBub24tbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJpZXMgd2hlbiB0aGUgYXBwcm9wcmlhdGUgdHJhbnNmb3JtYXRpb25zIG9yIGtlcm5lbHMgYXJlIHVzZWQuIFRoZSBsaW5lYXIgbW9kZWxzIHdlcmUgdW5hYmxlIHRvIHNlcGFyYXRlIHRoZSBjbGFzc2VzLCB3aGlsZSB0aGUgbm9uLWxpbmVhciBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCByYWRpYWwgU1ZNIHByb3ZpZGVkIGEgbXVjaCBiZXR0ZXIgZml0IHRvIHRoZSBkYXRhLg0KDQotLS0NCg0KIyMjIFByb2JsZW0gNw0KDQpfX0luIHRoaXMgcHJvYmxlbSwgeW91IHdpbGwgdXNlIHN1cHBvcnQgdmVjdG9yIGFwcHJvYWNoZXMgaW4gb3JkZXIgdG8gcHJlZGljdCB3aGV0aGVyIGEgZ2l2ZW4gY2FyIGdldHMgaGlnaCBvciBsb3cgZ2FzIG1pbGVhZ2UgYmFzZWQgb24gdGhlIEF1dG8gZGF0YSBzZXQuX18NCg0KX18oYSkgQ3JlYXRlIGEgYmluYXJ5IHZhcmlhYmxlIHRoYXQgdGFrZXMgb24gYSAxIGZvciBjYXJzIHdpdGggZ2FzIG1pbGVhZ2UgYWJvdmUgdGhlIG1lZGlhbiwgYW5kIGEgMCBmb3IgY2FycyB3aXRoIGdhcyBtaWxlYWdlIGJlbG93IHRoZSBtZWRpYW4uX18NCmBgYHtyfQ0KbGlicmFyeShJU0xSMikNCg0KQXV0byRtcGcwMSA8LSBhcy5mYWN0b3IoaWZlbHNlKEF1dG8kbXBnID4gbWVkaWFuKEF1dG8kbXBnKSwgMSwgMCkpDQpgYGANCg0KX18oYikgRml0IGEgc3VwcG9ydCB2ZWN0b3IgY2xhc3NpZmllciB0byB0aGUgZGF0YSB3aXRoIHZhcmlvdXMgdmFsdWVzIG9mIGNvc3QsIGluIG9yZGVyIHRvIHByZWRpY3Qgd2hldGhlciBhIGNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlLiBSZXBvcnQgdGhlIGNyb3NzLXZhbGlkYXRpb24gZXJyb3JzIGFzc29jaWF0ZWQgd2l0aCBkaWZmZXJlbnQgdmFsdWVzIG9mIHRoaXMgcGFyYW1ldGVyLiBDb21tZW50IG9uIHlvdXIgcmVzdWx0cy4gTm90ZSB5b3Ugd2lsbCBuZWVkIHRvIGZpdCB0aGUgY2xhc3NpZmllciB3aXRob3V0IHRoZSBnYXMgbWlsZWFnZSB2YXJpYWJsZSB0byBwcm9kdWNlIHNlbnNpYmxlIHJlc3VsdHMuX18NCmBgYHtyfQ0KbGlicmFyeShlMTA3MSkNCg0Kc2V0LnNlZWQoMSkNCkF1dG9fc3ZtIDwtIEF1dG9bLCAhKG5hbWVzKEF1dG8pICVpbiUgYygibXBnIiwgIm5hbWUiKSldDQoNCnRyYWluX2NvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApDQp0dW5lX2xpbmVhciA8LSB0cmFpbihtcGcwMX4uLGRhdGE9QXV0b19zdm0sbWV0aG9kPSdzdm1MaW5lYXInLHRyQ29udHJvbD10cmFpbl9jb250cm9sDQogICAgICAgICAgICAgICAgICAgICAscHJlUHJvY2Vzcz1jKCdjZW50ZXInLCdzY2FsZScpLHR1bmVHcmlkPWV4cGFuZC5ncmlkKEM9YygwLjAxLCAwLjEsIDEsIDUsIDEwLCAxMDApKSkNCnR1bmVfbGluZWFyJHJlc3VsdHMNCmBgYA0KDQogIC0gVGhlIGNyb3NzLXZhbGlkYXRpb24gZXJyb3IgcmFuZ2VkIGZyb20gYXBwcm94aW1hdGVseSA4LjclIHRvIDkuNSUgYWNyb3NzIHRoZSB0ZXN0ZWQgY29zdCB2YWx1ZXMuIFRoZSBsb3dlc3QgZXJyb3Igb2NjdXJyZWQgd2hlbiBDID0gMTAsIGJ1dCB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgbW9kZWxzIHdlcmUgbWluaW1hbCwgaW5kaWNhdGluZyB0aGF0IGNoYW5naW5nIHRoZSBjb3N0IHBhcmFtZXRlciBoYWQgbGl0dGxlIGVmZmVjdCBvbiBjbGFzc2lmaWNhdGlvbiBwZXJmb3JtYW5jZS4NCg0KX18oYykgTm93IHJlcGVhdCAoYiksIHRoaXMgdGltZSB1c2luZyBTVk1zIHdpdGggcmFkaWFsIGFuZCBwb2x5bm9taWFsIGJhc2lzIGtlcm5lbHMsIHdpdGggZGlmZmVyZW50IHZhbHVlcyBvZiBnYW1tYSBhbmQgZGVncmVlIGFuZCBjb3N0LiBDb21tZW50IG9uIHlvdXIgcmVzdWx0cy5fXw0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHJhaW5fY29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCnR1bmVfcmFkaWFsIDwtIHRyYWluKG1wZzAxfi4sZGF0YT1BdXRvX3N2bSxtZXRob2Q9J3N2bVJhZGlhbCcsdHJDb250cm9sPXRyYWluX2NvbnRyb2wNCiAgICAgICAgICAgICAgICAgICAgICxwcmVQcm9jZXNzPWMoJ2NlbnRlcicsJ3NjYWxlJyksdHVuZUdyaWQ9DQogICAgICAgICAgICAgICAgICAgICAgIGV4cGFuZC5ncmlkKEMgPSBjKDAuMSwgMSwgNSwgMTAsIDEwMCksc2lnbWEgPSBjKDAuMDEsIDAuMSwgMSwgNSkpKQ0KDQp0dW5lX3JhZGlhbCRyZXN1bHRzDQpgYGANCg0KICAtIFRoZSByYWRpYWwgU1ZNIHByb2R1Y2VkIGNyb3NzLXZhbGlkYXRpb24gYWNjdXJhY2llcyBiZXR3ZWVuIGFwcHJveGltYXRlbHkgODkuMCUgYW5kIDkxLjMlLiBUaGUgYmVzdCBwZXJmb3JtYW5jZSBvY2N1cnJlZCB3aGVuIHNpZ21hID0gMC4xMCBhbmQgQyA9IDUgb3IgMTAsIHdpdGggYW4gYWNjdXJhY3kgb2YgYWJvdXQgOTEuMyUuIENvbXBhcmVkIHdpdGggdGhlIGxpbmVhciBTVk0sIHRoZSByYWRpYWwga2VybmVsIHNob3dlZCBvbmx5IGEgc2xpZ2h0IGltcHJvdmVtZW50LCBzdWdnZXN0aW5nIHRoYXQgYm90aCBtb2RlbHMgcGVyZm9ybWVkIHNpbWlsYXJseSBvbiB0aGlzIGRhdGFzZXQuDQogIA0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHJhaW5fY29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCnR1bmVfcG9seSA8LSB0cmFpbihtcGcwMX4uLGRhdGE9QXV0b19zdm0sbWV0aG9kPSdzdm1Qb2x5Jyx0ckNvbnRyb2w9dHJhaW5fY29udHJvbA0KICAgICAgICAgICAgICAgICAgICAgLHByZVByb2Nlc3M9YygnY2VudGVyJywnc2NhbGUnKSx0dW5lR3JpZD0NCiAgICAgICAgICAgICAgICAgICAgICBleHBhbmQuZ3JpZChDID0gYygwLjEsIDEsIDUsIDEwLCAxMDApLCBkZWdyZWUgPSBjKDIsIDMsIDQpLCBzY2FsZSA9IGMoMC4xLC4xKSkpDQp0dW5lX3BvbHkkcmVzdWx0cw0KYGBgDQoNCiAgLSBUaGUgcG9seW5vbWlhbCBTVk0gYWNoaWV2ZWQgYSBtYXhpbXVtIGNyb3NzLXZhbGlkYXRpb24gYWNjdXJhY3kgb2YgYXBwcm94aW1hdGVseSA5MS4xJSB3aXRoIGEgZGVncmVlIG9mIDMgYW5kIEMgPSA1LiBJdHMgcGVyZm9ybWFuY2Ugd2FzIHZlcnkgc2ltaWxhciB0byB0aGUgbGluZWFyIGFuZCByYWRpYWwgU1ZNcywgaW5kaWNhdGluZyB0aGF0IHRoZSBjaG9pY2Ugb2Yga2VybmVsIGhhZCBvbmx5IGEgbW9kZXN0IGltcGFjdCBvbiBwcmVkaWN0aW9uIGFjY3VyYWN5IGZvciB0aGlzIGRhdGFzZXQuDQoNCl9fKGQpIE1ha2Ugc29tZSBwbG90cyB0byBiYWNrIHVwIHlvdXIgYXNzZXJ0aW9ucyBpbiAoYikgYW5kIChjKS5fXw0KYGBge3J9DQpzdm1fYmVzdF9saW5lYXIgPC0gc3ZtKG1wZzAxIH4gLiwgZGF0YSA9IEF1dG9fc3ZtLCBrZXJuZWwgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgY29zdCA9IHR1bmVfbGluZWFyJGJlc3RUdW5lKQ0Kc3ZtX2Jlc3RfcmFkaWFsIDwtIHN2bShtcGcwMSB+IC4sIGRhdGEgPSBBdXRvX3N2bSwga2VybmVsID0gInJhZGlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgIGNvc3QgPSB0dW5lX3JhZGlhbCRiZXN0VHVuZSwNCiAgICAgICAgICAgICAgICAgICAgICAgc2lnbWEgPSB0dW5lX3JhZGlhbCRiZXN0VHVuZSkNCg0KcGxvdChzdm1fYmVzdF9saW5lYXIsIEF1dG9fc3ZtLCBob3JzZXBvd2VyIH4gd2VpZ2h0KQ0KcGxvdChzdm1fYmVzdF9yYWRpYWwsIEF1dG9fc3ZtLCBob3JzZXBvd2VyIH4gd2VpZ2h0KQ0KcGxvdChzdm1fYmVzdF9saW5lYXIsIEF1dG9fc3ZtLCBkaXNwbGFjZW1lbnQgfiBhY2NlbGVyYXRpb24pDQpgYGANCg0KICAtIFRoZXNlIHBhaXJ3aXNlIHBsb3RzIHNob3cgdGhlIGNsYXNzaWZpY2F0aW9uIHJlc3VsdHMgZnJvbSB0aGUgU1ZNIG1vZGVsIHVzaW5nIGhvcnNlcG93ZXIgdnMuIHdlaWdodCBhbmQgZGlzcGxhY2VtZW50IHZzLiBhY2NlbGVyYXRpb24uIFRoZSB2YXJpYWJsZXMgcHJvdmlkZSBhIHJlYXNvbmFibGUgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSB0d28gbXBnIGNsYXNzZXMsIGFzIGhlYXZpZXIgYW5kIG1vcmUgcG93ZXJmdWwgdmVoaWNsZXMgdGVuZCB0byBmYWxsIGludG8gdGhlIGxvdy1tcGcgY2F0ZWdvcnksIHdoaWxlIGxpZ2h0ZXIgdmVoaWNsZXMgdGVuZCB0byBmYWxsIGludG8gdGhlIGhpZ2gtbXBnIGNhdGVnb3J5Lg0KICANCi0tLQ0KDQojIyMgUHJvYmxlbSA4DQoNCl9fVGhpcyBwcm9ibGVtIGludm9sdmVzIHRoZSBPSiBkYXRhIHNldCB3aGljaCBpcyBwYXJ0IG9mIHRoZSBJU0xSMiBwYWNrYWdlLl9fDQoNCl9fKGEpIENyZWF0ZSBhIHRyYWluaW5nIHNldCBjb250YWluaW5nIGEgcmFuZG9tIHNhbXBsZSBvZiA4MDAgb2JzZXJ2YXRpb25zLCBhbmQgYSB0ZXN0IHNldCBjb250YWluaW5nIHRoZSByZW1haW5pbmcgb2JzZXJ2YXRpb25zLl9fDQpgYGB7cn0NCnNldC5zZWVkKDEpDQp0cmFpbl9vaiA8LSBzYW1wbGUoMTpucm93KE9KKSwgODAwKQ0Kb2pfdHJhaW4gPC0gT0pbdHJhaW5fb2osIF0NCm9qX3Rlc3QgIDwtIE9KWy10cmFpbl9vaiwgXQ0KYGBgDQoNCl9fKGIpIEZpdCBhIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgdG8gdGhlIHRyYWluaW5nIGRhdGEgdXNpbmcgY29zdCA9IDAuMDEsIHdpdGggUHVyY2hhc2UgYXMgdGhlIHJlc3BvbnNlIGFuZCB0aGUgb3RoZXIgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMuIFVzZSB0aGUgc3VtbWFyeSgpIGZ1bmN0aW9uIHRvIHByb2R1Y2Ugc3VtbWFyeSBzdGF0aXN0aWNzLCBhbmQgZGVzY3JpYmUgdGhlIHJlc3VsdHMgb2J0YWluZWQuX18NCmBgYHtyfQ0Kc3ZtX29qX2xpbmVhciA8LSB0cmFpbihQdXJjaGFzZX4uLGRhdGEgPSBval90cmFpbiwgbWV0aG9kID0gInN2bUxpbmVhciIsIA0KICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoQz0wLjAxKSkNCnN2bV9val9saW5lYXIkcmVzdWx0cw0KYGBgDQoNCiAgLSBXaXRoIGNvc3QgPSAwLjAxLCB0aGUgU1ZNIHVzZXMgYSBsYXJnZSBudW1iZXIgb2Ygc3VwcG9ydCB2ZWN0b3JzLiBUaGlzIGlzIGV4cGVjdGVkIGJlY2F1c2UgYSBzbWFsbCBjb3N0IHZhbHVlIGNyZWF0ZXMgYSB3aWRlciBtYXJnaW4gYW5kIGFsbG93cyBtb3JlIG9ic2VydmF0aW9ucyB0byBjb250cmlidXRlIHRvIGRlZmluaW5nIHRoZSBkZWNpc2lvbiBib3VuZGFyeS4NCg0KX18oYykgV2hhdCBhcmUgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9yIHJhdGVzP19fDQpgYGB7cn0NCnRyYWluX3ByZWRfbGluZWFyIDwtIHByZWRpY3Qoc3ZtX29qX2xpbmVhciwgb2pfdHJhaW4pDQp0ZXN0X3ByZWRfbGluZWFyICAgPC0gcHJlZGljdChzdm1fb2pfbGluZWFyLCBval90ZXN0KQ0KDQptZWFuKHRyYWluX3ByZWRfbGluZWFyICE9IG9qX3RyYWluJFB1cmNoYXNlKQ0KbWVhbih0ZXN0X3ByZWRfbGluZWFyICE9IG9qX3Rlc3QkUHVyY2hhc2UpDQpgYGANCg0KICAtIEJvdGggdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9yIHJhdGVzIGFyZSBhYm91dCAxNy0xOCUsIHNob3dpbmcgdXMgdGhlIGNsYXNzaWZpZXIgZ2VuZXJhbGl6ZXMgd2VsbCBhbmQgaXNuJ3Qgb3ZlcmZpdHRpbmcgZGVzcGl0ZSB0aGUgbG93IGNvc3QgdmFsdWUuDQoNCl9fKGQpIFVzZSB0aGUgdHVuZSgpIGZ1bmN0aW9uIHRvIHNlbGVjdCBhbiBvcHRpbWFsIGNvc3QuIENvbnNpZGVyIHZhbHVlcyBpbiB0aGUgcmFuZ2UgMC4wMSB0byAxMC5fXw0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHVuZV9val9saW5lYXIgPC0gdHJhaW4oUHVyY2hhc2V+LiwgZGF0YSA9IG9qX3RyYWluLCBtZXRob2QgPSAic3ZtTGluZWFyIiwgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKEMgPSBzZXEoMC4wMSwgMTAsIGxlbmd0aC5vdXQgPSAxMCkpKQ0KdHVuZV9val9saW5lYXIkcmVzdWx0cw0KdHVuZV9val9saW5lYXIkYmVzdFR1bmUNCmBgYA0KDQpfXyhlKSBDb21wdXRlIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBlcnJvciByYXRlcyB1c2luZyB0aGlzIG5ldyB2YWx1ZSBmb3IgY29zdC5fXw0KYGBge3J9DQp0cmFpbl9wcmVkX2xpbmVhciA8LSBwcmVkaWN0KHR1bmVfb2pfbGluZWFyLCBval90cmFpbikNCnRlc3RfcHJlZF9saW5lYXIgPC0gcHJlZGljdCh0dW5lX29qX2xpbmVhciwgb2pfdGVzdCkNCg0KbWVhbih0cmFpbl9wcmVkICE9IG9qX3RyYWluJFB1cmNoYXNlKQ0KbWVhbih0ZXN0X3ByZWQgIT0gb2pfdGVzdCRQdXJjaGFzZSkNCg0KYGBgDQoNCiAgLSBVc2luZyB0aGUgdHVuZWQgY29zdCB2YWx1ZSBwcm9kdWNlcyBhIHZlcnkgc2xpZ2h0IGltcHJvdmVtZW50IGluIGJvdGggdHJhaW5pbmcgYW5kIHRlc3QgZXJyb3IgY29tcGFyZWQgdG8gY29zdCA9IDAuMDEsIHRob3VnaCB0aGUgZGlmZmVyZW5jZSBpcyBzbWFsbCBzaW5jZSB0aGUgb3JpZ2luYWwgZXJyb3IgcmF0ZXMgd2VyZSBhbHJlYWR5IHByZXR0eSBsb3cuDQoNCl9fKGYpIFJlcGVhdCBwYXJ0cyAoYikgdGhyb3VnaCAoZSkgdXNpbmcgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIHdpdGggYSByYWRpYWwga2VybmVsLiBVc2UgdGhlIGRlZmF1bHQgdmFsdWUgZm9yIGdhbW1hLl9fDQpgYGB7cn0NCiNQbG95IHJlc3VsdHMgYW5kIGJlc3QgdHVuZSBiZWZvcmUgdHVuZQ0Kc3ZtX29qX3JhZGlhbCA8LSB0cmFpbihQdXJjaGFzZX4uLGRhdGEgPSBval90cmFpbiwgbWV0aG9kID0gInN2bVJhZGlhbCIsIA0KICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLCBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksIA0KICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKEM9MC4wMSxzaWdtYSA9IDAuMDEpKQ0Kc3ZtX29qX3JhZGlhbCRyZXN1bHRzDQoNCmBgYA0KDQpgYGB7cn0NCiNyYWRpYWwgdGVzdCB0cmFpbiBlcnJvcnMgYmVmb3JlIHR1bmUNCnRyYWluX3ByZWRfcmFkaWFsIDwtIHByZWRpY3Qoc3ZtX29qX3JhZGlhbCwgb2pfdHJhaW4pDQp0ZXN0X3ByZWRfcmFkaWFsIDwtIHByZWRpY3Qoc3ZtX29qX3JhZGlhbCwgb2pfdGVzdCkNCg0KbWVhbih0cmFpbl9wcmVkX3JhZGlhbCAhPSBval90cmFpbiRQdXJjaGFzZSkNCm1lYW4odGVzdF9wcmVkX3JhZGlhbCAhPSBval90ZXN0JFB1cmNoYXNlKQ0KYGBgDQoNCmBgYHtyfQ0KI1JhZGlhbCByZXN1bHRzIGFuZCBiZXN0IHR1bmUNCnNldC5zZWVkKDEpDQp0dW5lX29qX3JhZGlhbCA8LSB0cmFpbihQdXJjaGFzZX4uLCBkYXRhID0gb2pfdHJhaW4sIG1ldGhvZCA9ICJzdm1SYWRpYWwiLCB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMCkNCnR1bmVfb2pfcmFkaWFsJHJlc3VsdHMNCnR1bmVfb2pfcmFkaWFsJGJlc3RUdW5lDQoNCmBgYA0KDQpgYGB7cn0NCiNyYWRpYWwgdGVzdCB0cmFpbiBlcnJvcnMNCnRyYWluX3ByZWRfcmFkaWFsIDwtIHByZWRpY3QodHVuZV9val9yYWRpYWwsIG9qX3RyYWluKQ0KdGVzdF9wcmVkX3JhZGlhbCA8LSBwcmVkaWN0KHR1bmVfb2pfcmFkaWFsLCBval90ZXN0KQ0KDQptZWFuKHRyYWluX3ByZWRfcmFkaWFsICE9IG9qX3RyYWluJFB1cmNoYXNlKQ0KbWVhbih0ZXN0X3ByZWRfcmFkaWFsICE9IG9qX3Rlc3QkUHVyY2hhc2UpDQpgYGANCg0KICAtIFdpdGggY29zdCA9IDAuMDEsIHRoZSByYWRpYWwga2VybmVsIGVzc2VudGlhbGx5IHByZWRpY3RzIHRoZSBtYWpvcml0eSBjbGFzcyBmb3IgZXZlcnkgb2JzZXJ2YXRpb24sIHNpbmNlIHRoZSBkZWZhdWx0IGdhbW1hIGNvbWJpbmVkIHdpdGggc3VjaCBhIGxvdyBjb3N0IHByb2R1Y2VzIGFuIG92ZXJseSBzaW1wbGUgYm91bmRhcnkuIEFmdGVyIHR1bmluZyBjb3N0LCB0aGUgcmFkaWFsIGtlcm5lbCdzIHBlcmZvcm1hbmNlIGltcHJvdmVzIHN1YnN0YW50aWFsbHkgYW5kIGJlY29tZXMgY29tcGV0aXRpdmUgd2l0aCB0aGUgbGluZWFyIGtlcm5lbCwgYWNoaWV2aW5nIGEgdGVzdCBlcnJvciByYXRlIGluIGEgc2ltaWxhciByYW5nZSAocm91Z2hseSAxNy0xOSUpLg0KDQpfXyhnKSBSZXBlYXQgcGFydHMgKGIpIHRocm91Z2ggKGUpIHVzaW5nIGEgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZSB3aXRoIGEgcG9seW5vbWlhbCBrZXJuZWwuIFNldCBkZWdyZWUgPSAyLl9fDQpgYGB7cn0NCiNQbG95IHJlc3VsdHMgYW5kIGJlc3QgdHVuZQ0Kc3ZtX29qX3BvbHkgPC0gdHJhaW4oUHVyY2hhc2V+LixkYXRhID0gb2pfdHJhaW4sIG1ldGhvZCA9ICJzdm1Qb2x5IiwgDQogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwgDQogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoQz0wLjAxLHNjYWxlID0gMC4wMSwgZGVncmVlID0gMikpDQpzdm1fb2pfcG9seSRyZXN1bHRzDQpgYGANCg0KYGBge3J9DQojUG9seSB0ZXN0IHRyYWluIGVycm9ycyBiZWZvcmUgdHVuZQ0KdHJhaW5fcHJlZF9wb2x5IDwtIHByZWRpY3Qoc3ZtX29qX3BvbHksIG9qX3RyYWluKQ0KdGVzdF9wcmVkX3BvbHkgPC0gcHJlZGljdChzdm1fb2pfcG9seSwgb2pfdGVzdCkNCg0KbWVhbih0cmFpbl9wcmVkX3BvbHkgIT0gb2pfdHJhaW4kUHVyY2hhc2UpDQptZWFuKHRlc3RfcHJlZF9wb2x5ICE9IG9qX3Rlc3QkUHVyY2hhc2UpDQpgYGANCg0KYGBge3J9DQojUGxveSByZXN1bHRzIGFuZCBiZXN0IHR1bmUNCnNldC5zZWVkKDEpDQp0dW5lX29qX3BvbHkgPC0gdHJhaW4oUHVyY2hhc2V+LiwgZGF0YSA9IG9qX3RyYWluLCBtZXRob2QgPSAic3ZtUG9seSIsIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChkZWdyZWUgPSBjKDIsIDMsIDQpLHNjYWxlID0gMC4wMSxDID0gYygwLjAxLCAwLjEsIDEsIDEwKSkpDQp0dW5lX29qX3BvbHkkcmVzdWx0cw0KdHVuZV9val9wb2x5JGJlc3RUdW5lDQpgYGANCg0KYGBge3J9DQojUG9seSB0ZXN0IHRyYWluIGVycm9ycw0KdHJhaW5fcHJlZF9wb2x5IDwtIHByZWRpY3QodHVuZV9val9wb2x5LCBval90cmFpbikNCnRlc3RfcHJlZF9wb2x5IDwtIHByZWRpY3QodHVuZV9val9wb2x5LCBval90ZXN0KQ0KDQptZWFuKHRyYWluX3ByZWRfcG9seSAhPSBval90cmFpbiRQdXJjaGFzZSkNCm1lYW4odGVzdF9wcmVkX3BvbHkgIT0gb2pfdGVzdCRQdXJjaGFzZSkNCmBgYA0KDQogIC0gVGhlIHBvbHlub21pYWwga2VybmVsIHdpdGggYSBzbWFsbCBjb3N0IHZhbHVlIChDID0gMC4wMSkgcGVyZm9ybXMgcG9vcmx5IGNvbXBhcmVkIHdpdGggdGhlIHR1bmVkIG1vZGVsLiBBZnRlciB0dW5pbmcsIHRoZSBwb2x5bm9taWFsIFNWTSBpbXByb3ZlcyBpdHMgY2xhc3NpZmljYXRpb24gcGVyZm9ybWFuY2UsIGJ1dCBpdCBkb2VzIG5vdCBvdXRwZXJmb3JtIHRoZSBsaW5lYXIgb3IgcmFkaWFsIGtlcm5lbHMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgYWRkaXRpb25hbCBjb21wbGV4aXR5IGZyb20gdGhlIHBvbHlub21pYWwgYm91bmRhcnkgZG9lcyBub3Qgc2lnbmlmaWNhbnRseSBpbXByb3ZlIHByZWRpY3Rpb24gZm9yIHRoaXMgZGF0YXNldC4NCg0KX18oaCkgT3ZlcmFsbCwgd2hpY2ggYXBwcm9hY2ggc2VlbXMgdG8gZ2l2ZSB0aGUgYmVzdCByZXN1bHRzIG9uIHRoaXMgZGF0YT9fXw0KYGBge3J9DQpyZXN1bHRzX29qIDwtIGRhdGEuZnJhbWUoDQogIEtlcm5lbCA9IGMoIkxpbmVhciIsICJSYWRpYWwiLCAiUG9seW5vbWlhbCIpLA0KICBUZXN0X0Vycm9yID0gYygNCiAgICBtZWFuKHRlc3RfcHJlZF9saW5lYXIgIT0gb2pfdGVzdCRQdXJjaGFzZSksDQogICAgbWVhbih0ZXN0X3ByZWRfcmFkaWFsICE9IG9qX3Rlc3QkUHVyY2hhc2UpLA0KICAgIG1lYW4odGVzdF9wcmVkX3BvbHkgIT0gb2pfdGVzdCRQdXJjaGFzZSkNCiAgKSkNCnJlc3VsdHNfb2oNCmBgYA0KDQogIC0gT3ZlcmFsbCwgdGhlIHR1bmVkIGxpbmVhciBTVk0gcHJvZHVjZWQgdGhlIGxvd2VzdCB0ZXN0IGVycm9yIHJhdGUsIG91dHBlcmZvcm1pbmcgYm90aCB0aGUgcmFkaWFsIGFuZCBwb2x5bm9taWFsIGtlcm5lbHMuIFRoZSByYWRpYWwgYW5kIHBvbHlub21pYWwga2VybmVscyBoYWQgc2ltaWxhciBwZXJmb3JtYW5jZSwgd2l0aCBzbGlnaHRseSBoaWdoZXIgZXJyb3IgcmF0ZXMgYXJvdW5kIDE4JS4gVGhpcyBzdWdnZXN0cyB0aGF0IHRoZSBhZGRpdGlvbmFsIGNvbXBsZXhpdHkgcHJvdmlkZWQgYnkgdGhlIG5vbi1saW5lYXIga2VybmVscyBkaWQgbm90IGltcHJvdmUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgZm9yIHRoZSBPSiBkYXRhc2V0LCBhbmQgYSBzaW1wbGVyIGxpbmVhciBkZWNpc2lvbiBib3VuZGFyeSB3YXMgc3VmZmljaWVudC4NCg==