library(tidyverse)
library(openintro)
library(e1071)
library(caret)
library(ISLR)
library(ISLR2)
library(kernlab)
library(knitr)

Exercise 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. For instance, you can do this as follows:


set.seed(1)
x1 <- runif (500) - 0.5
x2 <- runif (500) - 0.5
y <- 1 * (x1^2 - x2^2 > 0)
\((b)\) Plot the observations, colored according to their class labels. Your plot should display \(X_1\) on the x-axis, and \(X_2\) on the y-axis.


df <- data.frame(x1 = x1, x2 = x2, y = factor(y))

ggplot(df, aes(x = x1, y = x2, color = y)) +
  geom_point(alpha = 0.6) +
  labs(x = "X1", y = "X2", color = "Class") +
  theme_minimal() +
  coord_fixed()

\((c)\) Fit a logistic regression model to the data, using \(X_1\) and \(X_2\) as predictors.


log_model <- glm(y ~ x1 + x2, data = df, family = binomial)
\((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_prob <- predict(log_model, newdata=df, type='response')
glm_pred <- ifelse(glm_prob > 0.5,1,0)
df$predicted <- factor(glm_pred)
ggplot(df, aes(x = x1, y = x2, color = predicted)) +
  geom_point(alpha = 0.6) +
  labs(title = "Logistic Regression with Linear Terms: Predicted Classes",
       x = "X1", y = "X2", color = "Predicted Class") +
  theme_minimal() +
  coord_fixed()

\((e)\) Now fit a logistic regression model to the data using non-linear functions of \(X_1\) and \(X_2\) as predictors (e.g. \(X_{12}\), \(X_1 \times X_2\), log(\(X_2\)), and so forth).


df$x1_sq <- df$x1^2
df$x2_sq <- df$x2^2
df$x1_x2 <- df$x1 * df$x2

log_model_2 <- glm(y ~ x1 + x2 + x1_sq + x2_sq + x1_x2, data = df, family = binomial)
## Warning: glm.fit: algorithm did not converge
## Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
\((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.


glm_prob_2 <- predict(log_model_2, newdata=df, type='response')
glm_pred_2 <- ifelse(glm_prob_2>0.5,1,0)
df$predicted_2 <- factor(glm_pred_2)
ggplot(df, aes(x = x1, y = x2, color = predicted_2)) +
  geom_point(alpha = 0.6) +
  labs(title = "Logistic Regression with Non-linear Terms: Predicted Classes",
       x = "X1", y = "X2", color = "Predicted Class") +
  theme_minimal() +
  coord_fixed()

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


svm <- svm(y ~ x1 + x2, data = df, kernel = "linear", cost = 1)
df$svm_pred <- predict(svm)

ggplot(df, aes(x = x1, y = x2, color = svm_pred)) +
  geom_point(alpha = 0.6) +
  labs(title = "Support Vector Classifier (Linear Kernel): Predicted Classes",
       x = "X1", y = "X2", color = "Predicted Class") +
  theme_minimal() +
  coord_fixed()

\((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.


svm_2 <- svm(y ~ x1 + x2, data = df, kernel = "radial", cost = 1, gamma = 1)
df$svm_2_pred <- predict(svm_2)
ggplot(df, aes(x = x1, y = x2, color = svm_2_pred)) +
  geom_point(alpha = 0.6) +
  labs(title = "SVM with Radial Kernel: Predicted Classes",
       x = "X1", y = "X2", color = "Predicted Class") +
  theme_minimal() +
  coord_fixed()

\((i)\) Comment on your results.


cm_log <- confusionMatrix(factor(df$predicted), factor(df$y))
cm_log_2 <- confusionMatrix(factor(df$predicted_2), factor(df$y))
cm_svm <- confusionMatrix(factor(df$svm_pred), factor(df$y))
## Warning in confusionMatrix.default(factor(df$svm_pred), factor(df$y)): Levels
## are not in the same order for reference and data. Refactoring data to match.
cm_svm_2 <- confusionMatrix(factor(df$svm_2_pred), factor(df$y))

accuracy_table <- data.frame(
  Model = c("Logistic Regression (Linear)",
            "Logistic Regression (Non-linear)",
            "SVM (Linear Kernel)",
            "SVM (RBF Kernel)"),
  Accuracy = c(
    cm_log$overall["Accuracy"],
    cm_log_2$overall["Accuracy"],
    cm_svm$overall["Accuracy"],
    cm_svm_2$overall["Accuracy"]
  )
)

print(accuracy_table)
##                              Model Accuracy
## 1     Logistic Regression (Linear)    0.570
## 2 Logistic Regression (Non-linear)    1.000
## 3              SVM (Linear Kernel)    0.522
## 4                 SVM (RBF Kernel)    0.972

Both the non-linear SVM (rbf kernel) and the non-linear logistic regression performed much better than their linear counterparts. The non-linear varieties were able to capture the binomial (0/1) nature of the data and had better prediction accuracy. Whereas the linear models were continuing to predict the majority class of 0 due to being unable to capture the quadratic pattern.

Exercise 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.


attach(Auto)
## The following object is masked from package:lubridate:
## 
##     origin
## The following object is masked from package:ggplot2:
## 
##     mpg
mpg_med <- median(Auto$mpg)
Auto$mpglevel <- ifelse(Auto$mpg > mpg_med, 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.


Auto_svm <- Auto[, !(names(Auto) %in% c("mpg"))]

set.seed(1)
auto_svm_model <- tune(e1071::svm, mpglevel ~ ., data = Auto_svm, kernel = "linear",
                       ranges = list(cost = c(0.01, 0.1, 1, 10, 100)), scale = TRUE) 
summary(auto_svm_model)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##     1
## 
## - best performance: 0.09603609 
## 
## - Detailed performance results:
##    cost      error dispersion
## 1 1e-02 0.10421950 0.03138085
## 2 1e-01 0.10227373 0.03634911
## 3 1e+00 0.09603609 0.03666741
## 4 1e+01 0.10531309 0.03683207
## 5 1e+02 0.12079079 0.03864160

When using the linear SVM model, a cost of 1 results in the lowest error rate and highest accuracy (90.4%). Increasing cost doesn’t seem to improve this accuracy and could potentially lead to overfitting.

\((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)
auto_svm_model_2 <- tune(e1071::svm, mpglevel ~ ., data = Auto_svm, kernel = "radial",
                         ranges = list(cost = c(0.01, 0.1, 1, 10), gamma = c(0.01, 0.1, 1)))
print(auto_svm_model_2)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost gamma
##    10   0.1
## 
## - best performance: 0.06878595
auto_svm_model_3 <- tune(e1071::svm, mpglevel ~ ., data = Auto_svm, kernel = "polynomial",
                         ranges = list(cost = c(0.01, 0.1, 1, 10), degree = c(2, 3, 4),
                                       scale = c(1, 2)))
print(auto_svm_model_3)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost degree scale
##    10      2     1
## 
## - best performance: 0.3982798

When using the radial SVM model, a cost of 10 and gamma of 0.1 results in the lowest cross-validation error and best accuracy 93.1%). The polynomial SVM model has a peak performance when cost is 10, degree is 2, and scale is 1. However, the accuracy is much worse at 60.2%. The radial model severely outperforms the polynomial model in this case. The radial model also slightly outperforms the the linear model.

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


set.seed(1)
Auto_svm$mpglevel <- as.factor(Auto_svm$mpglevel)

var_pairs <- list(c("weight", "horsepower"), 
                  c("displacement", "weight"), 
                  c("acceleration", "cylinders"))

best_auto_model_linear <- svm(mpglevel ~ ., data = Auto_svm, kernel = "linear", cost = 1)
best_auto_model_radial <- svm(mpglevel ~ ., data = Auto_svm, kernel = "radial", cost = 10, gamma = 0.1)
best_auto_model_poly <- svm(mpglevel ~ ., data = Auto_svm, kernel = "polynomial", cost = 10, degree = 2, scale = 1)
## Warning in any(scale): coercing argument of type 'double' to logical
for (pair in var_pairs) {
  formula <- as.formula(paste(pair[1], "~", pair[2]))
  xlim <- range(Auto_svm[[pair[2]]])
  offset <- diff(xlim) * 0.13

  plot(best_auto_model_linear, Auto_svm, formula, 
       cex.lab = 1.2, cex.axis = 1.1) 
  mtext(paste("Linear SVM: ", pair[1], "~", pair[2]), 
        side = 3, line = 0.5, at = mean(xlim) - offset, cex = 1, adj = 0.5)  

  plot(best_auto_model_radial, Auto_svm, formula, 
       cex.lab = 1.2, cex.axis = 1.1) 
  mtext(paste("Radial SVM: ", pair[1], "~", pair[2]), 
        side = 3, line = 0.5, at = mean(xlim) - offset, cex = 1, adj = 0.5) 

  plot(best_auto_model_poly, Auto_svm, formula, 
       cex.lab = 1.2, cex.axis = 1.1) 
  mtext(paste("Poly SVM: ", pair[1], "~", pair[2]), 
        side = 3, line = 0.5, at = mean(xlim) - offset, cex = 1, adj = 0.5) 
}

Exercise 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.


library(ISLR2)
attach(OJ)
set.seed(1)
oj_data <- sample(nrow(OJ), 800)
oj_train <- OJ[oj_data,]
oj_test <- OJ[-oj_data,]
\((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.


oj_svm <- svm(Purchase~ ., data = oj_train, kernel='linear', cost = 0.01)
summary(oj_svm)
## 
## Call:
## svm(formula = Purchase ~ ., data = oj_train, kernel = "linear", cost = 0.01)
## 
## 
## 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

435 support vectors have been created. 219 vectors belong to level CH and 216 vectors belong to level MM.

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


train_pred <- predict(oj_svm, oj_train)
train_error <- mean(train_pred != oj_train$Purchase)

test_pred <- predict(oj_svm, oj_test)
test_error <- mean(test_pred != oj_test$Purchase)

cat("Training Error Rate:", round(train_error, 4), "\n")
## Training Error Rate: 0.175
cat("Test Error Rate:", round(test_error, 4), "\n")
## Test Error Rate: 0.1778
\((d)\) Use the tune() function to select an optimal cost. Consider values in the range 0.01 to 10.


set.seed(1)
tune_oj <- tune(e1071::svm, Purchase ~ ., data = oj_train, 
                 kernel = "linear",
                 ranges = list(cost = c(0.01, 0.1, 0.5, 1, 5, 10)))
summary(tune_oj)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##   0.5
## 
## - best performance: 0.16875 
## 
## - Detailed performance results:
##    cost   error dispersion
## 1  0.01 0.17625 0.02853482
## 2  0.10 0.17250 0.03162278
## 3  0.50 0.16875 0.02651650
## 4  1.00 0.17500 0.02946278
## 5  5.00 0.17250 0.03162278
## 6 10.00 0.17375 0.03197764

The smallest error is found when the cost is 0.5. The accuracy is around 83.12%.

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


best_oj <- tune_oj$best.model

train_pred_best <- predict(best_oj, oj_train)
train_error_best <- mean(train_pred_best != oj_train$Purchase)

test_pred_best <- predict(best_oj, oj_test)
test_error_best <- mean(test_pred_best != oj_test$Purchase)

cat("Training Error Rate (Best Cost):", round(train_error_best, 4), "\n")
## Training Error Rate (Best Cost): 0.165
cat("Test Error Rate (Best Cost):", round(test_error_best, 4), "\n")
## Test Error Rate (Best Cost): 0.1556
\((f)\) Repeat parts (b) through (e) using a support vector machine with a radial kernel. Use the default value for gamma.


set.seed(1)
oj_svm_radial <- svm(Purchase ~ ., data = oj_train, kernel = "radial", cost = 0.01)
summary(oj_svm_radial)
## 
## Call:
## svm(formula = Purchase ~ ., data = oj_train, 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
train_pred_radial <- predict(oj_svm_radial, oj_train)
train_error_radial <- mean(train_pred_radial != oj_train$Purchase)
test_pred_radial <- predict(oj_svm_radial, oj_test)
test_error_radial <- mean(test_pred_radial != oj_test$Purchase)
cat("Training Error Rate (Radial, cost=0.01):", round(train_error_radial, 4), "\n")
## Training Error Rate (Radial, cost=0.01): 0.3938
cat("Test Error Rate (Radial, cost=0.01):", round(test_error_radial, 4), "\n")
## Test Error Rate (Radial, cost=0.01): 0.3778
tune_radial_oj <- tune(e1071::svm, Purchase ~ ., data = oj_train,
                       kernel = "radial",
                       ranges = list(cost = c(0.01, 0.1, 1, 10)))
summary(tune_radial_oj)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##     1
## 
## - best performance: 0.17125 
## 
## - Detailed performance results:
##    cost   error dispersion
## 1  0.01 0.39375 0.04007372
## 2  0.10 0.18625 0.02853482
## 3  1.00 0.17125 0.02128673
## 4 10.00 0.18625 0.02853482
best_radial_svm <- tune_radial_oj$best.model
train_pred_best_radial <- predict(best_radial_svm, oj_train)
train_error_best_radial <- mean(train_pred_best_radial != oj_train$Purchase)
test_pred_best_radial <- predict(best_radial_svm, oj_test)
test_error_best_radial <- mean(test_pred_best_radial != oj_test$Purchase)
cat("Training Error Rate (Radial, Best Cost):", round(train_error_best_radial, 4), "\n")
## Training Error Rate (Radial, Best Cost): 0.1512
cat("Test Error Rate (Radial, Best Cost):", round(test_error_best_radial, 4), "\n")
## Test Error Rate (Radial, Best Cost): 0.1852

A total of 634 support vectors are made with 319 belonging to CH and 315 belonging to MM. This model has a training error of 39.38% and testing error of 37.78%. The lowest error can be found when cost is 1 with an accuracy of 82.87%. The training error of this best model is 15.12% and the testing error is 18.52%.

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


set.seed(1)
oj_svm_poly <- svm(Purchase ~ ., data = oj_train, 
                   kernel = "polynomial", degree = 2, cost = 0.01)
summary(oj_svm_poly)
## 
## Call:
## svm(formula = Purchase ~ ., data = oj_train, 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
train_pred_poly <- predict(oj_svm_poly, oj_train)
train_error_poly <- mean(train_pred_poly != oj_train$Purchase)
test_pred_poly <- predict(oj_svm_poly, oj_test)
test_error_poly <- mean(test_pred_poly != oj_test$Purchase)
cat("Training Error Rate (Poly, cost=0.01):", round(train_error_poly, 4), "\n")
## Training Error Rate (Poly, cost=0.01): 0.3725
cat("Test Error Rate (Poly, cost=0.01):", round(test_error_poly, 4), "\n")
## Test Error Rate (Poly, cost=0.01): 0.3667
tune_poly_oj <- tune(e1071::svm, Purchase ~ ., data = oj_train,
                     kernel = "polynomial", degree = 2,
                     ranges = list(cost = c(0.01, 0.1, 1, 10)))
summary(tune_poly_oj)
## 
## Parameter tuning of 'e1071::svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##    10
## 
## - best performance: 0.18125 
## 
## - Detailed performance results:
##    cost   error dispersion
## 1  0.01 0.39125 0.04210189
## 2  0.10 0.32125 0.05001736
## 3  1.00 0.20250 0.04116363
## 4 10.00 0.18125 0.02779513
best_poly_svm <- tune_poly_oj$best.model
train_pred_best_poly <- predict(best_poly_svm, oj_train)
train_error_best_poly <- mean(train_pred_best_poly != oj_train$Purchase)
test_pred_best_poly <- predict(best_poly_svm, oj_test)
test_error_best_poly <- mean(test_pred_best_poly != oj_test$Purchase)
cat("Training Error Rate (Poly, Best Cost):", round(train_error_best_poly, 4), "\n")
## Training Error Rate (Poly, Best Cost): 0.15
cat("Test Error Rate (Poly, Best Cost):", round(test_error_best_poly, 4), "\n")
## Test Error Rate (Poly, Best Cost): 0.1889

A total of 636 support vectors are made with 321 belonging to CH and 315 belonging to MM. This model has a training error of 37.25% and testing error of 36.67%. The lowest error can be found when cost is 10 with an accuracy of 81.87%%. The training error of this best model is 15% and the testing error is 18.89%.

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


error_comparison <- data.frame(
  Kernel = c("Linear", "Radial", "Polynomial"),
  Training_Accuracy = 1 - c(train_error_best, train_error_best_radial, train_error_best_poly),
  Test_Accuracy = 1 - c(test_error_best, test_error_best_radial, test_error_best_poly)
)

error_comparison %>%
  mutate(
    Training_Accuracy = sprintf("%.2f%%", Training_Accuracy * 100),
    Test_Accuracy = sprintf("%.2f%%", Test_Accuracy * 100)
  ) %>%
  kable(caption = "Comparison of Training and Test Error Rates Across Kernels")
Comparison of Training and Test Error Rates Across Kernels
Kernel Training_Accuracy Test_Accuracy
Linear 83.50% 84.44%
Radial 84.88% 81.48%
Polynomial 85.00% 81.11%

The linear kernel has the highest testing accuracy and seems to give the best results on this data.

LS0tCnRpdGxlOiAiQXNzaWdubWVudCA4IgphdXRob3I6ICJSYW5pIE1pc3JhIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogb3BlbmludHJvOjpsYWJfcmVwb3J0Ci0tLQoKYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkob3BlbmludHJvKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoSVNMUjIpCmxpYnJhcnkoa2VybmxhYikKbGlicmFyeShrbml0cikKYGBgCgojIyBFeGVyY2lzZSA1CgojIyMjIyAgV2UgaGF2ZSBzZWVuIHRoYXQgd2UgY2FuIGZpdCBhbiBTVk0gd2l0aCBhIG5vbi1saW5lYXIga2VybmVsIGluIG9yZGVyIHRvIHBlcmZvcm0gY2xhc3NpZmljYXRpb24gdXNpbmcgYSBub24tbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5LiBXZSB3aWxsIG5vdyBzZWUgdGhhdCB3ZSBjYW4gYWxzbyBvYnRhaW4gYSBub24tbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5IGJ5IHBlcmZvcm1pbmcgbG9naXN0aWMgcmVncmVzc2lvbiB1c2luZyBub24tbGluZWFyIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUgZmVhdHVyZXMuCgojIyMjIyAkKGEpJCBHZW5lcmF0ZSBhIGRhdGEgc2V0IHdpdGggJG4kID0gNTAwIGFuZCAkcCQgPSAyLCBzdWNoIHRoYXQgdGhlIG9ic2VydmF0aW9ucyBiZWxvbmcgdG8gdHdvIGNsYXNzZXMgd2l0aCBhIHF1YWRyYXRpYyBkZWNpc2lvbiBib3VuZGFyeSBiZXR3ZWVuIHRoZW0uIEZvciBpbnN0YW5jZSwgeW91IGNhbiBkbyB0aGlzIGFzIGZvbGxvd3M6ClwKYGBge3J9CnNldC5zZWVkKDEpCngxIDwtIHJ1bmlmICg1MDApIC0gMC41CngyIDwtIHJ1bmlmICg1MDApIC0gMC41CnkgPC0gMSAqICh4MV4yIC0geDJeMiA+IDApCmBgYAoKIyMjIyMgJChiKSQgUGxvdCB0aGUgb2JzZXJ2YXRpb25zLCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGVpciBjbGFzcyBsYWJlbHMuIFlvdXIgcGxvdCBzaG91bGQgZGlzcGxheSAkWF8xJCBvbiB0aGUgeC1heGlzLCBhbmQgJFhfMiQgb24gdGhlIHktYXhpcy4KXApgYGB7cn0KZGYgPC0gZGF0YS5mcmFtZSh4MSA9IHgxLCB4MiA9IHgyLCB5ID0gZmFjdG9yKHkpKQoKZ2dwbG90KGRmLCBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0geSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgbGFicyh4ID0gIlgxIiwgeSA9ICJYMiIsIGNvbG9yID0gIkNsYXNzIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgY29vcmRfZml4ZWQoKQpgYGAKCiMjIyMjICQoYykkIEZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdG8gdGhlIGRhdGEsIHVzaW5nICRYXzEkIGFuZCAkWF8yJCBhcyBwcmVkaWN0b3JzLgpcCmBgYHtyfQpsb2dfbW9kZWwgPC0gZ2xtKHkgfiB4MSArIHgyLCBkYXRhID0gZGYsIGZhbWlseSA9IGJpbm9taWFsKQpgYGAKCiMjIyMjICQoZCkkIEFwcGx5IHRoaXMgbW9kZWwgdG8gdGhlICp0cmFpbmluZyBkYXRhKiBpbiBvcmRlciB0byBvYnRhaW4gYSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWwgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlICpwcmVkaWN0ZWQqIGNsYXNzIGxhYmVscy4gVGhlIGRlY2lzaW9uIGJvdW5kYXJ5IHNob3VsZCBiZSBsaW5lYXIuClwKYGBge3J9CmdsbV9wcm9iIDwtIHByZWRpY3QobG9nX21vZGVsLCBuZXdkYXRhPWRmLCB0eXBlPSdyZXNwb25zZScpCmdsbV9wcmVkIDwtIGlmZWxzZShnbG1fcHJvYiA+IDAuNSwxLDApCmRmJHByZWRpY3RlZCA8LSBmYWN0b3IoZ2xtX3ByZWQpCmdncGxvdChkZiwgYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9IHByZWRpY3RlZCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgbGFicyh0aXRsZSA9ICJMb2dpc3RpYyBSZWdyZXNzaW9uIHdpdGggTGluZWFyIFRlcm1zOiBQcmVkaWN0ZWQgQ2xhc3NlcyIsCiAgICAgICB4ID0gIlgxIiwgeSA9ICJYMiIsIGNvbG9yID0gIlByZWRpY3RlZCBDbGFzcyIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGNvb3JkX2ZpeGVkKCkKYGBgCgojIyMjIyAkKGUpJCBOb3cgZml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byB0aGUgZGF0YSB1c2luZyBub24tbGluZWFyIGZ1bmN0aW9ucyBvZiAkWF8xJCBhbmQgJFhfMiQgYXMgcHJlZGljdG9ycyAoZS5nLiAkWF97MTJ9JCwgJFhfMSBcdGltZXMgWF8yJCwgbG9nKCRYXzIkKSwgYW5kIHNvIGZvcnRoKS4KXApgYGB7cn0KZGYkeDFfc3EgPC0gZGYkeDFeMgpkZiR4Ml9zcSA8LSBkZiR4Ml4yCmRmJHgxX3gyIDwtIGRmJHgxICogZGYkeDIKCmxvZ19tb2RlbF8yIDwtIGdsbSh5IH4geDEgKyB4MiArIHgxX3NxICsgeDJfc3EgKyB4MV94MiwgZGF0YSA9IGRmLCBmYW1pbHkgPSBiaW5vbWlhbCkKYGBgCgojIyMjIyAkKGYpJCBBcHBseSB0aGlzIG1vZGVsIHRvIHRoZSAqdHJhaW5pbmcgZGF0YSogaW4gb3JkZXIgdG8gb2J0YWluIGEgcHJlZGljdGVkIGNsYXNzIGxhYmVsIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9uLiBQbG90IHRoZSBvYnNlcnZhdGlvbnMsIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSAqcHJlZGljdGVkKiBjbGFzcyBsYWJlbHMuIFRoZSBkZWNpc2lvbiBib3VuZGFyeSBzaG91bGQgYmUgb2J2aW91c2x5IG5vbi1saW5lYXIuIElmIGl0IGlzIG5vdCwgdGhlbiByZXBlYXQgKGEpLShlKSB1bnRpbCB5b3UgY29tZSB1cCB3aXRoIGFuIGV4YW1wbGUgaW4gd2hpY2ggdGhlIHByZWRpY3RlZCBjbGFzcyBsYWJlbHMgYXJlIG9idmlvdXNseSBub24tbGluZWFyLgpcCmBgYHtyfQpnbG1fcHJvYl8yIDwtIHByZWRpY3QobG9nX21vZGVsXzIsIG5ld2RhdGE9ZGYsIHR5cGU9J3Jlc3BvbnNlJykKZ2xtX3ByZWRfMiA8LSBpZmVsc2UoZ2xtX3Byb2JfMj4wLjUsMSwwKQpkZiRwcmVkaWN0ZWRfMiA8LSBmYWN0b3IoZ2xtX3ByZWRfMikKZ2dwbG90KGRmLCBhZXMoeCA9IHgxLCB5ID0geDIsIGNvbG9yID0gcHJlZGljdGVkXzIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogIGxhYnModGl0bGUgPSAiTG9naXN0aWMgUmVncmVzc2lvbiB3aXRoIE5vbi1saW5lYXIgVGVybXM6IFByZWRpY3RlZCBDbGFzc2VzIiwKICAgICAgIHggPSAiWDEiLCB5ID0gIlgyIiwgY29sb3IgPSAiUHJlZGljdGVkIENsYXNzIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgY29vcmRfZml4ZWQoKQpgYGAKCiMjIyMjICQoZykkIEZpdCBhIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgdG8gdGhlIGRhdGEgd2l0aCAkWF8xJCBhbmQgJFhfMiQgYXMgcHJlZGljdG9ycy4gT2J0YWluIGEgY2xhc3MgcHJlZGljdGlvbiBmb3IgZWFjaCB0cmFpbmluZyBvYnNlcnZhdGlvbi4gUGxvdCB0aGUgb2JzZXJ2YXRpb25zLCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgKnByZWRpY3RlZCBjbGFzcyBsYWJlbHMqLgpcCmBgYHtyfQpzdm0gPC0gc3ZtKHkgfiB4MSArIHgyLCBkYXRhID0gZGYsIGtlcm5lbCA9ICJsaW5lYXIiLCBjb3N0ID0gMSkKZGYkc3ZtX3ByZWQgPC0gcHJlZGljdChzdm0pCgpnZ3Bsb3QoZGYsIGFlcyh4ID0geDEsIHkgPSB4MiwgY29sb3IgPSBzdm1fcHJlZCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgbGFicyh0aXRsZSA9ICJTdXBwb3J0IFZlY3RvciBDbGFzc2lmaWVyIChMaW5lYXIgS2VybmVsKTogUHJlZGljdGVkIENsYXNzZXMiLAogICAgICAgeCA9ICJYMSIsIHkgPSAiWDIiLCBjb2xvciA9ICJQcmVkaWN0ZWQgQ2xhc3MiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBjb29yZF9maXhlZCgpCmBgYAoKIyMjIyMgJChoKSQgRml0IGEgU1ZNIHVzaW5nIGEgbm9uLWxpbmVhciBrZXJuZWwgdG8gdGhlIGRhdGEuIE9idGFpbiBhIGNsYXNzIHByZWRpY3Rpb24gZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlICpwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzKi4KXApgYGB7cn0Kc3ZtXzIgPC0gc3ZtKHkgfiB4MSArIHgyLCBkYXRhID0gZGYsIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMSwgZ2FtbWEgPSAxKQpkZiRzdm1fMl9wcmVkIDwtIHByZWRpY3Qoc3ZtXzIpCmdncGxvdChkZiwgYWVzKHggPSB4MSwgeSA9IHgyLCBjb2xvciA9IHN2bV8yX3ByZWQpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogIGxhYnModGl0bGUgPSAiU1ZNIHdpdGggUmFkaWFsIEtlcm5lbDogUHJlZGljdGVkIENsYXNzZXMiLAogICAgICAgeCA9ICJYMSIsIHkgPSAiWDIiLCBjb2xvciA9ICJQcmVkaWN0ZWQgQ2xhc3MiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBjb29yZF9maXhlZCgpCmBgYAoKIyMjIyMgJChpKSQgQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuClwKYGBge3J9CmNtX2xvZyA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKGRmJHByZWRpY3RlZCksIGZhY3RvcihkZiR5KSkKY21fbG9nXzIgPC0gY29uZnVzaW9uTWF0cml4KGZhY3RvcihkZiRwcmVkaWN0ZWRfMiksIGZhY3RvcihkZiR5KSkKY21fc3ZtIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IoZGYkc3ZtX3ByZWQpLCBmYWN0b3IoZGYkeSkpCmNtX3N2bV8yIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IoZGYkc3ZtXzJfcHJlZCksIGZhY3RvcihkZiR5KSkKCmFjY3VyYWN5X3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSBjKCJMb2dpc3RpYyBSZWdyZXNzaW9uIChMaW5lYXIpIiwKICAgICAgICAgICAgIkxvZ2lzdGljIFJlZ3Jlc3Npb24gKE5vbi1saW5lYXIpIiwKICAgICAgICAgICAgIlNWTSAoTGluZWFyIEtlcm5lbCkiLAogICAgICAgICAgICAiU1ZNIChSQkYgS2VybmVsKSIpLAogIEFjY3VyYWN5ID0gYygKICAgIGNtX2xvZyRvdmVyYWxsWyJBY2N1cmFjeSJdLAogICAgY21fbG9nXzIkb3ZlcmFsbFsiQWNjdXJhY3kiXSwKICAgIGNtX3N2bSRvdmVyYWxsWyJBY2N1cmFjeSJdLAogICAgY21fc3ZtXzIkb3ZlcmFsbFsiQWNjdXJhY3kiXQogICkKKQoKcHJpbnQoYWNjdXJhY3lfdGFibGUpCmBgYAoKQm90aCB0aGUgbm9uLWxpbmVhciBTVk0gKHJiZiBrZXJuZWwpIGFuZCB0aGUgbm9uLWxpbmVhciBsb2dpc3RpYyByZWdyZXNzaW9uIHBlcmZvcm1lZCBtdWNoIGJldHRlciB0aGFuIHRoZWlyIGxpbmVhciBjb3VudGVycGFydHMuIFRoZSBub24tbGluZWFyIHZhcmlldGllcyB3ZXJlIGFibGUgdG8gY2FwdHVyZSB0aGUgYmlub21pYWwgKDAvMSkgbmF0dXJlIG9mIHRoZSBkYXRhIGFuZCBoYWQgYmV0dGVyIHByZWRpY3Rpb24gYWNjdXJhY3kuIFdoZXJlYXMgdGhlIGxpbmVhciBtb2RlbHMgd2VyZSBjb250aW51aW5nIHRvIHByZWRpY3QgdGhlIG1ham9yaXR5IGNsYXNzIG9mIDAgZHVlIHRvIGJlaW5nIHVuYWJsZSB0byBjYXB0dXJlIHRoZSBxdWFkcmF0aWMgcGF0dGVybi4gCgojIyBFeGVyY2lzZSA3CgojIyMjIyBJbiB0aGlzIHByb2JsZW0sIHlvdSB3aWxsIHVzZSBzdXBwb3J0IHZlY3RvciBhcHByb2FjaGVzIGluIG9yZGVyIHRvIHByZWRpY3Qgd2hldGhlciBhIGdpdmVuIGNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlIGJhc2VkIG9uIHRoZSBgQXV0b2AgZGF0YSBzZXQuCgojIyMjIyAkKGEpJCBDcmVhdGUgYSBiaW5hcnkgdmFyaWFibGUgdGhhdCB0YWtlcyBvbiBhIDEgZm9yIGNhcnMgd2l0aCBnYXMgbWlsZWFnZSBhYm92ZSB0aGUgbWVkaWFuLCBhbmQgYSAwIGZvciBjYXJzIHdpdGggZ2FzIG1pbGVhZ2UgYmVsb3cgdGhlIG1lZGlhbi4KXApgYGB7cn0KYXR0YWNoKEF1dG8pCm1wZ19tZWQgPC0gbWVkaWFuKEF1dG8kbXBnKQpBdXRvJG1wZ2xldmVsIDwtIGlmZWxzZShBdXRvJG1wZyA+IG1wZ19tZWQsIDEsIDApCmBgYAoKIyMjIyMgJChiKSQgRml0IGEgc3VwcG9ydCB2ZWN0b3IgY2xhc3NpZmllciB0byB0aGUgZGF0YSB3aXRoIHZhcmlvdXMgdmFsdWVzIG9mIGBjb3N0YCwgaW4gb3JkZXIgdG8gcHJlZGljdCB3aGV0aGVyIGEgY2FyIGdldHMgaGlnaCBvciBsb3cgZ2FzIG1pbGVhZ2UuIFJlcG9ydCB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvcnMgYXNzb2NpYXRlZCB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2YgdGhpcyBwYXJhbWV0ZXIuIENvbW1lbnQgb24geW91ciByZXN1bHRzLiBOb3RlIHlvdSB3aWxsIG5lZWQgdG8gZml0IHRoZSBjbGFzc2lmaWVyIHdpdGhvdXQgdGhlIGdhcyBtaWxlYWdlIHZhcmlhYmxlIHRvIHByb2R1Y2Ugc2Vuc2libGUgcmVzdWx0cy4KXApgYGB7ciwgd2FybmluZz1GQUxTRX0KQXV0b19zdm0gPC0gQXV0b1ssICEobmFtZXMoQXV0bykgJWluJSBjKCJtcGciKSldCgpzZXQuc2VlZCgxKQphdXRvX3N2bV9tb2RlbCA8LSB0dW5lKGUxMDcxOjpzdm0sIG1wZ2xldmVsIH4gLiwgZGF0YSA9IEF1dG9fc3ZtLCBrZXJuZWwgPSAibGluZWFyIiwKICAgICAgICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMSwgMTAsIDEwMCkpLCBzY2FsZSA9IFRSVUUpIApzdW1tYXJ5KGF1dG9fc3ZtX21vZGVsKQpgYGAKCldoZW4gdXNpbmcgdGhlIGxpbmVhciBTVk0gbW9kZWwsIGEgYGNvc3RgIG9mIDEgcmVzdWx0cyBpbiB0aGUgbG93ZXN0IGVycm9yIHJhdGUgYW5kIGhpZ2hlc3QgYWNjdXJhY3kgKDkwLjQlKS4gSW5jcmVhc2luZyBjb3N0IGRvZXNuJ3Qgc2VlbSB0byBpbXByb3ZlIHRoaXMgYWNjdXJhY3kgYW5kIGNvdWxkIHBvdGVudGlhbGx5IGxlYWQgdG8gb3ZlcmZpdHRpbmcuICAKCiMjIyMjICQoYykkIE5vdyByZXBlYXQgKGIpLCB0aGlzIHRpbWUgdXNpbmcgU1ZNcyB3aXRoIHJhZGlhbCBhbmQgcG9seW5vbWlhbCBiYXNpcyBrZXJuZWxzLCB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2YgYGdhbW1hYCBhbmQgYGRlZ3JlZWAgYW5kIGBjb3N0YC4gQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuClwKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDEpCmF1dG9fc3ZtX21vZGVsXzIgPC0gdHVuZShlMTA3MTo6c3ZtLCBtcGdsZXZlbCB+IC4sIGRhdGEgPSBBdXRvX3N2bSwga2VybmVsID0gInJhZGlhbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMSwgMTApLCBnYW1tYSA9IGMoMC4wMSwgMC4xLCAxKSkpCnByaW50KGF1dG9fc3ZtX21vZGVsXzIpCgphdXRvX3N2bV9tb2RlbF8zIDwtIHR1bmUoZTEwNzE6OnN2bSwgbXBnbGV2ZWwgfiAuLCBkYXRhID0gQXV0b19zdm0sIGtlcm5lbCA9ICJwb2x5bm9taWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4xLCAxLCAxMCksIGRlZ3JlZSA9IGMoMiwgMywgNCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gYygxLCAyKSkpCnByaW50KGF1dG9fc3ZtX21vZGVsXzMpCmBgYAoKV2hlbiB1c2luZyB0aGUgcmFkaWFsIFNWTSBtb2RlbCwgYSBgY29zdGAgb2YgMTAgYW5kIGBnYW1tYWAgb2YgMC4xIHJlc3VsdHMgaW4gdGhlIGxvd2VzdCBjcm9zcy12YWxpZGF0aW9uIGVycm9yIGFuZCBiZXN0IGFjY3VyYWN5IDkzLjElKS4gVGhlIHBvbHlub21pYWwgU1ZNIG1vZGVsIGhhcyBhIHBlYWsgcGVyZm9ybWFuY2Ugd2hlbiBgY29zdGAgaXMgMTAsIGBkZWdyZWVgIGlzIDIsIGFuZCBgc2NhbGVgIGlzIDEuIEhvd2V2ZXIsIHRoZSBhY2N1cmFjeSBpcyBtdWNoIHdvcnNlIGF0IDYwLjIlLiBUaGUgcmFkaWFsIG1vZGVsIHNldmVyZWx5IG91dHBlcmZvcm1zIHRoZSBwb2x5bm9taWFsIG1vZGVsIGluIHRoaXMgY2FzZS4gVGhlIHJhZGlhbCBtb2RlbCBhbHNvIHNsaWdodGx5IG91dHBlcmZvcm1zIHRoZSB0aGUgbGluZWFyIG1vZGVsLgoKIyMjIyMgJChkKSQgTWFrZSBzb21lIHBsb3RzIHRvIGJhY2sgdXAgeW91ciBhc3NlcnRpb25zIGluIChiKSBhbmQgKGMpLgpcCmBgYHtyfQpzZXQuc2VlZCgxKQpBdXRvX3N2bSRtcGdsZXZlbCA8LSBhcy5mYWN0b3IoQXV0b19zdm0kbXBnbGV2ZWwpCgp2YXJfcGFpcnMgPC0gbGlzdChjKCJ3ZWlnaHQiLCAiaG9yc2Vwb3dlciIpLCAKICAgICAgICAgICAgICAgICAgYygiZGlzcGxhY2VtZW50IiwgIndlaWdodCIpLCAKICAgICAgICAgICAgICAgICAgYygiYWNjZWxlcmF0aW9uIiwgImN5bGluZGVycyIpKQoKYmVzdF9hdXRvX21vZGVsX2xpbmVhciA8LSBzdm0obXBnbGV2ZWwgfiAuLCBkYXRhID0gQXV0b19zdm0sIGtlcm5lbCA9ICJsaW5lYXIiLCBjb3N0ID0gMSkKYmVzdF9hdXRvX21vZGVsX3JhZGlhbCA8LSBzdm0obXBnbGV2ZWwgfiAuLCBkYXRhID0gQXV0b19zdm0sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMTAsIGdhbW1hID0gMC4xKQpiZXN0X2F1dG9fbW9kZWxfcG9seSA8LSBzdm0obXBnbGV2ZWwgfiAuLCBkYXRhID0gQXV0b19zdm0sIGtlcm5lbCA9ICJwb2x5bm9taWFsIiwgY29zdCA9IDEwLCBkZWdyZWUgPSAyLCBzY2FsZSA9IDEpCgpmb3IgKHBhaXIgaW4gdmFyX3BhaXJzKSB7CiAgZm9ybXVsYSA8LSBhcy5mb3JtdWxhKHBhc3RlKHBhaXJbMV0sICJ+IiwgcGFpclsyXSkpCiAgeGxpbSA8LSByYW5nZShBdXRvX3N2bVtbcGFpclsyXV1dKQogIG9mZnNldCA8LSBkaWZmKHhsaW0pICogMC4xMwoKICBwbG90KGJlc3RfYXV0b19tb2RlbF9saW5lYXIsIEF1dG9fc3ZtLCBmb3JtdWxhLCAKICAgICAgIGNleC5sYWIgPSAxLjIsIGNleC5heGlzID0gMS4xKSAKICBtdGV4dChwYXN0ZSgiTGluZWFyIFNWTTogIiwgcGFpclsxXSwgIn4iLCBwYWlyWzJdKSwgCiAgICAgICAgc2lkZSA9IDMsIGxpbmUgPSAwLjUsIGF0ID0gbWVhbih4bGltKSAtIG9mZnNldCwgY2V4ID0gMSwgYWRqID0gMC41KSAgCgogIHBsb3QoYmVzdF9hdXRvX21vZGVsX3JhZGlhbCwgQXV0b19zdm0sIGZvcm11bGEsIAogICAgICAgY2V4LmxhYiA9IDEuMiwgY2V4LmF4aXMgPSAxLjEpIAogIG10ZXh0KHBhc3RlKCJSYWRpYWwgU1ZNOiAiLCBwYWlyWzFdLCAifiIsIHBhaXJbMl0pLCAKICAgICAgICBzaWRlID0gMywgbGluZSA9IDAuNSwgYXQgPSBtZWFuKHhsaW0pIC0gb2Zmc2V0LCBjZXggPSAxLCBhZGogPSAwLjUpIAoKICBwbG90KGJlc3RfYXV0b19tb2RlbF9wb2x5LCBBdXRvX3N2bSwgZm9ybXVsYSwgCiAgICAgICBjZXgubGFiID0gMS4yLCBjZXguYXhpcyA9IDEuMSkgCiAgbXRleHQocGFzdGUoIlBvbHkgU1ZNOiAiLCBwYWlyWzFdLCAifiIsIHBhaXJbMl0pLCAKICAgICAgICBzaWRlID0gMywgbGluZSA9IDAuNSwgYXQgPSBtZWFuKHhsaW0pIC0gb2Zmc2V0LCBjZXggPSAxLCBhZGogPSAwLjUpIAp9CmBgYAoKIyMgRXhlcmNpc2UgOAoKIyMjIyMgVGhpcyBwcm9ibGVtIGludm9sdmVzIHRoZSBgT0pgIGRhdGEgc2V0IHdoaWNoIGlzIHBhcnQgb2YgdGhlIGBJU0xSMmAgcGFja2FnZS4KCiMjIyMjICQoYSkkIENyZWF0ZSBhIHRyYWluaW5nIHNldCBjb250YWluaW5nIGEgcmFuZG9tIHNhbXBsZSBvZiA4MDAgb2JzZXJ2YXRpb25zLCBhbmQgYSB0ZXN0IHNldCBjb250YWluaW5nIHRoZSByZW1haW5pbmcgb2JzZXJ2YXRpb25zLgpcCmBgYHtyfQpsaWJyYXJ5KElTTFIyKQphdHRhY2goT0opCnNldC5zZWVkKDEpCm9qX2RhdGEgPC0gc2FtcGxlKG5yb3coT0opLCA4MDApCm9qX3RyYWluIDwtIE9KW29qX2RhdGEsXQpval90ZXN0IDwtIE9KWy1val9kYXRhLF0KYGBgCgojIyMjIyAkKGIpJCBGaXQgYSBzdXBwb3J0IHZlY3RvciBjbGFzc2lmaWVyIHRvIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nIGBjb3N0ID0gMC4wMWAsIHdpdGggYFB1cmNoYXNlYCBhcyB0aGUgcmVzcG9uc2UgYW5kIHRoZSBvdGhlciB2YXJpYWJsZXMgYXMgcHJlZGljdG9ycy4gVXNlIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbiB0byBwcm9kdWNlIHN1bW1hcnkgc3RhdGlzdGljcywgYW5kIGRlc2NyaWJlIHRoZSByZXN1bHRzIG9idGFpbmVkLgpcCmBgYHtyfQpval9zdm0gPC0gc3ZtKFB1cmNoYXNlfiAuLCBkYXRhID0gb2pfdHJhaW4sIGtlcm5lbD0nbGluZWFyJywgY29zdCA9IDAuMDEpCnN1bW1hcnkob2pfc3ZtKQpgYGAKCjQzNSBzdXBwb3J0IHZlY3RvcnMgaGF2ZSBiZWVuIGNyZWF0ZWQuIDIxOSB2ZWN0b3JzIGJlbG9uZyB0byBsZXZlbCBDSCBhbmQgMjE2IHZlY3RvcnMgYmVsb25nIHRvIGxldmVsIE1NLgoKIyMjIyMgJChjKSQgV2hhdCBhcmUgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9yIHJhdGVzPwpcCmBgYHtyfQp0cmFpbl9wcmVkIDwtIHByZWRpY3Qob2pfc3ZtLCBval90cmFpbikKdHJhaW5fZXJyb3IgPC0gbWVhbih0cmFpbl9wcmVkICE9IG9qX3RyYWluJFB1cmNoYXNlKQoKdGVzdF9wcmVkIDwtIHByZWRpY3Qob2pfc3ZtLCBval90ZXN0KQp0ZXN0X2Vycm9yIDwtIG1lYW4odGVzdF9wcmVkICE9IG9qX3Rlc3QkUHVyY2hhc2UpCgpjYXQoIlRyYWluaW5nIEVycm9yIFJhdGU6Iiwgcm91bmQodHJhaW5fZXJyb3IsIDQpLCAiXG4iKQpjYXQoIlRlc3QgRXJyb3IgUmF0ZToiLCByb3VuZCh0ZXN0X2Vycm9yLCA0KSwgIlxuIikKYGBgCgojIyMjIyAkKGQpJCBVc2UgdGhlIGB0dW5lKClgIGZ1bmN0aW9uIHRvIHNlbGVjdCBhbiBvcHRpbWFsIGNvc3QuIENvbnNpZGVyIHZhbHVlcyBpbiB0aGUgcmFuZ2UgMC4wMSB0byAxMC4KXApgYGB7cn0Kc2V0LnNlZWQoMSkKdHVuZV9vaiA8LSB0dW5lKGUxMDcxOjpzdm0sIFB1cmNoYXNlIH4gLiwgZGF0YSA9IG9qX3RyYWluLCAKICAgICAgICAgICAgICAgICBrZXJuZWwgPSAibGluZWFyIiwKICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMC41LCAxLCA1LCAxMCkpKQpzdW1tYXJ5KHR1bmVfb2opCmBgYAoKVGhlIHNtYWxsZXN0IGVycm9yIGlzIGZvdW5kIHdoZW4gdGhlIGNvc3QgaXMgMC41LiBUaGUgYWNjdXJhY3kgaXMgYXJvdW5kIDgzLjEyJS4gCgojIyMjIyAkKGUpJCBDb21wdXRlIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBlcnJvciByYXRlcyB1c2luZyB0aGlzIG5ldyB2YWx1ZSBmb3IgYGNvc3RgLgpcCmBgYHtyfQpiZXN0X29qIDwtIHR1bmVfb2okYmVzdC5tb2RlbAoKdHJhaW5fcHJlZF9iZXN0IDwtIHByZWRpY3QoYmVzdF9vaiwgb2pfdHJhaW4pCnRyYWluX2Vycm9yX2Jlc3QgPC0gbWVhbih0cmFpbl9wcmVkX2Jlc3QgIT0gb2pfdHJhaW4kUHVyY2hhc2UpCgp0ZXN0X3ByZWRfYmVzdCA8LSBwcmVkaWN0KGJlc3Rfb2osIG9qX3Rlc3QpCnRlc3RfZXJyb3JfYmVzdCA8LSBtZWFuKHRlc3RfcHJlZF9iZXN0ICE9IG9qX3Rlc3QkUHVyY2hhc2UpCgpjYXQoIlRyYWluaW5nIEVycm9yIFJhdGUgKEJlc3QgQ29zdCk6Iiwgcm91bmQodHJhaW5fZXJyb3JfYmVzdCwgNCksICJcbiIpCmNhdCgiVGVzdCBFcnJvciBSYXRlIChCZXN0IENvc3QpOiIsIHJvdW5kKHRlc3RfZXJyb3JfYmVzdCwgNCksICJcbiIpCmBgYAoKIyMjIyMgJChmKSQgUmVwZWF0IHBhcnRzIChiKSB0aHJvdWdoIChlKSB1c2luZyBhIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmUgd2l0aCBhIHJhZGlhbCBrZXJuZWwuIFVzZSB0aGUgZGVmYXVsdCB2YWx1ZSBmb3IgYGdhbW1hYC4KXApgYGB7cn0Kc2V0LnNlZWQoMSkKb2pfc3ZtX3JhZGlhbCA8LSBzdm0oUHVyY2hhc2UgfiAuLCBkYXRhID0gb2pfdHJhaW4sIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMC4wMSkKc3VtbWFyeShval9zdm1fcmFkaWFsKQoKdHJhaW5fcHJlZF9yYWRpYWwgPC0gcHJlZGljdChval9zdm1fcmFkaWFsLCBval90cmFpbikKdHJhaW5fZXJyb3JfcmFkaWFsIDwtIG1lYW4odHJhaW5fcHJlZF9yYWRpYWwgIT0gb2pfdHJhaW4kUHVyY2hhc2UpCnRlc3RfcHJlZF9yYWRpYWwgPC0gcHJlZGljdChval9zdm1fcmFkaWFsLCBval90ZXN0KQp0ZXN0X2Vycm9yX3JhZGlhbCA8LSBtZWFuKHRlc3RfcHJlZF9yYWRpYWwgIT0gb2pfdGVzdCRQdXJjaGFzZSkKY2F0KCJUcmFpbmluZyBFcnJvciBSYXRlIChSYWRpYWwsIGNvc3Q9MC4wMSk6Iiwgcm91bmQodHJhaW5fZXJyb3JfcmFkaWFsLCA0KSwgIlxuIikKY2F0KCJUZXN0IEVycm9yIFJhdGUgKFJhZGlhbCwgY29zdD0wLjAxKToiLCByb3VuZCh0ZXN0X2Vycm9yX3JhZGlhbCwgNCksICJcbiIpCgp0dW5lX3JhZGlhbF9vaiA8LSB0dW5lKGUxMDcxOjpzdm0sIFB1cmNoYXNlIH4gLiwgZGF0YSA9IG9qX3RyYWluLAogICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9ICJyYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4xLCAxLCAxMCkpKQpzdW1tYXJ5KHR1bmVfcmFkaWFsX29qKQoKYmVzdF9yYWRpYWxfc3ZtIDwtIHR1bmVfcmFkaWFsX29qJGJlc3QubW9kZWwKdHJhaW5fcHJlZF9iZXN0X3JhZGlhbCA8LSBwcmVkaWN0KGJlc3RfcmFkaWFsX3N2bSwgb2pfdHJhaW4pCnRyYWluX2Vycm9yX2Jlc3RfcmFkaWFsIDwtIG1lYW4odHJhaW5fcHJlZF9iZXN0X3JhZGlhbCAhPSBval90cmFpbiRQdXJjaGFzZSkKdGVzdF9wcmVkX2Jlc3RfcmFkaWFsIDwtIHByZWRpY3QoYmVzdF9yYWRpYWxfc3ZtLCBval90ZXN0KQp0ZXN0X2Vycm9yX2Jlc3RfcmFkaWFsIDwtIG1lYW4odGVzdF9wcmVkX2Jlc3RfcmFkaWFsICE9IG9qX3Rlc3QkUHVyY2hhc2UpCmNhdCgiVHJhaW5pbmcgRXJyb3IgUmF0ZSAoUmFkaWFsLCBCZXN0IENvc3QpOiIsIHJvdW5kKHRyYWluX2Vycm9yX2Jlc3RfcmFkaWFsLCA0KSwgIlxuIikKY2F0KCJUZXN0IEVycm9yIFJhdGUgKFJhZGlhbCwgQmVzdCBDb3N0KToiLCByb3VuZCh0ZXN0X2Vycm9yX2Jlc3RfcmFkaWFsLCA0KSwgIlxuIikKYGBgCgpBIHRvdGFsIG9mIDYzNCBzdXBwb3J0IHZlY3RvcnMgYXJlIG1hZGUgd2l0aCAzMTkgYmVsb25naW5nIHRvIENIIGFuZCAzMTUgYmVsb25naW5nIHRvIE1NLiBUaGlzIG1vZGVsIGhhcyBhIHRyYWluaW5nIGVycm9yIG9mIDM5LjM4JSBhbmQgdGVzdGluZyBlcnJvciBvZiAzNy43OCUuIFRoZSBsb3dlc3QgZXJyb3IgY2FuIGJlIGZvdW5kIHdoZW4gY29zdCBpcyAxIHdpdGggYW4gYWNjdXJhY3kgb2YgODIuODclLiBUaGUgdHJhaW5pbmcgZXJyb3Igb2YgdGhpcyBiZXN0IG1vZGVsIGlzIDE1LjEyJSBhbmQgdGhlIHRlc3RpbmcgZXJyb3IgaXMgMTguNTIlLiAKCiMjIyMjICQoZykkIFJlcGVhdCBwYXJ0cyAoYikgdGhyb3VnaCAoZSkgdXNpbmcgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIHdpdGggYSBwb2x5bm9taWFsIGtlcm5lbC4gU2V0IGBkZWdyZWUgPSAyYC4KXApgYGB7cn0Kc2V0LnNlZWQoMSkKb2pfc3ZtX3BvbHkgPC0gc3ZtKFB1cmNoYXNlIH4gLiwgZGF0YSA9IG9qX3RyYWluLCAKICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9ICJwb2x5bm9taWFsIiwgZGVncmVlID0gMiwgY29zdCA9IDAuMDEpCnN1bW1hcnkob2pfc3ZtX3BvbHkpCgp0cmFpbl9wcmVkX3BvbHkgPC0gcHJlZGljdChval9zdm1fcG9seSwgb2pfdHJhaW4pCnRyYWluX2Vycm9yX3BvbHkgPC0gbWVhbih0cmFpbl9wcmVkX3BvbHkgIT0gb2pfdHJhaW4kUHVyY2hhc2UpCnRlc3RfcHJlZF9wb2x5IDwtIHByZWRpY3Qob2pfc3ZtX3BvbHksIG9qX3Rlc3QpCnRlc3RfZXJyb3JfcG9seSA8LSBtZWFuKHRlc3RfcHJlZF9wb2x5ICE9IG9qX3Rlc3QkUHVyY2hhc2UpCmNhdCgiVHJhaW5pbmcgRXJyb3IgUmF0ZSAoUG9seSwgY29zdD0wLjAxKToiLCByb3VuZCh0cmFpbl9lcnJvcl9wb2x5LCA0KSwgIlxuIikKY2F0KCJUZXN0IEVycm9yIFJhdGUgKFBvbHksIGNvc3Q9MC4wMSk6Iiwgcm91bmQodGVzdF9lcnJvcl9wb2x5LCA0KSwgIlxuIikKCnR1bmVfcG9seV9vaiA8LSB0dW5lKGUxMDcxOjpzdm0sIFB1cmNoYXNlIH4gLiwgZGF0YSA9IG9qX3RyYWluLAogICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSAicG9seW5vbWlhbCIsIGRlZ3JlZSA9IDIsCiAgICAgICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4xLCAxLCAxMCkpKQpzdW1tYXJ5KHR1bmVfcG9seV9vaikKCmJlc3RfcG9seV9zdm0gPC0gdHVuZV9wb2x5X29qJGJlc3QubW9kZWwKdHJhaW5fcHJlZF9iZXN0X3BvbHkgPC0gcHJlZGljdChiZXN0X3BvbHlfc3ZtLCBval90cmFpbikKdHJhaW5fZXJyb3JfYmVzdF9wb2x5IDwtIG1lYW4odHJhaW5fcHJlZF9iZXN0X3BvbHkgIT0gb2pfdHJhaW4kUHVyY2hhc2UpCnRlc3RfcHJlZF9iZXN0X3BvbHkgPC0gcHJlZGljdChiZXN0X3BvbHlfc3ZtLCBval90ZXN0KQp0ZXN0X2Vycm9yX2Jlc3RfcG9seSA8LSBtZWFuKHRlc3RfcHJlZF9iZXN0X3BvbHkgIT0gb2pfdGVzdCRQdXJjaGFzZSkKY2F0KCJUcmFpbmluZyBFcnJvciBSYXRlIChQb2x5LCBCZXN0IENvc3QpOiIsIHJvdW5kKHRyYWluX2Vycm9yX2Jlc3RfcG9seSwgNCksICJcbiIpCmNhdCgiVGVzdCBFcnJvciBSYXRlIChQb2x5LCBCZXN0IENvc3QpOiIsIHJvdW5kKHRlc3RfZXJyb3JfYmVzdF9wb2x5LCA0KSwgIlxuIikKYGBgCgpBIHRvdGFsIG9mIDYzNiBzdXBwb3J0IHZlY3RvcnMgYXJlIG1hZGUgd2l0aCAzMjEgYmVsb25naW5nIHRvIENIIGFuZCAzMTUgYmVsb25naW5nIHRvIE1NLiBUaGlzIG1vZGVsIGhhcyBhIHRyYWluaW5nIGVycm9yIG9mIDM3LjI1JSBhbmQgdGVzdGluZyBlcnJvciBvZiAzNi42NyUuIFRoZSBsb3dlc3QgZXJyb3IgY2FuIGJlIGZvdW5kIHdoZW4gY29zdCBpcyAxMCB3aXRoIGFuIGFjY3VyYWN5IG9mIDgxLjg3JSUuIFRoZSB0cmFpbmluZyBlcnJvciBvZiB0aGlzIGJlc3QgbW9kZWwgaXMgMTUlIGFuZCB0aGUgdGVzdGluZyBlcnJvciBpcyAxOC44OSUuIAoKIyMjIyMgJChoKSQgT3ZlcmFsbCwgd2hpY2ggYXBwcm9hY2ggc2VlbXMgdG8gZ2l2ZSB0aGUgYmVzdCByZXN1bHRzIG9uIHRoaXMgZGF0YT8KXApgYGB7cn0KZXJyb3JfY29tcGFyaXNvbiA8LSBkYXRhLmZyYW1lKAogIEtlcm5lbCA9IGMoIkxpbmVhciIsICJSYWRpYWwiLCAiUG9seW5vbWlhbCIpLAogIFRyYWluaW5nX0FjY3VyYWN5ID0gMSAtIGModHJhaW5fZXJyb3JfYmVzdCwgdHJhaW5fZXJyb3JfYmVzdF9yYWRpYWwsIHRyYWluX2Vycm9yX2Jlc3RfcG9seSksCiAgVGVzdF9BY2N1cmFjeSA9IDEgLSBjKHRlc3RfZXJyb3JfYmVzdCwgdGVzdF9lcnJvcl9iZXN0X3JhZGlhbCwgdGVzdF9lcnJvcl9iZXN0X3BvbHkpCikKCmVycm9yX2NvbXBhcmlzb24gJT4lCiAgbXV0YXRlKAogICAgVHJhaW5pbmdfQWNjdXJhY3kgPSBzcHJpbnRmKCIlLjJmJSUiLCBUcmFpbmluZ19BY2N1cmFjeSAqIDEwMCksCiAgICBUZXN0X0FjY3VyYWN5ID0gc3ByaW50ZigiJS4yZiUlIiwgVGVzdF9BY2N1cmFjeSAqIDEwMCkKICApICU+JQogIGthYmxlKGNhcHRpb24gPSAiQ29tcGFyaXNvbiBvZiBUcmFpbmluZyBhbmQgVGVzdCBFcnJvciBSYXRlcyBBY3Jvc3MgS2VybmVscyIpCmBgYAoKVGhlIGxpbmVhciBrZXJuZWwgaGFzIHRoZSBoaWdoZXN0IHRlc3RpbmcgYWNjdXJhY3kgYW5kIHNlZW1zIHRvIGdpdmUgdGhlIGJlc3QgcmVzdWx0cyBvbiB0aGlzIGRhdGEu