Question 5

We have seen that we can fit an SVM with a non-liner kernel order to perform classification using 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 behind to two classes with a quadratic decision boundary between them.

library(caret)
library(ggplot2)
library(e1071)

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 = factor(y)
)

head(data)
summary(data)
       X1                  X2            y      
 Min.   :-0.498163   Min.   :-0.498685   0:261  
 1st Qu.:-0.241871   1st Qu.:-0.242053   1:239  
 Median :-0.023730   Median :-0.000780          
 Mean   :-0.004345   Mean   : 0.003728          
 3rd Qu.: 0.234146   3rd Qu.: 0.251293          
 Max.   : 0.496077   Max.   : 0.499931          

b.)

Plot the observations, colored according to their class labels.

(x1 on x axis, x2 on y axis)

plot_boundary <- function(model, data, title){
  
  x1_grid <- seq(min(data$x1), max(data$x1), length.out = 200)
  x2_grid <- seq(min(data$x2), max(data$x2), length.out = 200)
  
  grid <- expand.grid(
    x1 = x1_grid,
    x2 = x2_grid
  )
  
  grid$pred <- predict(model, newdata = grid)
  
  ggplot(data, aes(x=x1, y=x2, color=y)) +
    geom_point(size=2) +
    geom_contour(
      data = grid,
      aes(z=as.numeric(pred)),
      breaks=c(1.5),
      color="black",
      linewidth=1
    ) +
    labs(
      title=title,
      x="X1",
      y="X2"
    ) +
    theme_minimal()
}
ggplot(data, aes(x=x1, y=x2, color=y)) +
  geom_point(size=2) +
  labs(
    title="Original Data",
    x="X1",
    y="X2"
  ) +
  theme_minimal()

c.)

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

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

summary(logistic_linear)

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.

linear_prob <- predict(
  logistic_linear,
  type="response"
)

linear_pred <- ifelse(
  linear_prob > .5,
  1,
  0
)

data$linear_pred <- factor(linear_pred)


ggplot(data, aes(x=x1, y=x2, color=linear_pred)) +
  geom_point(size=2) +
  labs(
    title="Linear Logistic Regression Predictions",
    x="X1",
    y="X2"
  ) +
  theme_minimal()

e.)

Now fit a logistic regression model to the data using non-linear functions of X1 and X2 as predictors.

logistic_quad <- glm(
  y ~ x1 + x2 + I(x1^2) + I(x2^2) + I(x1*x2),
  data=data,
  family=binomial
)

summary(logistic_quad)

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

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept)    -10.16     713.54  -0.014    0.989
x1              42.10   15492.58   0.003    0.998
x2             -66.81   14788.95  -0.005    0.996
I(x1^2)      16757.98  519013.02   0.032    0.974
I(x2^2)     -16671.65  508668.89  -0.033    0.974
I(x1 * x2)    -206.38   41802.81  -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. If it is not then repeat (a)-(e) until you come up with an example in which the predicted class labels are obviously non-linear.

quad_prob <- predict(
  logistic_quad,
  type="response"
)

quad_pred <- ifelse(
  quad_prob > .5,
  1,
  0
)

data$quad_pred <- factor(quad_pred)


ggplot(data, aes(x=x1, y=x2, color=quad_pred)) +
  geom_point(size=2) +
  labs(
    title="Quadratic Logistic Regression Predictions",
    x="X1",
    y="X2"
  ) +
  theme_minimal()

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.

svm_linear <- svm(
  y ~ x1 + x2,
  data=data,
  kernel="linear",
  cost=1
)

summary(svm_linear)

Call:
svm(formula = y ~ x1 + x2, data = data, kernel = "linear", 
    cost = 1)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  1 

Number of Support Vectors:  480

 ( 239 241 )


Number of Classes:  2 

Levels: 
 0 1
svm_linear_pred <- predict(
  svm_linear,
  data
)

data$svm_linear_pred <- svm_linear_pred


ggplot(data, aes(x=x1, y=x2, color=svm_linear_pred)) +
  geom_point(size=2) +
  labs(
    title="Linear Support Vector Classifier",
    x="X1",
    y="X2"
  ) +
  theme_minimal()

h.)

Fit a SVM vector classifier to the data with X1 and X2 as predictors. Obtain a class prediction for each training observations, colored according to the predicted class labels.

svm_nonlinear <- svm(
  y ~ x1 + x2,
  data=data,
  kernel="radial",
  cost=1,
  gamma=1
)

summary(svm_nonlinear)

Call:
svm(formula = y ~ x1 + x2, data = data, kernel = "radial", 
    cost = 1, gamma = 1)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 

Number of Support Vectors:  147

 ( 73 74 )


Number of Classes:  2 

Levels: 
 0 1
svm_nonlinear_pred <- predict(
  svm_nonlinear,
  data
)

data$svm_nonlinear_pred <- svm_nonlinear_pred


ggplot(data, aes(x=x1, y=x2, color=svm_nonlinear_pred)) +
  geom_point(size=2) +
  labs(
    title="Nonlinear SVM (Radial Kernel)",
    x="X1",
    y="X2"
  ) +
  theme_minimal()

i.) Results

Comment on your results.

The Support Vector Model and linear regression model both perform poorly, indicating a non-linear relationship between X1 and X2. Meanwhile the non-linear SVM produced the most flexible decision boundary since the kernel allows for a better separation of observations.

Question 7

In this problem, you will use support vector approached 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)
library(e1071)
library(caret)
library(ggplot2)

data(Auto)

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

Auto$mpg01 <- factor(Auto$mpg01,
                     levels = c(0,1),
                     labels = c("low", "high"))
table(Auto$mpg01)

 low high 
 196  196 

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.

set.seed(1)
train_index <- createDataPartition(Auto$mpg01,
                                   p = .7,
                                   list = FALSE)
train <- Auto[train_index,]
test <- Auto[-train_index,]

train.svm <- train[, !names(train) %in% c("mpg")]
Error in .rs.exprMutatesPackageLibrary(part) : 
  argument "part" is missing, with no default
test.svm <- test[, !names(test) %in% c("mpg")]
Error in .rs.exprMutatesPackageLibrary(part) : 
  argument "part" is missing, with no default
cost_values <- c(0.001,0.01,0.1,1,10,100)

linear_results <- data.frame(
  Cost = cost_values,
  CV_Error = NA
)


for(i in 1:length(cost_values)){
  
  svm_model <- svm(
    mpg01 ~ .,
    data=train.svm,
    kernel="linear",
    cost=cost_values[i],
    cross=10
  )
  
  linear_results$CV_Error[i] <- svm_model$tot.accuracy
}


linear_results

linear_results$CV_Error <- 100 - linear_results$CV_Error

linear_results

c.)

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

radial_results <- data.frame()


for(cost in c(0.01,0.1,1,10,100)){
  
  for(gamma in c(0.001,0.01,0.1,1)){
    
    svm_model <- svm(
      mpg01 ~ .,
      data=train.svm,
      kernel="radial",
      cost=cost,
      gamma=gamma,
      cross=10
    )
    
    radial_results <- rbind(
      radial_results,
      data.frame(
        Cost=cost,
        Gamma=gamma,
        Accuracy=svm_model$tot.accuracy,
        Error=100-svm_model$tot.accuracy
      )
    )
  }
}


radial_results
radial_results[which.min(radial_results$Error),]
Error in .rs.exprMutatesPackageLibrary(expr) : 
  argument "part" is missing, with no default
poly_results <- data.frame()


for(cost in c(0.01,0.1,1,10,100)){
  
  for(degree in c(2,3,4)){
    
    svm_model <- svm(
      mpg01 ~ .,
      data=train.svm,
      kernel="polynomial",
      cost=cost,
      degree=degree,
      cross=10
    )
    
    
    poly_results <- rbind(
      poly_results,
      data.frame(
        Cost=cost,
        Degree=degree,
        Accuracy=svm_model$tot.accuracy,
        Error=100-svm_model$tot.accuracy
      )
    )
  }
}


poly_results
poly_results[which.min(poly_results$Error),]
Error in .rs.exprMutatesPackageLibrary(expr) : 
  argument "part" is missing, with no default

d.)

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

best_linear <- linear_results[
which.min(linear_results$CV_Error),]

best_radial <- radial_results[
which.min(radial_results$Error),]

best_poly <- poly_results[
which.min(poly_results$Error),]


best_linear
best_radial
best_poly

ggplot(linear_results,
       aes(x=Cost,
           y=CV_Error))+
  geom_line()+
  geom_point()+
  scale_x_log10()+
  labs(
    title="Linear SVM Cross Validation Error",
    x="Cost",
    y="CV Error (%)"
  )

ggplot(radial_results,
       aes(x=Gamma,
           y=Error,
           color=factor(Cost)))+
  geom_line()+
  geom_point()+
  scale_x_log10()+
  labs(
    title="Radial SVM Error by Gamma and Cost",
    x="Gamma",
    y="CV Error (%)",
    color="Cost"
  )

ggplot(poly_results,
       aes(x=Degree,
           y=Error,
           color=factor(Cost)))+
  geom_point(size=3)+
  geom_line()+
  labs(
    title="Polynomial SVM Error by Degree and Cost",
    x="Polynomial Degree",
    y="CV Error (%)",
    color="Cost"
  )

Question 8

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

a.)

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

library(ISLR2)
library(e1071)
library(caret)

data(OJ)

set.seed(1)

train <- sample(1:nrow(OJ), 800)

train.oj <- OJ[train,]
test.oj <- OJ[-train,]

b.)

Fit a support vector classifier to the remaining data using cost = .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.linear.oj <- svm(Purchase ~ ., 
                     data = train.oj,
                     kernel = "linear",
                     cost = .01,
                     scale = TRUE)
summary(svm.linear.oj)

Call:
svm(formula = Purchase ~ ., data = train.oj, kernel = "linear", 
    cost = 0.01, scale = TRUE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  0.01 

Number of Support Vectors:  435

 ( 219 216 )


Number of Classes:  2 

Levels: 
 CH MM

c.)

What are the training and test error rates?

train.pred <- predict(svm.linear.oj, train.oj)

test.pred <- predict(svm.linear.oj, test.oj)

confusionMatrix(train.pred, train.oj$Purchase)
Confusion Matrix and Statistics

          Reference
Prediction  CH  MM
        CH 420  75
        MM  65 240
                                          
               Accuracy : 0.825           
                 95% CI : (0.7969, 0.8507)
    No Information Rate : 0.6062          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.6314          
                                          
 Mcnemar's Test P-Value : 0.4469          
                                          
            Sensitivity : 0.8660          
            Specificity : 0.7619          
         Pos Pred Value : 0.8485          
         Neg Pred Value : 0.7869          
             Prevalence : 0.6062          
         Detection Rate : 0.5250          
   Detection Prevalence : 0.6188          
      Balanced Accuracy : 0.8139          
                                          
       'Positive' Class : CH              
                                          
confusionMatrix(test.pred, test.oj$Purchase)
Confusion Matrix and Statistics

          Reference
Prediction  CH  MM
        CH 153  33
        MM  15  69
                                          
               Accuracy : 0.8222          
                 95% CI : (0.7713, 0.8659)
    No Information Rate : 0.6222          
    P-Value [Acc > NIR] : 6.769e-13       
                                          
                  Kappa : 0.6083          
                                          
 Mcnemar's Test P-Value : 0.01414         
                                          
            Sensitivity : 0.9107          
            Specificity : 0.6765          
         Pos Pred Value : 0.8226          
         Neg Pred Value : 0.8214          
             Prevalence : 0.6222          
         Detection Rate : 0.5667          
   Detection Prevalence : 0.6889          
      Balanced Accuracy : 0.7936          
                                          
       'Positive' Class : CH              
                                          
train.err <- mean(train.pred != train.oj$Purchase)
train.err
[1] 0.175

d.)

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

set.seed(1)

tune.linear <- tune(svm,
                    Purchase ~ .,
                    data = train.oj,
                    kernel = "linear",
                    ranges = list(cost = c(0.01,
                                           0.1,
                                           1,
                                           5,
                                           10)))

summary(tune.linear)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.1725 

- Detailed performance results:
best.linear <- tune.linear$best.model

summary(best.linear)

Call:
best.tune(METHOD = svm, train.x = Purchase ~ ., data = train.oj, 
    ranges = list(cost = c(0.01, 0.1, 1, 5, 10)), kernel = "linear")


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  0.1 

Number of Support Vectors:  342

 ( 171 171 )


Number of Classes:  2 

Levels: 
 CH MM

e.)

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

train.pred.best <- predict(best.linear, train.oj)
test.pred.best <- predict(best.linear, test.oj)

train.err.best <- mean(train.pred.best != train.oj$Purchase)
test.err.best <- mean(test.pred.best != test.oj$Purchase)

cat("Train Error:", train.err.best, "\n")
Train Error: 0.165 
cat("Test Error:", test.err.best, "\n")
Test Error: 0.162963 

f.)

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

svm.radial <- svm(
  Purchase ~ .,
  data = train.oj,
  kernel = "radial",
  cost = 0.01
)

summary(svm.radial)

Call:
svm(formula = Purchase ~ ., data = train.oj, kernel = "radial", 
    cost = 0.01)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  0.01 

Number of Support Vectors:  634

 ( 319 315 )


Number of Classes:  2 

Levels: 
 CH MM
# Errors

train.pred.radial <- predict(svm.radial, train.oj)

test.pred.radial <- predict(svm.radial, test.oj)

train.err.rad <- mean(train.pred.radial != train.oj$Purchase)

test.err.rad <- mean(test.pred.radial != test.oj$Purchase)

cat("Train Error:", train.err.rad, "\n")
Train Error: 0.39375 
cat("Test Error:", test.err.rad, "\n")
Test Error: 0.3777778 
#tuning

set.seed(1)

tune.radial <- tune(
  svm,
  Purchase ~ .,
  data = train.oj,
  kernel = "radial",
  ranges = list(cost = c(0.01,
                         0.1,
                         1,
                         5,
                         10))
)

summary(tune.radial)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.17125 

- Detailed performance results:
best.radial <- tune.radial$best.model

train.error.radial <-
  mean(predict(best.radial, train.oj) != train.oj$Purchase)

test.error.radial <-
  mean(predict(best.radial, test.oj) != test.oj$Purchase)

cat("Radial Training Error:", train.error.radial, "\n")
Radial Training Error: 0.15125 
cat("Radial Test Error:", test.error.radial, "\n")
Radial Test Error: 0.1851852 

g.)

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

svm.poly <- svm(
  Purchase ~ .,
  data = train.oj,
  kernel = "polynomial",
  degree = 2,
  cost = 0.01
)

summary(svm.poly)

Call:
svm(formula = Purchase ~ ., data = train.oj, kernel = "polynomial", 
    degree = 2, cost = 0.01)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  polynomial 
       cost:  0.01 
     degree:  2 
     coef.0:  0 

Number of Support Vectors:  636

 ( 321 315 )


Number of Classes:  2 

Levels: 
 CH MM
# Errors

train.pred.poly <- predict(svm.poly, train.oj)

test.pred.poly <- predict(svm.poly, test.oj)

mean(train.pred.poly != train.oj$Purchase)
[1] 0.3725
mean(test.pred.poly != test.oj$Purchase)
[1] 0.3666667
#Tuning:

set.seed(1)

tune.poly <- tune(
  svm,
  Purchase ~ .,
  data = train.oj,
  kernel = "polynomial",
  degree = 2,
  ranges = list(cost = c(0.01,
                         0.1,
                         1,
                         5,
                         10))
)

summary(tune.poly)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.18125 

- Detailed performance results:
best.poly <- tune.poly$best.model

train.error.poly <-
  mean(predict(best.poly, train.oj) != train.oj$Purchase)

test.error.poly <-
  mean(predict(best.poly, test.oj) != test.oj$Purchase)

train.error.poly
[1] 0.15
test.error.poly
[1] 0.1888889

h.)

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

The tuned SVM classifier with a linear kernel performed the best when compared to the other SVM classifiers. It achieved both the lowest training and testing errors.

LS0tDQp0aXRsZTogIlN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIg0KYXV0aG9yOiAiQXNobGV5IFRvcnJlcyINCmRhdGU6ICIyMDI2LTA3LTIwIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2MtZGVwdGg6IDMNCiAgICB0aGVtZTogY29zbW8NCi0tLQ0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICByZXN1bHRzID0gJ2hvbGQnLA0KICBmaWcuc2hvdyA9ICdob2xkJw0KKQ0KYGBgDQojIyBRdWVzdGlvbiA1DQoNCldlIGhhdmUgc2VlbiB0aGF0IHdlIGNhbiBmaXQgYW4gU1ZNIHdpdGggYSBub24tbGluZXIga2VybmVsIG9yZGVyIHRvIHBlcmZvcm0gY2xhc3NpZmljYXRpb24gdXNpbmcgYSBub24tbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5IGJ5IHBlcmZvcm1pbmcgbG9naXN0aWMgcmVncmVzc2lvbiB1c2luZyBub24tbGluZWFyIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgZmVhdHVyZXMuIA0KDQojIyMgYS4pIA0KR2VuZXJhdGUgYSBkYXRhIHNldCB3aXRoIG4gPSA1MDAgYW5kIHAgPSAyLCBzdWNoIHRoYXQgdGhlIG9ic2VydmF0aW9ucyBiZWhpbmQgdG8gdHdvIGNsYXNzZXMgd2l0aCBhIHF1YWRyYXRpYyBkZWNpc2lvbiBib3VuZGFyeSBiZXR3ZWVuIHRoZW0uIA0KDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGUxMDcxKQ0KDQpzZXQuc2VlZCgxKQ0KDQp4MSA8LSBydW5pZig1MDApIC0gMC41DQp4MiA8LSBydW5pZig1MDApIC0gMC41DQoNCnkgPC0gMSAqICh4MV4yIC0geDJeMiA+IDApDQoNCmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgWDEgPSB4MSwNCiAgWDIgPSB4MiwNCiAgeSA9IGZhY3Rvcih5KQ0KKQ0KDQpoZWFkKGRhdGEpDQpzdW1tYXJ5KGRhdGEpDQpgYGANCg0KDQojIyMgYi4pIA0KUGxvdCB0aGUgb2JzZXJ2YXRpb25zLCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGVpciBjbGFzcyBsYWJlbHMuDQoNCih4MSBvbiB4IGF4aXMsIHgyIG9uIHkgYXhpcykNCg0KYGBge3J9DQpwbG90X2JvdW5kYXJ5IDwtIGZ1bmN0aW9uKG1vZGVsLCBkYXRhLCB0aXRsZSl7DQogIA0KICB4MV9ncmlkIDwtIHNlcShtaW4oZGF0YSR4MSksIG1heChkYXRhJHgxKSwgbGVuZ3RoLm91dCA9IDIwMCkNCiAgeDJfZ3JpZCA8LSBzZXEobWluKGRhdGEkeDIpLCBtYXgoZGF0YSR4MiksIGxlbmd0aC5vdXQgPSAyMDApDQogIA0KICBncmlkIDwtIGV4cGFuZC5ncmlkKA0KICAgIHgxID0geDFfZ3JpZCwNCiAgICB4MiA9IHgyX2dyaWQNCiAgKQ0KICANCiAgZ3JpZCRwcmVkIDwtIHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSBncmlkKQ0KICANCiAgZ2dwbG90KGRhdGEsIGFlcyh4PXgxLCB5PXgyLCBjb2xvcj15KSkgKw0KICAgIGdlb21fcG9pbnQoc2l6ZT0yKSArDQogICAgZ2VvbV9jb250b3VyKA0KICAgICAgZGF0YSA9IGdyaWQsDQogICAgICBhZXMoej1hcy5udW1lcmljKHByZWQpKSwNCiAgICAgIGJyZWFrcz1jKDEuNSksDQogICAgICBjb2xvcj0iYmxhY2siLA0KICAgICAgbGluZXdpZHRoPTENCiAgICApICsNCiAgICBsYWJzKA0KICAgICAgdGl0bGU9dGl0bGUsDQogICAgICB4PSJYMSIsDQogICAgICB5PSJYMiINCiAgICApICsNCiAgICB0aGVtZV9taW5pbWFsKCkNCn0NCmBgYA0KDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEsIGFlcyh4PXgxLCB5PXgyLCBjb2xvcj15KSkgKw0KICBnZW9tX3BvaW50KHNpemU9MikgKw0KICBsYWJzKA0KICAgIHRpdGxlPSJPcmlnaW5hbCBEYXRhIiwNCiAgICB4PSJYMSIsDQogICAgeT0iWDIiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCiMjIyBjLikNCkZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdG8gdGhlIGRhdGEsIHVzaW5nIFgxIGFuZCBYMiBhcyBwcmVkaWN0b3JzLg0KDQpgYGB7cn0NCmxvZ2lzdGljX2xpbmVhciA8LSBnbG0oDQogIHkgfiB4MSArIHgyLA0KICBkYXRhPWRhdGEsDQogIGZhbWlseT1iaW5vbWlhbA0KKQ0KDQpzdW1tYXJ5KGxvZ2lzdGljX2xpbmVhcikNCmBgYA0KDQojIyMgZC4pIA0KQXBwbHkgdGhpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBvYnRhaW4gYSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWwgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbHMuIFRoZSBkZWNpc2lvbiBib3VuZGFyeSBzaG91bGQgYmUgbGluZWFyLiANCg0KYGBge3J9DQpsaW5lYXJfcHJvYiA8LSBwcmVkaWN0KA0KICBsb2dpc3RpY19saW5lYXIsDQogIHR5cGU9InJlc3BvbnNlIg0KKQ0KDQpsaW5lYXJfcHJlZCA8LSBpZmVsc2UoDQogIGxpbmVhcl9wcm9iID4gLjUsDQogIDEsDQogIDANCikNCg0KZGF0YSRsaW5lYXJfcHJlZCA8LSBmYWN0b3IobGluZWFyX3ByZWQpDQoNCg0KZ2dwbG90KGRhdGEsIGFlcyh4PXgxLCB5PXgyLCBjb2xvcj1saW5lYXJfcHJlZCkpICsNCiAgZ2VvbV9wb2ludChzaXplPTIpICsNCiAgbGFicygNCiAgICB0aXRsZT0iTGluZWFyIExvZ2lzdGljIFJlZ3Jlc3Npb24gUHJlZGljdGlvbnMiLA0KICAgIHg9IlgxIiwNCiAgICB5PSJYMiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCiMjIyBlLikgDQpOb3cgZml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byB0aGUgZGF0YSB1c2luZyBub24tbGluZWFyIGZ1bmN0aW9ucyBvZiBYMSBhbmQgWDIgYXMgcHJlZGljdG9ycy4gDQoNCmBgYHtyfQ0KbG9naXN0aWNfcXVhZCA8LSBnbG0oDQogIHkgfiB4MSArIHgyICsgSSh4MV4yKSArIEkoeDJeMikgKyBJKHgxKngyKSwNCiAgZGF0YT1kYXRhLA0KICBmYW1pbHk9Ymlub21pYWwNCikNCg0Kc3VtbWFyeShsb2dpc3RpY19xdWFkKQ0KYGBgDQoNCiMjIyBmLikgDQpBcHBseSB0aGlzIG1vZGVsIHRvIHRoZSB0cmFpbmluZyBkYXRhIGluIG9yZGVyIHRvIG9idGFpbiBhIHByZWRpY3RlZCBjbGFzcyBsYWJlbCBmb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbi4gUGxvdCB0aGUgb2JzZXJ2YXRpb25zLCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgcHJlZGljdGVkIGNsYXNzIGxhYmVscy4gVGhlIGRlY2lzaW9uIGJvdW5kYXJ5IHNob3VsZCBiZSBvYnZpb3VzbHkgbm9uLWxpbmVhci4gSWYgaXQgaXMgbm90IHRoZW4gcmVwZWF0IChhKS0oZSkgdW50aWwgeW91IGNvbWUgdXAgd2l0aCBhbiBleGFtcGxlIGluIHdoaWNoIHRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzIGFyZSBvYnZpb3VzbHkgbm9uLWxpbmVhci4gDQoNCmBgYHtyfQ0KcXVhZF9wcm9iIDwtIHByZWRpY3QoDQogIGxvZ2lzdGljX3F1YWQsDQogIHR5cGU9InJlc3BvbnNlIg0KKQ0KDQpxdWFkX3ByZWQgPC0gaWZlbHNlKA0KICBxdWFkX3Byb2IgPiAuNSwNCiAgMSwNCiAgMA0KKQ0KDQpkYXRhJHF1YWRfcHJlZCA8LSBmYWN0b3IocXVhZF9wcmVkKQ0KDQoNCmdncGxvdChkYXRhLCBhZXMoeD14MSwgeT14MiwgY29sb3I9cXVhZF9wcmVkKSkgKw0KICBnZW9tX3BvaW50KHNpemU9MikgKw0KICBsYWJzKA0KICAgIHRpdGxlPSJRdWFkcmF0aWMgTG9naXN0aWMgUmVncmVzc2lvbiBQcmVkaWN0aW9ucyIsDQogICAgeD0iWDEiLA0KICAgIHk9IlgyIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KDQojIyMgZy4pIA0KRml0IGEgc3VwcG9ydCB2ZWN0b3IgY2xhc3NpZmllciB0byB0aGUgZGF0YSB3aXRoIFgxIGFuZCBYMiBhcyBwcmVkaWN0b3JzLiBPYnRhaW4gYSBjbGFzcyBwcmVkaWN0aW9uIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9uLiBQbG90IHRoZSBvYnNlcnZhdGlvbnMsIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzLiANCg0KYGBge3J9DQpzdm1fbGluZWFyIDwtIHN2bSgNCiAgeSB+IHgxICsgeDIsDQogIGRhdGE9ZGF0YSwNCiAga2VybmVsPSJsaW5lYXIiLA0KICBjb3N0PTENCikNCg0Kc3VtbWFyeShzdm1fbGluZWFyKQ0KDQpzdm1fbGluZWFyX3ByZWQgPC0gcHJlZGljdCgNCiAgc3ZtX2xpbmVhciwNCiAgZGF0YQ0KKQ0KDQpkYXRhJHN2bV9saW5lYXJfcHJlZCA8LSBzdm1fbGluZWFyX3ByZWQNCg0KDQpnZ3Bsb3QoZGF0YSwgYWVzKHg9eDEsIHk9eDIsIGNvbG9yPXN2bV9saW5lYXJfcHJlZCkpICsNCiAgZ2VvbV9wb2ludChzaXplPTIpICsNCiAgbGFicygNCiAgICB0aXRsZT0iTGluZWFyIFN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXIiLA0KICAgIHg9IlgxIiwNCiAgICB5PSJYMiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KIyMjIGguKSANCkZpdCBhIFNWTSB2ZWN0b3IgY2xhc3NpZmllciB0byB0aGUgZGF0YSB3aXRoIFgxIGFuZCBYMiBhcyBwcmVkaWN0b3JzLiBPYnRhaW4gYSBjbGFzcyBwcmVkaWN0aW9uIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbHMuIA0KDQpgYGB7cn0NCnN2bV9ub25saW5lYXIgPC0gc3ZtKA0KICB5IH4geDEgKyB4MiwNCiAgZGF0YT1kYXRhLA0KICBrZXJuZWw9InJhZGlhbCIsDQogIGNvc3Q9MSwNCiAgZ2FtbWE9MQ0KKQ0KDQpzdW1tYXJ5KHN2bV9ub25saW5lYXIpDQoNCnN2bV9ub25saW5lYXJfcHJlZCA8LSBwcmVkaWN0KA0KICBzdm1fbm9ubGluZWFyLA0KICBkYXRhDQopDQoNCmRhdGEkc3ZtX25vbmxpbmVhcl9wcmVkIDwtIHN2bV9ub25saW5lYXJfcHJlZA0KDQoNCmdncGxvdChkYXRhLCBhZXMoeD14MSwgeT14MiwgY29sb3I9c3ZtX25vbmxpbmVhcl9wcmVkKSkgKw0KICBnZW9tX3BvaW50KHNpemU9MikgKw0KICBsYWJzKA0KICAgIHRpdGxlPSJOb25saW5lYXIgU1ZNIChSYWRpYWwgS2VybmVsKSIsDQogICAgeD0iWDEiLA0KICAgIHk9IlgyIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KDQojIyMgaS4pIFJlc3VsdHMNCkNvbW1lbnQgb24geW91ciByZXN1bHRzLiANCg0KVGhlIFN1cHBvcnQgVmVjdG9yIE1vZGVsIGFuZCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBib3RoIHBlcmZvcm0gcG9vcmx5LCBpbmRpY2F0aW5nIGEgbm9uLWxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBYMSBhbmQgWDIuIE1lYW53aGlsZSB0aGUgbm9uLWxpbmVhciBTVk0gcHJvZHVjZWQgdGhlIG1vc3QgZmxleGlibGUgZGVjaXNpb24gYm91bmRhcnkgc2luY2UgdGhlIGtlcm5lbCBhbGxvd3MgZm9yIGEgYmV0dGVyIHNlcGFyYXRpb24gb2Ygb2JzZXJ2YXRpb25zLiANCg0KDQojIyBRdWVzdGlvbiA3DQpJbiB0aGlzIHByb2JsZW0sIHlvdSB3aWxsIHVzZSBzdXBwb3J0IHZlY3RvciBhcHByb2FjaGVkIGluIG9yZGVyIHRvIHByZWRpY3Qgd2hldGhlciBhIGdpdmVuIGNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlIGJhc2VkIG9uIHRoZSBBdXRvIGRhdGEgc2V0LiANCg0KDQojIyMgYS4pIA0KQ3JlYXRlIGEgYmluYXJ5IHZhcmlhYmxlIHRoYXQgdGFrZXMgb24gYSAxIGZvciBjYXJzIHdpdGggZ2FzIG1pbGVhZ2UgYWJvdmUgdGhlIG1lZGlhbiwgYW5kIGEgMCBmb3IgY2FycyB3aXRoIGdhcyBtaWxlYWdlIGJlbG93IHRoZSBtZWRpYW4uIA0KYGBge3J9DQpsaWJyYXJ5KElTTFIyKQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCmRhdGEoQXV0bykNCg0KbWVkaWFuLm1wZyA8LSBtZWRpYW4oQXV0byRtcGcpDQpBdXRvJG1wZzAxIDwtIGlmZWxzZShBdXRvJG1wZyA+IG1lZGlhbi5tcGcsIDEsIDApDQoNCkF1dG8kbXBnMDEgPC0gZmFjdG9yKEF1dG8kbXBnMDEsDQogICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKDAsMSksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJsb3ciLCAiaGlnaCIpKQ0KdGFibGUoQXV0byRtcGcwMSkNCmBgYA0KDQoNCiMjIyBiLikNCkZpdCBhIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgdG8gdGhlIGRhdGEgd2l0aCB2YXJpb3VzIHZhbHVlcyBvZiBjb3N0LCBpbiBvcmRlciB0byBwcmVkaWN0IHdoZXRoZXIgYSBjYXIgZ2V0cyBoaWdoIG9yIGxvdyBnYXMgbWlsZWFnZS4gUmVwb3J0IHRoZSBjcm9zcy12YWxpZGF0aW9uIGVycm9ycyBhc3NvY2lhdGVkIHdpdGggZGlmZmVyZW50IHZhbHVlcyBvZiB0aGlzIHBhcmFtZXRlci4gQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuIE5vdGUgeW91IHdpbGwgbmVlZCB0byBmaXQgdGhlIGNsYXNzaWZpZXIgd2l0aG91dCB0aGUgZ2FzIG1pbGVhZ2UgdmFyaWFibGUgdG8gcHJvZHVjZSBzZW5zaWJsZSByZXN1bHRzLiANCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oQXV0byRtcGcwMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcCA9IC43LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UpDQp0cmFpbiA8LSBBdXRvW3RyYWluX2luZGV4LF0NCnRlc3QgPC0gQXV0b1stdHJhaW5faW5kZXgsXQ0KDQp0cmFpbi5zdm0gPC0gdHJhaW5bLCAhbmFtZXModHJhaW4pICVpbiUgYygibXBnIildDQp0ZXN0LnN2bSA8LSB0ZXN0WywgIW5hbWVzKHRlc3QpICVpbiUgYygibXBnIildDQoNCmNvc3RfdmFsdWVzIDwtIGMoMC4wMDEsMC4wMSwwLjEsMSwxMCwxMDApDQoNCmxpbmVhcl9yZXN1bHRzIDwtIGRhdGEuZnJhbWUoDQogIENvc3QgPSBjb3N0X3ZhbHVlcywNCiAgQ1ZfRXJyb3IgPSBOQQ0KKQ0KDQoNCmZvcihpIGluIDE6bGVuZ3RoKGNvc3RfdmFsdWVzKSl7DQogIA0KICBzdm1fbW9kZWwgPC0gc3ZtKA0KICAgIG1wZzAxIH4gLiwNCiAgICBkYXRhPXRyYWluLnN2bSwNCiAgICBrZXJuZWw9ImxpbmVhciIsDQogICAgY29zdD1jb3N0X3ZhbHVlc1tpXSwNCiAgICBjcm9zcz0xMA0KICApDQogIA0KICBsaW5lYXJfcmVzdWx0cyRDVl9FcnJvcltpXSA8LSBzdm1fbW9kZWwkdG90LmFjY3VyYWN5DQp9DQoNCg0KbGluZWFyX3Jlc3VsdHMNCg0KbGluZWFyX3Jlc3VsdHMkQ1ZfRXJyb3IgPC0gMTAwIC0gbGluZWFyX3Jlc3VsdHMkQ1ZfRXJyb3INCg0KbGluZWFyX3Jlc3VsdHMNCmBgYA0KDQoNCiMjIyBjLikgDQpOb3cgcmVwZWF0IChiKSwgdGhpcyB0aW1lIHVzaW5nIFNWTSB3aXRoIHJhZGlhbCBhbmQgcG9seW5vbWlhbCBiYXNpcyBrZXJuZWxzLCB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2YgZ2FtbWEgYW5kIGRlZ3JlZSBhbmQgY29zdC4gQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuIA0KDQpgYGB7cn0NCnJhZGlhbF9yZXN1bHRzIDwtIGRhdGEuZnJhbWUoKQ0KDQoNCmZvcihjb3N0IGluIGMoMC4wMSwwLjEsMSwxMCwxMDApKXsNCiAgDQogIGZvcihnYW1tYSBpbiBjKDAuMDAxLDAuMDEsMC4xLDEpKXsNCiAgICANCiAgICBzdm1fbW9kZWwgPC0gc3ZtKA0KICAgICAgbXBnMDEgfiAuLA0KICAgICAgZGF0YT10cmFpbi5zdm0sDQogICAgICBrZXJuZWw9InJhZGlhbCIsDQogICAgICBjb3N0PWNvc3QsDQogICAgICBnYW1tYT1nYW1tYSwNCiAgICAgIGNyb3NzPTEwDQogICAgKQ0KICAgIA0KICAgIHJhZGlhbF9yZXN1bHRzIDwtIHJiaW5kKA0KICAgICAgcmFkaWFsX3Jlc3VsdHMsDQogICAgICBkYXRhLmZyYW1lKA0KICAgICAgICBDb3N0PWNvc3QsDQogICAgICAgIEdhbW1hPWdhbW1hLA0KICAgICAgICBBY2N1cmFjeT1zdm1fbW9kZWwkdG90LmFjY3VyYWN5LA0KICAgICAgICBFcnJvcj0xMDAtc3ZtX21vZGVsJHRvdC5hY2N1cmFjeQ0KICAgICAgKQ0KICAgICkNCiAgfQ0KfQ0KDQoNCnJhZGlhbF9yZXN1bHRzDQpyYWRpYWxfcmVzdWx0c1t3aGljaC5taW4ocmFkaWFsX3Jlc3VsdHMkRXJyb3IpLF0NCmBgYA0KDQpgYGB7cn0NCnBvbHlfcmVzdWx0cyA8LSBkYXRhLmZyYW1lKCkNCg0KDQpmb3IoY29zdCBpbiBjKDAuMDEsMC4xLDEsMTAsMTAwKSl7DQogIA0KICBmb3IoZGVncmVlIGluIGMoMiwzLDQpKXsNCiAgICANCiAgICBzdm1fbW9kZWwgPC0gc3ZtKA0KICAgICAgbXBnMDEgfiAuLA0KICAgICAgZGF0YT10cmFpbi5zdm0sDQogICAgICBrZXJuZWw9InBvbHlub21pYWwiLA0KICAgICAgY29zdD1jb3N0LA0KICAgICAgZGVncmVlPWRlZ3JlZSwNCiAgICAgIGNyb3NzPTEwDQogICAgKQ0KICAgIA0KICAgIA0KICAgIHBvbHlfcmVzdWx0cyA8LSByYmluZCgNCiAgICAgIHBvbHlfcmVzdWx0cywNCiAgICAgIGRhdGEuZnJhbWUoDQogICAgICAgIENvc3Q9Y29zdCwNCiAgICAgICAgRGVncmVlPWRlZ3JlZSwNCiAgICAgICAgQWNjdXJhY3k9c3ZtX21vZGVsJHRvdC5hY2N1cmFjeSwNCiAgICAgICAgRXJyb3I9MTAwLXN2bV9tb2RlbCR0b3QuYWNjdXJhY3kNCiAgICAgICkNCiAgICApDQogIH0NCn0NCg0KDQpwb2x5X3Jlc3VsdHMNCnBvbHlfcmVzdWx0c1t3aGljaC5taW4ocG9seV9yZXN1bHRzJEVycm9yKSxdDQpgYGANCg0KIyMjIGQuKSANCk1ha2Ugc29tZSBwbG90cyB0byBiYWNrIHVwIHlvdXIgYXNzZXJ0aW9ucyBpbiAoYikgYW5kIChjKS4NCg0KYGBge3J9DQpiZXN0X2xpbmVhciA8LSBsaW5lYXJfcmVzdWx0c1sNCndoaWNoLm1pbihsaW5lYXJfcmVzdWx0cyRDVl9FcnJvciksXQ0KDQpiZXN0X3JhZGlhbCA8LSByYWRpYWxfcmVzdWx0c1sNCndoaWNoLm1pbihyYWRpYWxfcmVzdWx0cyRFcnJvciksXQ0KDQpiZXN0X3BvbHkgPC0gcG9seV9yZXN1bHRzWw0Kd2hpY2gubWluKHBvbHlfcmVzdWx0cyRFcnJvciksXQ0KDQoNCmJlc3RfbGluZWFyDQpiZXN0X3JhZGlhbA0KYmVzdF9wb2x5DQoNCmdncGxvdChsaW5lYXJfcmVzdWx0cywNCiAgICAgICBhZXMoeD1Db3N0LA0KICAgICAgICAgICB5PUNWX0Vycm9yKSkrDQogIGdlb21fbGluZSgpKw0KICBnZW9tX3BvaW50KCkrDQogIHNjYWxlX3hfbG9nMTAoKSsNCiAgbGFicygNCiAgICB0aXRsZT0iTGluZWFyIFNWTSBDcm9zcyBWYWxpZGF0aW9uIEVycm9yIiwNCiAgICB4PSJDb3N0IiwNCiAgICB5PSJDViBFcnJvciAoJSkiDQogICkNCmdncGxvdChyYWRpYWxfcmVzdWx0cywNCiAgICAgICBhZXMoeD1HYW1tYSwNCiAgICAgICAgICAgeT1FcnJvciwNCiAgICAgICAgICAgY29sb3I9ZmFjdG9yKENvc3QpKSkrDQogIGdlb21fbGluZSgpKw0KICBnZW9tX3BvaW50KCkrDQogIHNjYWxlX3hfbG9nMTAoKSsNCiAgbGFicygNCiAgICB0aXRsZT0iUmFkaWFsIFNWTSBFcnJvciBieSBHYW1tYSBhbmQgQ29zdCIsDQogICAgeD0iR2FtbWEiLA0KICAgIHk9IkNWIEVycm9yICglKSIsDQogICAgY29sb3I9IkNvc3QiDQogICkNCmdncGxvdChwb2x5X3Jlc3VsdHMsDQogICAgICAgYWVzKHg9RGVncmVlLA0KICAgICAgICAgICB5PUVycm9yLA0KICAgICAgICAgICBjb2xvcj1mYWN0b3IoQ29zdCkpKSsNCiAgZ2VvbV9wb2ludChzaXplPTMpKw0KICBnZW9tX2xpbmUoKSsNCiAgbGFicygNCiAgICB0aXRsZT0iUG9seW5vbWlhbCBTVk0gRXJyb3IgYnkgRGVncmVlIGFuZCBDb3N0IiwNCiAgICB4PSJQb2x5bm9taWFsIERlZ3JlZSIsDQogICAgeT0iQ1YgRXJyb3IgKCUpIiwNCiAgICBjb2xvcj0iQ29zdCINCiAgKQ0KYGBgDQoNCg0KIyMgUXVlc3Rpb24gOA0KVGhpcyBwcm9ibGVtIGludm9sdmVzIHRoZSBPSiBkYXRhIHNldCB3aGljaCBpcyBhIHBhcnQgb2YgdGhlIElTTFIgcGFja2FnZS4NCg0KIyMjIGEuKSANCkNyZWF0ZSBhIHRyYWluaW5nIHNldCBjb250YWluaW5nIGEgcmFuZG9tIHNhbXBsZSBvZiA4MDAgb2JzZXJ2YXRpb25zLCBhbmQgYSB0ZXN0IHNldCBjb250YWluaW5nIHRoZSByZW1haW5pbmcgb2JzZXJ2YXRpb25zLiANCg0KYGBge3J9DQpsaWJyYXJ5KElTTFIyKQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoY2FyZXQpDQoNCmRhdGEoT0opDQoNCnNldC5zZWVkKDEpDQoNCnRyYWluIDwtIHNhbXBsZSgxOm5yb3coT0opLCA4MDApDQoNCnRyYWluLm9qIDwtIE9KW3RyYWluLF0NCnRlc3Qub2ogPC0gT0pbLXRyYWluLF0NCmBgYA0KDQoNCiMjIyBiLikgDQpGaXQgYSBzdXBwb3J0IHZlY3RvciBjbGFzc2lmaWVyIHRvIHRoZSByZW1haW5pbmcgZGF0YSB1c2luZyBjb3N0ID0gLjAxLCB3aXRoIFB1cmNoYXNlIGFzIHRoZSByZXNwb25zZSBhbmQgdGhlIG90aGVyIHZhcmlhYmxlcyBhcyBwcmVkaWN0b3JzLiBVc2UgdGhlIHN1bW1hcnkoKSBmdW5jdGlvbiB0byBwcm9kdWNlIHN1bW1hcnkgc3RhdGlzdGljcywgYW5kIGRlc2NyaWJlIHRoZSByZXN1bHRzIG9idGFpbmVkLiANCg0KYGBge3J9DQpzdm0ubGluZWFyLm9qIDwtIHN2bShQdXJjaGFzZSB+IC4sIA0KICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLm9qLA0KICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gImxpbmVhciIsDQogICAgICAgICAgICAgICAgICAgICBjb3N0ID0gLjAxLA0KICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBUUlVFKQ0Kc3VtbWFyeShzdm0ubGluZWFyLm9qKQ0KYGBgDQoNCg0KIyMjIGMuKSANCldoYXQgYXJlIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBlcnJvciByYXRlcz8NCg0KYGBge3J9DQp0cmFpbi5wcmVkIDwtIHByZWRpY3Qoc3ZtLmxpbmVhci5vaiwgdHJhaW4ub2opDQoNCnRlc3QucHJlZCA8LSBwcmVkaWN0KHN2bS5saW5lYXIub2osIHRlc3Qub2opDQoNCmNvbmZ1c2lvbk1hdHJpeCh0cmFpbi5wcmVkLCB0cmFpbi5vaiRQdXJjaGFzZSkNCmNvbmZ1c2lvbk1hdHJpeCh0ZXN0LnByZWQsIHRlc3Qub2okUHVyY2hhc2UpDQoNCnRyYWluLmVyciA8LSBtZWFuKHRyYWluLnByZWQgIT0gdHJhaW4ub2okUHVyY2hhc2UpDQp0cmFpbi5lcnINCmBgYA0KDQoNCiMjIyBkLikgDQpVc2UgdGhlIHR1bmUoKSBmdW5jdGlvbiB0byBzZWxlY3QgYW4gb3B0aW1hbCBjb3N0LiBDb25zaWRlciB2YWx1ZXMgaW4gdGhlIHJhbmdlIDAuMDEgdG8gMTAuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KdHVuZS5saW5lYXIgPC0gdHVuZShzdm0sDQogICAgICAgICAgICAgICAgICAgIFB1cmNoYXNlIH4gLiwNCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLm9qLA0KICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgcmFuZ2VzID0gbGlzdChjb3N0ID0gYygwLjAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAuMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTApKSkNCg0Kc3VtbWFyeSh0dW5lLmxpbmVhcikNCg0KYmVzdC5saW5lYXIgPC0gdHVuZS5saW5lYXIkYmVzdC5tb2RlbA0KDQpzdW1tYXJ5KGJlc3QubGluZWFyKQ0KYGBgDQoNCg0KIyMjIGUuKSANCkNvbXB1dGUgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9yIHJhdGVzIHVzaW5nIHRoaXMgbmV3IHZhbHVlIGZvciBjb3N0LiANCg0KYGBge3J9DQp0cmFpbi5wcmVkLmJlc3QgPC0gcHJlZGljdChiZXN0LmxpbmVhciwgdHJhaW4ub2opDQp0ZXN0LnByZWQuYmVzdCA8LSBwcmVkaWN0KGJlc3QubGluZWFyLCB0ZXN0Lm9qKQ0KDQp0cmFpbi5lcnIuYmVzdCA8LSBtZWFuKHRyYWluLnByZWQuYmVzdCAhPSB0cmFpbi5vaiRQdXJjaGFzZSkNCnRlc3QuZXJyLmJlc3QgPC0gbWVhbih0ZXN0LnByZWQuYmVzdCAhPSB0ZXN0Lm9qJFB1cmNoYXNlKQ0KDQpjYXQoIlRyYWluIEVycm9yOiIsIHRyYWluLmVyci5iZXN0LCAiXG4iKQ0KY2F0KCJUZXN0IEVycm9yOiIsIHRlc3QuZXJyLmJlc3QsICJcbiIpDQoNCmBgYA0KDQoNCiMjIyBmLikgDQpSZXBlYXQgcGFydHMgKGIpIHRocm91Z2ggKGUpIHVzaW5nIGEgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZSB3aXRoIGEgcmFkaWFsIGtlcm5lbC4gVXNlIHRoZSBkZWZhdWx0IHZhbHVlIGZvciBnYW1tYS4NCg0KYGBge3J9DQpzdm0ucmFkaWFsIDwtIHN2bSgNCiAgUHVyY2hhc2UgfiAuLA0KICBkYXRhID0gdHJhaW4ub2osDQogIGtlcm5lbCA9ICJyYWRpYWwiLA0KICBjb3N0ID0gMC4wMQ0KKQ0KDQpzdW1tYXJ5KHN2bS5yYWRpYWwpDQoNCiMgRXJyb3JzDQoNCnRyYWluLnByZWQucmFkaWFsIDwtIHByZWRpY3Qoc3ZtLnJhZGlhbCwgdHJhaW4ub2opDQoNCnRlc3QucHJlZC5yYWRpYWwgPC0gcHJlZGljdChzdm0ucmFkaWFsLCB0ZXN0Lm9qKQ0KDQp0cmFpbi5lcnIucmFkIDwtIG1lYW4odHJhaW4ucHJlZC5yYWRpYWwgIT0gdHJhaW4ub2okUHVyY2hhc2UpDQoNCnRlc3QuZXJyLnJhZCA8LSBtZWFuKHRlc3QucHJlZC5yYWRpYWwgIT0gdGVzdC5vaiRQdXJjaGFzZSkNCg0KY2F0KCJUcmFpbiBFcnJvcjoiLCB0cmFpbi5lcnIucmFkLCAiXG4iKQ0KY2F0KCJUZXN0IEVycm9yOiIsIHRlc3QuZXJyLnJhZCwgIlxuIikNCg0KDQpgYGANCg0KYGBge3J9DQojdHVuaW5nDQoNCnNldC5zZWVkKDEpDQoNCnR1bmUucmFkaWFsIDwtIHR1bmUoDQogIHN2bSwNCiAgUHVyY2hhc2UgfiAuLA0KICBkYXRhID0gdHJhaW4ub2osDQogIGtlcm5lbCA9ICJyYWRpYWwiLA0KICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAxMCkpDQopDQoNCnN1bW1hcnkodHVuZS5yYWRpYWwpDQoNCmJlc3QucmFkaWFsIDwtIHR1bmUucmFkaWFsJGJlc3QubW9kZWwNCg0KdHJhaW4uZXJyb3IucmFkaWFsIDwtDQogIG1lYW4ocHJlZGljdChiZXN0LnJhZGlhbCwgdHJhaW4ub2opICE9IHRyYWluLm9qJFB1cmNoYXNlKQ0KDQp0ZXN0LmVycm9yLnJhZGlhbCA8LQ0KICBtZWFuKHByZWRpY3QoYmVzdC5yYWRpYWwsIHRlc3Qub2opICE9IHRlc3Qub2okUHVyY2hhc2UpDQoNCmNhdCgiUmFkaWFsIFRyYWluaW5nIEVycm9yOiIsIHRyYWluLmVycm9yLnJhZGlhbCwgIlxuIikNCmNhdCgiUmFkaWFsIFRlc3QgRXJyb3I6IiwgdGVzdC5lcnJvci5yYWRpYWwsICJcbiIpDQoNCg0KYGBgDQoNCiMjIyBnLikgDQpSZXBlYXQgcGFydHMgKGIpIHRocm91Z2ggKGUpIHVzaW5nIGEgc3VwcG9ydCB2ZWN0b3IgbWFjaGluZSB3aXRoIGEgcG9seW5vbWlhbCBrZXJuZWwuIFNldCBkZWdyZWUgPSAyLg0KDQpgYGB7cn0NCnN2bS5wb2x5IDwtIHN2bSgNCiAgUHVyY2hhc2UgfiAuLA0KICBkYXRhID0gdHJhaW4ub2osDQogIGtlcm5lbCA9ICJwb2x5bm9taWFsIiwNCiAgZGVncmVlID0gMiwNCiAgY29zdCA9IDAuMDENCikNCg0Kc3VtbWFyeShzdm0ucG9seSkNCg0KIyBFcnJvcnMNCg0KdHJhaW4ucHJlZC5wb2x5IDwtIHByZWRpY3Qoc3ZtLnBvbHksIHRyYWluLm9qKQ0KDQp0ZXN0LnByZWQucG9seSA8LSBwcmVkaWN0KHN2bS5wb2x5LCB0ZXN0Lm9qKQ0KDQptZWFuKHRyYWluLnByZWQucG9seSAhPSB0cmFpbi5vaiRQdXJjaGFzZSkNCg0KbWVhbih0ZXN0LnByZWQucG9seSAhPSB0ZXN0Lm9qJFB1cmNoYXNlKQ0KDQojVHVuaW5nOg0KDQpzZXQuc2VlZCgxKQ0KDQp0dW5lLnBvbHkgPC0gdHVuZSgNCiAgc3ZtLA0KICBQdXJjaGFzZSB+IC4sDQogIGRhdGEgPSB0cmFpbi5vaiwNCiAga2VybmVsID0gInBvbHlub21pYWwiLA0KICBkZWdyZWUgPSAyLA0KICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAxMCkpDQopDQoNCnN1bW1hcnkodHVuZS5wb2x5KQ0KDQpiZXN0LnBvbHkgPC0gdHVuZS5wb2x5JGJlc3QubW9kZWwNCg0KdHJhaW4uZXJyb3IucG9seSA8LQ0KICBtZWFuKHByZWRpY3QoYmVzdC5wb2x5LCB0cmFpbi5vaikgIT0gdHJhaW4ub2okUHVyY2hhc2UpDQoNCnRlc3QuZXJyb3IucG9seSA8LQ0KICBtZWFuKHByZWRpY3QoYmVzdC5wb2x5LCB0ZXN0Lm9qKSAhPSB0ZXN0Lm9qJFB1cmNoYXNlKQ0KDQp0cmFpbi5lcnJvci5wb2x5DQp0ZXN0LmVycm9yLnBvbHkNCmBgYA0KDQoNCiMjIyBoLikgDQpPdmVyYWxsLCB3aGljaCBhcHByb2FjaCBzZWVtcyB0byBnaXZlIHRoZSBiZXN0IHJlc3VsdHMgb24gdGhpcyBkYXRhPw0KDQoNClRoZSB0dW5lZCBTVk0gY2xhc3NpZmllciB3aXRoIGEgbGluZWFyIGtlcm5lbCBwZXJmb3JtZWQgdGhlIGJlc3Qgd2hlbiBjb21wYXJlZCB0byB0aGUgb3RoZXIgU1ZNIGNsYXNzaWZpZXJzLiBJdCBhY2hpZXZlZCBib3RoIHRoZSBsb3dlc3QgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZXJyb3JzLg==