Section 5: Classification with More than Two Classes and the Caret Package

In the Classification with More than Two Classes and the Caret Package section, you will learn how to overcome the curse of dimensionality using methods that adapt to higher dimensions and how to use the caret package to implement many different machine learning algorithms.

After completing this section, you will be able to:

This section has three parts:

  1. Classification with more than two classes
  2. The Caret Package
  3. Set of exercises on the Titanic

5.1 Classification with more than two classes

5.1.1 Trees Motivation

Key points

  • LDA and QDA are not meant to be used with many predictors \(p\) because the number of parameters needed to be estimated becomes too large.
  • Curse of dimensionality: for kernel methods such as kNN or local regression, when they have multiple predictors used, the span/neighborhood/window made to include a given percentage of the data become large. With larger neighborhoods, our methods lose flexibility. The dimension here refers to the fact that when we have \(p\) predictors, the distance between two observations is computed in p-dimensional space.

5.1.2 Classification and Regression Trees (CART)

Key points

  • A tree is basically a flow chart of yes or no questions. The general idea of the methods we are describing is to define an algorithm that uses data to create these trees with predictions at the ends, referred to as nodes.
  • When the outcome is continuous, we call the decision tree method a regression tree.
  • Regression and decision trees operate by predicting an outcome variable \(Y\) by partitioning the predictors.
  • The general idea here is to build a decision tree and, at end of each node, obtain a predictor \(\hat{y}\). Mathematically, we are partitioning the predictor space into \(\mathbf{J}\) non-overlapping regions, \(\mathbf{R_1, R_2,..., R_J}\) and then for any predictor \(x\) that falls within region \(\mathbf{R_J}\), estimate \(f(x)\) with the average of the training observations \(\mathbf{y_i}\) for which the associated predictor \(\mathbf{x_i}\) in also in \(\mathbf{R_j}\).
  • To pick \(j\) and its value \(s\), we find the pair that minimizes the residual sum of squares (RSS):

\[\sum_{i:x_i,R_1(j,s)}(y_i-\hat{y}_{R_1})^2 + \sum_{i:x_i, R_2(j,s)}(y_i-\hat{y}_{R_2})^2\]

  • To fit the regression tree model, we can use the rpart function in the rpart package.
  • Two common parameters used for partition decision are the complexity parameter (cp) and the minimum number of observations required in a partition before partitioning it further (minsplit in the rpart package).
  • If we already have a tree and want to apply a higher cp value, we can use the prune function. We call this pruning a tree because we are snipping off partitions that do not meet a cp criterion.

Code

# Load data
library(tidyverse)
library(dslabs)
data("olive")
olive %>% as_tibble()
table(olive$region)
olive <- select(olive, -area)
# Predict region using KNN
library(caret)
fit <- train(region ~ .,  method = "knn", 
             tuneGrid = data.frame(k = seq(1, 15, 2)), 
             data = olive)
ggplot(fit)
# Plot distribution of each predictor stratified by region
olive %>% gather(fatty_acid, percentage, -region) %>%
  ggplot(aes(region, percentage, fill = region)) +
  geom_boxplot() +
  facet_wrap(~fatty_acid, scales = "free") +
  theme(axis.text.x = element_blank())
# plot values for eicosenoic and linoleic
p <- olive %>% 
  ggplot(aes(eicosenoic, linoleic, color = region)) + 
  geom_point()
p + geom_vline(xintercept = 0.065, lty = 2) + 
  geom_segment(x = -0.2, y = 10.54, xend = 0.065, yend = 10.54, color = "black", lty = 2)
# load data for regression tree
data("polls_2008")
qplot(day, margin, data = polls_2008)
library(rpart)
fit <- rpart(margin ~ ., data = polls_2008)
# visualize the splits 
plot(fit, margin = 0.1)
text(fit, cex = 0.75)
polls_2008 %>% 
  mutate(y_hat = predict(fit)) %>% 
  ggplot() +
  geom_point(aes(day, margin)) +
  geom_step(aes(day, y_hat), col="red")
# change parameters
fit <- rpart(margin ~ ., data = polls_2008, control = rpart.control(cp = 0, minsplit = 2))
polls_2008 %>% 
  mutate(y_hat = predict(fit)) %>% 
  ggplot() +
  geom_point(aes(day, margin)) +
  geom_step(aes(day, y_hat), col="red")
# use cross validation to choose cp
library(caret)
train_rpart <- train(margin ~ .,method = "rpart",tuneGrid = data.frame(cp = seq(0, 0.05, len = 25)),data = polls_2008)
ggplot(train_rpart)
# access the final model and plot it
plot(train_rpart$finalModel, margin = 0.1)
text(train_rpart$finalModel, cex = 0.75)
polls_2008 %>% 
  mutate(y_hat = predict(train_rpart)) %>% 
  ggplot() +
  geom_point(aes(day, margin)) +
  geom_step(aes(day, y_hat), col="red")
# prune the tree 
pruned_fit <- prune(fit, cp = 0.01)

5.1.3 Classification (Decision) Trees

Key points

  • Classification trees, or decision trees, are used in prediction problems where the outcome is categorical.
  • Decision trees form predictions by calculating which class is the most common among the training set observations within the partition, rather than taking the average in each partition.
  • Two of the more popular metrics to choose the partitions are the Gini index and entropy.

\[Gini(j) = \sum_{k=1}^{K}\hat{p}_{j,k}(1-\hat{p}_{j,k})\] \[entropy(j) = -\sum_{k=1}^{K}\hat{p}_{j,k}\log(\hat{p}_{j,k}),\ with\ 0*\log(0)\ defined\ as\ 0\]

  • Pros: Classification trees are highly interpretable and easy to visualize.They can model human decision processes and don’t require use of dummy predictors for categorical variables.
  • Cons: The approach via recursive partitioning can easily over-train and is therefore a bit harder to train than. Furthermore, in terms of accuracy, it is rarely the best performing method since it is not very flexible and is highly unstable to changes in training data.

Code

# fit a classification tree and plot it
train_rpart <- train(y ~ .,
              method = "rpart",
              tuneGrid = data.frame(cp = seq(0.0, 0.1, len = 25)),
              data = mnist_27$train)
plot(train_rpart)
# compute accuracy
confusionMatrix(predict(train_rpart, mnist_27$test), mnist_27$test$y)$overall["Accuracy"]

5.1.4 Random Forests

Key points

  • Random forests are a very popular machine learning approach that addresses the shortcomings of decision trees. The goal is to improve prediction performance and reduce instability by averaging multiple decision trees (a forest of trees constructed with randomness).
  • The general idea of random forests is to generate many predictors, each using regression or classification trees, and then forming a final prediction based on the average prediction of all these trees. To assure that the individual trees are not the same, we use the bootstrap to induce randomness.
  • A disadvantage of random forests is that we lose interpretability.
  • An approach that helps with interpretability is to examine variable importance. To define variable importance we count how often a predictor is used in the individual trees. The caret package includes the function varImp that extracts variable importance from any model in which the calculation is implemented.

Code

library(randomForest)
fit <- randomForest(margin~., data = polls_2008) 
plot(fit)
polls_2008 %>%
  mutate(y_hat = predict(fit, newdata = polls_2008)) %>% 
  ggplot() +
  geom_point(aes(day, margin)) +
  geom_line(aes(day, y_hat), col="red")
library(randomForest)
train_rf <- randomForest(y ~ ., data=mnist_27$train)
confusionMatrix(predict(train_rf, mnist_27$test), mnist_27$test$y)$overall["Accuracy"]
# use cross validation to choose parameter
train_rf_2 <- train(y ~ .,
      method = "Rborist",
      tuneGrid = data.frame(predFixed = 2, minNode = c(3, 50)),
      data = mnist_27$train)
confusionMatrix(predict(train_rf_2, mnist_27$test), mnist_27$test$y)$overall["Accuracy"]

5.1.5 Comprehension Check: Trees and Random Forests

Question 1

Create a simple dataset where the outcome grows 0.75 units on average for every increase in a predictor, using this code:

library(rpart)
n <- 1000
sigma <- 0.25
set.seed(1, sample.kind = "Rounding") # if using R 3.6 or later
x <- rnorm(n, 0, 1)
y <- 0.75 * x + rnorm(n, 0, sigma)
dat <- data.frame(x = x, y = y)
# Q: Which code correctly uses rpart to fit a regression tree and saves the result to fit?
#fit1b <- train(y~x, data=dat, method='rpart')
fit1 <- rpart(y ~ ., data = dat)

Question 2

Which of the following plots has the same tree shape obtained in Q1?

plot(fit1)
text(fit1)

Question 3

Below is most of the code to make a scatter plot of y versus x along with the predicted values based on the fit.

dat %>% 
    mutate(y_hat = predict(fit1)) %>% 
    ggplot() +
    geom_point(aes(x, y)) +
    # MISSING CODE
    geom_step(aes(x, y_hat), col=2)
    # END MISSING CODE

Question 4

Now run Random Forests instead of a regression tree using randomForest from the randomForest package, and remake the scatterplot with the prediction line. Part of the code is provided for you below.

library(randomForest)
# MISSING CODE
fit4 <- randomForest(y ~ x, data = dat)
# END MISSING CODE
dat %>% 
    mutate(y_hat = predict(fit4)) %>% 
    ggplot() +
    geom_point(aes(x, y)) +
    geom_step(aes(x, y_hat), col = 2)

Question 5

Use the plot function to see if the Random Forest from Q4 has converged or if we need more trees.

plot(fit4)
# Q: Which of these graphs is produced by plotting the random forest?

Question 6

It seems that the default values for the Random Forest result in an estimate that is too flexible (unsmooth). Re-run the Random Forest but this time with a node size of 50 and a maximum of 25 nodes. Remake the plot.

Part of the code is provided for you below.

library(randomForest)
# MISSING CODE
fit5 <- randomForest(y~x, data = dat, nodesize = 50, maxnodes = 25)
# END MISSING CODE
dat %>% 
    mutate(y_hat = predict(fit5)) %>% 
    ggplot() +
    geom_point(aes(x, y)) +
    geom_step(aes(x, y_hat), col = 2)
# Q: What code should replace #BLANK in the provided code?
plot(fit5)

5.2 The Caret Package

5.2.1 The Caret Package

Caret package links

http://topepo.github.io/caret/available-models.html

http://topepo.github.io/caret/train-models-by-tag.html

Key points

  • The caret package helps provides a uniform interface and standardized syntax for the many different machine learning packages in R. Note that caret does not automatically install the packages needed.

Code

library(tidyverse)
library(dslabs)
data("mnist_27")
library(caret)
train_glm <- train(y ~ ., method = "glm", data = mnist_27$train)
train_knn <- train(y ~ ., method = "knn", data = mnist_27$train)
y_hat_glm <- predict(train_glm, mnist_27$test, type = "raw")
y_hat_knn <- predict(train_knn, mnist_27$test, type = "raw")
confusionMatrix(y_hat_glm, mnist_27$test$y)$overall[["Accuracy"]]
confusionMatrix(y_hat_knn, mnist_27$test$y)$overall[["Accuracy"]]

5.2.2 Tuning Parameters with Caret

Key points

  • The train function automatically uses cross-validation to decide among a few default values of a tuning parameter.
  • The getModelInfo and modelLookup functions can be used to learn more about a model and the parameters that can be optimized.
  • We can use the tunegrid parameter in the train function to select a grid of values to be compared.
  • The trControl parameter and trainControl function can be used to change the way cross-validation is performed.
  • Note that not all parameters in machine learning algorithms are tuned. We use the train function to only optimize parameters that are tunable.

Code

library(tidyverse)
library(caret)
getModelInfo("knn")
modelLookup("knn")
train_knn <- train(y ~ ., method = "knn", data = mnist_27$train)
ggplot(train_knn, highlight = TRUE)
train_knn <- train(y ~ ., method = "knn", 
                   data = mnist_27$train,
                   tuneGrid = data.frame(k = seq(9, 71, 2)))
ggplot(train_knn, highlight = TRUE)
train_knn$bestTune
train_knn$finalModel
confusionMatrix(predict(train_knn, mnist_27$test, type = "raw"),
                mnist_27$test$y)$overall["Accuracy"]
control <- trainControl(method = "cv", number = 10, p = .9)
train_knn_cv <- train(y ~ ., method = "knn", 
                      data = mnist_27$train,
                      tuneGrid = data.frame(k = seq(9, 71, 2)),
                      trControl = control)
ggplot(train_knn_cv, highlight = TRUE)
train_knn$results %>% 
     ggplot(aes(x = k, y = Accuracy)) +
     geom_line() +
     geom_point() +
     geom_errorbar(aes(x = k, 
                       ymin = Accuracy - AccuracySD,
                       ymax = Accuracy + AccuracySD))
plot_cond_prob <- function(p_hat=NULL){
     tmp <- mnist_27$true_p
     if(!is.null(p_hat)){
          tmp <- mutate(tmp, p=p_hat)
     }
     tmp %>% ggplot(aes(x_1, x_2, z=p, fill=p)) +
          geom_raster(show.legend = FALSE) +
          scale_fill_gradientn(colors=c("#F8766D","white","#00BFC4")) +
          stat_contour(breaks=c(0.5),color="black")
}
plot_cond_prob(predict(train_knn, mnist_27$true_p, type = "prob", silent=TRUE)[,2])
modelLookup("gamLoess")
grid <- expand.grid(span = seq(0.15, 0.65, len = 10), degree = 1)
train_loess <- train(y ~ ., 
               method = "gamLoess",
               tuneGrid=grid,
               data = mnist_27$train, silent=TRUE)
ggplot(train_loess, highlight = TRUE)
confusionMatrix(data = predict(train_loess, mnist_27$test), 
                reference = mnist_27$test$y)$overall["Accuracy"]
#p1 <- plot_cond_prob(predict(train_loess, mnist_27$true_p, type = "prob", silent = TRUE)[,2])
p1

5.2.3 Comprehension Check: Caret Package

Question 1

Use the rpart function to fit a classification tree to the tissue_gene_expression dataset. Use the train function to estimate the accuracy. Try out cp values of seq(0, 0.1, 0.01). Plot the accuracies to report the results of the best model. Set the seed to 1991.

library(caret)
library(dslabs)
set.seed(1991, sample.kind = "Rounding") # if using R 3.6 or later
data("tissue_gene_expression")
    
fit1 <- with(tissue_gene_expression, 
                train(x, y, method = "rpart",
                      tuneGrid = data.frame(cp = seq(0, 0.1, 0.01))))
    
ggplot(fit1)  

Q: Which value of cp gives the highest accuracy?
A: 0

Question 2

Note that there are only 6 placentas in the dataset. By default, rpart requires 20 observations before splitting a node. That means that it is difficult to have a node in which placentas are the majority. Rerun the analysis you did in the exercise in Q1, but this time, allow rpart to split any node by using the argument control = rpart.control(minsplit = 0). Look at the confusion matrix again to determine whether the accuracy increases. Again, set the seed to 1991.

library(caret)
library(broom)
library(dslabs)
set.seed(1991, sample.kind = "Rounding") # if using R 3.6 or later
data("tissue_gene_expression")
fit2 <- with(tissue_gene_expression, 
                train(x, y, method = "rpart",
                      tuneGrid = data.frame(cp = seq(0, 0.1, 0.01)),
                      control = rpart.control(minsplit = 0)
                      ))
# Q: What is the accuracy now?
# A: 
confusionMatrix(fit2)
ggplot(fit2) 

Question 3

Plot the tree from the best fitting model of the analysis you ran in Q1.

plot(fit2$finalModel, margin = 0.1)
text(fit2$finalModel, cex = 0.75)
# Q: Which gene is at the 1st split?
# A: GPA33

Question 4

We can see that with just seven genes, we are able to predict the tissue type. Now let’s see if we can predict the tissue type with even fewer genes using a Random Forest. Use the train function and the rf method to train a Random Forest. Try out values of mtry ranging from seq(50, 200, 25) (you can also explore other values on your own). What mtry value maximizes accuracy? To permit small nodesize to grow as we did with the classification trees, use the following argument: nodesize = 1.
Note: This exercise will take some time to run. If you want to test out your code first, try using smaller values with ntree. Set the seed to 1991 again.

library(randomForest)
set.seed(1991, sample.kind = "Rounding") # if using R 3.6 or later
fit4 <- with(tissue_gene_expression, 
                train(x, y, method = "rf", tuneGrid = data.frame(mtry = seq(50, 200, 25)),  nodesize =1))
fit4
# Q: What value of mtry maximizes accuracy?
# A: 100
ggplot(fit4)

Question 5

Use the function varImp on the output of train and save it to an object called imp.
NB: ?varImp : a generic method for calculating variable importance for objects produced by train and method specific methods.

imp <- varImp(fit4) ## FILL MISSING CODE
imp

Question 6

The rpart model we ran above produced a tree that used just seven predictors. Extracting the predictor names is not straightforward, but can be done. If the output of the call to train was fit_rpart, we can extract the names like this:

tree_terms <- as.character(unique(fit_rpart$finalModel$frame$var[!(fit_rpart$finalModel$frame$var == "<leaf>")]))
tree_terms

Calculate the variable importance in the Random Forest call for these seven predictors and examine where they rank.

tree_terms <- as.character(unique(fit2$finalModel$frame$var[!(fit2$finalModel$frame$var == "<leaf>")]))
tree_terms
data_frame(term = rownames(imp$importance), 
            importance = imp$importance$Overall) %>%
    mutate(rank = rank(-importance)) %>% arrange(desc(importance)) %>%
    filter(term %in% tree_terms)
# Q: What is the importance of the CFHR4 gene in the Random Forest call?
# A: 35.03
# Q: What is the rank of the CFHR4 gene in the Random Forest call?
# A: 7

5.3 Set of exercises on the Titanic

5.3.1 Titanic Exercises, Part 1

Titanic Exercises

These exercises cover everything you have learned in this course so far. You will use the background information to provided to train a number of different types of models on this dataset.

Background

The Titanic was a British ocean liner that struck an iceberg and sunk on its maiden voyage in 1912 from the United Kingdom to New York. More than 1,500 of the estimated 2,224 passengers and crew died in the accident, making this one of the largest maritime disasters ever outside of war. The ship carried a wide range of passengers of all ages and both genders, from luxury travelers in first-class to immigrants in the lower classes. However, not all passengers were equally likely to survive the accident. You will use real data about a selection of 891 passengers to predict which passengers survived.

Libraries and data Use the titanic_train data frame from the titanic library as the starting point for this project.

library(titanic)    # loads titanic_train data frame
library(caret)
library(tidyverse)
library(rpart)
# 3 significant digits
options(digits = 3)
# clean the data - `titanic_train` is loaded with the titanic package
titanic_clean <- titanic_train %>%
    mutate(Survived = factor(Survived),
           Embarked = factor(Embarked),
           Age = ifelse(is.na(Age), median(Age, na.rm = TRUE), Age), # NA age to median age
           FamilySize = SibSp + Parch + 1) %>%    # count family members
    select(Survived,  Sex, Pclass, Age, Fare, SibSp, Parch, FamilySize, Embarked)

Question 1: Training and test sets

Split titanic_clean into test and training sets - after running the setup code, it should have 891 rows and 9 variables.

Set the seed to 42, then use the caret package to create a 20% data partition based on the Survived column. Assign the 20% partition to test_set and the remaining 80% partition to train_set.

Q: How many observations are in the training set? Q: How many observations are in the test set? Q :What proportion of individuals in the training set survived?

set.seed(42, sample.kind = 'Rounding') # if R version >= 3.6
test_index <- createDataPartition(titanic_clean$Survived, times = 1, p = 0.2, list = FALSE)
train_set <- titanic_clean[-test_index,]
test_set <- titanic_clean[test_index,]
nrow(train_set)
nrow(test_set)
mean(train_set$Survived == 1)

Question 2: Baseline prediction by guessing the outcome

The simplest prediction method is randomly guessing the outcome without using additional predictors. These methods will help us determine whether our machine learning algorithm performs better than chance. How accurate are two methods of guessing Titanic passenger survival?

Set the seed to 3. For each individual in the test set, randomly guess whether that person survived or not. Assume that each person has an equal chance of surviving or not surviving.

Q: What is the accuracy of this guessing method?

set.seed(3, sample.kind = 'Rounding') # if R version >= 3.6
guess_ <- sample(c(0,1), nrow(test_set), replace = TRUE)
test_set %>% 
    filter(Survived == guess_) %>%
    summarize(n() / nrow(test_set))
# guess with equal probability of survival
#guess <- sample(c(0,1), nrow(test_set), replace = TRUE)
#mean(guess == test_set$Survived)

Question 3a: Predicting survival by sex

Use the training set to determine whether members of a given sex were more likely to survive or die. Apply this insight to generate survival predictions on the test set.

train_set %>%
    group_by(Sex) %>%
    summarize(Survived = mean(Survived == 1))
# Q: What proportion of training set females survived? A: 0.731
# Q: What proportion of training set males survived? A: 0.197

Question 3b: Question 3b: Predicting survival by sex

Use the training set to determine whether members of a given sex were more likely to survive or die. Apply this insight to generate survival predictions on the test set.

Predict survival using sex on the test set: if the survival rate for a sex is over 0.5, predict survival for all individuals of that sex, and predict death if the survival rate for a sex is under 0.5.

What is the accuracy of this sex-based prediction method on the test set?

test_set %>%
    summarize( (sum(Sex == 'female' & Survived == 1) + sum(Sex == 'male' & Survived == 0)) / n())

Question 4a: Predicting survival by passenger class

In which class(es) (Pclass) were passengers more likely to survive than die?

survival_class <- titanic_clean %>%
    group_by(Pclass) %>%
    summarize(PredictingSurvival = ifelse(mean(Survived == 1) >=0.5, 1, 0))
survival_class

Question 4b: Predicting survival by passenger class

Predict survival using passenger class on the test set: predict survival if the survival rate for a class is over 0.5, otherwise predict death.

What is the accuracy of this class-based prediction method on the test set?

test_set %>%
    inner_join(survival_class, by='Pclass') %>%
    summarize(PredictingSurvival = mean(Survived == PredictingSurvival))

Question 4c: Predicting survival by passenger class

Group passengers by both sex and passenger class.

Which sex and class combinations were more likely to survive than die?

survival_class <- titanic_clean %>%
    group_by(Sex, Pclass) %>%
    summarize(PredictingSurvival = ifelse(mean(Survived == 1) > 0.5, 1, 0))
survival_class

Question 4d: What is the accuracy of this sex- and class-based prediction method on the test set?

Predict survival using both sex and passenger class on the test set. Predict survival if the survival rate for a sex/class combination is over 0.5, otherwise predict death.

test_set %>%
    inner_join(survival_class, by=c('Sex', 'Pclass')) %>%
    summarize(PredictingSurvival = mean(Survived == PredictingSurvival))

Question 5a: Confusion matrix

Use the confusionMatrix function to create confusion matrices for the sex model, class model, and combined sex and class model. You will need to convert predictions and survival status to factors to use this function.

What is the “positive” class used to calculate confusion matrix metrics? Which model has the highest sensitivity? Which model has the highest specificity? Which model has the highest balanced accuracy?

# Confusion Matrix: sex model
sex_model <- train_set %>%
    group_by(Sex) %>%
    summarize(Survived_predict = ifelse(mean(Survived == 1) > 0.5, 1, 0))
test_set1 <- test_set %>%
    inner_join(sex_model, by = 'Sex')
cm1 <- confusionMatrix(data = factor(test_set1$Survived_predict), reference = factor(test_set1$Survived))
cm1 %>%
    tidy() %>%
    filter(term == 'sensitivity') %>%
    .$estimate
cm1 %>%
    tidy() %>%
    filter(term == 'specificity') %>%
    .$estimate
cm1 %>%
    tidy() %>%
    filter(term == 'balanced_accuracy') %>%
    .$estimate
# Confusion Matrix: class model
class_model <- train_set %>%
    group_by(Pclass) %>%
    summarize(Survived_predict = ifelse(mean(Survived == 1) > 0.5, 1, 0))
test_set2 <- test_set %>%
    inner_join(class_model, by = 'Pclass')
cm2 <- confusionMatrix(data = factor(test_set2$Survived_predict), reference = factor(test_set2$Survived))
cm2 %>%
    tidy() %>%
    filter(term == 'sensitivity') %>%
    .$estimate
cm2 %>%
    tidy() %>%
    filter(term == 'specificity') %>%
    .$estimate
cm2 %>%
    tidy() %>%
    filter(term == 'balanced_accuracy') %>%
    .$estimate
# Confusion Matrix: sex and class model
sex_class_model <- train_set %>%
    group_by(Sex, Pclass) %>%
    summarize(Survived_predict = ifelse(mean(Survived == 1) > 0.5, 1, 0))
test_set3 <- test_set %>%
    inner_join(sex_class_model, by=c('Sex', 'Pclass'))
cm3 <- confusionMatrix(data = factor(test_set3$Survived_predict), reference = factor(test_set3$Survived))
cm3 %>%
    tidy() %>%
    filter(term == 'sensitivity') %>%
    .$estimate
cm3 %>%
    tidy() %>%
    filter(term == 'specificity') %>%
    .$estimate
cm3 %>%
    tidy() %>%
    filter(term == 'balanced_accuracy') %>%
    .$estimate

Question 5b: Confusion matrix

Q: What is the maximum value of balanced accuracy? A: cf. 5a

Question 6: F1 scores

Use the F_meas function to calculate F1 scores for the sex model, class model, and combined sex and class model. You will need to convert predictions to factors to use this function.

Which model has the highest F1 score?

F_meas(data=factor(test_set1$Survived), reference = factor(test_set1$Survived_predict))
F_meas(data=factor(test_set2$Survived), reference = factor(test_set2$Survived_predict))
F_meas(data=factor(test_set3$Survived), reference = factor(test_set3$Survived_predict))

5.3.2 Titanic Exercises, Part 2

Question 7: Survival by fare - LDA and QDA

Train a model using linear discriminant analysis (LDA) with the caret lda method using Fare as the only predictor. What is the accuracy on the test set for the LDA model?

fit_lda <- train(Survived ~ Fare, data = train_set, method = 'lda')
Survived_hat <- predict(fit_lda, test_set)
mean(test_set$Survived == Survived_hat)

Train a model using quadratic discriminant analysis (QDA) with the caret qda method using fare as the only predictor. What is the accuracy on the test set for the QDA model?

fit_qda <- train(Survived ~ Fare, data = train_set, method = 'qda')
Survived_hat <- predict(fit_qda, test_set)
mean(test_set$Survived == Survived_hat)

Question 8: Logistic regression models

Train a logistic regression model with the caret glm method using age as the only predictor. What is the accuracy on the test set using age as the only predictor?

fit_logreg_a <- glm(Survived ~ Age, data = train_set, family = 'binomial')
survived_hat_a <- ifelse(predict(fit_logreg_a, test_set) >= 0, 1, 0)
mean(survived_hat_a == test_set$Survived)

Train a logistic regression model with the caret glm method using four predictors: sex, class, fare, and age. What is the accuracy on the test set using these four predictors?

fit_logreg_b <- glm(Survived ~ Sex + Pclass + Fare + Age, data = train_set, family = 'binomial')
survived_hat_b <- ifelse(predict(fit_logreg_b, test_set) >= 0, 1, 0)
mean(survived_hat_b == test_set$Survived)

Train a logistic regression model with the caret glm method using all predictors. Ignore warnings about rank-deficient fit. What is the accuracy on the test set using all predictors?

str(train_set)
fit_logreg_c <- glm(Survived ~ ., data = train_set, family = 'binomial')
survived_hat_c <- ifelse(predict(fit_logreg_c, test_set) >= 0, 1, 0)
mean(survived_hat_c == test_set$Survived)

Question 9a: kNN model

Set the seed to 6. Train a kNN model on the training set using caret. Try tuning with k = seq(3, 51, 2). What is the optimal value of the number of neighbors k?

set.seed(6, sample.kind = "Rounding")
# Method below doesn't give same result as EdX (though it is correct)
# ks <- seq(3,51,2)
# res_knn9a <- sapply(ks, function(k) {
#     fit_knn9a <- knn3(Survived ~ ., data = train_set, k = k)
#     survived_hat <- predict(fit_knn9a, train_set, type = "class") %>% factor(levels = levels(train_set$Survived))
#     cm_test <- confusionMatrix(data = survived_hat, reference = train_set$Survived)
#     cm_test$overall["Accuracy"]
# })
# ks[which.max(res_knn9a)]
# Other method using train function
k <- seq(3,51,2)
fit_knn9a <- train(Survived ~ ., data = train_set, method = "knn", tuneGrid = data.frame(k))
fit_knn9a$bestTune

Question 9b: kNN model

Plot the kNN model to investigate the relationship between the number of neighbors and accuracy on the training set.

ggplot(fit_knn9a)

Question 9c: kNN model

What is the accuracy of the kNN model on the test set?

survived_hat <- predict(fit_knn9a, test_set) %>% factor(levels = levels(test_set$Survived))
cm_test <- confusionMatrix(data = survived_hat, reference = test_set$Survived)
cm_test$overall["Accuracy"]

Question 10: Cross-validation

Set the seed to 8 and train a new kNN model. Instead of the default training control, use 10-fold cross-validation where each partition consists of 10% of the total.

What is the optimal value of k using cross-validation? What is the accuracy on the test set using the cross-validated kNN model?

set.seed(8, sample.kind = "Rounding")
fit_knn10 <- train(Survived ~ ., 
                   data=train_set, 
                   method = "knn",
                   tuneGrid = data.frame(k = seq(3, 51, 2)),
                   trControl = trainControl(method = "cv", number=10, p=0.9))
fit_knn10
survived_hat <- predict(fit_knn10, test_set)
cm_test <- confusionMatrix(data = survived_hat, reference = test_set$Survived)
cm_test$overall["Accuracy"]

Question 11a: Classification tree model

Set the seed to 10. Use caret to train a decision tree with the rpart method. Tune the complexity parameter with cp = seq(0, 0.05, 0.002).

What is the optimal value of the complexity parameter (cp)? What is the accuracy of the decision tree model on the test set?

set.seed(10, sample.kind = 'Rounding')
fit_rpart11 <- train(Survived ~ ., 
                   data=train_set, 
                   method = "rpart",
                   tuneGrid = data.frame(cp = seq(0, 0.05, 0.002)))
plot(fit_rpart11)
survived_hat <- predict(fit_rpart11, test_set)
cm_test <- confusionMatrix(data = survived_hat, reference = test_set$Survived)
cm_test$overall["Accuracy"]

Question 11b: Classification tree model

Inspect the final model and plot the decision tree.

Which variables are used in the decision tree?

fit_rpart11$finalModel
plot(fit_rpart11$finalModel, margin=0.1)
text(fit_rpart11$finalModel, cex = 0.75)

Question 12: Random forest model

Set the seed to 14. Use the caret train function with the rf method to train a random forest. Test values of mtry ranging from 1 to 7. Set ntree to 100.

What mtry value maximizes accuracy?
What is the accuracy of the random forest model on the test set?
Use varImp on the random forest model object to determine the importance of various predictors to the random forest model.
What is the most important variable?

set.seed(14, sample.kind = 'Rounding')
fit12_rf <- train(Survived ~., 
                  data = train_set,
                  method = "rf", 
                  tuneGrid = data.frame(mtry = seq(1, 7)), 
                  ntree = 100)
fit12_rf$bestTune
survived_hat <- predict(fit12_rf, test_set)
mean(survived_hat == test_set$Survived)
varImp(fit12_rf)
LS0tDQp0aXRsZTogJ0RhdGEgU2NpZW5jZTogTWFjaGluZSBMZWFybmluZyAtIEhhcnZhcmRYOiBQSDEyNS44eCcNCmF1dGhvcjogJ1JlemEgSGFzaGVtaScNCmRhdGU6ICcyMDE5LTA4LTEyJw0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCiMgU2V0IGtuaXRyIG9wdGlvbnMgZm9yIGtuaXR0aW5nIGNvZGUgaW50byB0aGUgcmVwb3J0Og0KIyAtIGVjaG89VFJVRTogcHJpbnQgb3V0IGNvZGUgKGVjaG8pLCB0aG91Z2ggYW55IHJlc3VsdHMvb3V0cHV0IHdvdWxkIHN0aWxsIGJlIGRpc3BsYXllZA0KIyAtIGNhY2hlPVRSVUU6IFNhdmUgcmVzdWx0cyBzbyB0aGF0IGNvZGUgYmxvY2tzIGFyZW4ndCByZS1ydW4gdW5sZXNzIGNvZGUgY2hhbmdlcyAoY2FjaGUpLA0KIyBfb3JfIGEgcmVsZXZhbnQgZWFybGllciBjb2RlIGJsb2NrIGNoYW5nZWQgKGF1dG9kZXApLCBidXQgZG9uJ3QgcmUtcnVuIGlmIHRoZQ0KIyBvbmx5IHRoaW5nIHRoYXQgY2hhbmdlZCB3YXMgdGhlIGNvbW1lbnRzIChjYWNoZS5jb21tZW50cykNCiMgLSBtZXNzYWdlLCB3YXJuaW5nID0gRkFMU0U6IERvbid0IGNsdXR0ZXIgUiBvdXRwdXQgd2l0aCBtZXNzYWdlcyBvciB3YXJuaW5ncw0KIyBUaGlzIF93aWxsXyBsZWF2ZSBlcnJvciBtZXNzYWdlcyBzaG93aW5nIHVwIGluIHRoZSBrbml0dGVkIHJlcG9ydA0KIyAtIHJlc3VsdHM9ImhpZGUiOiBoaWRlIHRoZSByZXN1bHRzL291dHB1dCAoYnV0IGhlcmUgdGhlIGNvZGUgd291bGQgc3RpbGwgYmUgZGlzcGxheWVkKQ0KIyAtIGluY2x1ZGU9RkFMU0U6ICBjaHVuayBpcyBldmFsdWF0ZWQsIGJ1dCBuZWl0aGVyIHRoZSBjb2RlIG5vciBpdHMgb3V0cHV0IGlzIGRpc3BsYXllZA0KIyAtIGV2YWw9RkFMU0U6IGNodW5rIGlzIG5vdCBldmFsdWF0ZWQNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVRSVUUsDQogICAgICAgICAgICAgICBjYWNoZT1UUlVFLCBhdXRvZGVwPVRSVUUsIGNhY2hlLmNvbW1lbnRzPUZBTFNFLA0KICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkNCmBgYA0KDQojIFNlY3Rpb24gNTogQ2xhc3NpZmljYXRpb24gd2l0aCBNb3JlIHRoYW4gVHdvIENsYXNzZXMgYW5kIHRoZSBDYXJldCBQYWNrYWdlDQoNCkluIHRoZSAqKkNsYXNzaWZpY2F0aW9uIHdpdGggTW9yZSB0aGFuIFR3byBDbGFzc2VzIGFuZCB0aGUgQ2FyZXQgUGFja2FnZSoqIHNlY3Rpb24sIHlvdSB3aWxsIGxlYXJuIGhvdyB0byBvdmVyY29tZSB0aGUgY3Vyc2Ugb2YgZGltZW5zaW9uYWxpdHkgdXNpbmcgbWV0aG9kcyB0aGF0IGFkYXB0IHRvIGhpZ2hlciBkaW1lbnNpb25zIGFuZCBob3cgdG8gdXNlIHRoZSBjYXJldCBwYWNrYWdlIHRvIGltcGxlbWVudCBtYW55IGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMuDQoNCkFmdGVyIGNvbXBsZXRpbmcgdGhpcyBzZWN0aW9uLCB5b3Ugd2lsbCBiZSBhYmxlIHRvOg0KDQoqIFVzZSAqKmNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHRyZWVzKiouDQoqIFVzZSAqKmNsYXNzaWZpY2F0aW9uIChkZWNpc2lvbikgdHJlZXMqKi4NCiogQXBwbHkgKipyYW5kb20gZm9yZXN0cyoqIHRvIGFkZHJlc3MgdGhlIHNob3J0Y29taW5ncyBvZiBkZWNpc2lvbiB0cmVlcy4gDQoqIFVzZSB0aGUgKipjYXJldCoqIHBhY2thZ2UgdG8gaW1wbGVtZW50IGEgdmFyaWV0eSBvZiBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMuDQoNClRoaXMgc2VjdGlvbiBoYXMgdGhyZWUgcGFydHM6IA0KDQogMS4gKipDbGFzc2lmaWNhdGlvbiB3aXRoIG1vcmUgdGhhbiB0d28gY2xhc3NlcyoqIA0KIDIuIFRoZSAqKkNhcmV0KiogUGFja2FnZQ0KIDMuIFNldCBvZiBleGVyY2lzZXMgb24gdGhlIFRpdGFuaWMNCiANCiMjIDUuMSBDbGFzc2lmaWNhdGlvbiB3aXRoIG1vcmUgdGhhbiB0d28gY2xhc3Nlcw0KIA0KIyMjIDUuMS4xIFRyZWVzIE1vdGl2YXRpb24NCg0KKipLZXkgcG9pbnRzKioNCg0KKiBMREEgYW5kIFFEQSBhcmUgKipub3QgbWVhbnQgdG8gYmUgdXNlZCB3aXRoIG1hbnkgcHJlZGljdG9ycyAkcCQqKiBiZWNhdXNlIHRoZSBudW1iZXIgb2YgcGFyYW1ldGVycyBuZWVkZWQgdG8gYmUgZXN0aW1hdGVkIGJlY29tZXMgdG9vIGxhcmdlLg0KKiAqKkN1cnNlIG9mIGRpbWVuc2lvbmFsaXR5OioqIGZvciBrZXJuZWwgbWV0aG9kcyBzdWNoIGFzIGtOTiBvciBsb2NhbCByZWdyZXNzaW9uLCB3aGVuIHRoZXkgaGF2ZSBtdWx0aXBsZSBwcmVkaWN0b3JzIHVzZWQsICB0aGUgc3Bhbi9uZWlnaGJvcmhvb2Qvd2luZG93IG1hZGUgdG8gaW5jbHVkZSBhIGdpdmVuIHBlcmNlbnRhZ2Ugb2YgdGhlIGRhdGEgYmVjb21lIGxhcmdlLiBXaXRoIGxhcmdlciBuZWlnaGJvcmhvb2RzLCBvdXIgbWV0aG9kcyBsb3NlIGZsZXhpYmlsaXR5LiBUaGUgZGltZW5zaW9uIGhlcmUgcmVmZXJzIHRvIHRoZSBmYWN0IHRoYXQgd2hlbiB3ZSBoYXZlICRwJCBwcmVkaWN0b3JzLCB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0d28gb2JzZXJ2YXRpb25zIGlzIGNvbXB1dGVkIGluICpwLWRpbWVuc2lvbmFsKiBzcGFjZS4NCg0KIyMjIDUuMS4yIENsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzIChDQVJUKQ0KDQoqKktleSBwb2ludHMqKg0KDQoqIEEgdHJlZSBpcyBiYXNpY2FsbHkgYSAqKmZsb3cgY2hhcnQgb2YgeWVzIG9yIG5vIHF1ZXN0aW9ucyoqLiBUaGUgZ2VuZXJhbCBpZGVhIG9mIHRoZSBtZXRob2RzIHdlIGFyZSBkZXNjcmliaW5nIGlzIHRvIGRlZmluZSBhbiBhbGdvcml0aG0gdGhhdCB1c2VzIGRhdGEgdG8gY3JlYXRlIHRoZXNlIHRyZWVzIHdpdGggcHJlZGljdGlvbnMgYXQgdGhlIGVuZHMsIHJlZmVycmVkIHRvIGFzIG5vZGVzLg0KKiBXaGVuIHRoZSBvdXRjb21lIGlzIGNvbnRpbnVvdXMsIHdlIGNhbGwgdGhlIGRlY2lzaW9uIHRyZWUgbWV0aG9kIGEgKipyZWdyZXNzaW9uIHRyZWUqKi4NCiogUmVncmVzc2lvbiBhbmQgZGVjaXNpb24gdHJlZXMgb3BlcmF0ZSBieSBwcmVkaWN0aW5nIGFuIG91dGNvbWUgdmFyaWFibGUgJFkkIGJ5ICoqcGFydGl0aW9uaW5nIHRoZSBwcmVkaWN0b3JzKiouDQoqIFRoZSBnZW5lcmFsIGlkZWEgaGVyZSBpcyB0byAqKmJ1aWxkIGEgZGVjaXNpb24gdHJlZSoqIGFuZCwgYXQgZW5kIG9mIGVhY2ggbm9kZSwgb2J0YWluIGEgcHJlZGljdG9yICRcaGF0e3l9JC4gTWF0aGVtYXRpY2FsbHksIHdlIGFyZSAqKnBhcnRpdGlvbmluZyB0aGUgcHJlZGljdG9yIHNwYWNlKiogaW50byAkXG1hdGhiZntKfSQgbm9uLW92ZXJsYXBwaW5nIHJlZ2lvbnMsICRcbWF0aGJme1JfMSwgUl8yLC4uLiwgUl9KfSQgYW5kIHRoZW4gZm9yIGFueSBwcmVkaWN0b3IgJHgkIHRoYXQgZmFsbHMgd2l0aGluIHJlZ2lvbiAkXG1hdGhiZntSX0p9JCwgZXN0aW1hdGUgJGYoeCkkIHdpdGggdGhlIGF2ZXJhZ2Ugb2YgdGhlIHRyYWluaW5nIG9ic2VydmF0aW9ucyAkXG1hdGhiZnt5X2l9JCBmb3Igd2hpY2ggdGhlIGFzc29jaWF0ZWQgcHJlZGljdG9yICRcbWF0aGJme3hfaX0kIGluIGFsc28gaW4gJFxtYXRoYmZ7Ul9qfSQuDQoqIFRvIHBpY2sgJGokIGFuZCBpdHMgdmFsdWUgJHMkLCB3ZSBmaW5kIHRoZSBwYWlyIHRoYXQgKiptaW5pbWl6ZXMgdGhlIHJlc2lkdWFsIHN1bSBvZiBzcXVhcmVzIChSU1MpOioqDQoNCiQkXHN1bV97aTp4X2ksUl8xKGoscyl9KHlfaS1caGF0e3l9X3tSXzF9KV4yICsgXHN1bV97aTp4X2ksIFJfMihqLHMpfSh5X2ktXGhhdHt5fV97Ul8yfSleMiQkDQoNCiogVG8gZml0IHRoZSByZWdyZXNzaW9uIHRyZWUgbW9kZWwsIHdlIGNhbiB1c2UgdGhlICoqcnBhcnQqKiBmdW5jdGlvbiBpbiB0aGUgcnBhcnQgcGFja2FnZS4NCiogVHdvIGNvbW1vbiBwYXJhbWV0ZXJzIHVzZWQgZm9yIHBhcnRpdGlvbiBkZWNpc2lvbiBhcmUgdGhlICoqY29tcGxleGl0eSBwYXJhbWV0ZXIgKGNwKSoqIGFuZCB0aGUgKiptaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgcmVxdWlyZWQgaW4gYSBwYXJ0aXRpb24qKiBiZWZvcmUgcGFydGl0aW9uaW5nIGl0IGZ1cnRoZXIgKCoqbWluc3BsaXQqKiBpbiB0aGUgcnBhcnQgcGFja2FnZSkuIA0KKiBJZiB3ZSBhbHJlYWR5IGhhdmUgYSB0cmVlIGFuZCB3YW50IHRvIGFwcGx5IGEgaGlnaGVyIGNwIHZhbHVlLCB3ZSBjYW4gdXNlIHRoZSAqKnBydW5lKiogZnVuY3Rpb24uIFdlIGNhbGwgdGhpcyBwcnVuaW5nIGEgdHJlZSBiZWNhdXNlIHdlIGFyZSBzbmlwcGluZyBvZmYgcGFydGl0aW9ucyB0aGF0IGRvIG5vdCBtZWV0IGEgY3AgY3JpdGVyaW9uLiANCg0KKipDb2RlKioNCmBgYHtyfQ0KIyBMb2FkIGRhdGENCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkc2xhYnMpDQpkYXRhKCJvbGl2ZSIpDQpvbGl2ZSAlPiUgYXNfdGliYmxlKCkNCnRhYmxlKG9saXZlJHJlZ2lvbikNCm9saXZlIDwtIHNlbGVjdChvbGl2ZSwgLWFyZWEpDQojIFByZWRpY3QgcmVnaW9uIHVzaW5nIEtOTg0KbGlicmFyeShjYXJldCkNCmZpdCA8LSB0cmFpbihyZWdpb24gfiAuLCAgbWV0aG9kID0gImtubiIsIA0KICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShrID0gc2VxKDEsIDE1LCAyKSksIA0KICAgICAgICAgICAgIGRhdGEgPSBvbGl2ZSkNCmdncGxvdChmaXQpDQojIFBsb3QgZGlzdHJpYnV0aW9uIG9mIGVhY2ggcHJlZGljdG9yIHN0cmF0aWZpZWQgYnkgcmVnaW9uDQpvbGl2ZSAlPiUgZ2F0aGVyKGZhdHR5X2FjaWQsIHBlcmNlbnRhZ2UsIC1yZWdpb24pICU+JQ0KICBnZ3Bsb3QoYWVzKHJlZ2lvbiwgcGVyY2VudGFnZSwgZmlsbCA9IHJlZ2lvbikpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBmYWNldF93cmFwKH5mYXR0eV9hY2lkLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpDQojIHBsb3QgdmFsdWVzIGZvciBlaWNvc2Vub2ljIGFuZCBsaW5vbGVpYw0KcCA8LSBvbGl2ZSAlPiUgDQogIGdncGxvdChhZXMoZWljb3Nlbm9pYywgbGlub2xlaWMsIGNvbG9yID0gcmVnaW9uKSkgKyANCiAgZ2VvbV9wb2ludCgpDQpwICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMC4wNjUsIGx0eSA9IDIpICsgDQogIGdlb21fc2VnbWVudCh4ID0gLTAuMiwgeSA9IDEwLjU0LCB4ZW5kID0gMC4wNjUsIHllbmQgPSAxMC41NCwgY29sb3IgPSAiYmxhY2siLCBsdHkgPSAyKQ0KIyBsb2FkIGRhdGEgZm9yIHJlZ3Jlc3Npb24gdHJlZQ0KZGF0YSgicG9sbHNfMjAwOCIpDQpxcGxvdChkYXksIG1hcmdpbiwgZGF0YSA9IHBvbGxzXzIwMDgpDQpsaWJyYXJ5KHJwYXJ0KQ0KZml0IDwtIHJwYXJ0KG1hcmdpbiB+IC4sIGRhdGEgPSBwb2xsc18yMDA4KQ0KIyB2aXN1YWxpemUgdGhlIHNwbGl0cyANCnBsb3QoZml0LCBtYXJnaW4gPSAwLjEpDQp0ZXh0KGZpdCwgY2V4ID0gMC43NSkNCnBvbGxzXzIwMDggJT4lIA0KICBtdXRhdGUoeV9oYXQgPSBwcmVkaWN0KGZpdCkpICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX3BvaW50KGFlcyhkYXksIG1hcmdpbikpICsNCiAgZ2VvbV9zdGVwKGFlcyhkYXksIHlfaGF0KSwgY29sPSJyZWQiKQ0KIyBjaGFuZ2UgcGFyYW1ldGVycw0KZml0IDwtIHJwYXJ0KG1hcmdpbiB+IC4sIGRhdGEgPSBwb2xsc18yMDA4LCBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDAsIG1pbnNwbGl0ID0gMikpDQpwb2xsc18yMDA4ICU+JSANCiAgbXV0YXRlKHlfaGF0ID0gcHJlZGljdChmaXQpKSAlPiUgDQogIGdncGxvdCgpICsNCiAgZ2VvbV9wb2ludChhZXMoZGF5LCBtYXJnaW4pKSArDQogIGdlb21fc3RlcChhZXMoZGF5LCB5X2hhdCksIGNvbD0icmVkIikNCiMgdXNlIGNyb3NzIHZhbGlkYXRpb24gdG8gY2hvb3NlIGNwDQpsaWJyYXJ5KGNhcmV0KQ0KdHJhaW5fcnBhcnQgPC0gdHJhaW4obWFyZ2luIH4gLixtZXRob2QgPSAicnBhcnQiLHR1bmVHcmlkID0gZGF0YS5mcmFtZShjcCA9IHNlcSgwLCAwLjA1LCBsZW4gPSAyNSkpLGRhdGEgPSBwb2xsc18yMDA4KQ0KZ2dwbG90KHRyYWluX3JwYXJ0KQ0KIyBhY2Nlc3MgdGhlIGZpbmFsIG1vZGVsIGFuZCBwbG90IGl0DQpwbG90KHRyYWluX3JwYXJ0JGZpbmFsTW9kZWwsIG1hcmdpbiA9IDAuMSkNCnRleHQodHJhaW5fcnBhcnQkZmluYWxNb2RlbCwgY2V4ID0gMC43NSkNCnBvbGxzXzIwMDggJT4lIA0KICBtdXRhdGUoeV9oYXQgPSBwcmVkaWN0KHRyYWluX3JwYXJ0KSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQoYWVzKGRheSwgbWFyZ2luKSkgKw0KICBnZW9tX3N0ZXAoYWVzKGRheSwgeV9oYXQpLCBjb2w9InJlZCIpDQojIHBydW5lIHRoZSB0cmVlIA0KcHJ1bmVkX2ZpdCA8LSBwcnVuZShmaXQsIGNwID0gMC4wMSkNCmBgYA0KDQoNCiMjIyA1LjEuMyBDbGFzc2lmaWNhdGlvbiAoRGVjaXNpb24pIFRyZWVzDQoNCioqS2V5IHBvaW50cyoqDQoNCiogKipDbGFzc2lmaWNhdGlvbiB0cmVlcyoqLCBvciBkZWNpc2lvbiB0cmVlcywgYXJlIHVzZWQgaW4gcHJlZGljdGlvbiBwcm9ibGVtcyB3aGVyZSB0aGUgKipvdXRjb21lIGlzIGNhdGVnb3JpY2FsKiouIA0KKiBEZWNpc2lvbiB0cmVlcyBmb3JtIHByZWRpY3Rpb25zIGJ5IGNhbGN1bGF0aW5nICoqd2hpY2ggY2xhc3MgaXMgdGhlIG1vc3QgY29tbW9uKiogYW1vbmcgdGhlIHRyYWluaW5nIHNldCBvYnNlcnZhdGlvbnMgd2l0aGluIHRoZSBwYXJ0aXRpb24sIHJhdGhlciB0aGFuIHRha2luZyB0aGUgYXZlcmFnZSBpbiBlYWNoIHBhcnRpdGlvbi4NCiogVHdvIG9mIHRoZSBtb3JlIHBvcHVsYXIgbWV0cmljcyB0byBjaG9vc2UgdGhlIHBhcnRpdGlvbnMgYXJlIHRoZSAqKkdpbmkgaW5kZXgqKiBhbmQgKiplbnRyb3B5KiouDQoNCiQkR2luaShqKSA9IFxzdW1fe2s9MX1ee0t9XGhhdHtwfV97aixrfSgxLVxoYXR7cH1fe2osa30pJCQNCiQkZW50cm9weShqKSA9IC1cc3VtX3trPTF9XntLfVxoYXR7cH1fe2osa31cbG9nKFxoYXR7cH1fe2osa30pLFwgd2l0aFwgMCpcbG9nKDApXCBkZWZpbmVkXCBhc1wgMCQkDQoNCiogUHJvczogQ2xhc3NpZmljYXRpb24gdHJlZXMgYXJlIGhpZ2hseSBpbnRlcnByZXRhYmxlIGFuZCBlYXN5IHRvIHZpc3VhbGl6ZS5UaGV5IGNhbiBtb2RlbCBodW1hbiBkZWNpc2lvbiBwcm9jZXNzZXMgYW5kIGRvbid0IHJlcXVpcmUgdXNlIG9mIGR1bW15IHByZWRpY3RvcnMgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gIA0KKiBDb25zOiBUaGUgYXBwcm9hY2ggdmlhIHJlY3Vyc2l2ZSBwYXJ0aXRpb25pbmcgY2FuIGVhc2lseSBvdmVyLXRyYWluIGFuZCBpcyB0aGVyZWZvcmUgYSBiaXQgaGFyZGVyIHRvIHRyYWluIHRoYW4uIEZ1cnRoZXJtb3JlLCBpbiB0ZXJtcyBvZiBhY2N1cmFjeSwgaXQgaXMgcmFyZWx5IHRoZSBiZXN0IHBlcmZvcm1pbmcgbWV0aG9kIHNpbmNlIGl0IGlzIG5vdCB2ZXJ5IGZsZXhpYmxlIGFuZCBpcyBoaWdobHkgdW5zdGFibGUgdG8gY2hhbmdlcyBpbiB0cmFpbmluZyBkYXRhLiANCg0KKipDb2RlKioNCmBgYHtyfQ0KIyBmaXQgYSBjbGFzc2lmaWNhdGlvbiB0cmVlIGFuZCBwbG90IGl0DQp0cmFpbl9ycGFydCA8LSB0cmFpbih5IH4gLiwNCiAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwNCiAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKGNwID0gc2VxKDAuMCwgMC4xLCBsZW4gPSAyNSkpLA0KICAgICAgICAgICAgICBkYXRhID0gbW5pc3RfMjckdHJhaW4pDQpwbG90KHRyYWluX3JwYXJ0KQ0KIyBjb21wdXRlIGFjY3VyYWN5DQpjb25mdXNpb25NYXRyaXgocHJlZGljdCh0cmFpbl9ycGFydCwgbW5pc3RfMjckdGVzdCksIG1uaXN0XzI3JHRlc3QkeSkkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KYGBgDQoNCg0KIyMjIDUuMS40IFJhbmRvbSBGb3Jlc3RzDQoNCioqS2V5IHBvaW50cyoqDQoNCiogKipSYW5kb20gZm9yZXN0cyoqIGFyZSBhIHZlcnkgcG9wdWxhciBtYWNoaW5lIGxlYXJuaW5nIGFwcHJvYWNoIHRoYXQgYWRkcmVzc2VzIHRoZSBzaG9ydGNvbWluZ3Mgb2YgZGVjaXNpb24gdHJlZXMuIFRoZSBnb2FsIGlzIHRvIGltcHJvdmUgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBhbmQgcmVkdWNlIGluc3RhYmlsaXR5IGJ5ICoqYXZlcmFnaW5nIG11bHRpcGxlIGRlY2lzaW9uIHRyZWVzKiogKGEgZm9yZXN0IG9mIHRyZWVzIGNvbnN0cnVjdGVkIHdpdGggcmFuZG9tbmVzcykuDQoqIFRoZSBnZW5lcmFsIGlkZWEgb2YgcmFuZG9tIGZvcmVzdHMgaXMgdG8gZ2VuZXJhdGUgbWFueSBwcmVkaWN0b3JzLCBlYWNoIHVzaW5nIHJlZ3Jlc3Npb24gb3IgY2xhc3NpZmljYXRpb24gdHJlZXMsIGFuZCB0aGVuICoqZm9ybWluZyBhIGZpbmFsIHByZWRpY3Rpb24gYmFzZWQgb24gdGhlIGF2ZXJhZ2UgcHJlZGljdGlvbiBvZiBhbGwgdGhlc2UgdHJlZXMqKi4gVG8gYXNzdXJlIHRoYXQgdGhlIGluZGl2aWR1YWwgdHJlZXMgYXJlIG5vdCB0aGUgc2FtZSwgd2UgdXNlIHRoZSAqKmJvb3RzdHJhcCB0byBpbmR1Y2UgcmFuZG9tbmVzcyoqLiANCiogQSAqKmRpc2FkdmFudGFnZSoqIG9mIHJhbmRvbSBmb3Jlc3RzIGlzIHRoYXQgd2UgKipsb3NlIGludGVycHJldGFiaWxpdHkqKi4NCiogQW4gYXBwcm9hY2ggdGhhdCBoZWxwcyB3aXRoIGludGVycHJldGFiaWxpdHkgaXMgdG8gZXhhbWluZSAqKnZhcmlhYmxlIGltcG9ydGFuY2UqKi4gVG8gZGVmaW5lIHZhcmlhYmxlIGltcG9ydGFuY2Ugd2UgKipjb3VudCBob3cgb2Z0ZW4gYSBwcmVkaWN0b3IgaXMgdXNlZCBpbiB0aGUgaW5kaXZpZHVhbCB0cmVlcyoqLiBUaGUgY2FyZXQgcGFja2FnZSBpbmNsdWRlcyB0aGUgZnVuY3Rpb24gKip2YXJJbXAqKiB0aGF0IGV4dHJhY3RzIHZhcmlhYmxlIGltcG9ydGFuY2UgZnJvbSBhbnkgbW9kZWwgaW4gd2hpY2ggdGhlIGNhbGN1bGF0aW9uIGlzIGltcGxlbWVudGVkLiANCg0KKipDb2RlKioNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpmaXQgPC0gcmFuZG9tRm9yZXN0KG1hcmdpbn4uLCBkYXRhID0gcG9sbHNfMjAwOCkgDQpwbG90KGZpdCkNCnBvbGxzXzIwMDggJT4lDQogIG11dGF0ZSh5X2hhdCA9IHByZWRpY3QoZml0LCBuZXdkYXRhID0gcG9sbHNfMjAwOCkpICU+JSANCiAgZ2dwbG90KCkgKw0KICBnZW9tX3BvaW50KGFlcyhkYXksIG1hcmdpbikpICsNCiAgZ2VvbV9saW5lKGFlcyhkYXksIHlfaGF0KSwgY29sPSJyZWQiKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQp0cmFpbl9yZiA8LSByYW5kb21Gb3Jlc3QoeSB+IC4sIGRhdGE9bW5pc3RfMjckdHJhaW4pDQpjb25mdXNpb25NYXRyaXgocHJlZGljdCh0cmFpbl9yZiwgbW5pc3RfMjckdGVzdCksIG1uaXN0XzI3JHRlc3QkeSkkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KIyB1c2UgY3Jvc3MgdmFsaWRhdGlvbiB0byBjaG9vc2UgcGFyYW1ldGVyDQp0cmFpbl9yZl8yIDwtIHRyYWluKHkgfiAuLA0KICAgICAgbWV0aG9kID0gIlJib3Jpc3QiLA0KICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKHByZWRGaXhlZCA9IDIsIG1pbk5vZGUgPSBjKDMsIDUwKSksDQogICAgICBkYXRhID0gbW5pc3RfMjckdHJhaW4pDQpjb25mdXNpb25NYXRyaXgocHJlZGljdCh0cmFpbl9yZl8yLCBtbmlzdF8yNyR0ZXN0KSwgbW5pc3RfMjckdGVzdCR5KSRvdmVyYWxsWyJBY2N1cmFjeSJdDQpgYGANCg0KIyMjIDUuMS41IENvbXByZWhlbnNpb24gQ2hlY2s6IFRyZWVzIGFuZCBSYW5kb20gRm9yZXN0cw0KDQojIyMjIFF1ZXN0aW9uIDENCkNyZWF0ZSBhIHNpbXBsZSBkYXRhc2V0IHdoZXJlIHRoZSBvdXRjb21lIGdyb3dzIDAuNzUgdW5pdHMgb24gYXZlcmFnZSBmb3IgZXZlcnkgaW5jcmVhc2UgaW4gYSBwcmVkaWN0b3IsIHVzaW5nIHRoaXMgY29kZToNCg0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbiA8LSAxMDAwDQpzaWdtYSA8LSAwLjI1DQpzZXQuc2VlZCgxLCBzYW1wbGUua2luZCA9ICJSb3VuZGluZyIpICMgaWYgdXNpbmcgUiAzLjYgb3IgbGF0ZXINCnggPC0gcm5vcm0obiwgMCwgMSkNCnkgPC0gMC43NSAqIHggKyBybm9ybShuLCAwLCBzaWdtYSkNCmRhdCA8LSBkYXRhLmZyYW1lKHggPSB4LCB5ID0geSkNCiMgUTogV2hpY2ggY29kZSBjb3JyZWN0bHkgdXNlcyBycGFydCB0byBmaXQgYSByZWdyZXNzaW9uIHRyZWUgYW5kIHNhdmVzIHRoZSByZXN1bHQgdG8gZml0Pw0KI2ZpdDFiIDwtIHRyYWluKHl+eCwgZGF0YT1kYXQsIG1ldGhvZD0ncnBhcnQnKQ0KZml0MSA8LSBycGFydCh5IH4gLiwgZGF0YSA9IGRhdCkNCmBgYA0KDQoNCiMjIyMgUXVlc3Rpb24gMg0KV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBwbG90cyBoYXMgdGhlIHNhbWUgdHJlZSBzaGFwZSBvYnRhaW5lZCBpbiBRMT8NCmBgYHtyfQ0KcGxvdChmaXQxKQ0KdGV4dChmaXQxKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gMw0KQmVsb3cgaXMgbW9zdCBvZiB0aGUgY29kZSB0byBtYWtlIGEgc2NhdHRlciBwbG90IG9mIHkgdmVyc3VzIHggYWxvbmcgd2l0aCB0aGUgcHJlZGljdGVkIHZhbHVlcyBiYXNlZCBvbiB0aGUgZml0Lg0KYGBge3J9DQpkYXQgJT4lIA0KCW11dGF0ZSh5X2hhdCA9IHByZWRpY3QoZml0MSkpICU+JSANCglnZ3Bsb3QoKSArDQoJZ2VvbV9wb2ludChhZXMoeCwgeSkpICsNCgkjIE1JU1NJTkcgQ09ERQ0KICAgIGdlb21fc3RlcChhZXMoeCwgeV9oYXQpLCBjb2w9MikNCiAgICAjIEVORCBNSVNTSU5HIENPREUNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDQNCk5vdyBydW4gUmFuZG9tIEZvcmVzdHMgaW5zdGVhZCBvZiBhIHJlZ3Jlc3Npb24gdHJlZSB1c2luZyAqKnJhbmRvbUZvcmVzdCoqIGZyb20gdGhlIF9fcmFuZG9tRm9yZXN0X18gcGFja2FnZSwgYW5kIHJlbWFrZSB0aGUgc2NhdHRlcnBsb3Qgd2l0aCB0aGUgcHJlZGljdGlvbiBsaW5lLiBQYXJ0IG9mIHRoZSBjb2RlIGlzIHByb3ZpZGVkIGZvciB5b3UgYmVsb3cuDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KIyBNSVNTSU5HIENPREUNCmZpdDQgPC0gcmFuZG9tRm9yZXN0KHkgfiB4LCBkYXRhID0gZGF0KQ0KIyBFTkQgTUlTU0lORyBDT0RFDQpkYXQgJT4lIA0KCW11dGF0ZSh5X2hhdCA9IHByZWRpY3QoZml0NCkpICU+JSANCglnZ3Bsb3QoKSArDQoJZ2VvbV9wb2ludChhZXMoeCwgeSkpICsNCglnZW9tX3N0ZXAoYWVzKHgsIHlfaGF0KSwgY29sID0gMikNCmBgYA0KDQoNCiMjIyMgUXVlc3Rpb24gNQ0KVXNlIHRoZSBwbG90IGZ1bmN0aW9uIHRvIHNlZSBpZiB0aGUgUmFuZG9tIEZvcmVzdCBmcm9tIFE0IGhhcyBjb252ZXJnZWQgb3IgaWYgd2UgbmVlZCBtb3JlIHRyZWVzLg0KYGBge3J9DQpwbG90KGZpdDQpDQojIFE6IFdoaWNoIG9mIHRoZXNlIGdyYXBocyBpcyBwcm9kdWNlZCBieSBwbG90dGluZyB0aGUgcmFuZG9tIGZvcmVzdD8NCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDYNCkl0IHNlZW1zIHRoYXQgdGhlIGRlZmF1bHQgdmFsdWVzIGZvciB0aGUgUmFuZG9tIEZvcmVzdCByZXN1bHQgaW4gYW4gZXN0aW1hdGUgdGhhdCBpcyB0b28gZmxleGlibGUgKHVuc21vb3RoKS4gUmUtcnVuIHRoZSBSYW5kb20gRm9yZXN0IGJ1dCB0aGlzIHRpbWUgd2l0aCBhIG5vZGUgc2l6ZSBvZiA1MCBhbmQgYSBtYXhpbXVtIG9mIDI1IG5vZGVzLiBSZW1ha2UgdGhlIHBsb3QuDQoNClBhcnQgb2YgdGhlIGNvZGUgaXMgcHJvdmlkZWQgZm9yIHlvdSBiZWxvdy4NCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQojIE1JU1NJTkcgQ09ERQ0KZml0NSA8LSByYW5kb21Gb3Jlc3QoeX54LCBkYXRhID0gZGF0LCBub2Rlc2l6ZSA9IDUwLCBtYXhub2RlcyA9IDI1KQ0KIyBFTkQgTUlTU0lORyBDT0RFDQpkYXQgJT4lIA0KCW11dGF0ZSh5X2hhdCA9IHByZWRpY3QoZml0NSkpICU+JSANCglnZ3Bsb3QoKSArDQoJZ2VvbV9wb2ludChhZXMoeCwgeSkpICsNCglnZW9tX3N0ZXAoYWVzKHgsIHlfaGF0KSwgY29sID0gMikNCiMgUTogV2hhdCBjb2RlIHNob3VsZCByZXBsYWNlICNCTEFOSyBpbiB0aGUgcHJvdmlkZWQgY29kZT8NCnBsb3QoZml0NSkNCmBgYA0KDQoNCiMjIDUuMiBUaGUgQ2FyZXQgUGFja2FnZQ0KDQojIyMgNS4yLjEgVGhlIENhcmV0IFBhY2thZ2UNCg0KKipDYXJldCBwYWNrYWdlIGxpbmtzKioNCg0KaHR0cDovL3RvcGVwby5naXRodWIuaW8vY2FyZXQvYXZhaWxhYmxlLW1vZGVscy5odG1sDQoNCmh0dHA6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L3RyYWluLW1vZGVscy1ieS10YWcuaHRtbA0KDQoqKktleSBwb2ludHMqKg0KDQoqIFRoZSAqKmNhcmV0KiogcGFja2FnZSBoZWxwcyBwcm92aWRlcyBhIHVuaWZvcm0gaW50ZXJmYWNlIGFuZCBzdGFuZGFyZGl6ZWQgc3ludGF4IGZvciB0aGUgbWFueSBkaWZmZXJlbnQgbWFjaGluZSBsZWFybmluZyBwYWNrYWdlcyBpbiBSLiBOb3RlIHRoYXQgY2FyZXQgZG9lcyBub3QgYXV0b21hdGljYWxseSBpbnN0YWxsIHRoZSBwYWNrYWdlcyBuZWVkZWQuDQoNCioqQ29kZSoqDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkc2xhYnMpDQpkYXRhKCJtbmlzdF8yNyIpDQpsaWJyYXJ5KGNhcmV0KQ0KdHJhaW5fZ2xtIDwtIHRyYWluKHkgfiAuLCBtZXRob2QgPSAiZ2xtIiwgZGF0YSA9IG1uaXN0XzI3JHRyYWluKQ0KdHJhaW5fa25uIDwtIHRyYWluKHkgfiAuLCBtZXRob2QgPSAia25uIiwgZGF0YSA9IG1uaXN0XzI3JHRyYWluKQ0KeV9oYXRfZ2xtIDwtIHByZWRpY3QodHJhaW5fZ2xtLCBtbmlzdF8yNyR0ZXN0LCB0eXBlID0gInJhdyIpDQp5X2hhdF9rbm4gPC0gcHJlZGljdCh0cmFpbl9rbm4sIG1uaXN0XzI3JHRlc3QsIHR5cGUgPSAicmF3IikNCmNvbmZ1c2lvbk1hdHJpeCh5X2hhdF9nbG0sIG1uaXN0XzI3JHRlc3QkeSkkb3ZlcmFsbFtbIkFjY3VyYWN5Il1dDQpjb25mdXNpb25NYXRyaXgoeV9oYXRfa25uLCBtbmlzdF8yNyR0ZXN0JHkpJG92ZXJhbGxbWyJBY2N1cmFjeSJdXQ0KYGBgDQoNCg0KIyMjIDUuMi4yIFR1bmluZyBQYXJhbWV0ZXJzIHdpdGggQ2FyZXQNCg0KKipLZXkgcG9pbnRzKioNCg0KKiBUaGUgKip0cmFpbioqIGZ1bmN0aW9uIGF1dG9tYXRpY2FsbHkgdXNlcyBjcm9zcy12YWxpZGF0aW9uIHRvIGRlY2lkZSBhbW9uZyBhIGZldyBkZWZhdWx0IHZhbHVlcyBvZiBhIHR1bmluZyBwYXJhbWV0ZXIuDQoqIFRoZSAqKmdldE1vZGVsSW5mbyoqIGFuZCAqKm1vZGVsTG9va3VwKiogZnVuY3Rpb25zIGNhbiBiZSB1c2VkIHRvIGxlYXJuIG1vcmUgYWJvdXQgYSBtb2RlbCBhbmQgdGhlIHBhcmFtZXRlcnMgdGhhdCBjYW4gYmUgb3B0aW1pemVkLg0KKiBXZSBjYW4gdXNlIHRoZSAqKnR1bmVncmlkKiogcGFyYW1ldGVyIGluIHRoZSB0cmFpbiBmdW5jdGlvbiB0byBzZWxlY3QgYSBncmlkIG9mIHZhbHVlcyB0byBiZSBjb21wYXJlZC4NCiogVGhlICoqdHJDb250cm9sKiogcGFyYW1ldGVyIGFuZCAqKnRyYWluQ29udHJvbCoqIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGNoYW5nZSB0aGUgd2F5IGNyb3NzLXZhbGlkYXRpb24gaXMgcGVyZm9ybWVkLg0KKiBOb3RlIHRoYXQgKipub3QgYWxsIHBhcmFtZXRlcnMgaW4gbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGFyZSB0dW5lZCoqLiBXZSB1c2UgdGhlICoqdHJhaW4qKiBmdW5jdGlvbiB0byBvbmx5IG9wdGltaXplIHBhcmFtZXRlcnMgdGhhdCBhcmUgdHVuYWJsZS4NCg0KKipDb2RlKioNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGNhcmV0KQ0KZ2V0TW9kZWxJbmZvKCJrbm4iKQ0KbW9kZWxMb29rdXAoImtubiIpDQp0cmFpbl9rbm4gPC0gdHJhaW4oeSB+IC4sIG1ldGhvZCA9ICJrbm4iLCBkYXRhID0gbW5pc3RfMjckdHJhaW4pDQpnZ3Bsb3QodHJhaW5fa25uLCBoaWdobGlnaHQgPSBUUlVFKQ0KdHJhaW5fa25uIDwtIHRyYWluKHkgfiAuLCBtZXRob2QgPSAia25uIiwgDQogICAgICAgICAgICAgICAgICAgZGF0YSA9IG1uaXN0XzI3JHRyYWluLA0KICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShrID0gc2VxKDksIDcxLCAyKSkpDQpnZ3Bsb3QodHJhaW5fa25uLCBoaWdobGlnaHQgPSBUUlVFKQ0KdHJhaW5fa25uJGJlc3RUdW5lDQp0cmFpbl9rbm4kZmluYWxNb2RlbA0KY29uZnVzaW9uTWF0cml4KHByZWRpY3QodHJhaW5fa25uLCBtbmlzdF8yNyR0ZXN0LCB0eXBlID0gInJhdyIpLA0KICAgICAgICAgICAgICAgIG1uaXN0XzI3JHRlc3QkeSkkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAsIHAgPSAuOSkNCnRyYWluX2tubl9jdiA8LSB0cmFpbih5IH4gLiwgbWV0aG9kID0gImtubiIsIA0KICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBtbmlzdF8yNyR0cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoayA9IHNlcSg5LCA3MSwgMikpLA0KICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2wpDQpnZ3Bsb3QodHJhaW5fa25uX2N2LCBoaWdobGlnaHQgPSBUUlVFKQ0KdHJhaW5fa25uJHJlc3VsdHMgJT4lIA0KICAgICBnZ3Bsb3QoYWVzKHggPSBrLCB5ID0gQWNjdXJhY3kpKSArDQogICAgIGdlb21fbGluZSgpICsNCiAgICAgZ2VvbV9wb2ludCgpICsNCiAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeCA9IGssIA0KICAgICAgICAgICAgICAgICAgICAgICB5bWluID0gQWNjdXJhY3kgLSBBY2N1cmFjeVNELA0KICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gQWNjdXJhY3kgKyBBY2N1cmFjeVNEKSkNCnBsb3RfY29uZF9wcm9iIDwtIGZ1bmN0aW9uKHBfaGF0PU5VTEwpew0KICAgICB0bXAgPC0gbW5pc3RfMjckdHJ1ZV9wDQogICAgIGlmKCFpcy5udWxsKHBfaGF0KSl7DQogICAgICAgICAgdG1wIDwtIG11dGF0ZSh0bXAsIHA9cF9oYXQpDQogICAgIH0NCiAgICAgdG1wICU+JSBnZ3Bsb3QoYWVzKHhfMSwgeF8yLCB6PXAsIGZpbGw9cCkpICsNCiAgICAgICAgICBnZW9tX3Jhc3RlcihzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzPWMoIiNGODc2NkQiLCJ3aGl0ZSIsIiMwMEJGQzQiKSkgKw0KICAgICAgICAgIHN0YXRfY29udG91cihicmVha3M9YygwLjUpLGNvbG9yPSJibGFjayIpDQp9DQpwbG90X2NvbmRfcHJvYihwcmVkaWN0KHRyYWluX2tubiwgbW5pc3RfMjckdHJ1ZV9wLCB0eXBlID0gInByb2IiLCBzaWxlbnQ9VFJVRSlbLDJdKQ0KbW9kZWxMb29rdXAoImdhbUxvZXNzIikNCmdyaWQgPC0gZXhwYW5kLmdyaWQoc3BhbiA9IHNlcSgwLjE1LCAwLjY1LCBsZW4gPSAxMCksIGRlZ3JlZSA9IDEpDQp0cmFpbl9sb2VzcyA8LSB0cmFpbih5IH4gLiwgDQogICAgICAgICAgICAgICBtZXRob2QgPSAiZ2FtTG9lc3MiLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQ9Z3JpZCwNCiAgICAgICAgICAgICAgIGRhdGEgPSBtbmlzdF8yNyR0cmFpbiwgc2lsZW50PVRSVUUpDQpnZ3Bsb3QodHJhaW5fbG9lc3MsIGhpZ2hsaWdodCA9IFRSVUUpDQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IHByZWRpY3QodHJhaW5fbG9lc3MsIG1uaXN0XzI3JHRlc3QpLCANCiAgICAgICAgICAgICAgICByZWZlcmVuY2UgPSBtbmlzdF8yNyR0ZXN0JHkpJG92ZXJhbGxbIkFjY3VyYWN5Il0NCiNwMSA8LSBwbG90X2NvbmRfcHJvYihwcmVkaWN0KHRyYWluX2xvZXNzLCBtbmlzdF8yNyR0cnVlX3AsIHR5cGUgPSAicHJvYiIsIHNpbGVudCA9IFRSVUUpWywyXSkNCnAxDQpgYGANCg0KDQoNCiMjIyA1LjIuMyBDb21wcmVoZW5zaW9uIENoZWNrOiBDYXJldCBQYWNrYWdlDQoNCiMjIyMgUXVlc3Rpb24gMQ0KVXNlIHRoZSBycGFydCBmdW5jdGlvbiB0byBmaXQgYSBjbGFzc2lmaWNhdGlvbiB0cmVlIHRvIHRoZSB0aXNzdWVfZ2VuZV9leHByZXNzaW9uIGRhdGFzZXQuIFVzZSB0aGUgdHJhaW4gZnVuY3Rpb24gdG8gZXN0aW1hdGUgdGhlIGFjY3VyYWN5LiBUcnkgb3V0IGNwIHZhbHVlcyBvZiBzZXEoMCwgMC4xLCAwLjAxKS4gUGxvdCB0aGUgYWNjdXJhY2llcyB0byByZXBvcnQgdGhlIHJlc3VsdHMgb2YgdGhlIGJlc3QgbW9kZWwuIFNldCB0aGUgc2VlZCB0byAxOTkxLg0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShkc2xhYnMpDQpzZXQuc2VlZCgxOTkxLCBzYW1wbGUua2luZCA9ICJSb3VuZGluZyIpICMgaWYgdXNpbmcgUiAzLjYgb3IgbGF0ZXINCmRhdGEoInRpc3N1ZV9nZW5lX2V4cHJlc3Npb24iKQ0KICAgIA0KZml0MSA8LSB3aXRoKHRpc3N1ZV9nZW5lX2V4cHJlc3Npb24sIA0KICAgICAgICAgICAgICAgIHRyYWluKHgsIHksIG1ldGhvZCA9ICJycGFydCIsDQogICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBkYXRhLmZyYW1lKGNwID0gc2VxKDAsIDAuMSwgMC4wMSkpKSkNCiAgICANCmdncGxvdChmaXQxKSAgDQpgYGANCg0KUTogV2hpY2ggdmFsdWUgb2YgY3AgZ2l2ZXMgdGhlIGhpZ2hlc3QgYWNjdXJhY3k/ICANCkE6IDANCg0KIyMjIyBRdWVzdGlvbiAyDQpOb3RlIHRoYXQgdGhlcmUgYXJlIG9ubHkgNiBwbGFjZW50YXMgaW4gdGhlIGRhdGFzZXQuIEJ5IGRlZmF1bHQsIHJwYXJ0IHJlcXVpcmVzIDIwIG9ic2VydmF0aW9ucyBiZWZvcmUgc3BsaXR0aW5nIGEgbm9kZS4gVGhhdCBtZWFucyB0aGF0IGl0IGlzIGRpZmZpY3VsdCB0byBoYXZlIGEgbm9kZSBpbiB3aGljaCBwbGFjZW50YXMgYXJlIHRoZSBtYWpvcml0eS4gUmVydW4gdGhlIGFuYWx5c2lzIHlvdSBkaWQgaW4gdGhlIGV4ZXJjaXNlIGluIFExLCBidXQgdGhpcyB0aW1lLCBhbGxvdyBycGFydCB0byBzcGxpdCBhbnkgbm9kZSBieSB1c2luZyB0aGUgYXJndW1lbnQgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAwKS4gTG9vayBhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCBhZ2FpbiB0byBkZXRlcm1pbmUgd2hldGhlciB0aGUgYWNjdXJhY3kgaW5jcmVhc2VzLiBBZ2Fpbiwgc2V0IHRoZSBzZWVkIHRvIDE5OTEuDQoNCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGRzbGFicykNCnNldC5zZWVkKDE5OTEsIHNhbXBsZS5raW5kID0gIlJvdW5kaW5nIikgIyBpZiB1c2luZyBSIDMuNiBvciBsYXRlcg0KZGF0YSgidGlzc3VlX2dlbmVfZXhwcmVzc2lvbiIpDQpmaXQyIDwtIHdpdGgodGlzc3VlX2dlbmVfZXhwcmVzc2lvbiwgDQogICAgICAgICAgICAgICAgdHJhaW4oeCwgeSwgbWV0aG9kID0gInJwYXJ0IiwNCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoY3AgPSBzZXEoMCwgMC4xLCAwLjAxKSksDQogICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAwKQ0KICAgICAgICAgICAgICAgICAgICAgICkpDQojIFE6IFdoYXQgaXMgdGhlIGFjY3VyYWN5IG5vdz8NCiMgQTogDQpjb25mdXNpb25NYXRyaXgoZml0MikNCmdncGxvdChmaXQyKSANCmBgYA0KDQoNCiMjIyMgUXVlc3Rpb24gMw0KUGxvdCB0aGUgdHJlZSBmcm9tIHRoZSBiZXN0IGZpdHRpbmcgbW9kZWwgb2YgdGhlIGFuYWx5c2lzIHlvdSByYW4gaW4gUTEuDQpgYGB7cn0NCnBsb3QoZml0MiRmaW5hbE1vZGVsLCBtYXJnaW4gPSAwLjEpDQp0ZXh0KGZpdDIkZmluYWxNb2RlbCwgY2V4ID0gMC43NSkNCiMgUTogV2hpY2ggZ2VuZSBpcyBhdCB0aGUgMXN0IHNwbGl0Pw0KIyBBOiBHUEEzMw0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gNA0KV2UgY2FuIHNlZSB0aGF0IHdpdGgganVzdCBzZXZlbiBnZW5lcywgd2UgYXJlIGFibGUgdG8gcHJlZGljdCB0aGUgdGlzc3VlIHR5cGUuIE5vdyBsZXQncyBzZWUgaWYgd2UgY2FuIHByZWRpY3QgdGhlIHRpc3N1ZSB0eXBlIHdpdGggZXZlbiBmZXdlciBnZW5lcyB1c2luZyBhIFJhbmRvbSBGb3Jlc3QuIFVzZSB0aGUgdHJhaW4gZnVuY3Rpb24gYW5kIHRoZSByZiBtZXRob2QgdG8gdHJhaW4gYSBSYW5kb20gRm9yZXN0LiBUcnkgb3V0IHZhbHVlcyBvZiBtdHJ5IHJhbmdpbmcgZnJvbSBzZXEoNTAsIDIwMCwgMjUpICh5b3UgY2FuIGFsc28gZXhwbG9yZSBvdGhlciB2YWx1ZXMgb24geW91ciBvd24pLiBXaGF0IG10cnkgdmFsdWUgbWF4aW1pemVzIGFjY3VyYWN5PyBUbyBwZXJtaXQgc21hbGwgbm9kZXNpemUgdG8gZ3JvdyBhcyB3ZSBkaWQgd2l0aCB0aGUgY2xhc3NpZmljYXRpb24gdHJlZXMsIHVzZSB0aGUgZm9sbG93aW5nIGFyZ3VtZW50OiBub2Rlc2l6ZSA9IDEuICANCk5vdGU6IFRoaXMgZXhlcmNpc2Ugd2lsbCB0YWtlIHNvbWUgdGltZSB0byBydW4uIElmIHlvdSB3YW50IHRvIHRlc3Qgb3V0IHlvdXIgY29kZSBmaXJzdCwgdHJ5IHVzaW5nIHNtYWxsZXIgdmFsdWVzIHdpdGggbnRyZWUuIFNldCB0aGUgc2VlZCB0byAxOTkxIGFnYWluLg0KDQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpzZXQuc2VlZCgxOTkxLCBzYW1wbGUua2luZCA9ICJSb3VuZGluZyIpICMgaWYgdXNpbmcgUiAzLjYgb3IgbGF0ZXINCmZpdDQgPC0gd2l0aCh0aXNzdWVfZ2VuZV9leHByZXNzaW9uLCANCiAgICAgICAgICAgICAgICB0cmFpbih4LCB5LCBtZXRob2QgPSAicmYiLCB0dW5lR3JpZCA9IGRhdGEuZnJhbWUobXRyeSA9IHNlcSg1MCwgMjAwLCAyNSkpLCAgbm9kZXNpemUgPTEpKQ0KZml0NA0KIyBROiBXaGF0IHZhbHVlIG9mIG10cnkgbWF4aW1pemVzIGFjY3VyYWN5Pw0KIyBBOiAxMDANCmdncGxvdChmaXQ0KQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gNQ0KVXNlIHRoZSBmdW5jdGlvbiAqdmFySW1wKiBvbiB0aGUgb3V0cHV0IG9mIHRyYWluIGFuZCBzYXZlIGl0IHRvIGFuIG9iamVjdCBjYWxsZWQgaW1wLiAgDQpOQjogP3ZhckltcCA6IGEgZ2VuZXJpYyBtZXRob2QgZm9yIGNhbGN1bGF0aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgZm9yIG9iamVjdHMgcHJvZHVjZWQgYnkgKnRyYWluKiBhbmQgbWV0aG9kIHNwZWNpZmljIG1ldGhvZHMuDQoNCmBgYHtyfQ0KaW1wIDwtIHZhckltcChmaXQ0KSAjIyBGSUxMIE1JU1NJTkcgQ09ERQ0KaW1wDQpgYGANCg0KIyMjIyBRdWVzdGlvbiA2DQpUaGUgcnBhcnQgbW9kZWwgd2UgcmFuIGFib3ZlIHByb2R1Y2VkIGEgdHJlZSB0aGF0IHVzZWQganVzdCBzZXZlbiBwcmVkaWN0b3JzLiBFeHRyYWN0aW5nIHRoZSBwcmVkaWN0b3IgbmFtZXMgaXMgbm90IHN0cmFpZ2h0Zm9yd2FyZCwgYnV0IGNhbiBiZSBkb25lLiBJZiB0aGUgb3V0cHV0IG9mIHRoZSBjYWxsIHRvIHRyYWluIHdhcyBmaXRfcnBhcnQsIHdlIGNhbiBleHRyYWN0IHRoZSBuYW1lcyBsaWtlIHRoaXM6DQoNCiAgICB0cmVlX3Rlcm1zIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUoZml0X3JwYXJ0JGZpbmFsTW9kZWwkZnJhbWUkdmFyWyEoZml0X3JwYXJ0JGZpbmFsTW9kZWwkZnJhbWUkdmFyID09ICI8bGVhZj4iKV0pKQ0KICAgIHRyZWVfdGVybXMNCg0KQ2FsY3VsYXRlIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlIGluIHRoZSBSYW5kb20gRm9yZXN0IGNhbGwgZm9yIHRoZXNlIHNldmVuIHByZWRpY3RvcnMgYW5kIGV4YW1pbmUgd2hlcmUgdGhleSByYW5rLg0KYGBge3J9DQp0cmVlX3Rlcm1zIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUoZml0MiRmaW5hbE1vZGVsJGZyYW1lJHZhclshKGZpdDIkZmluYWxNb2RlbCRmcmFtZSR2YXIgPT0gIjxsZWFmPiIpXSkpDQp0cmVlX3Rlcm1zDQpkYXRhX2ZyYW1lKHRlcm0gPSByb3duYW1lcyhpbXAkaW1wb3J0YW5jZSksIA0KCQkJaW1wb3J0YW5jZSA9IGltcCRpbXBvcnRhbmNlJE92ZXJhbGwpICU+JQ0KCW11dGF0ZShyYW5rID0gcmFuaygtaW1wb3J0YW5jZSkpICU+JSBhcnJhbmdlKGRlc2MoaW1wb3J0YW5jZSkpICU+JQ0KCWZpbHRlcih0ZXJtICVpbiUgdHJlZV90ZXJtcykNCiMgUTogV2hhdCBpcyB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgQ0ZIUjQgZ2VuZSBpbiB0aGUgUmFuZG9tIEZvcmVzdCBjYWxsPw0KIyBBOiAzNS4wMw0KIyBROiBXaGF0IGlzIHRoZSByYW5rIG9mIHRoZSBDRkhSNCBnZW5lIGluIHRoZSBSYW5kb20gRm9yZXN0IGNhbGw/DQojIEE6IDcNCmBgYA0KDQoNCiMjIDUuMyBTZXQgb2YgZXhlcmNpc2VzIG9uIHRoZSBUaXRhbmljDQoNCiMjIyA1LjMuMSBUaXRhbmljIEV4ZXJjaXNlcywgUGFydCAxDQoNCioqVGl0YW5pYyBFeGVyY2lzZXMqKg0KDQpUaGVzZSBleGVyY2lzZXMgY292ZXIgZXZlcnl0aGluZyB5b3UgaGF2ZSBsZWFybmVkIGluIHRoaXMgY291cnNlIHNvIGZhci4gWW91IHdpbGwgdXNlIHRoZSBiYWNrZ3JvdW5kIGluZm9ybWF0aW9uIHRvIHByb3ZpZGVkIHRvIHRyYWluIGEgbnVtYmVyIG9mIGRpZmZlcmVudCB0eXBlcyBvZiBtb2RlbHMgb24gdGhpcyBkYXRhc2V0Lg0KDQoqKkJhY2tncm91bmQqKg0KDQpUaGUgVGl0YW5pYyB3YXMgYSBCcml0aXNoIG9jZWFuIGxpbmVyIHRoYXQgc3RydWNrIGFuIGljZWJlcmcgYW5kIHN1bmsgb24gaXRzIG1haWRlbiB2b3lhZ2UgaW4gMTkxMiBmcm9tIHRoZSBVbml0ZWQgS2luZ2RvbSB0byBOZXcgWW9yay4gTW9yZSB0aGFuIDEsNTAwIG9mIHRoZSBlc3RpbWF0ZWQgMiwyMjQgcGFzc2VuZ2VycyBhbmQgY3JldyBkaWVkIGluIHRoZSBhY2NpZGVudCwgbWFraW5nIHRoaXMgb25lIG9mIHRoZSBsYXJnZXN0IG1hcml0aW1lIGRpc2FzdGVycyBldmVyIG91dHNpZGUgb2Ygd2FyLiBUaGUgc2hpcCBjYXJyaWVkIGEgd2lkZSByYW5nZSBvZiBwYXNzZW5nZXJzIG9mIGFsbCBhZ2VzIGFuZCBib3RoIGdlbmRlcnMsIGZyb20gbHV4dXJ5IHRyYXZlbGVycyBpbiBmaXJzdC1jbGFzcyB0byBpbW1pZ3JhbnRzIGluIHRoZSBsb3dlciBjbGFzc2VzLiBIb3dldmVyLCBub3QgYWxsIHBhc3NlbmdlcnMgd2VyZSBlcXVhbGx5IGxpa2VseSB0byBzdXJ2aXZlIHRoZSBhY2NpZGVudC4gWW91IHdpbGwgdXNlIHJlYWwgZGF0YSBhYm91dCBhIHNlbGVjdGlvbiBvZiA4OTEgcGFzc2VuZ2VycyB0byBwcmVkaWN0IHdoaWNoIHBhc3NlbmdlcnMgc3Vydml2ZWQuDQoNCioqTGlicmFyaWVzIGFuZCBkYXRhKioNClVzZSB0aGUgdGl0YW5pY190cmFpbiBkYXRhIGZyYW1lIGZyb20gdGhlIHRpdGFuaWMgbGlicmFyeSBhcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIHRoaXMgcHJvamVjdC4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpdGFuaWMpICAgICMgbG9hZHMgdGl0YW5pY190cmFpbiBkYXRhIGZyYW1lDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJwYXJ0KQ0KIyAzIHNpZ25pZmljYW50IGRpZ2l0cw0Kb3B0aW9ucyhkaWdpdHMgPSAzKQ0KIyBjbGVhbiB0aGUgZGF0YSAtIGB0aXRhbmljX3RyYWluYCBpcyBsb2FkZWQgd2l0aCB0aGUgdGl0YW5pYyBwYWNrYWdlDQp0aXRhbmljX2NsZWFuIDwtIHRpdGFuaWNfdHJhaW4gJT4lDQogICAgbXV0YXRlKFN1cnZpdmVkID0gZmFjdG9yKFN1cnZpdmVkKSwNCiAgICAgICAgICAgRW1iYXJrZWQgPSBmYWN0b3IoRW1iYXJrZWQpLA0KICAgICAgICAgICBBZ2UgPSBpZmVsc2UoaXMubmEoQWdlKSwgbWVkaWFuKEFnZSwgbmEucm0gPSBUUlVFKSwgQWdlKSwgIyBOQSBhZ2UgdG8gbWVkaWFuIGFnZQ0KICAgICAgICAgICBGYW1pbHlTaXplID0gU2liU3AgKyBQYXJjaCArIDEpICU+JSAgICAjIGNvdW50IGZhbWlseSBtZW1iZXJzDQogICAgc2VsZWN0KFN1cnZpdmVkLCAgU2V4LCBQY2xhc3MsIEFnZSwgRmFyZSwgU2liU3AsIFBhcmNoLCBGYW1pbHlTaXplLCBFbWJhcmtlZCkNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDE6IFRyYWluaW5nIGFuZCB0ZXN0IHNldHMNClNwbGl0IHRpdGFuaWNfY2xlYW4gaW50byB0ZXN0IGFuZCB0cmFpbmluZyBzZXRzIC0gYWZ0ZXIgcnVubmluZyB0aGUgc2V0dXAgY29kZSwgaXQgc2hvdWxkIGhhdmUgODkxIHJvd3MgYW5kIDkgdmFyaWFibGVzLg0KDQpTZXQgdGhlIHNlZWQgdG8gNDIsIHRoZW4gdXNlIHRoZSBjYXJldCBwYWNrYWdlIHRvIGNyZWF0ZSBhIDIwJSBkYXRhIHBhcnRpdGlvbiBiYXNlZCBvbiB0aGUgU3Vydml2ZWQgY29sdW1uLiBBc3NpZ24gdGhlIDIwJSBwYXJ0aXRpb24gdG8gdGVzdF9zZXQgYW5kIHRoZSByZW1haW5pbmcgODAlIHBhcnRpdGlvbiB0byB0cmFpbl9zZXQuDQoNClE6IEhvdyBtYW55IG9ic2VydmF0aW9ucyBhcmUgaW4gdGhlIHRyYWluaW5nIHNldD8NClE6IEhvdyBtYW55IG9ic2VydmF0aW9ucyBhcmUgaW4gdGhlIHRlc3Qgc2V0Pw0KUSA6V2hhdCBwcm9wb3J0aW9uIG9mIGluZGl2aWR1YWxzIGluIHRoZSB0cmFpbmluZyBzZXQgc3Vydml2ZWQ/DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDIsIHNhbXBsZS5raW5kID0gJ1JvdW5kaW5nJykgIyBpZiBSIHZlcnNpb24gPj0gMy42DQp0ZXN0X2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odGl0YW5pY19jbGVhbiRTdXJ2aXZlZCwgdGltZXMgPSAxLCBwID0gMC4yLCBsaXN0ID0gRkFMU0UpDQp0cmFpbl9zZXQgPC0gdGl0YW5pY19jbGVhblstdGVzdF9pbmRleCxdDQp0ZXN0X3NldCA8LSB0aXRhbmljX2NsZWFuW3Rlc3RfaW5kZXgsXQ0KbnJvdyh0cmFpbl9zZXQpDQpucm93KHRlc3Rfc2V0KQ0KbWVhbih0cmFpbl9zZXQkU3Vydml2ZWQgPT0gMSkNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDI6ICBCYXNlbGluZSBwcmVkaWN0aW9uIGJ5IGd1ZXNzaW5nIHRoZSBvdXRjb21lDQpUaGUgc2ltcGxlc3QgcHJlZGljdGlvbiBtZXRob2QgaXMgcmFuZG9tbHkgZ3Vlc3NpbmcgdGhlIG91dGNvbWUgd2l0aG91dCB1c2luZyBhZGRpdGlvbmFsIHByZWRpY3RvcnMuIFRoZXNlIG1ldGhvZHMgd2lsbCBoZWxwIHVzIGRldGVybWluZSB3aGV0aGVyIG91ciBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBwZXJmb3JtcyBiZXR0ZXIgdGhhbiBjaGFuY2UuIEhvdyBhY2N1cmF0ZSBhcmUgdHdvIG1ldGhvZHMgb2YgZ3Vlc3NpbmcgVGl0YW5pYyBwYXNzZW5nZXIgc3Vydml2YWw/DQoNClNldCB0aGUgc2VlZCB0byAzLiBGb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSB0ZXN0IHNldCwgcmFuZG9tbHkgZ3Vlc3Mgd2hldGhlciB0aGF0IHBlcnNvbiBzdXJ2aXZlZCBvciBub3QuIEFzc3VtZSB0aGF0IGVhY2ggcGVyc29uIGhhcyBhbiBlcXVhbCBjaGFuY2Ugb2Ygc3Vydml2aW5nIG9yIG5vdCBzdXJ2aXZpbmcuDQoNClE6IFdoYXQgaXMgdGhlIGFjY3VyYWN5IG9mIHRoaXMgZ3Vlc3NpbmcgbWV0aG9kPw0KDQpgYGB7cn0NCnNldC5zZWVkKDMsIHNhbXBsZS5raW5kID0gJ1JvdW5kaW5nJykgIyBpZiBSIHZlcnNpb24gPj0gMy42DQpndWVzc18gPC0gc2FtcGxlKGMoMCwxKSwgbnJvdyh0ZXN0X3NldCksIHJlcGxhY2UgPSBUUlVFKQ0KdGVzdF9zZXQgJT4lIA0KICAgIGZpbHRlcihTdXJ2aXZlZCA9PSBndWVzc18pICU+JQ0KICAgIHN1bW1hcml6ZShuKCkgLyBucm93KHRlc3Rfc2V0KSkNCiMgZ3Vlc3Mgd2l0aCBlcXVhbCBwcm9iYWJpbGl0eSBvZiBzdXJ2aXZhbA0KI2d1ZXNzIDwtIHNhbXBsZShjKDAsMSksIG5yb3codGVzdF9zZXQpLCByZXBsYWNlID0gVFJVRSkNCiNtZWFuKGd1ZXNzID09IHRlc3Rfc2V0JFN1cnZpdmVkKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gM2E6IFByZWRpY3Rpbmcgc3Vydml2YWwgYnkgc2V4DQpVc2UgdGhlIHRyYWluaW5nIHNldCB0byBkZXRlcm1pbmUgd2hldGhlciBtZW1iZXJzIG9mIGEgZ2l2ZW4gc2V4IHdlcmUgbW9yZSBsaWtlbHkgdG8gc3Vydml2ZSBvciBkaWUuIEFwcGx5IHRoaXMgaW5zaWdodCB0byBnZW5lcmF0ZSBzdXJ2aXZhbCBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQuDQoNCmBgYHtyfQ0KdHJhaW5fc2V0ICU+JQ0KICAgIGdyb3VwX2J5KFNleCkgJT4lDQogICAgc3VtbWFyaXplKFN1cnZpdmVkID0gbWVhbihTdXJ2aXZlZCA9PSAxKSkNCiMgUTogV2hhdCBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIHNldCBmZW1hbGVzIHN1cnZpdmVkPyBBOiAwLjczMQ0KIyBROiBXaGF0IHByb3BvcnRpb24gb2YgdHJhaW5pbmcgc2V0IG1hbGVzIHN1cnZpdmVkPyBBOiAwLjE5Nw0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gM2I6IFF1ZXN0aW9uIDNiOiBQcmVkaWN0aW5nIHN1cnZpdmFsIGJ5IHNleA0KVXNlIHRoZSB0cmFpbmluZyBzZXQgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgbWVtYmVycyBvZiBhIGdpdmVuIHNleCB3ZXJlIG1vcmUgbGlrZWx5IHRvIHN1cnZpdmUgb3IgZGllLiBBcHBseSB0aGlzIGluc2lnaHQgdG8gZ2VuZXJhdGUgc3Vydml2YWwgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Qgc2V0Lg0KDQpQcmVkaWN0IHN1cnZpdmFsIHVzaW5nIHNleCBvbiB0aGUgdGVzdCBzZXQ6IGlmIHRoZSBzdXJ2aXZhbCByYXRlIGZvciBhIHNleCBpcyBvdmVyIDAuNSwgcHJlZGljdCBzdXJ2aXZhbCBmb3IgYWxsIGluZGl2aWR1YWxzIG9mIHRoYXQgc2V4LCBhbmQgcHJlZGljdCBkZWF0aCBpZiB0aGUgc3Vydml2YWwgcmF0ZSBmb3IgYSBzZXggaXMgdW5kZXIgMC41Lg0KDQpXaGF0IGlzIHRoZSBhY2N1cmFjeSBvZiB0aGlzIHNleC1iYXNlZCBwcmVkaWN0aW9uIG1ldGhvZCBvbiB0aGUgdGVzdCBzZXQ/DQpgYGB7cn0NCnRlc3Rfc2V0ICU+JQ0KICAgIHN1bW1hcml6ZSggKHN1bShTZXggPT0gJ2ZlbWFsZScgJiBTdXJ2aXZlZCA9PSAxKSArIHN1bShTZXggPT0gJ21hbGUnICYgU3Vydml2ZWQgPT0gMCkpIC8gbigpKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gNGE6IFByZWRpY3Rpbmcgc3Vydml2YWwgYnkgcGFzc2VuZ2VyIGNsYXNzDQpJbiB3aGljaCBjbGFzcyhlcykgKFBjbGFzcykgd2VyZSBwYXNzZW5nZXJzIG1vcmUgbGlrZWx5IHRvIHN1cnZpdmUgdGhhbiBkaWU/DQpgYGB7cn0NCnN1cnZpdmFsX2NsYXNzIDwtIHRpdGFuaWNfY2xlYW4gJT4lDQogICAgZ3JvdXBfYnkoUGNsYXNzKSAlPiUNCiAgICBzdW1tYXJpemUoUHJlZGljdGluZ1N1cnZpdmFsID0gaWZlbHNlKG1lYW4oU3Vydml2ZWQgPT0gMSkgPj0wLjUsIDEsIDApKQ0Kc3Vydml2YWxfY2xhc3MNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDRiOiBQcmVkaWN0aW5nIHN1cnZpdmFsIGJ5IHBhc3NlbmdlciBjbGFzcw0KUHJlZGljdCBzdXJ2aXZhbCB1c2luZyBwYXNzZW5nZXIgY2xhc3Mgb24gdGhlIHRlc3Qgc2V0OiBwcmVkaWN0IHN1cnZpdmFsIGlmIHRoZSBzdXJ2aXZhbCByYXRlIGZvciBhIGNsYXNzIGlzIG92ZXIgMC41LCBvdGhlcndpc2UgcHJlZGljdCBkZWF0aC4NCg0KV2hhdCBpcyB0aGUgYWNjdXJhY3kgb2YgdGhpcyBjbGFzcy1iYXNlZCBwcmVkaWN0aW9uIG1ldGhvZCBvbiB0aGUgdGVzdCBzZXQ/DQpgYGB7cn0NCnRlc3Rfc2V0ICU+JQ0KICAgIGlubmVyX2pvaW4oc3Vydml2YWxfY2xhc3MsIGJ5PSdQY2xhc3MnKSAlPiUNCiAgICBzdW1tYXJpemUoUHJlZGljdGluZ1N1cnZpdmFsID0gbWVhbihTdXJ2aXZlZCA9PSBQcmVkaWN0aW5nU3Vydml2YWwpKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gNGM6IFByZWRpY3Rpbmcgc3Vydml2YWwgYnkgcGFzc2VuZ2VyIGNsYXNzDQpHcm91cCBwYXNzZW5nZXJzIGJ5IGJvdGggc2V4IGFuZCBwYXNzZW5nZXIgY2xhc3MuDQoNCldoaWNoIHNleCBhbmQgY2xhc3MgY29tYmluYXRpb25zIHdlcmUgbW9yZSBsaWtlbHkgdG8gc3Vydml2ZSB0aGFuIGRpZT8NCmBgYHtyfQ0Kc3Vydml2YWxfY2xhc3MgPC0gdGl0YW5pY19jbGVhbiAlPiUNCiAgICBncm91cF9ieShTZXgsIFBjbGFzcykgJT4lDQogICAgc3VtbWFyaXplKFByZWRpY3RpbmdTdXJ2aXZhbCA9IGlmZWxzZShtZWFuKFN1cnZpdmVkID09IDEpID4gMC41LCAxLCAwKSkNCnN1cnZpdmFsX2NsYXNzDQpgYGANCg0KIyMjIyBRdWVzdGlvbiA0ZDogV2hhdCBpcyB0aGUgYWNjdXJhY3kgb2YgdGhpcyBzZXgtIGFuZCBjbGFzcy1iYXNlZCBwcmVkaWN0aW9uIG1ldGhvZCBvbiB0aGUgdGVzdCBzZXQ/DQpQcmVkaWN0IHN1cnZpdmFsIHVzaW5nIGJvdGggc2V4IGFuZCBwYXNzZW5nZXIgY2xhc3Mgb24gdGhlIHRlc3Qgc2V0LiBQcmVkaWN0IHN1cnZpdmFsIGlmIHRoZSBzdXJ2aXZhbCByYXRlIGZvciBhIHNleC9jbGFzcyBjb21iaW5hdGlvbiBpcyBvdmVyIDAuNSwgb3RoZXJ3aXNlIHByZWRpY3QgZGVhdGguDQoNCmBgYHtyfQ0KdGVzdF9zZXQgJT4lDQogICAgaW5uZXJfam9pbihzdXJ2aXZhbF9jbGFzcywgYnk9YygnU2V4JywgJ1BjbGFzcycpKSAlPiUNCiAgICBzdW1tYXJpemUoUHJlZGljdGluZ1N1cnZpdmFsID0gbWVhbihTdXJ2aXZlZCA9PSBQcmVkaWN0aW5nU3Vydml2YWwpKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gNWE6IENvbmZ1c2lvbiBtYXRyaXgNClVzZSB0aGUgY29uZnVzaW9uTWF0cml4IGZ1bmN0aW9uIHRvIGNyZWF0ZSBjb25mdXNpb24gbWF0cmljZXMgZm9yIHRoZSBzZXggbW9kZWwsIGNsYXNzIG1vZGVsLCBhbmQgY29tYmluZWQgc2V4IGFuZCBjbGFzcyBtb2RlbC4gWW91IHdpbGwgbmVlZCB0byBjb252ZXJ0IHByZWRpY3Rpb25zIGFuZCBzdXJ2aXZhbCBzdGF0dXMgdG8gZmFjdG9ycyB0byB1c2UgdGhpcyBmdW5jdGlvbi4NCg0KV2hhdCBpcyB0aGUgInBvc2l0aXZlIiBjbGFzcyB1c2VkIHRvIGNhbGN1bGF0ZSBjb25mdXNpb24gbWF0cml4IG1ldHJpY3M/DQpXaGljaCBtb2RlbCBoYXMgdGhlIGhpZ2hlc3Qgc2Vuc2l0aXZpdHk/DQpXaGljaCBtb2RlbCBoYXMgdGhlIGhpZ2hlc3Qgc3BlY2lmaWNpdHk/DQpXaGljaCBtb2RlbCBoYXMgdGhlIGhpZ2hlc3QgYmFsYW5jZWQgYWNjdXJhY3k/DQoNCmBgYHtyfQ0KIyBDb25mdXNpb24gTWF0cml4OiBzZXggbW9kZWwNCnNleF9tb2RlbCA8LSB0cmFpbl9zZXQgJT4lDQogICAgZ3JvdXBfYnkoU2V4KSAlPiUNCiAgICBzdW1tYXJpemUoU3Vydml2ZWRfcHJlZGljdCA9IGlmZWxzZShtZWFuKFN1cnZpdmVkID09IDEpID4gMC41LCAxLCAwKSkNCnRlc3Rfc2V0MSA8LSB0ZXN0X3NldCAlPiUNCiAgICBpbm5lcl9qb2luKHNleF9tb2RlbCwgYnkgPSAnU2V4JykNCmNtMSA8LSBjb25mdXNpb25NYXRyaXgoZGF0YSA9IGZhY3Rvcih0ZXN0X3NldDEkU3Vydml2ZWRfcHJlZGljdCksIHJlZmVyZW5jZSA9IGZhY3Rvcih0ZXN0X3NldDEkU3Vydml2ZWQpKQ0KY20xICU+JQ0KICAgIHRpZHkoKSAlPiUNCiAgICBmaWx0ZXIodGVybSA9PSAnc2Vuc2l0aXZpdHknKSAlPiUNCiAgICAuJGVzdGltYXRlDQpjbTEgJT4lDQogICAgdGlkeSgpICU+JQ0KICAgIGZpbHRlcih0ZXJtID09ICdzcGVjaWZpY2l0eScpICU+JQ0KICAgIC4kZXN0aW1hdGUNCmNtMSAlPiUNCiAgICB0aWR5KCkgJT4lDQogICAgZmlsdGVyKHRlcm0gPT0gJ2JhbGFuY2VkX2FjY3VyYWN5JykgJT4lDQogICAgLiRlc3RpbWF0ZQ0KIyBDb25mdXNpb24gTWF0cml4OiBjbGFzcyBtb2RlbA0KY2xhc3NfbW9kZWwgPC0gdHJhaW5fc2V0ICU+JQ0KICAgIGdyb3VwX2J5KFBjbGFzcykgJT4lDQogICAgc3VtbWFyaXplKFN1cnZpdmVkX3ByZWRpY3QgPSBpZmVsc2UobWVhbihTdXJ2aXZlZCA9PSAxKSA+IDAuNSwgMSwgMCkpDQp0ZXN0X3NldDIgPC0gdGVzdF9zZXQgJT4lDQogICAgaW5uZXJfam9pbihjbGFzc19tb2RlbCwgYnkgPSAnUGNsYXNzJykNCmNtMiA8LSBjb25mdXNpb25NYXRyaXgoZGF0YSA9IGZhY3Rvcih0ZXN0X3NldDIkU3Vydml2ZWRfcHJlZGljdCksIHJlZmVyZW5jZSA9IGZhY3Rvcih0ZXN0X3NldDIkU3Vydml2ZWQpKQ0KY20yICU+JQ0KICAgIHRpZHkoKSAlPiUNCiAgICBmaWx0ZXIodGVybSA9PSAnc2Vuc2l0aXZpdHknKSAlPiUNCiAgICAuJGVzdGltYXRlDQpjbTIgJT4lDQogICAgdGlkeSgpICU+JQ0KICAgIGZpbHRlcih0ZXJtID09ICdzcGVjaWZpY2l0eScpICU+JQ0KICAgIC4kZXN0aW1hdGUNCmNtMiAlPiUNCiAgICB0aWR5KCkgJT4lDQogICAgZmlsdGVyKHRlcm0gPT0gJ2JhbGFuY2VkX2FjY3VyYWN5JykgJT4lDQogICAgLiRlc3RpbWF0ZQ0KIyBDb25mdXNpb24gTWF0cml4OiBzZXggYW5kIGNsYXNzIG1vZGVsDQpzZXhfY2xhc3NfbW9kZWwgPC0gdHJhaW5fc2V0ICU+JQ0KICAgIGdyb3VwX2J5KFNleCwgUGNsYXNzKSAlPiUNCiAgICBzdW1tYXJpemUoU3Vydml2ZWRfcHJlZGljdCA9IGlmZWxzZShtZWFuKFN1cnZpdmVkID09IDEpID4gMC41LCAxLCAwKSkNCnRlc3Rfc2V0MyA8LSB0ZXN0X3NldCAlPiUNCiAgICBpbm5lcl9qb2luKHNleF9jbGFzc19tb2RlbCwgYnk9YygnU2V4JywgJ1BjbGFzcycpKQ0KY20zIDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhID0gZmFjdG9yKHRlc3Rfc2V0MyRTdXJ2aXZlZF9wcmVkaWN0KSwgcmVmZXJlbmNlID0gZmFjdG9yKHRlc3Rfc2V0MyRTdXJ2aXZlZCkpDQpjbTMgJT4lDQogICAgdGlkeSgpICU+JQ0KICAgIGZpbHRlcih0ZXJtID09ICdzZW5zaXRpdml0eScpICU+JQ0KICAgIC4kZXN0aW1hdGUNCmNtMyAlPiUNCiAgICB0aWR5KCkgJT4lDQogICAgZmlsdGVyKHRlcm0gPT0gJ3NwZWNpZmljaXR5JykgJT4lDQogICAgLiRlc3RpbWF0ZQ0KY20zICU+JQ0KICAgIHRpZHkoKSAlPiUNCiAgICBmaWx0ZXIodGVybSA9PSAnYmFsYW5jZWRfYWNjdXJhY3knKSAlPiUNCiAgICAuJGVzdGltYXRlDQpgYGANCg0KIyMjIyBRdWVzdGlvbiA1YjogQ29uZnVzaW9uIG1hdHJpeA0KUTogV2hhdCBpcyB0aGUgbWF4aW11bSB2YWx1ZSBvZiBiYWxhbmNlZCBhY2N1cmFjeT8NCkE6IGNmLiA1YQ0KDQojIyMjIFF1ZXN0aW9uIDY6IEYxIHNjb3Jlcw0KVXNlIHRoZSAqRl9tZWFzKiBmdW5jdGlvbiB0byBjYWxjdWxhdGUgRjEgc2NvcmVzIGZvciB0aGUgc2V4IG1vZGVsLCBjbGFzcyBtb2RlbCwgYW5kIGNvbWJpbmVkIHNleCBhbmQgY2xhc3MgbW9kZWwuIFlvdSB3aWxsIG5lZWQgdG8gY29udmVydCBwcmVkaWN0aW9ucyB0byBmYWN0b3JzIHRvIHVzZSB0aGlzIGZ1bmN0aW9uLg0KDQpXaGljaCBtb2RlbCBoYXMgdGhlIGhpZ2hlc3QgRjEgc2NvcmU/DQoNCmBgYHtyfQ0KRl9tZWFzKGRhdGE9ZmFjdG9yKHRlc3Rfc2V0MSRTdXJ2aXZlZCksIHJlZmVyZW5jZSA9IGZhY3Rvcih0ZXN0X3NldDEkU3Vydml2ZWRfcHJlZGljdCkpDQpGX21lYXMoZGF0YT1mYWN0b3IodGVzdF9zZXQyJFN1cnZpdmVkKSwgcmVmZXJlbmNlID0gZmFjdG9yKHRlc3Rfc2V0MiRTdXJ2aXZlZF9wcmVkaWN0KSkNCkZfbWVhcyhkYXRhPWZhY3Rvcih0ZXN0X3NldDMkU3Vydml2ZWQpLCByZWZlcmVuY2UgPSBmYWN0b3IodGVzdF9zZXQzJFN1cnZpdmVkX3ByZWRpY3QpKQ0KYGBgDQoNCiMjIyA1LjMuMiBUaXRhbmljIEV4ZXJjaXNlcywgUGFydCAyDQoNCiMjIyMgUXVlc3Rpb24gNzogU3Vydml2YWwgYnkgZmFyZSAtIExEQSBhbmQgUURBDQpUcmFpbiBhIG1vZGVsIHVzaW5nIGxpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgKExEQSkgd2l0aCB0aGUgY2FyZXQgbGRhIG1ldGhvZCB1c2luZyBGYXJlIGFzIHRoZSBvbmx5IHByZWRpY3Rvci4NCldoYXQgaXMgdGhlIGFjY3VyYWN5IG9uIHRoZSB0ZXN0IHNldCBmb3IgdGhlIExEQSBtb2RlbD8NCg0KYGBge3J9DQpmaXRfbGRhIDwtIHRyYWluKFN1cnZpdmVkIH4gRmFyZSwgZGF0YSA9IHRyYWluX3NldCwgbWV0aG9kID0gJ2xkYScpDQpTdXJ2aXZlZF9oYXQgPC0gcHJlZGljdChmaXRfbGRhLCB0ZXN0X3NldCkNCm1lYW4odGVzdF9zZXQkU3Vydml2ZWQgPT0gU3Vydml2ZWRfaGF0KQ0KYGBgDQoNClRyYWluIGEgbW9kZWwgdXNpbmcgcXVhZHJhdGljIGRpc2NyaW1pbmFudCBhbmFseXNpcyAoUURBKSB3aXRoIHRoZSBjYXJldCBxZGEgbWV0aG9kIHVzaW5nIGZhcmUgYXMgdGhlIG9ubHkgcHJlZGljdG9yLg0KV2hhdCBpcyB0aGUgYWNjdXJhY3kgb24gdGhlIHRlc3Qgc2V0IGZvciB0aGUgUURBIG1vZGVsPw0KYGBge3J9DQpmaXRfcWRhIDwtIHRyYWluKFN1cnZpdmVkIH4gRmFyZSwgZGF0YSA9IHRyYWluX3NldCwgbWV0aG9kID0gJ3FkYScpDQpTdXJ2aXZlZF9oYXQgPC0gcHJlZGljdChmaXRfcWRhLCB0ZXN0X3NldCkNCm1lYW4odGVzdF9zZXQkU3Vydml2ZWQgPT0gU3Vydml2ZWRfaGF0KQ0KYGBgDQoNCg0KIyMjIyBRdWVzdGlvbiA4OiBMb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVscw0KVHJhaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIGNhcmV0IGdsbSBtZXRob2QgdXNpbmcgYWdlIGFzIHRoZSBvbmx5IHByZWRpY3Rvci4NCldoYXQgaXMgdGhlIGFjY3VyYWN5IG9uIHRoZSB0ZXN0IHNldCB1c2luZyBhZ2UgYXMgdGhlIG9ubHkgcHJlZGljdG9yPw0KYGBge3J9DQpmaXRfbG9ncmVnX2EgPC0gZ2xtKFN1cnZpdmVkIH4gQWdlLCBkYXRhID0gdHJhaW5fc2V0LCBmYW1pbHkgPSAnYmlub21pYWwnKQ0Kc3Vydml2ZWRfaGF0X2EgPC0gaWZlbHNlKHByZWRpY3QoZml0X2xvZ3JlZ19hLCB0ZXN0X3NldCkgPj0gMCwgMSwgMCkNCm1lYW4oc3Vydml2ZWRfaGF0X2EgPT0gdGVzdF9zZXQkU3Vydml2ZWQpDQpgYGANCg0KVHJhaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIGNhcmV0IGdsbSBtZXRob2QgdXNpbmcgZm91ciBwcmVkaWN0b3JzOiBzZXgsIGNsYXNzLCBmYXJlLCBhbmQgYWdlLg0KV2hhdCBpcyB0aGUgYWNjdXJhY3kgb24gdGhlIHRlc3Qgc2V0IHVzaW5nIHRoZXNlIGZvdXIgcHJlZGljdG9ycz8NCmBgYHtyfQ0KZml0X2xvZ3JlZ19iIDwtIGdsbShTdXJ2aXZlZCB+IFNleCArIFBjbGFzcyArIEZhcmUgKyBBZ2UsIGRhdGEgPSB0cmFpbl9zZXQsIGZhbWlseSA9ICdiaW5vbWlhbCcpDQpzdXJ2aXZlZF9oYXRfYiA8LSBpZmVsc2UocHJlZGljdChmaXRfbG9ncmVnX2IsIHRlc3Rfc2V0KSA+PSAwLCAxLCAwKQ0KbWVhbihzdXJ2aXZlZF9oYXRfYiA9PSB0ZXN0X3NldCRTdXJ2aXZlZCkNCmBgYA0KDQpUcmFpbiBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCB0aGUgY2FyZXQgZ2xtIG1ldGhvZCB1c2luZyBhbGwgcHJlZGljdG9ycy4gSWdub3JlIHdhcm5pbmdzIGFib3V0IHJhbmstZGVmaWNpZW50IGZpdC4NCldoYXQgaXMgdGhlIGFjY3VyYWN5IG9uIHRoZSB0ZXN0IHNldCB1c2luZyBhbGwgcHJlZGljdG9ycz8NCg0KYGBge3J9DQpzdHIodHJhaW5fc2V0KQ0KZml0X2xvZ3JlZ19jIDwtIGdsbShTdXJ2aXZlZCB+IC4sIGRhdGEgPSB0cmFpbl9zZXQsIGZhbWlseSA9ICdiaW5vbWlhbCcpDQpzdXJ2aXZlZF9oYXRfYyA8LSBpZmVsc2UocHJlZGljdChmaXRfbG9ncmVnX2MsIHRlc3Rfc2V0KSA+PSAwLCAxLCAwKQ0KbWVhbihzdXJ2aXZlZF9oYXRfYyA9PSB0ZXN0X3NldCRTdXJ2aXZlZCkNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDlhOiBrTk4gbW9kZWwNClNldCB0aGUgc2VlZCB0byA2LiBUcmFpbiBhIGtOTiBtb2RlbCBvbiB0aGUgdHJhaW5pbmcgc2V0IHVzaW5nIGNhcmV0LiBUcnkgdHVuaW5nIHdpdGggayA9IHNlcSgzLCA1MSwgMikuDQpXaGF0IGlzIHRoZSBvcHRpbWFsIHZhbHVlIG9mIHRoZSBudW1iZXIgb2YgbmVpZ2hib3JzIGs/DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNiwgc2FtcGxlLmtpbmQgPSAiUm91bmRpbmciKQ0KIyBNZXRob2QgYmVsb3cgZG9lc24ndCBnaXZlIHNhbWUgcmVzdWx0IGFzIEVkWCAodGhvdWdoIGl0IGlzIGNvcnJlY3QpDQojIGtzIDwtIHNlcSgzLDUxLDIpDQojIHJlc19rbm45YSA8LSBzYXBwbHkoa3MsIGZ1bmN0aW9uKGspIHsNCiMgICAgIGZpdF9rbm45YSA8LSBrbm4zKFN1cnZpdmVkIH4gLiwgZGF0YSA9IHRyYWluX3NldCwgayA9IGspDQojICAgICBzdXJ2aXZlZF9oYXQgPC0gcHJlZGljdChmaXRfa25uOWEsIHRyYWluX3NldCwgdHlwZSA9ICJjbGFzcyIpICU+JSBmYWN0b3IobGV2ZWxzID0gbGV2ZWxzKHRyYWluX3NldCRTdXJ2aXZlZCkpDQojICAgICBjbV90ZXN0IDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhID0gc3Vydml2ZWRfaGF0LCByZWZlcmVuY2UgPSB0cmFpbl9zZXQkU3Vydml2ZWQpDQojICAgICBjbV90ZXN0JG92ZXJhbGxbIkFjY3VyYWN5Il0NCiMgfSkNCiMga3Nbd2hpY2gubWF4KHJlc19rbm45YSldDQojIE90aGVyIG1ldGhvZCB1c2luZyB0cmFpbiBmdW5jdGlvbg0KayA8LSBzZXEoMyw1MSwyKQ0KZml0X2tubjlhIDwtIHRyYWluKFN1cnZpdmVkIH4gLiwgZGF0YSA9IHRyYWluX3NldCwgbWV0aG9kID0gImtubiIsIHR1bmVHcmlkID0gZGF0YS5mcmFtZShrKSkNCmZpdF9rbm45YSRiZXN0VHVuZQ0KYGBgDQoNCg0KIyMjIyBRdWVzdGlvbiA5Yjoga05OIG1vZGVsDQpQbG90IHRoZSBrTk4gbW9kZWwgdG8gaW52ZXN0aWdhdGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgbmVpZ2hib3JzIGFuZCBhY2N1cmFjeSBvbiB0aGUgdHJhaW5pbmcgc2V0Lg0KYGBge3J9DQpnZ3Bsb3QoZml0X2tubjlhKQ0KYGBgDQoNCiMjIyMgUXVlc3Rpb24gOWM6IGtOTiBtb2RlbA0KV2hhdCBpcyB0aGUgYWNjdXJhY3kgb2YgdGhlIGtOTiBtb2RlbCBvbiB0aGUgdGVzdCBzZXQ/DQoNCmBgYHtyfQ0Kc3Vydml2ZWRfaGF0IDwtIHByZWRpY3QoZml0X2tubjlhLCB0ZXN0X3NldCkgJT4lIGZhY3RvcihsZXZlbHMgPSBsZXZlbHModGVzdF9zZXQkU3Vydml2ZWQpKQ0KY21fdGVzdCA8LSBjb25mdXNpb25NYXRyaXgoZGF0YSA9IHN1cnZpdmVkX2hhdCwgcmVmZXJlbmNlID0gdGVzdF9zZXQkU3Vydml2ZWQpDQpjbV90ZXN0JG92ZXJhbGxbIkFjY3VyYWN5Il0NCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDEwOiBDcm9zcy12YWxpZGF0aW9uDQpTZXQgdGhlIHNlZWQgdG8gOCBhbmQgdHJhaW4gYSBuZXcga05OIG1vZGVsLiBJbnN0ZWFkIG9mIHRoZSBkZWZhdWx0IHRyYWluaW5nIGNvbnRyb2wsIHVzZSAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gd2hlcmUgZWFjaCBwYXJ0aXRpb24gY29uc2lzdHMgb2YgMTAlIG9mIHRoZSB0b3RhbC4NCg0KV2hhdCBpcyB0aGUgb3B0aW1hbCB2YWx1ZSBvZiBrIHVzaW5nIGNyb3NzLXZhbGlkYXRpb24/DQpXaGF0IGlzIHRoZSBhY2N1cmFjeSBvbiB0aGUgdGVzdCBzZXQgdXNpbmcgdGhlIGNyb3NzLXZhbGlkYXRlZCBrTk4gbW9kZWw/DQpgYGB7cn0NCnNldC5zZWVkKDgsIHNhbXBsZS5raW5kID0gIlJvdW5kaW5nIikNCmZpdF9rbm4xMCA8LSB0cmFpbihTdXJ2aXZlZCB+IC4sIA0KICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fc2V0LCANCiAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwNCiAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoayA9IHNlcSgzLCA1MSwgMikpLA0KICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXI9MTAsIHA9MC45KSkNCmZpdF9rbm4xMA0Kc3Vydml2ZWRfaGF0IDwtIHByZWRpY3QoZml0X2tubjEwLCB0ZXN0X3NldCkNCmNtX3Rlc3QgPC0gY29uZnVzaW9uTWF0cml4KGRhdGEgPSBzdXJ2aXZlZF9oYXQsIHJlZmVyZW5jZSA9IHRlc3Rfc2V0JFN1cnZpdmVkKQ0KY21fdGVzdCRvdmVyYWxsWyJBY2N1cmFjeSJdDQpgYGANCg0KIyMjIyBRdWVzdGlvbiAxMWE6IENsYXNzaWZpY2F0aW9uIHRyZWUgbW9kZWwNClNldCB0aGUgc2VlZCB0byAxMC4gVXNlIGNhcmV0IHRvIHRyYWluIGEgZGVjaXNpb24gdHJlZSB3aXRoIHRoZSBycGFydCBtZXRob2QuIFR1bmUgdGhlIGNvbXBsZXhpdHkgcGFyYW1ldGVyIHdpdGggY3AgPSBzZXEoMCwgMC4wNSwgMC4wMDIpLg0KDQpXaGF0IGlzIHRoZSBvcHRpbWFsIHZhbHVlIG9mIHRoZSBjb21wbGV4aXR5IHBhcmFtZXRlciAoY3ApPw0KV2hhdCBpcyB0aGUgYWNjdXJhY3kgb2YgdGhlIGRlY2lzaW9uIHRyZWUgbW9kZWwgb24gdGhlIHRlc3Qgc2V0Pw0KYGBge3J9DQpzZXQuc2VlZCgxMCwgc2FtcGxlLmtpbmQgPSAnUm91bmRpbmcnKQ0KZml0X3JwYXJ0MTEgPC0gdHJhaW4oU3Vydml2ZWQgfiAuLCANCiAgICAgICAgICAgICAgICAgICBkYXRhPXRyYWluX3NldCwgDQogICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwNCiAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoY3AgPSBzZXEoMCwgMC4wNSwgMC4wMDIpKSkNCnBsb3QoZml0X3JwYXJ0MTEpDQpzdXJ2aXZlZF9oYXQgPC0gcHJlZGljdChmaXRfcnBhcnQxMSwgdGVzdF9zZXQpDQpjbV90ZXN0IDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhID0gc3Vydml2ZWRfaGF0LCByZWZlcmVuY2UgPSB0ZXN0X3NldCRTdXJ2aXZlZCkNCmNtX3Rlc3Qkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KYGBgDQoNCg0KIyMjIyBRdWVzdGlvbiAxMWI6IENsYXNzaWZpY2F0aW9uIHRyZWUgbW9kZWwNCkluc3BlY3QgdGhlIGZpbmFsIG1vZGVsIGFuZCBwbG90IHRoZSBkZWNpc2lvbiB0cmVlLg0KDQpXaGljaCB2YXJpYWJsZXMgYXJlIHVzZWQgaW4gdGhlIGRlY2lzaW9uIHRyZWU/DQpgYGB7cn0NCmZpdF9ycGFydDExJGZpbmFsTW9kZWwNCnBsb3QoZml0X3JwYXJ0MTEkZmluYWxNb2RlbCwgbWFyZ2luPTAuMSkNCnRleHQoZml0X3JwYXJ0MTEkZmluYWxNb2RlbCwgY2V4ID0gMC43NSkNCmBgYA0KDQojIyMjIFF1ZXN0aW9uIDEyOiBSYW5kb20gZm9yZXN0IG1vZGVsDQoNClNldCB0aGUgc2VlZCB0byAxNC4gVXNlIHRoZSBjYXJldCB0cmFpbiBmdW5jdGlvbiB3aXRoIHRoZSByZiBtZXRob2QgdG8gdHJhaW4gYSByYW5kb20gZm9yZXN0LiBUZXN0IHZhbHVlcyBvZiBtdHJ5IHJhbmdpbmcgZnJvbSAxIHRvIDcuIFNldCBudHJlZSB0byAxMDAuDQoNCldoYXQgbXRyeSB2YWx1ZSBtYXhpbWl6ZXMgYWNjdXJhY3k/ICANCldoYXQgaXMgdGhlIGFjY3VyYWN5IG9mIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIG9uIHRoZSB0ZXN0IHNldD8gIA0KKipVc2UgdmFySW1wIG9uIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIG9iamVjdCB0byBkZXRlcm1pbmUgdGhlIGltcG9ydGFuY2Ugb2YgdmFyaW91cyBwcmVkaWN0b3JzIHRvIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsKiouICANCldoYXQgaXMgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlPyAgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTQsIHNhbXBsZS5raW5kID0gJ1JvdW5kaW5nJykNCmZpdDEyX3JmIDwtIHRyYWluKFN1cnZpdmVkIH4uLCANCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9zZXQsDQogICAgICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLCANCiAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShtdHJ5ID0gc2VxKDEsIDcpKSwgDQogICAgICAgICAgICAgICAgICBudHJlZSA9IDEwMCkNCmZpdDEyX3JmJGJlc3RUdW5lDQpzdXJ2aXZlZF9oYXQgPC0gcHJlZGljdChmaXQxMl9yZiwgdGVzdF9zZXQpDQptZWFuKHN1cnZpdmVkX2hhdCA9PSB0ZXN0X3NldCRTdXJ2aXZlZCkNCnZhckltcChmaXQxMl9yZikNCmBgYA==