#install.packages("pacman")
library(pacman)
p_load(infotheo)
p_load(tidyverse)
p_load(ggplot2)
p_load(cowplot)
p_load(mlbench)
p_load(Metrics)
#remove.packages("rlang")
#install.packages("rlang", repos = "https://cloud.r-project.org")
set.seed(123)

1 Wisconsin Breast Cancer Dataset

BreastCancer Dataset
A data frame with 699 observations on 11 variables, one being a character variable, 9 being ordered or nominal, and 1 target class.

  1. Sample code number: id number
  2. Clump Thickness: 1 - 10
  3. Uniformity of Cell Size: 1 - 10
  4. Uniformity of Cell Shape: 1 - 10
  5. Marginal Adhesion: 1 - 10
  6. Single Epithelial Cell Size: 1 - 10
  7. Bare Nuclei: 1 - 10
  8. Bland Chromatin: 1 - 10
  9. Normal Nucleoli: 1 - 10
  10. Mitoses: 1 - 10
  11. Class: (benign, malignant)

Breast Cancer Wisconsin (Original) Data Set

“Multisurface method of pattern separation for medical diagnosis applied to breast cytology.”, Wolberg,W.H., Mangasarian,O.L. (1990). In Proceedings of the National Academy of Sciences, 87, 9193-9196.

Zhang,J. (1992). Selecting typical instances in instance-based learning. In Proceedings of the Ninth International Machine Learning Conference (pp. 470-479). Aberdeen, Scotland: Morgan Kaufmann.

1.1 Cleaning and documentation

data(BreastCancer)
glimpse(BreastCancer)
Observations: 699
Variables: 11
$ Id              <chr> "1000025", "1002945", "1015425", "1016277", "1017023", "1017122", "1018099", "1018561", "1033078",...
$ Cl.thickness    <ord> 5, 5, 3, 6, 4, 8, 1, 2, 2, 4, 1, 2, 5, 1, 8, 7, 4, 4, 10, 6, 7, 10, 3, 8, 1, 5, 3, 5, 2, 1, 3, 2, ...
$ Cell.size       <ord> 1, 4, 1, 8, 1, 10, 1, 1, 1, 2, 1, 1, 3, 1, 7, 4, 1, 1, 7, 1, 3, 5, 1, 4, 1, 2, 2, 1, 1, 1, 1, 1, 7...
$ Cell.shape      <ord> 1, 4, 1, 8, 1, 10, 1, 2, 1, 1, 1, 1, 3, 1, 5, 6, 1, 1, 7, 1, 2, 5, 1, 5, 1, 3, 1, 1, 1, 3, 1, 1, 7...
$ Marg.adhesion   <ord> 1, 5, 1, 1, 3, 8, 1, 1, 1, 1, 1, 1, 3, 1, 10, 4, 1, 1, 6, 1, 10, 3, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, ...
$ Epith.c.size    <ord> 2, 7, 2, 3, 2, 7, 2, 2, 2, 2, 1, 2, 2, 2, 7, 6, 2, 2, 4, 2, 5, 6, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 8,...
$ Bare.nuclei     <fct> 1, 10, 2, 4, 1, 10, 10, 1, 1, 1, 1, 1, 3, 3, 9, 1, 1, 1, 10, 1, 10, 7, 1, NA, 1, 7, 1, 1, 1, 1, 1,...
$ Bl.cromatin     <fct> 3, 3, 3, 3, 3, 9, 3, 3, 1, 2, 3, 2, 4, 3, 5, 4, 2, 3, 4, 3, 5, 7, 2, 7, 3, 3, 2, 2, 2, 1, 2, 3, 7,...
$ Normal.nucleoli <fct> 1, 2, 1, 7, 1, 7, 1, 1, 1, 1, 1, 1, 4, 1, 5, 3, 1, 1, 1, 1, 4, 10, 1, 3, 1, 6, 1, 1, 1, 1, 1, 1, 4...
$ Mitoses         <fct> 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 4, 1, 1, 1, 2, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,...
$ Class           <fct> benign, benign, benign, benign, benign, malignant, benign, benign, benign, benign, benign, benign,...
summary(BreastCancer$Class)
   benign malignant 
      458       241 
BreastCancer$y <- as.factor(as.numeric(BreastCancer$Class=="malignant"))
BreastCancer$Class <- NULL
BreastCancer$Id <- NULL
BreastCancer[,1:5] <- lapply(BreastCancer[,1:5] , as.numeric)
summary(BreastCancer)
  Cl.thickness      Cell.size        Cell.shape     Marg.adhesion     Epith.c.size     Bare.nuclei   Bl.cromatin 
 Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   Min.   : 1.000   1      :402   2      :166  
 1st Qu.: 2.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 1.000   1st Qu.: 2.000   10     :132   3      :165  
 Median : 4.000   Median : 1.000   Median : 1.000   Median : 1.000   Median : 2.000   2      : 30   1      :152  
 Mean   : 4.418   Mean   : 3.134   Mean   : 3.207   Mean   : 2.807   Mean   : 3.216   5      : 30   7      : 73  
 3rd Qu.: 6.000   3rd Qu.: 5.000   3rd Qu.: 5.000   3rd Qu.: 4.000   3rd Qu.: 4.000   3      : 28   4      : 40  
 Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.000   Max.   :10.000   (Other): 61   5      : 34  
                                                                                      NA's   : 16   (Other): 69  
 Normal.nucleoli    Mitoses    y      
 1      :443     1      :579   0:458  
 10     : 61     2      : 35   1:241  
 3      : 44     3      : 33          
 2      : 36     10     : 14          
 8      : 24     4      : 12          
 6      : 22     7      :  9          
 (Other): 69     (Other): 17          
p_load(GGally)
ggpairs(BreastCancer, title = "Breast Cancer Dataset")

p_load(corrplot)
p_load(infotheo)
BreastCancer_mi <- mutinformation(BreastCancer, method="emp") %>% natstobits()
#BreastCancer_mi <- BreastCancer_mi/max(BreastCancer_mi) 
mi_max <- max( BreastCancer_mi[lower.tri(BreastCancer_mi, diag = FALSE)])
diag(BreastCancer_mi) <-0
corrplot.mixed(BreastCancer_mi,
               cl.lim = c(0,mi_max),
               title = "Normalised Mutual Information Breast Cancer Dataset",
               mar=c(0,0,1,0),
               lower = "ellipse",
               upper="number",
               is.corr = FALSE,
               order = "hclust"
)

p_load(infotheo)
BreastCancer_mi <- mutinformation(BreastCancer, method="emp") %>% natstobits()
BreastCancer_mi_d <- as.dist(max(BreastCancer_mi)-BreastCancer_mi)
hc <- hclust(BreastCancer_mi_d, method="ward.D2")
plot(hc)

There are 16 unexplained missing values on one of the features. We’re going to impute those values, being careful to not use the outcome as one of the predictors. This will allows us to make comparisons across methods that do not handle missing values well, and also will protect us on predicting onto new test data which might also have unexplained missingness.

MissForest—non-parametric missing value imputation for mixed-type data, Daniel J. Stekhoven Peter Bühlmann, Bioinformatics, Volume 28, Issue 1, 1 January 2012, Pages 112–118,

#There are 16 missing values in Bare.nuclei, they're continous 
p_load("missForest")
BreastCancer_imputed <- BreastCancer
BreastCancer_imputed <- missForest(BreastCancer %>% select(-y), verbose = TRUE)$ximp
  missForest iteration 1 in progress...done!
    estimated error(s): 0 0.09553441 
    difference(s): 0 0.001430615 
    time: 1.23 seconds

  missForest iteration 2 in progress...done!
    estimated error(s): 0 0.08674963 
    difference(s): 0 0.0003576538 
    time: 1.19 seconds

  missForest iteration 3 in progress...done!
    estimated error(s): 0 0.09699854 
    difference(s): 0 0.0007153076 
    time: 1.02 seconds
BreastCancer_imputed$y <- BreastCancer$y

Convert categorical variables to ‘one-hot’ dummy variables

Making dummy variables with dummy_cols(), Jacob Kaplan, 2018-06-21

#install.packages('data.table')
p_load(fastDummies)
BreastCancer_onehot <- fastDummies::dummy_cols(BreastCancer_imputed,
                                               select_columns=c("Bare.nuclei",
                                                                "Bl.cromatin",
                                                                "Normal.nucleoli",
                                                                "Mitoses"))
BreastCancer_onehot[,c('Bare.nuclei','Bl.cromatin','Normal.nucleoli','Mitoses')] <- NULL

2 Hold out a Test Set

The Very first thing we’re going to do is pull 20% of the Breat Cancer dataset out as a test set and we’re never going to touch it for any reason other than final model evaluation.

Immediately split off a test set that we will not touch until the very final evaluation.

N=nrow(BreastCancer)
condition_train <- runif(N)<.8; table(condition_train)
condition_train
FALSE  TRUE 
  127   572 
BreastCancer_train <- BreastCancer_imputed[condition_train,]
BreastCancer_test <- BreastCancer_imputed[!condition_train,]
BreastCancer_onehot_train <- BreastCancer_onehot[condition_train,]
BreastCancer_onehot_test <- BreastCancer_onehot[!condition_train,]

3 Supervised Learning

formula= y ~    Cl.thickness + 
                           Cell.size + 
                           Cell.shape + 
                           Marg.adhesion + 
                           Epith.c.size + 
                           Bare.nuclei + 
                           Bl.cromatin + 
                           Normal.nucleoli + 
                           Mitoses
#One Hot formula dummies
formula_onehot = y ~ 
Cl.thickness +
Cell.size +
Cell.shape +
Marg.adhesion +
Epith.c.size +
Bare.nuclei_1 + Bare.nuclei_10 + Bare.nuclei_2 + Bare.nuclei_4 + Bare.nuclei_3 + Bare.nuclei_9 + Bare.nuclei_7 + 
Bare.nuclei_5 + Bare.nuclei_8 + Bare.nuclei_6 +  Bl.cromatin_3  + 
Bl.cromatin_9 + Bl.cromatin_1+Bl.cromatin_2+Bl.cromatin_4+Bl.cromatin_5+Bl.cromatin_7  +   
Bl.cromatin_8+Bl.cromatin_6+Bl.cromatin_10+
  
Normal.nucleoli_1 +  Normal.nucleoli_2 + Normal.nucleoli_7 + 
Normal.nucleoli_4 + Normal.nucleoli_5 +  Normal.nucleoli_3 + 
Normal.nucleoli_10 + Normal.nucleoli_6 +  Normal.nucleoli_9 + 
Normal.nucleoli_8 +  
  
Mitoses_1+ Mitoses_5 + Mitoses_4 + Mitoses_2+Mitoses_3 + Mitoses_7 + Mitoses_10 + Mitoses_8 + Mitoses_6

Register a single back end for cross-validation

p_load(caret)
set.seed(123)
cctrl1 <- trainControl(method="cv", 
                       number=10,
                       returnResamp="all",
                       classProbs=TRUE,
                       summaryFunction=twoClassSummary,
                       savePredictions=TRUE
                       )

4 Linear Models

p_load(glmnet)
set.seed(123)
glm1 <- glm(formula_onehot ,
               data=BreastCancer_onehot_train ,
               family=binomial(link='probit')
            )
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
library(broom)
tidy(glm1 ) #There are 44 features, counting dummified categorical variables

Out of sample accuracy?

FALSE [1] 0.9368907

5 Variable Selection

5.1 Feature Importance and P Values

Introduction to vimp, Brian D. Williamson, 2018-06-19

5.2 Regularization, e.g. Lasso/Ridge Regression

set.seed(123)
glmnet1 <- glmnet(x=BreastCancer_onehot_train %>% select(-y) %>%  as.matrix(),
               y=as.factor(BreastCancer_onehot_train$y),
               family="binomial"
               )
plot(glmnet1)

glmnet1_cv <- cv.glmnet(x=BreastCancer_onehot_train %>% select(-y) %>% data.matrix(),
                       y=as.factor(BreastCancer_onehot_train$y),
                       family="binomial",
                                nfolds=5)
glmnet1_cv$lambda.1se #smallest model with error within 1se error of the minimum ever observed
[1] 0.01817259
plot(glmnet1_cv)

glmnet_lambda.1se_betas <- coef(glmnet1_cv,s="lambda.1se") %>% as.matrix() %>% as.data.frame()  %>% 
                           rename(beta='1') %>% 
                           rownames_to_column() %>% arrange(desc(beta) ) 
#There are 44 features
#14 have been set to nonzero coefficients
#the cofficients are relatively small
#Design a single model around that optimal lambda
glmnet_lambda.1se <- glmnet(x=BreastCancer_onehot_train %>% select(-y) %>% data.matrix(),
                       y=BreastCancer_onehot_train$y,
                       family="binomial",
                       lambda=glmnet1_cv$lambda.1se
                       )
#cross validate that model to get estimate of accuracy on the test set
glmnet_lambda.1se_cv <- train(x=BreastCancer_onehot_train %>% select(-y) %>% data.matrix(),
                             y=as.factor(paste0('Outcome',BreastCancer_train$y)),
                             method = "glmnet",
                             trControl = cctrl1,
                             metric = "ROC",
                             tuneGrid = expand.grid(alpha = 1,lambda = glmnet1_cv$lambda.1se))
#Area Under the Curve Almost Perfect Now despite using only 14 of the 44 features
print(glmnet_lambda.1se_cv$results$ROC)  #0.99
[1] 0.9920091
p_load(plotROC)
out_of_sample_predictions2 <- data.frame(y_hat=glmnet_lambda.1se_cv$pred$Outcome1,
                                               y=BreastCancer_train$y[glmnet_lambda.1se_cv$pred$rowIndex],
                                               model="Lasso")
basicplot <- ggplot(bind_rows(out_of_sample_predictions,
                              out_of_sample_predictions2),
                    aes(d = as.numeric(y), m = y_hat, color=model)) + geom_roc(n.cuts=0) + 
                    style_roc(theme = theme_grey, xlab = "1 - Specificity") + ggtitle("AUC")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
basicplot

NA

5.3 Linear Expansions and Interaction Terms

We can put the same feature in a linear multiple times with polynomials to capture nonlinear relationships.

set.seed(123)
library(dplyr)
df <- data.frame(x=seq(0,100)) %>% 
  mutate(y=0+x+x^2+x^3)  %>% 
  mutate(pred_lm      = lm(y~x    )$fitted.values) %>% 
  mutate(pred_lm_quad = lm(y~x+I(x^2))$fitted.values)
library(ggplot2)
ggplot(df, aes(x,y))  + 
         geom_point( aes(x,y)) + 
         geom_line(aes(x=x,y=pred_lm), col='red')  + 
         geom_line(aes(x=x,y=pred_lm_quad), col='blue')  

5.4 Interaction Terms

Nonlinear Models * (ISLR) “Chapter 7 Moving Beyond Linearity” Linear_separability

set.seed(123)
form <-  ~ .^2
y <- BreastCancer_onehot_train$Class_binary
BreastCancer_onehot_train_twoway <-  model.matrix(form, data = BreastCancer_onehot_train[,-c(6)])
BreastCancer_onehot_test_twoway <-  model.matrix(form, data = BreastCancer_onehot_test[,-c(6)])
dim(BreastCancer_onehot_train_twoway)#991 terms
[1] 572 991
condition = colnames(BreastCancer_onehot_train_twoway)=='Class_binary'
glmnet_twoway <- glmnet(x=BreastCancer_onehot_train_twoway ,
               y=as.factor(BreastCancer_onehot_train$y),
               family="binomial"
               )
plot(glmnet_twoway)

glmnet_twoway_cv <- cv.glmnet(x=BreastCancer_onehot_train_twoway,
                       y=as.factor(BreastCancer_onehot_train$y),
                       family="binomial",
                                nfolds=5)
glmnet_twoway_cv$lambda.1se #smallest model with error within 1se error of the minimum ever observed
[1] 0.01994439
plot(glmnet_twoway_cv)

glmnet_twoway_lambda.1se_betas <- coef(glmnet1_cv,s="lambda.1se") %>% as.matrix() %>% as.data.frame()  %>% 
                           rename(beta='1') %>% 
                           rownames_to_column() %>% arrange(desc(beta) ) 
#Design a single model around that optimal lambda
glmnet_twoway_lambda.1se <- glmnet(x=BreastCancer_onehot_train %>% select(-y) %>% data.matrix(),
                       y=BreastCancer_onehot_train$y,
                       family="binomial",
                       lambda=glmnet1_cv$lambda.1se
                       )
#cross validate that model to get estimate of accuracy on the test set
glmnet_twoway_lambda.1se_cv <- train(x=BreastCancer_onehot_train_twoway,
                             y=as.factor(paste0('Outcome',BreastCancer_train$y)),
                             method = "glmnet",
                             trControl = cctrl1,
                             metric = "ROC",
                             tuneGrid = expand.grid(alpha = 1,lambda = glmnet1_cv$lambda.1se))
#Area Under the Curve Almost Perfect Now despite using only 14 of the 44 features
print(glmnet_twoway_lambda.1se_cv$results$ROC) #0.991
[1] 0.9912164
p_load(plotROC)
out_of_sample_predictions3 <- data.frame(y_hat=glmnet_twoway_lambda.1se_cv$pred$Outcome1,
                                               y=BreastCancer_train$y[glmnet_twoway_lambda.1se_cv$pred$rowIndex],
                                               model="Lasso Interactions")
basicplot <- ggplot(bind_rows(out_of_sample_predictions,
                              out_of_sample_predictions2,
                              out_of_sample_predictions3),
                    aes(d = as.numeric(y), m = y_hat, color=model)) + geom_roc(n.cuts=0) + 
                    style_roc(theme = theme_grey, xlab = "1 - Specificity") + ggtitle("AUC")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
basicplot

Interpreting the model
There are some measures that unambigiously look bad for cancer outcomes.
There are certain interactions that are good news.
Bare.nuclei_8:Normal.nucleoli_2
Bare.nuclei_1:Mitoses_1
Bare.nuclei_7:Normal.nucleoli_8
Normal.nucleoli_1:Mitoses_1
are.nuclei_1:Normal.nucleoli_1

Bare.nuclei_1 by itself looks like good news, but in combination with something else it’s especially helpful.

#There are 991 terms
#By a mirracle, also 14 chosen
#Some of the  cofficients are relatively small
glmnet_twoway_cv_betas <- coef(glmnet_twoway_cv,s="lambda.1se") %>% 
                            as.matrix() %>% as.data.frame()  %>% 
                           rename(beta='1') %>% 
                           rownames_to_column() %>% arrange(desc(beta) ) 
glmnet_twoway_cv_betas %>% filter(beta!=0)

6 Decision Trees

set.seed(123)
p_load(party)
single_decision_tree <- ctree(formula, data = BreastCancer_train)
plot(single_decision_tree)

Out of sample

Slightly worse but arguably an easier to interpret model.

set.seed(123)
single_decision_tree_cv_model <- train(x=BreastCancer_train[,-c(10)],
                             y=as.factor(paste0('Outcome',BreastCancer_train$y)),
                             method = "ctree",
                             trControl = cctrl1,
                             metric = "ROC",
                             tuneGrid = expand.grid(mincriterion = 0.99)
                             )
print(single_decision_tree_cv_model$results$ROC) #0.9668608
[1] 0.9656646
p_load(plotROC)
out_of_sample_predictions4 <- data.frame(y_hat=single_decision_tree_cv_model$pred$Outcome1,
                                               y=BreastCancer_train$y[single_decision_tree_cv_model$pred$rowIndex],
                                               model="Tree")
basicplot <- ggplot(bind_rows(out_of_sample_predictions,
                              out_of_sample_predictions2,
                              out_of_sample_predictions3,
                              out_of_sample_predictions4),
                    aes(d = as.numeric(y), m = y_hat, color=model)) + geom_roc(n.cuts=0) + 
                    style_roc(theme = theme_grey, xlab = "1 - Specificity") + ggtitle("AUC")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
basicplot

7 Overfitting

7.1 Bootstrapping Observations

7.2 Model Complexity/Parismony

8 Curse of dimensionality

8.1 Feature Bagging/Subspace Mtethods

9 Random Forests

set.seed(123)
#install.packages('randomForest', dependencies=T)
p_load(randomForest)
forest <- randomForest(formula,
                       data = BreastCancer_train,
                       localImp = TRUE,
                       na.action=na.omit)
print(forest)

Call:
 randomForest(formula = formula, data = BreastCancer_train, localImp = TRUE,      na.action = na.omit) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 3

        OOB estimate of  error rate: 2.62%
Confusion matrix:
    0   1 class.error
0 363  13  0.03457447
1   2 194  0.01020408
set.seed(123)
p_load(randomForest)
forest_cv_model <- train(x=BreastCancer_train[,-c(10)],
                             y=as.factor(paste0('Outcome',BreastCancer_train$y)),
                             method = "rf",
                             trControl = cctrl1,
                             metric = "ROC"
                             #tuneGrid = expand.grid(alpha = 1,lambda = glmnet1_cv$lambda.1se)
                             )
print(forest_cv_model$results)
p_load(plotROC)
condition <- forest_cv_model$pred$mtry==5
out_of_sample_predictions5 <- data.frame(y_hat=forest_cv_model$pred$Outcome1[condition] ,
                                               y=BreastCancer_train$y[forest_cv_model$pred$rowIndex[condition]] ,
                                               model="Forest")
basicplot <- ggplot(bind_rows(out_of_sample_predictions,
                              out_of_sample_predictions2,
                              out_of_sample_predictions3,
                              out_of_sample_predictions4,
                              out_of_sample_predictions5),
                    aes(d = as.numeric(y), m = y_hat, color=model)) + geom_roc(n.cuts=0) + 
                    style_roc(theme = theme_grey, xlab = "1 - Specificity") + ggtitle("AUC")
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
basicplot

9.1 Depth

Understanding random forests with randomForestExplainer, Aleksandra Paluszyńska

set.seed(123)
#devtools::install_github("MI2DataLab/randomForestExplainer")
p_load(randomForestExplainer)
#install.packages('rlang')
min_depth_frame <- min_depth_distribution(forest)
save(min_depth_frame, file = "min_depth_frame.rda")
load("min_depth_frame.rda")
head(min_depth_frame, n = 10)
# plot_min_depth_distribution(forest) # gives the same result as below but takes longer
plot_min_depth_distribution(min_depth_frame)

Variable Importance Pay particular attention to “accuracy_decrease” which is the drop in the classifier’s accuracy if that variable is shuffled destroying its information.

importance_frame <- measure_importance(forest)
importance_frame
plot_multi_way_importance(importance_frame, size_measure = "no_of_nodes")

(vars <- important_variables(importance_frame, k = 5, measures = c("mean_min_depth", "no_of_trees")))
[1] "Bare.nuclei"     "Normal.nucleoli" "Cell.shape"      "Cell.size"       "Cl.thickness"   
interactions_frame <- min_depth_interactions(forest, vars)
head(interactions_frame[order(interactions_frame$occurrences, decreasing = TRUE), ])
plot_min_depth_interactions(interactions_frame)

plot_predict_interaction(forest, BreastCancer_train[,-c(10)], "Cell.size", "Cl.thickness")

Can even generate an automated report

explain_forest(forest, interactions = TRUE, data = BreastCancer_train)

10 Compare out of Sample Accuracy

set.seed(123)
df_predictions <- data.frame(y_true=BreastCancer_test$y,
                             y_hat_glm=stats::predict.glm(glm1, newdata=BreastCancer_onehot_test, type = "response" ),
                             y_hat_lasso = predict(glmnet1_cv, newx=BreastCancer_onehot_test %>% 
                                                 select(-y) %>% data.matrix(), s=c("lambda.1se") ,
                                                 type = "response")[,1],
                              y_hat_lasso_twoway <- predict(glmnet_twoway_cv, 
                                                            newx=BreastCancer_onehot_test_twoway %>%
                                                              data.matrix(),
                                      s=c("lambda.1se") , type = "response")[,1],
                             y_hat_single_tree = predict(single_decision_tree, newdata=BreastCancer_test,
                                                         type = "prob") %>% sapply(rbind) %>% t() %>%
                               data.frame() %>% pull(X2),
                             y_hat_forest = predict(forest, newdata=BreastCancer_test, type = "prob")[,'1']#,
                             #y_hat_nn = predict(NN, newdata=BreastCancer_test, type = "prob")
                             )
prediction from a rank-deficient fit may be misleading
p_load(MLmetrics)
AUC(df_predictions$y_hat_glm,df_predictions$y_true) %>% round(3)
[1] 0.935
AUC(df_predictions$y_hat_lasso,df_predictions$y_true) %>% round(3)
[1] 0.991
AUC(df_predictions$y_hat_lasso_twoway,df_predictions$y_true) %>% round(3)
[1] 0.99
AUC(df_predictions$y_hat_single_tree,df_predictions$y_true) %>% round(3)
[1] 0.972
AUC(df_predictions$y_hat_forest,df_predictions$y_true) %>% round(3)
[1] 0.988
table(df_predictions$y_hat_lasso>.5,
      df_predictions$y_true)
       
         0  1
  FALSE 79  4
  TRUE   3 41

11 Neural Networks

p_load("neuralnet")
formula_onehot_2 = y + y_not ~ Cl.thickness + Cell.size + Cell.shape + Marg.adhesion + Epith.c.size + 
    Bare.nuclei_1 + Bare.nuclei_10 + Bare.nuclei_2 + Bare.nuclei_4 + 
    Bare.nuclei_3 + Bare.nuclei_9 + Bare.nuclei_7 + Bare.nuclei_5 + 
    Bare.nuclei_8 + Bare.nuclei_6 + Bl.cromatin_3 + Bl.cromatin_9 + 
    Bl.cromatin_1 + Bl.cromatin_2 + Bl.cromatin_4 + Bl.cromatin_5 + 
    Bl.cromatin_7 + Bl.cromatin_8 + Bl.cromatin_6 + Bl.cromatin_10 + 
    Normal.nucleoli_1 + Normal.nucleoli_2 + Normal.nucleoli_7 + 
    Normal.nucleoli_4 + Normal.nucleoli_5 + Normal.nucleoli_3 + 
    Normal.nucleoli_10 + Normal.nucleoli_6 + Normal.nucleoli_9 + 
    Normal.nucleoli_8 + Mitoses_1 + Mitoses_5 + Mitoses_4 + Mitoses_2 + 
    Mitoses_3 + Mitoses_7 + Mitoses_10 + Mitoses_8 + Mitoses_6
BreastCancer_onehot_train_2 = BreastCancer_onehot_train
BreastCancer_onehot_train_2$y_not = as.numeric(!as.logical(as.numeric(BreastCancer_onehot_train_2$y)-1))
BreastCancer_onehot_test_2 = BreastCancer_onehot_test
BreastCancer_onehot_test_2$y_not = as.numeric(!as.logical(as.numeric(BreastCancer_onehot_test_2$y)-1))
table(BreastCancer_onehot_test_2$y_not, BreastCancer_onehot_test_2$y)
   
     0  1
  0  0 45
  1 82  0
NN = neuralnet(formula_onehot_2,
               data= BreastCancer_onehot_train_2 %>% data.matrix(), 
               hidden = 10 , 
               linear.output = F
               )
# plot neural network
plot(NN)

13 Special Topics

13.1 Time

13.2 Text

14 Examples

15 Extras

15.1 Gradient Boosting

15.3 Nearest Neighbor

15.4 How the Sausage is Made

LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBNYWNoaW5lIExlYXJuaW5nIChTeWxsYWJ1cy9Db2RlIGZvciBEYXkgMik6IFNvbHV0aW9ucyBmb3IgTGVhcm5pbmcgaW4gU3VwZXJ2aXNlZCBhbmQgVW5zdXBlcnZpc2VkIFNldHRpbmdzIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUgIyB0YWJsZSBvZiBjb250ZW50IHRydWUNCiAgICB0b2NfZGVwdGg6IDMgICMgdXB0byB0aHJlZSBkZXB0aHMgb2YgaGVhZGluZ3MgKHNwZWNpZmllZCBieSAjLCAjIyBhbmQgIyMjKQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZSAgIyMgaWYgeW91IHdhbnQgbnVtYmVyIHNlY3Rpb25zIGF0IGVhY2ggdGFibGUgaGVhZGVyDQogICAgaGlnaGxpZ2h0OiB0YW5nbyAgIyBzcGVjaWZpZXMgdGhlIHN5bnRheCBoaWdobGlnaHRpbmcgc3R5bGUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCi0tLQ0KDQoNCmBgYHtjc3N9DQoNCnByZSBjb2RlLCBwcmUsIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogcHJlICFpbXBvcnRhbnQ7DQogIG92ZXJmbG93LXg6ICFzY3JvbGwgIWltcG9ydGFudDsNCiAgd29yZC1icmVhazoga2VlcC1hbGwgIWltcG9ydGFudDsNCiAgd29yZC13cmFwOiBpbml0aWFsICFpbXBvcnRhbnQ7DQp9DQoNCmNvZGUucnsNCiAgb3ZlcmZsb3cteDogIXNjcm9sbCAhaW1wb3J0YW50Ow0KfQ0KDQpgYGANCg0KYGBge3IsIGV2YWw9RiwgaW5jbHVkZT1GfQ0KI0kgaGFkIHNvbWUgdHJvdWJsZSBpbnN0YWxsIGNhcmV0IGFsbCBpbiBvbmUgZ28gc28gd2VudCBkZXBlbmRlbmN5IGJ5IGRlcGVuZGVuY3kNCmluc3RhbGwucGFja2FnZXMoJ3JvYnVzdGJhc2UnKQ0KaW5zdGFsbC5wYWNrYWdlcygnc2ZzbWlzYycpDQppbnN0YWxsLnBhY2thZ2VzKCdnZW9tZXRyeScpDQppbnN0YWxsLnBhY2thZ2VzKCdwcm9maWxlTW9kZWwnKQ0KaW5zdGFsbC5wYWNrYWdlcygnbGFiZWxsZWQnKQ0KaW5zdGFsbC5wYWNrYWdlcygnZGltUmVkJykNCmluc3RhbGwucGFja2FnZXMoJ3RpbWVEYXRlJykNCmluc3RhbGwucGFja2FnZXMoJ2RkYWxwaGEnKQ0KaW5zdGFsbC5wYWNrYWdlcygnZ293ZXInKQ0KaW5zdGFsbC5wYWNrYWdlcygnUmNwcFJvbGwnKQ0KaW5zdGFsbC5wYWNrYWdlcygnYnJnbG0nKQ0KaW5zdGFsbC5wYWNrYWdlcygncXZjYWxjJykNCmluc3RhbGwucGFja2FnZXMoJ3Bsb3RtbycpDQppbnN0YWxsLnBhY2thZ2VzKCdUZWFjaGluZ0RlbW9zJykNCmluc3RhbGwucGFja2FnZXMoJ2NvbWJpbmF0JykNCmluc3RhbGwucGFja2FnZXMoJ3F1ZXN0aW9ucicpDQppbnN0YWxsLnBhY2thZ2VzKCdJU3dSJykNCmluc3RhbGwucGFja2FnZXMoJ2NvcnBjb3InKQ0KaW5zdGFsbC5wYWNrYWdlcygnTW9kZWxNZXRyaWNzJykNCmluc3RhbGwucGFja2FnZXMoJ3JlY2lwZXMnKQ0KaW5zdGFsbC5wYWNrYWdlcygnQnJhZGxleVRlcnJ5MicpDQppbnN0YWxsLnBhY2thZ2VzKCdlYXJ0aCcpDQppbnN0YWxsLnBhY2thZ2VzKCdmYXN0SUNBJykNCmluc3RhbGwucGFja2FnZXMoJ2dhbScpDQppbnN0YWxsLnBhY2thZ2VzKCdpcHJlZCcpDQppbnN0YWxsLnBhY2thZ2VzKCdrbGFSJykNCmluc3RhbGwucGFja2FnZXMoJ2VsbGlwc2UnKQ0KaW5zdGFsbC5wYWNrYWdlcygnbWRhJykNCmluc3RhbGwucGFja2FnZXMoJ3BscycpDQppbnN0YWxsLnBhY2thZ2VzKCdwUk9DJykNCmluc3RhbGwucGFja2FnZXMoJ3Byb3h5JykNCmluc3RhbGwucGFja2FnZXMoJ3NwbHMnKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KbGlicmFyeShwYWNtYW4pDQpwX2xvYWQoaW5mb3RoZW8pDQpwX2xvYWQodGlkeXZlcnNlKQ0KcF9sb2FkKGdncGxvdDIpDQpwX2xvYWQoY293cGxvdCkNCnBfbG9hZChtbGJlbmNoKQ0KcF9sb2FkKE1ldHJpY3MpDQojcmVtb3ZlLnBhY2thZ2VzKCJybGFuZyIpDQojaW5zdGFsbC5wYWNrYWdlcygicmxhbmciLCByZXBvcyA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmciKQ0KDQpzZXQuc2VlZCgxMjMpDQoNCmBgYA0KDQoNCiMgV2lzY29uc2luIEJyZWFzdCBDYW5jZXIgRGF0YXNldA0KDQpCcmVhc3RDYW5jZXIgRGF0YXNldCA8YnIvPg0KQSBkYXRhIGZyYW1lIHdpdGggNjk5IG9ic2VydmF0aW9ucyBvbiAxMSB2YXJpYWJsZXMsIG9uZSBiZWluZyBhIGNoYXJhY3RlciB2YXJpYWJsZSwgOSBiZWluZyBvcmRlcmVkIG9yIG5vbWluYWwsIGFuZCAxIHRhcmdldCBjbGFzcy4gPGJyLz4NCg0KMS4gU2FtcGxlIGNvZGUgbnVtYmVyOiBpZCBudW1iZXIgDQoyLiBDbHVtcCBUaGlja25lc3M6IDEgLSAxMCANCjMuIFVuaWZvcm1pdHkgb2YgQ2VsbCBTaXplOiAxIC0gMTAgDQo0LiBVbmlmb3JtaXR5IG9mIENlbGwgU2hhcGU6IDEgLSAxMCANCjUuIE1hcmdpbmFsIEFkaGVzaW9uOiAxIC0gMTAgDQo2LiBTaW5nbGUgRXBpdGhlbGlhbCBDZWxsIFNpemU6IDEgLSAxMCANCjcuIEJhcmUgTnVjbGVpOiAxIC0gMTAgDQo4LiBCbGFuZCBDaHJvbWF0aW46IDEgLSAxMCANCjkuIE5vcm1hbCBOdWNsZW9saTogMSAtIDEwIA0KMTAuIE1pdG9zZXM6IDEgLSAxMCANCjExLiBDbGFzczogKGJlbmlnbiwgbWFsaWduYW50KQ0KDQoNCltCcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoT3JpZ2luYWwpIERhdGEgU2V0IF0oaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL2JyZWFzdCtjYW5jZXIrd2lzY29uc2luKyhvcmlnaW5hbCkpDQoNClsiTXVsdGlzdXJmYWNlIG1ldGhvZCBvZiBwYXR0ZXJuIHNlcGFyYXRpb24gZm9yIG1lZGljYWwgZGlhZ25vc2lzIGFwcGxpZWQgdG8gYnJlYXN0IGN5dG9sb2d5LiJdKGh0dHA6Ly93d3cucG5hcy5vcmcvY29udGVudC9wbmFzLzg3LzIzLzkxOTMuZnVsbC5wZGYpLCBXb2xiZXJnLFcuSC4sIE1hbmdhc2FyaWFuLE8uTC4gKDE5OTApLiAgSW4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDg3LCA5MTkzLTkxOTYuDQoNClpoYW5nLEouICgxOTkyKS4gU2VsZWN0aW5nIHR5cGljYWwgaW5zdGFuY2VzIGluIGluc3RhbmNlLWJhc2VkIGxlYXJuaW5nLiBJbiBQcm9jZWVkaW5ncyBvZiB0aGUgTmludGggSW50ZXJuYXRpb25hbCBNYWNoaW5lIExlYXJuaW5nIENvbmZlcmVuY2UgKHBwLiA0NzAtNDc5KS4gQWJlcmRlZW4sIFNjb3RsYW5kOiBNb3JnYW4gS2F1Zm1hbm4uDQoNCg0KIyMgQ2xlYW5pbmcgYW5kIGRvY3VtZW50YXRpb24NCg0KYGBge3J9DQpkYXRhKEJyZWFzdENhbmNlcikNCmdsaW1wc2UoQnJlYXN0Q2FuY2VyKQ0Kc3VtbWFyeShCcmVhc3RDYW5jZXIkQ2xhc3MpDQoNCkJyZWFzdENhbmNlciR5IDwtIGFzLmZhY3Rvcihhcy5udW1lcmljKEJyZWFzdENhbmNlciRDbGFzcz09Im1hbGlnbmFudCIpKQ0KQnJlYXN0Q2FuY2VyJENsYXNzIDwtIE5VTEwNCkJyZWFzdENhbmNlciRJZCA8LSBOVUxMDQoNCkJyZWFzdENhbmNlclssMTo1XSA8LSBsYXBwbHkoQnJlYXN0Q2FuY2VyWywxOjVdICwgYXMubnVtZXJpYykNCnN1bW1hcnkoQnJlYXN0Q2FuY2VyKQ0KYGBgDQoNCmBgYHtyLCBmaWcud2lkdGg9MjUsIGZpZy5oZWlnaHQ9MTUsIGNhY2hlPVQsIG1lc3NhZ2U9RkFMU0V9DQpwX2xvYWQoR0dhbGx5KQ0KZ2dwYWlycyhCcmVhc3RDYW5jZXIsIHRpdGxlID0gIkJyZWFzdCBDYW5jZXIgRGF0YXNldCIpDQpgYGANCg0KYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMCwgY2FjaGU9VCB9DQpwX2xvYWQoY29ycnBsb3QpDQpwX2xvYWQoaW5mb3RoZW8pDQpCcmVhc3RDYW5jZXJfbWkgPC0gbXV0aW5mb3JtYXRpb24oQnJlYXN0Q2FuY2VyLCBtZXRob2Q9ImVtcCIpICU+JSBuYXRzdG9iaXRzKCkNCiNCcmVhc3RDYW5jZXJfbWkgPC0gQnJlYXN0Q2FuY2VyX21pL21heChCcmVhc3RDYW5jZXJfbWkpIA0KbWlfbWF4IDwtIG1heCggQnJlYXN0Q2FuY2VyX21pW2xvd2VyLnRyaShCcmVhc3RDYW5jZXJfbWksIGRpYWcgPSBGQUxTRSldKQ0KZGlhZyhCcmVhc3RDYW5jZXJfbWkpIDwtMA0KDQpjb3JycGxvdC5taXhlZChCcmVhc3RDYW5jZXJfbWksDQogICAgICAgICAgICAgICBjbC5saW0gPSBjKDAsbWlfbWF4KSwNCiAgICAgICAgICAgICAgIHRpdGxlID0gIk5vcm1hbGlzZWQgTXV0dWFsIEluZm9ybWF0aW9uIEJyZWFzdCBDYW5jZXIgRGF0YXNldCIsDQogICAgICAgICAgICAgICBtYXI9YygwLDAsMSwwKSwNCiAgICAgICAgICAgICAgIGxvd2VyID0gImVsbGlwc2UiLA0KICAgICAgICAgICAgICAgdXBwZXI9Im51bWJlciIsDQogICAgICAgICAgICAgICBpcy5jb3JyID0gRkFMU0UsDQogICAgICAgICAgICAgICBvcmRlciA9ICJoY2x1c3QiDQopDQoNCg0KYGBgDQoNCmBgYHtyfQ0KcF9sb2FkKGluZm90aGVvKQ0KQnJlYXN0Q2FuY2VyX21pIDwtIG11dGluZm9ybWF0aW9uKEJyZWFzdENhbmNlciwgbWV0aG9kPSJlbXAiKSAlPiUgbmF0c3RvYml0cygpDQoNCkJyZWFzdENhbmNlcl9taV9kIDwtIGFzLmRpc3QobWF4KEJyZWFzdENhbmNlcl9taSktQnJlYXN0Q2FuY2VyX21pKQ0KaGMgPC0gaGNsdXN0KEJyZWFzdENhbmNlcl9taV9kLCBtZXRob2Q9IndhcmQuRDIiKQ0KcGxvdChoYykNCg0KYGBgDQoNClRoZXJlIGFyZSAxNiB1bmV4cGxhaW5lZCBtaXNzaW5nIHZhbHVlcyBvbiBvbmUgb2YgdGhlIGZlYXR1cmVzLiBXZSdyZSBnb2luZyB0byBpbXB1dGUgdGhvc2UgdmFsdWVzLCBiZWluZyBjYXJlZnVsIHRvIG5vdCB1c2UgdGhlIG91dGNvbWUgYXMgb25lIG9mIHRoZSBwcmVkaWN0b3JzLiBUaGlzIHdpbGwgYWxsb3dzIHVzIHRvIG1ha2UgY29tcGFyaXNvbnMgYWNyb3NzIG1ldGhvZHMgdGhhdCBkbyBub3QgaGFuZGxlIG1pc3NpbmcgdmFsdWVzIHdlbGwsIGFuZCBhbHNvIHdpbGwgcHJvdGVjdCB1cyBvbiBwcmVkaWN0aW5nIG9udG8gbmV3IHRlc3QgZGF0YSB3aGljaCBtaWdodCBhbHNvIGhhdmUgdW5leHBsYWluZWQgbWlzc2luZ25lc3MuDQoNCltNaXNzRm9yZXN04oCUbm9uLXBhcmFtZXRyaWMgbWlzc2luZyB2YWx1ZSBpbXB1dGF0aW9uIGZvciBtaXhlZC10eXBlIGRhdGFdKGh0dHBzOi8vYWNhZGVtaWMub3VwLmNvbS9iaW9pbmZvcm1hdGljcy9hcnRpY2xlLzI4LzEvMTEyLzIxOTEwMSksIERhbmllbCBKLiBTdGVraG92ZW4gIFBldGVyIELDvGhsbWFubiwgQmlvaW5mb3JtYXRpY3MsIFZvbHVtZSAyOCwgSXNzdWUgMSwgMSBKYW51YXJ5IDIwMTIsIFBhZ2VzIDExMuKAkzExOCwNCg0KYGBge3J9DQojVGhlcmUgYXJlIDE2IG1pc3NpbmcgdmFsdWVzIGluIEJhcmUubnVjbGVpLCB0aGV5J3JlIGNvbnRpbm91cyANCnBfbG9hZCgibWlzc0ZvcmVzdCIpDQpCcmVhc3RDYW5jZXJfaW1wdXRlZCA8LSBCcmVhc3RDYW5jZXINCkJyZWFzdENhbmNlcl9pbXB1dGVkIDwtIG1pc3NGb3Jlc3QoQnJlYXN0Q2FuY2VyICU+JSBzZWxlY3QoLXkpLCB2ZXJib3NlID0gVFJVRSkkeGltcA0KQnJlYXN0Q2FuY2VyX2ltcHV0ZWQkeSA8LSBCcmVhc3RDYW5jZXIkeQ0KDQpgYGANCg0KQ29udmVydCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gJ29uZS1ob3QnIGR1bW15IHZhcmlhYmxlcyA8YnIvPg0KDQpbTWFraW5nIGR1bW15IHZhcmlhYmxlcyB3aXRoIGR1bW15X2NvbHMoKV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Zhc3REdW1taWVzL3ZpZ25ldHRlcy9tYWtpbmctZHVtbXktdmFyaWFibGVzLmh0bWwpLCBKYWNvYiBLYXBsYW4sIDIwMTgtMDYtMjENCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygnZGF0YS50YWJsZScpDQpwX2xvYWQoZmFzdER1bW1pZXMpDQpCcmVhc3RDYW5jZXJfb25laG90IDwtIGZhc3REdW1taWVzOjpkdW1teV9jb2xzKEJyZWFzdENhbmNlcl9pbXB1dGVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RfY29sdW1ucz1jKCJCYXJlLm51Y2xlaSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJsLmNyb21hdGluIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTm9ybWFsLm51Y2xlb2xpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWl0b3NlcyIpKQ0KQnJlYXN0Q2FuY2VyX29uZWhvdFssYygnQmFyZS5udWNsZWknLCdCbC5jcm9tYXRpbicsJ05vcm1hbC5udWNsZW9saScsJ01pdG9zZXMnKV0gPC0gTlVMTA0KYGBgDQoNCiMgSG9sZCBvdXQgYSBUZXN0IFNldA0KDQpUaGUgVmVyeSBmaXJzdCB0aGluZyB3ZSdyZSBnb2luZyB0byBkbyBpcyBwdWxsIDIwJSBvZiB0aGUgQnJlYXQgQ2FuY2VyIGRhdGFzZXQgb3V0IGFzIGEgdGVzdCBzZXQgYW5kIHdlJ3JlIG5ldmVyIGdvaW5nIHRvIHRvdWNoIGl0IGZvciBhbnkgcmVhc29uIG90aGVyIHRoYW4gZmluYWwgbW9kZWwgZXZhbHVhdGlvbi4NCg0KSW1tZWRpYXRlbHkgc3BsaXQgb2ZmIGEgdGVzdCBzZXQgdGhhdCB3ZSB3aWxsIG5vdCB0b3VjaCB1bnRpbCB0aGUgdmVyeSBmaW5hbCBldmFsdWF0aW9uLg0KDQpgYGB7cn0NCk49bnJvdyhCcmVhc3RDYW5jZXIpDQpjb25kaXRpb25fdHJhaW4gPC0gcnVuaWYoTik8Ljg7IHRhYmxlKGNvbmRpdGlvbl90cmFpbikNCg0KQnJlYXN0Q2FuY2VyX3RyYWluIDwtIEJyZWFzdENhbmNlcl9pbXB1dGVkW2NvbmRpdGlvbl90cmFpbixdDQpCcmVhc3RDYW5jZXJfdGVzdCA8LSBCcmVhc3RDYW5jZXJfaW1wdXRlZFshY29uZGl0aW9uX3RyYWluLF0NCg0KQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbiA8LSBCcmVhc3RDYW5jZXJfb25laG90W2NvbmRpdGlvbl90cmFpbixdDQpCcmVhc3RDYW5jZXJfb25laG90X3Rlc3QgPC0gQnJlYXN0Q2FuY2VyX29uZWhvdFshY29uZGl0aW9uX3RyYWluLF0NCg0KYGBgDQoNCiMgU3VwZXJ2aXNlZCBMZWFybmluZw0KKiBJTUxSIFsiQ2hhcHRlciA1IFN1cGVydmlzZWQgTGVhcm5pbmciXShodHRwczovL2xnYXR0by5naXRodWIuaW8vSW50cm9NYWNoaW5lTGVhcm5pbmdXaXRoUi9zdXBlcnZpc2VkLWxlYXJuaW5nLmh0bWwpDQoNCmBgYHtyfQ0KDQpmb3JtdWxhPSB5IH4gICAgQ2wudGhpY2tuZXNzICsgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBDZWxsLnNpemUgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIENlbGwuc2hhcGUgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE1hcmcuYWRoZXNpb24gKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEVwaXRoLmMuc2l6ZSArIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgQmFyZS5udWNsZWkgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEJsLmNyb21hdGluICsgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBOb3JtYWwubnVjbGVvbGkgKyANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE1pdG9zZXMNCg0KI09uZSBIb3QgZm9ybXVsYSBkdW1taWVzDQpmb3JtdWxhX29uZWhvdCA9IHkgfiANCg0KQ2wudGhpY2tuZXNzICsNCkNlbGwuc2l6ZSArDQpDZWxsLnNoYXBlICsNCk1hcmcuYWRoZXNpb24gKw0KRXBpdGguYy5zaXplICsNCg0KQmFyZS5udWNsZWlfMSArIEJhcmUubnVjbGVpXzEwICsgQmFyZS5udWNsZWlfMiArIEJhcmUubnVjbGVpXzQgKyBCYXJlLm51Y2xlaV8zICsgQmFyZS5udWNsZWlfOSArIEJhcmUubnVjbGVpXzcgKyANCkJhcmUubnVjbGVpXzUgKyBCYXJlLm51Y2xlaV84ICsgQmFyZS5udWNsZWlfNiArICBCbC5jcm9tYXRpbl8zICArIA0KDQpCbC5jcm9tYXRpbl85ICsgQmwuY3JvbWF0aW5fMStCbC5jcm9tYXRpbl8yK0JsLmNyb21hdGluXzQrQmwuY3JvbWF0aW5fNStCbC5jcm9tYXRpbl83ICArICAgDQpCbC5jcm9tYXRpbl84K0JsLmNyb21hdGluXzYrQmwuY3JvbWF0aW5fMTArDQogIA0KTm9ybWFsLm51Y2xlb2xpXzEgKyAgTm9ybWFsLm51Y2xlb2xpXzIgKyBOb3JtYWwubnVjbGVvbGlfNyArIA0KTm9ybWFsLm51Y2xlb2xpXzQgKyBOb3JtYWwubnVjbGVvbGlfNSArICBOb3JtYWwubnVjbGVvbGlfMyArIA0KTm9ybWFsLm51Y2xlb2xpXzEwICsgTm9ybWFsLm51Y2xlb2xpXzYgKyAgTm9ybWFsLm51Y2xlb2xpXzkgKyANCk5vcm1hbC5udWNsZW9saV84ICsgIA0KICANCk1pdG9zZXNfMSsgTWl0b3Nlc181ICsgTWl0b3Nlc180ICsgTWl0b3Nlc18yK01pdG9zZXNfMyArIE1pdG9zZXNfNyArIE1pdG9zZXNfMTAgKyBNaXRvc2VzXzggKyBNaXRvc2VzXzYNCg0KYGBgDQoNClJlZ2lzdGVyIGEgc2luZ2xlIGJhY2sgZW5kIGZvciBjcm9zcy12YWxpZGF0aW9uDQoNCmBgYHtyfQ0KDQpwX2xvYWQoY2FyZXQpDQpzZXQuc2VlZCgxMjMpDQpjY3RybDEgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCANCiAgICAgICAgICAgICAgICAgICAgICAgbnVtYmVyPTEwLA0KICAgICAgICAgICAgICAgICAgICAgICByZXR1cm5SZXNhbXA9ImFsbCIsDQogICAgICAgICAgICAgICAgICAgICAgIGNsYXNzUHJvYnM9VFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSwNCiAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVByZWRpY3Rpb25zPVRSVUUNCiAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQpgYGANCg0KIyBMaW5lYXIgTW9kZWxzDQoqIChJU0xSKSAiQ2hhcHRlciAzIExpbmVhciBSZWdyZXNzaW9uIg0KKiBbT3JkaW5hcnlfbGVhc3Rfc3F1YXJlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvKSA8YnIvPg0KKiAoSVNMUikgIkNoYXB0ZXIgNC4zIExvZ2lzdGljIFJlZ3Jlc3Npb24iDQoqIFtMb2dpc3RpY19yZWdyZXNzaW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Mb2dpc3RpY19yZWdyZXNzaW9uKSA8YnIvPg0KDQoqIFtHbG1uZXQgVmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nbG1uZXQvdmlnbmV0dGVzL2dsbW5ldF9iZXRhLnBkZikNCg0KYGBge3J9DQpwX2xvYWQoZ2xtbmV0KQ0Kc2V0LnNlZWQoMTIzKQ0KZ2xtMSA8LSBnbG0oZm9ybXVsYV9vbmVob3QgLA0KICAgICAgICAgICAgICAgZGF0YT1CcmVhc3RDYW5jZXJfb25laG90X3RyYWluICwNCiAgICAgICAgICAgICAgIGZhbWlseT1iaW5vbWlhbChsaW5rPSdwcm9iaXQnKQ0KICAgICAgICAgICAgKQ0KDQpsaWJyYXJ5KGJyb29tKQ0KdGlkeShnbG0xICkgI1RoZXJlIGFyZSA0NCBmZWF0dXJlcywgY291bnRpbmcgZHVtbWlmaWVkIGNhdGVnb3JpY2FsIHZhcmlhYmxlcw0KDQpgYGANCg0KT3V0IG9mIHNhbXBsZSBhY2N1cmFjeT8NCg0KYGBge3IsIGVjaG89RkFMU0UsIGNhY2hlPVQsIHJlc3VsdHM9VCwgd2FybmluZz1GQUxTRSwgY29tbWVudD1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0Kc2V0LnNlZWQoMTIzKQ0KZ2xtX2N2IDwtIHRyYWluKHg9QnJlYXN0Q2FuY2VyX3RyYWluWywtYygxMCldLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5PWFzLmZhY3RvcihwYXN0ZTAoJ091dGNvbWUnLEJyZWFzdENhbmNlcl90cmFpbiR5KSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjY3RybDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiIywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI3R1bmVHcmlkID0gZXhwYW5kLmdyaWQoYWxwaGEgPSAxLGxhbWJkYSA9IHNlcSgwLjAwMSwwLjEsYnkgPSAwLjAwMSkgKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQoNCnByaW50KGdsbV9jdiRyZXN1bHRzJFJPQykgI1ZlcnkgZGVjZW50IGFyZWEgdW5kZXIgdGhlIFJPQyBmb3IganVzdCBhIGxpbmVhciBtb2RlbA0KDQojZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJoYWRsZXkvZ2dwbG90MiIpDQojZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJzYWNoc21jL3Bsb3RST0MiKQ0KcF9sb2FkKHBsb3RST0MpDQoNCm91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMgPC0gZGF0YS5mcmFtZSh5X2hhdD1nbG1fY3YkcHJlZCRPdXRjb21lMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1CcmVhc3RDYW5jZXJfdHJhaW4keVtnbG1fY3YkcHJlZCRyb3dJbmRleF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsPSJHTE0iKQ0KDQpiYXNpY3Bsb3QgPC0gZ2dwbG90KG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICAgIGFlcyhkID0gYXMubnVtZXJpYyh5KSwgbSA9IHlfaGF0LCBjb2xvcj1tb2RlbCkpICsgZ2VvbV9yb2Mobi5jdXRzPTApICsgDQogICAgICAgICAgICAgICAgICAgIHN0eWxlX3JvYyh0aGVtZSA9IHRoZW1lX2dyZXksIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IikgKyBnZ3RpdGxlKCJBVUMiKQ0KYmFzaWNwbG90DQoNCmBgYA0KDQoNCg0KDQojIFZhcmlhYmxlIFNlbGVjdGlvbg0KKiAoRVNMKSAiMyBMaW5lYXIgTWV0aG9kcyBmb3IgUmVncmVzc2lvbiwgMy4zIFN1YnNldCBNZXRob2RzIg0KKiBbU3RlcHdpc2VfcmVncmVzc2lvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU3RlcHdpc2VfcmVncmVzc2lvbikNCg0KDQojIyBGZWF0dXJlIEltcG9ydGFuY2UgYW5kIFAgVmFsdWVzDQoqIFtBIE1hY2hpbmUgTGVhcm5pbmcgQWx0ZXJuYXRpdmUgdG8gUC12YWx1ZXNdKGh0dHBzOi8vYXJ4aXYub3JnL3BkZi8xNzAxLjA0OTQ0LnBkZiksTWluIEx1IGFuZCBIZW1hbnQgSXNod2FyYW4sIEZlYnJ1YXJ5IDIyLCAyMDE3PGJyLz4NCiogW0VMSTVdKGh0dHBzOi8vZ2l0aHViLmNvbS9UZWFtSEctTWVtZXgvZWxpNSkgPGJyLz4NCiogIldoeSBTaG91bGQgSSBUcnVzdCBZb3U/IjogRXhwbGFpbmluZyB0aGUgUHJlZGljdGlvbnMgb2YgQW55IENsYXNzaWZpZXIsIE1hcmNvIFR1bGlvIFJpYmVpcm8sIFNhbWVlciBTaW5naCwgQ2FybG9zIEd1ZXN0cmluLCBodHRwczovL2FyeGl2Lm9yZy9hYnMvMTYwMi4wNDkzOA0KbGltZSwgUHl0aG9uIFBhY2thZ2UsIGh0dHBzOi8vZ2l0aHViLmNvbS9tYXJjb3Rjci9saW1lDQoqIFtGZWF0dXJlIFNlbGVjdGlvbiB3aXRoIHRoZSBSIFBhY2thZ2UgTVhNOiAgU3RhdGlzdGljYWxseS1FcXVpdmFsZW50IEZlYXR1cmUgU3Vic2V0c10oaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzE2MTEuMDMyMjcucGRmKSA8YnIvPg0KKiBbImJvdW5jZVIiXShodHRwczovL2dpdGh1Yi5jb20vU1RBVFdPUlgvYm91bmNlUiksIFIgUGFja2FnZSAgPGJyLz4NCg0KKiBbJ0kgSlVTVCBSQU4gVHdvIE1JTExJT04gUkVHUkVTU0lPTlMnXShodHRwOi8vd3d3LmVjb3N0YXQudW5pY2FsLml0L0FpZWxsby9EaWRhdHRpY2EvZWNvbm9taWFfQ3Jlc2NpdGEvQ1JFU0NJVEEvQ1JFU0NJVEFfU2FsYS1pLU1hcnRpbi1BRVItMTk5Ny5wZGYpLCBYYXZpZXIgU2FsYS1pLU1hcnRpbiwgMTk5NywgQW1lcmljYW4gRWNvbm9taWMgUmV2aWV3IDxici8+DQoqIFtFeHRyZW1lX2JvdW5kc19hbmFseXNpc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRXh0cmVtZV9ib3VuZHNfYW5hbHlzaXMpDQoqIFsiRXh0cmVtZUJvdW5kczogRXh0cmVtZSBCb3VuZHMgQW5hbHlzaXMgaW4gUiJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9FeHRyZW1lQm91bmRzL3ZpZ25ldHRlcy9FeHRyZW1lQm91bmRzLnBkZikgPGJyLz4NCg0KW0ludHJvZHVjdGlvbiB0byB2aW1wXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmltcC92aWduZXR0ZXMvaW50cm9kdWN0aW9uX3RvX3ZpbXAuaHRtbCksIEJyaWFuIEQuIFdpbGxpYW1zb24sIDIwMTgtMDYtMTkNCg0KDQoNCiMjIFJlZ3VsYXJpemF0aW9uLCBlLmcuIExhc3NvL1JpZGdlIFJlZ3Jlc3Npb24NCiogaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTGFzc29fKHN0YXRpc3RpY3MpDQoqIFsiUmVncmVzc2lvbiBzaHJpbmthZ2UgYW5kIHNlbGVjdGlvbiB2aWEgdGhlIGxhc3NvIl0oaHR0cDovL3N0YXR3ZWIuc3RhbmZvcmQuZWR1L350aWJzL2xhc3NvL2xhc3NvLnBkZiksIFRpYnNoaXJhbmksIFIuLCAxOTk2LCAgSi4gUm95YWwuIFN0YXRpc3QuIFNvYyBCLiwgVm9sLiA1OCwgTm8uIDEsIHBhZ2VzIDI2Ny0yODgpDQoqIFsiR2xtbmV0IFZpZ25ldHRlIl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dsbW5ldC92aWduZXR0ZXMvZ2xtbmV0X2JldGEucGRmKSwgVHJldm9yIEhhc3RpZSBhbmQgSnVueWFuZyBRaWFuLCBTZXB0ZW1iZXIgMTMsIDIwMTYNCiogKElTTFIpICI2IExpbmVhciBNb2RlbCBTZWxlY3Rpb24gYW5kIFJlZ3VsYXJpemF0aW9uIg0KKiAoRVNMKSAiMyBMaW5lYXIgTWV0aG9kcyBmb3IgUmVncmVzc2lvbiwgMy40IFNocmlua2FnZSBNZXRob2RzIg0KDQoNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpnbG1uZXQxIDwtIGdsbW5ldCh4PUJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4gJT4lIHNlbGVjdCgteSkgJT4lICBhcy5tYXRyaXgoKSwNCiAgICAgICAgICAgICAgIHk9YXMuZmFjdG9yKEJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4keSksDQogICAgICAgICAgICAgICBmYW1pbHk9ImJpbm9taWFsIg0KICAgICAgICAgICAgICAgKQ0KcGxvdChnbG1uZXQxKQ0KDQpnbG1uZXQxX2N2IDwtIGN2LmdsbW5ldCh4PUJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4gJT4lIHNlbGVjdCgteSkgJT4lIGRhdGEubWF0cml4KCksDQogICAgICAgICAgICAgICAgICAgICAgIHk9YXMuZmFjdG9yKEJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4keSksDQogICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT0iYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZm9sZHM9NSkNCg0KZ2xtbmV0MV9jdiRsYW1iZGEuMXNlICNzbWFsbGVzdCBtb2RlbCB3aXRoIGVycm9yIHdpdGhpbiAxc2UgZXJyb3Igb2YgdGhlIG1pbmltdW0gZXZlciBvYnNlcnZlZA0KDQpwbG90KGdsbW5ldDFfY3YpDQoNCmdsbW5ldF9sYW1iZGEuMXNlX2JldGFzIDwtIGNvZWYoZ2xtbmV0MV9jdixzPSJsYW1iZGEuMXNlIikgJT4lIGFzLm1hdHJpeCgpICU+JSBhcy5kYXRhLmZyYW1lKCkgICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbmFtZShiZXRhPScxJykgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lIGFycmFuZ2UoZGVzYyhiZXRhKSApIA0KDQojVGhlcmUgYXJlIDQ0IGZlYXR1cmVzDQojMTQgaGF2ZSBiZWVuIHNldCB0byBub256ZXJvIGNvZWZmaWNpZW50cw0KI3RoZSBjb2ZmaWNpZW50cyBhcmUgcmVsYXRpdmVseSBzbWFsbA0KDQojRGVzaWduIGEgc2luZ2xlIG1vZGVsIGFyb3VuZCB0aGF0IG9wdGltYWwgbGFtYmRhDQpnbG1uZXRfbGFtYmRhLjFzZSA8LSBnbG1uZXQoeD1CcmVhc3RDYW5jZXJfb25laG90X3RyYWluICU+JSBzZWxlY3QoLXkpICU+JSBkYXRhLm1hdHJpeCgpLA0KICAgICAgICAgICAgICAgICAgICAgICB5PUJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4keSwNCiAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5PSJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYT1nbG1uZXQxX2N2JGxhbWJkYS4xc2UNCiAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQoNCiNjcm9zcyB2YWxpZGF0ZSB0aGF0IG1vZGVsIHRvIGdldCBlc3RpbWF0ZSBvZiBhY2N1cmFjeSBvbiB0aGUgdGVzdCBzZXQNCmdsbW5ldF9sYW1iZGEuMXNlX2N2IDwtIHRyYWluKHg9QnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbiAlPiUgc2VsZWN0KC15KSAlPiUgZGF0YS5tYXRyaXgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1hcy5mYWN0b3IocGFzdGUwKCdPdXRjb21lJyxCcmVhc3RDYW5jZXJfdHJhaW4keSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtbmV0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY2N0cmwxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChhbHBoYSA9IDEsbGFtYmRhID0gZ2xtbmV0MV9jdiRsYW1iZGEuMXNlKSkNCg0KI0FyZWEgVW5kZXIgdGhlIEN1cnZlIEFsbW9zdCBQZXJmZWN0IE5vdyBkZXNwaXRlIHVzaW5nIG9ubHkgMTQgb2YgdGhlIDQ0IGZlYXR1cmVzDQpwcmludChnbG1uZXRfbGFtYmRhLjFzZV9jdiRyZXN1bHRzJFJPQykgICMwLjk5DQoNCnBfbG9hZChwbG90Uk9DKQ0Kb3V0X29mX3NhbXBsZV9wcmVkaWN0aW9uczIgPC0gZGF0YS5mcmFtZSh5X2hhdD1nbG1uZXRfbGFtYmRhLjFzZV9jdiRwcmVkJE91dGNvbWUxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5PUJyZWFzdENhbmNlcl90cmFpbiR5W2dsbW5ldF9sYW1iZGEuMXNlX2N2JHByZWQkcm93SW5kZXhdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbD0iTGFzc28iKQ0KYmFzaWNwbG90IDwtIGdncGxvdChiaW5kX3Jvd3Mob3V0X29mX3NhbXBsZV9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMyKSwNCiAgICAgICAgICAgICAgICAgICAgYWVzKGQgPSBhcy5udW1lcmljKHkpLCBtID0geV9oYXQsIGNvbG9yPW1vZGVsKSkgKyBnZW9tX3JvYyhuLmN1dHM9MCkgKyANCiAgICAgICAgICAgICAgICAgICAgc3R5bGVfcm9jKHRoZW1lID0gdGhlbWVfZ3JleSwgeGxhYiA9ICIxIC0gU3BlY2lmaWNpdHkiKSArIGdndGl0bGUoIkFVQyIpDQpiYXNpY3Bsb3QNCiANCmBgYA0KDQoNCiMjIExpbmVhciBFeHBhbnNpb25zIGFuZCBJbnRlcmFjdGlvbiBUZXJtcw0KDQpXZSBjYW4gcHV0IHRoZSBzYW1lIGZlYXR1cmUgaW4gYSBsaW5lYXIgbXVsdGlwbGUgdGltZXMgd2l0aCBwb2x5bm9taWFscyB0byBjYXB0dXJlIG5vbmxpbmVhciByZWxhdGlvbnNoaXBzLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmxpYnJhcnkoZHBseXIpDQpkZiA8LSBkYXRhLmZyYW1lKHg9c2VxKDAsMTAwKSkgJT4lIA0KICBtdXRhdGUoeT0wK3greF4yK3heMykgICU+JSANCiAgbXV0YXRlKHByZWRfbG0gICAgICA9IGxtKHl+eCAgICApJGZpdHRlZC52YWx1ZXMpICU+JSANCiAgbXV0YXRlKHByZWRfbG1fcXVhZCA9IGxtKHl+eCtJKHheMikpJGZpdHRlZC52YWx1ZXMpDQoNCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdChkZiwgYWVzKHgseSkpICArIA0KICAgICAgICAgZ2VvbV9wb2ludCggYWVzKHgseSkpICsgDQogICAgICAgICBnZW9tX2xpbmUoYWVzKHg9eCx5PXByZWRfbG0pLCBjb2w9J3JlZCcpICArIA0KICAgICAgICAgZ2VvbV9saW5lKGFlcyh4PXgseT1wcmVkX2xtX3F1YWQpLCBjb2w9J2JsdWUnKSAgDQoNCmBgYA0KDQojIyBJbnRlcmFjdGlvbiBUZXJtcw0KKiBbSW50ZXJhY3Rpb25fKHN0YXRpc3RpY3MpXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9JbnRlcmFjdGlvbl8oc3RhdGlzdGljcykpPGJyLz4NCiogWyJIb3cgTXVjaCBTaG91bGQgV2UgVHJ1c3QgRXN0aW1hdGVzIGZyb20gTXVsdGlwbGljYXRpdmUgSW50ZXJhY3Rpb24gTW9kZWxzPyBTaW1wbGUgVG9vbHMgdG8gSW1wcm92ZSBFbXBpcmljYWwgUHJhY3RpY2UsIl0oaHR0cDovL3lpcWluZ3h1Lm9yZy9wYXBlcnMvZW5nbGlzaC8yMDE4X0hNWF9pbnRlcmFjdGlvbi9tYWluLnBkZiksIEplbnMgSGFpbm11ZWxsZXIgSm9uYXRoYW4gTXVtbW9sbyBZaXFpbmcgWHUsLCBBcHJpbCAyMCwgMjAxOCwgUG9saXRpY2FsIEFuYWx5c2lzPGJyLz4NCiogWyJFeHBsb3JpbmcgaW50ZXJhY3Rpb25zIHdpdGggY29udGludW91cyBwcmVkaWN0b3JzIGluIHJlZ3Jlc3Npb24gbW9kZWxzIl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2p0b29scy92aWduZXR0ZXMvaW50ZXJhY3Rpb25zLmh0bWwpLCBKYWNvYiBMb25nLCAyMDE4LTA1LTA3DQoNCk5vbmxpbmVhciBNb2RlbHMNCiogKElTTFIpICJDaGFwdGVyIDcgTW92aW5nIEJleW9uZCBMaW5lYXJpdHkiDQpbTGluZWFyX3NlcGFyYWJpbGl0eV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTGluZWFyX3NlcGFyYWJpbGl0eSkNCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmZvcm0gPC0gIH4gLl4yDQoNCnkgPC0gQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbiRDbGFzc19iaW5hcnkNCg0KQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbl90d293YXkgPC0gIG1vZGVsLm1hdHJpeChmb3JtLCBkYXRhID0gQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpblssLWMoNildKQ0KQnJlYXN0Q2FuY2VyX29uZWhvdF90ZXN0X3R3b3dheSA8LSAgbW9kZWwubWF0cml4KGZvcm0sIGRhdGEgPSBCcmVhc3RDYW5jZXJfb25laG90X3Rlc3RbLC1jKDYpXSkNCg0KZGltKEJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW5fdHdvd2F5KSM5OTEgdGVybXMNCg0KY29uZGl0aW9uID0gY29sbmFtZXMoQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbl90d293YXkpPT0nQ2xhc3NfYmluYXJ5Jw0KZ2xtbmV0X3R3b3dheSA8LSBnbG1uZXQoeD1CcmVhc3RDYW5jZXJfb25laG90X3RyYWluX3R3b3dheSAsDQogICAgICAgICAgICAgICB5PWFzLmZhY3RvcihCcmVhc3RDYW5jZXJfb25laG90X3RyYWluJHkpLA0KICAgICAgICAgICAgICAgZmFtaWx5PSJiaW5vbWlhbCINCiAgICAgICAgICAgICAgICkNCnBsb3QoZ2xtbmV0X3R3b3dheSkNCg0KDQoNCmdsbW5ldF90d293YXlfY3YgPC0gY3YuZ2xtbmV0KHg9QnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbl90d293YXksDQogICAgICAgICAgICAgICAgICAgICAgIHk9YXMuZmFjdG9yKEJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4keSksDQogICAgICAgICAgICAgICAgICAgICAgIGZhbWlseT0iYmlub21pYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZm9sZHM9NSkNCg0KZ2xtbmV0X3R3b3dheV9jdiRsYW1iZGEuMXNlICNzbWFsbGVzdCBtb2RlbCB3aXRoIGVycm9yIHdpdGhpbiAxc2UgZXJyb3Igb2YgdGhlIG1pbmltdW0gZXZlciBvYnNlcnZlZA0KDQpwbG90KGdsbW5ldF90d293YXlfY3YpDQoNCmdsbW5ldF90d293YXlfbGFtYmRhLjFzZV9iZXRhcyA8LSBjb2VmKGdsbW5ldDFfY3Yscz0ibGFtYmRhLjFzZSIpICU+JSBhcy5tYXRyaXgoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICByZW5hbWUoYmV0YT0nMScpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JSBhcnJhbmdlKGRlc2MoYmV0YSkgKSANCg0KDQoNCiNEZXNpZ24gYSBzaW5nbGUgbW9kZWwgYXJvdW5kIHRoYXQgb3B0aW1hbCBsYW1iZGENCmdsbW5ldF90d293YXlfbGFtYmRhLjFzZSA8LSBnbG1uZXQoeD1CcmVhc3RDYW5jZXJfb25laG90X3RyYWluICU+JSBzZWxlY3QoLXkpICU+JSBkYXRhLm1hdHJpeCgpLA0KICAgICAgICAgICAgICAgICAgICAgICB5PUJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW4keSwNCiAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5PSJiaW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYT1nbG1uZXQxX2N2JGxhbWJkYS4xc2UNCiAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQoNCiNjcm9zcyB2YWxpZGF0ZSB0aGF0IG1vZGVsIHRvIGdldCBlc3RpbWF0ZSBvZiBhY2N1cmFjeSBvbiB0aGUgdGVzdCBzZXQNCmdsbW5ldF90d293YXlfbGFtYmRhLjFzZV9jdiA8LSB0cmFpbih4PUJyZWFzdENhbmNlcl9vbmVob3RfdHJhaW5fdHdvd2F5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5PWFzLmZhY3RvcihwYXN0ZTAoJ091dGNvbWUnLEJyZWFzdENhbmNlcl90cmFpbiR5KSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG1uZXQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjY3RybDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKGFscGhhID0gMSxsYW1iZGEgPSBnbG1uZXQxX2N2JGxhbWJkYS4xc2UpKQ0KDQojQXJlYSBVbmRlciB0aGUgQ3VydmUgQWxtb3N0IFBlcmZlY3QgTm93IGRlc3BpdGUgdXNpbmcgb25seSAxNCBvZiB0aGUgNDQgZmVhdHVyZXMNCnByaW50KGdsbW5ldF90d293YXlfbGFtYmRhLjFzZV9jdiRyZXN1bHRzJFJPQykgIzAuOTkxDQoNCnBfbG9hZChwbG90Uk9DKQ0Kb3V0X29mX3NhbXBsZV9wcmVkaWN0aW9uczMgPC0gZGF0YS5mcmFtZSh5X2hhdD1nbG1uZXRfdHdvd2F5X2xhbWJkYS4xc2VfY3YkcHJlZCRPdXRjb21lMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1CcmVhc3RDYW5jZXJfdHJhaW4keVtnbG1uZXRfdHdvd2F5X2xhbWJkYS4xc2VfY3YkcHJlZCRyb3dJbmRleF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsPSJMYXNzbyBJbnRlcmFjdGlvbnMiKQ0KYmFzaWNwbG90IDwtIGdncGxvdChiaW5kX3Jvd3Mob3V0X29mX3NhbXBsZV9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0X29mX3NhbXBsZV9wcmVkaWN0aW9uczMpLA0KICAgICAgICAgICAgICAgICAgICBhZXMoZCA9IGFzLm51bWVyaWMoeSksIG0gPSB5X2hhdCwgY29sb3I9bW9kZWwpKSArIGdlb21fcm9jKG4uY3V0cz0wKSArIA0KICAgICAgICAgICAgICAgICAgICBzdHlsZV9yb2ModGhlbWUgPSB0aGVtZV9ncmV5LCB4bGFiID0gIjEgLSBTcGVjaWZpY2l0eSIpICsgZ2d0aXRsZSgiQVVDIikNCmJhc2ljcGxvdA0KDQpgYGANCg0KSW50ZXJwcmV0aW5nIHRoZSBtb2RlbCA8YnIvPg0KVGhlcmUgYXJlIHNvbWUgbWVhc3VyZXMgdGhhdCB1bmFtYmlnaW91c2x5IGxvb2sgYmFkIGZvciBjYW5jZXIgb3V0Y29tZXMuIDxici8+DQpUaGVyZSBhcmUgY2VydGFpbiBpbnRlcmFjdGlvbnMgdGhhdCBhcmUgZ29vZCBuZXdzLiAgPGJyLz4NCkJhcmUubnVjbGVpXzg6Tm9ybWFsLm51Y2xlb2xpXzIgPGJyLz4NCkJhcmUubnVjbGVpXzE6TWl0b3Nlc18xIDxici8+DQpCYXJlLm51Y2xlaV83Ok5vcm1hbC5udWNsZW9saV84IDxici8+DQpOb3JtYWwubnVjbGVvbGlfMTpNaXRvc2VzXzEgPGJyLz4NCmFyZS5udWNsZWlfMTpOb3JtYWwubnVjbGVvbGlfMSA8YnIvPg0KDQpCYXJlLm51Y2xlaV8xIGJ5IGl0c2VsZiBsb29rcyBsaWtlIGdvb2QgbmV3cywgYnV0IGluIGNvbWJpbmF0aW9uIHdpdGggc29tZXRoaW5nIGVsc2UgaXQncyBlc3BlY2lhbGx5IGhlbHBmdWwuIDxici8+DQoNCmBgYHtyfQ0KI1RoZXJlIGFyZSA5OTEgdGVybXMNCiNCeSBhIG1pcnJhY2xlLCBhbHNvIDE0IGNob3Nlbg0KI1NvbWUgb2YgdGhlICBjb2ZmaWNpZW50cyBhcmUgcmVsYXRpdmVseSBzbWFsbA0KDQpnbG1uZXRfdHdvd2F5X2N2X2JldGFzIDwtIGNvZWYoZ2xtbmV0X3R3b3dheV9jdixzPSJsYW1iZGEuMXNlIikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm1hdHJpeCgpICU+JSBhcy5kYXRhLmZyYW1lKCkgICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbmFtZShiZXRhPScxJykgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93bmFtZXNfdG9fY29sdW1uKCkgJT4lIGFycmFuZ2UoZGVzYyhiZXRhKSApIA0KZ2xtbmV0X3R3b3dheV9jdl9iZXRhcyAlPiUgZmlsdGVyKGJldGEhPTApDQoNCg0KYGBgDQoNCg0KIyBEZWNpc2lvbiBUcmVlcw0KKiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWNpc2lvbl90cmVlIDxici8+DQoqIFsiVHJlZS1CYXNlZCBNb2RlbHMiXShodHRwczovL3d3dy5zdGF0bWV0aG9kcy5uZXQvYWR2c3RhdHMvY2FydC5odG1sKSA8YnIvPg0KKElTTFIpICI4IFRyZWUtQmFzZWQgTWV0aG9kcyINCihJbnRyb01hY2hpbmVMZWFybmluZ1dpdGhSKSAiNS41IFJhbmRvbSBmb3Jlc3QiDQoqIFvigJxJbmR1Y3Rpb24gb2YgRGVjaXNpb24gVHJlZXMu4oCdXShodHRwczovL2xpbmsuc3ByaW5nZXIuY29tL2NvbnRlbnQvcGRmLzEwLjEwMDcvQkYwMDExNjI1MS5wZGYpLCBRdWlubGFuLCBSb3NzLiAxOTg2LiwgTWFjaGluZSBMZWFybmluZyAxKDEpOjgx4oCTMTA2Lg0KDQpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9DQpzZXQuc2VlZCgxMjMpDQpwX2xvYWQocGFydHkpDQpzaW5nbGVfZGVjaXNpb25fdHJlZSA8LSBjdHJlZShmb3JtdWxhLCBkYXRhID0gQnJlYXN0Q2FuY2VyX3RyYWluKQ0KcGxvdChzaW5nbGVfZGVjaXNpb25fdHJlZSkNCg0KYGBgDQoNCk91dCBvZiBzYW1wbGUNCg0KU2xpZ2h0bHkgd29yc2UgYnV0IGFyZ3VhYmx5IGFuIGVhc2llciB0byBpbnRlcnByZXQgbW9kZWwuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0Kc2luZ2xlX2RlY2lzaW9uX3RyZWVfY3ZfbW9kZWwgPC0gdHJhaW4oeD1CcmVhc3RDYW5jZXJfdHJhaW5bLC1jKDEwKV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9YXMuZmFjdG9yKHBhc3RlMCgnT3V0Y29tZScsQnJlYXN0Q2FuY2VyX3RyYWluJHkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImN0cmVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY2N0cmwxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChtaW5jcml0ZXJpb24gPSAwLjk5KQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQoNCnByaW50KHNpbmdsZV9kZWNpc2lvbl90cmVlX2N2X21vZGVsJHJlc3VsdHMkUk9DKSAjMC45NjY4NjA4DQoNCg0KDQpwX2xvYWQocGxvdFJPQykNCm91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnM0IDwtIGRhdGEuZnJhbWUoeV9oYXQ9c2luZ2xlX2RlY2lzaW9uX3RyZWVfY3ZfbW9kZWwkcHJlZCRPdXRjb21lMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1CcmVhc3RDYW5jZXJfdHJhaW4keVtzaW5nbGVfZGVjaXNpb25fdHJlZV9jdl9tb2RlbCRwcmVkJHJvd0luZGV4XSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWw9IlRyZWUiKQ0KYmFzaWNwbG90IDwtIGdncGxvdChiaW5kX3Jvd3Mob3V0X29mX3NhbXBsZV9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0X29mX3NhbXBsZV9wcmVkaWN0aW9uczMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRfb2Zfc2FtcGxlX3ByZWRpY3Rpb25zNCksDQogICAgICAgICAgICAgICAgICAgIGFlcyhkID0gYXMubnVtZXJpYyh5KSwgbSA9IHlfaGF0LCBjb2xvcj1tb2RlbCkpICsgZ2VvbV9yb2Mobi5jdXRzPTApICsgDQogICAgICAgICAgICAgICAgICAgIHN0eWxlX3JvYyh0aGVtZSA9IHRoZW1lX2dyZXksIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IikgKyBnZ3RpdGxlKCJBVUMiKQ0KYmFzaWNwbG90DQoNCg0KDQpgYGANCg0KDQojIE92ZXJmaXR0aW5nDQoNCiMjIEJvb3RzdHJhcHBpbmcgT2JzZXJ2YXRpb25zDQoNCiogW0Jvb3RzdHJhcF9hZ2dyZWdhdGluZ10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQm9vdHN0cmFwX2FnZ3JlZ2F0aW5nKQ0KKiBbQ3Jvc3MtdmFsaWRhdGlvbl8oc3RhdGlzdGljcyldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKTxici8+DQoqIFsiTGluZWFyIE1vZGVsIFNlbGVjdGlvbiBieSBDcm9zcy1WYWxpZGF0aW9uLCJdKGh0dHA6Ly93d3cubGlicGxzLm5ldC9wdWJsaWNhdGlvbi9NQ0NWX1NoYW9fMTk5My5wZGYpLCBKdW4gU2hhbywgMTk5Mzxici8+DQoqIFsiQ3Jvc3MtdmFsaWRhdGlvbiBmYWlsdXJlOiBzbWFsbCBzYW1wbGUgc2l6ZXMgbGVhZCB0byBsYXJnZSBlcnJvciBiYXJzLCJdKGh0dHBzOi8vaGFsLmlucmlhLmZyL2hhbC0wMTU0NTAwMi8pLCBHYcOrbCBWYXJvcXVhdXgsIDIwMTc8YnIvPg0KKiAoRVNMKSAiNyBNb2RlbCBBc3Nlc3NtZW50IGFuZCBTZWxlY3Rpb24iDQoqIChJU0xSKSAiQ2hhcHRlciA1IFJlc2FtcGxpbmcgTWV0aG9kcyINCg0KIyMgTW9kZWwgQ29tcGxleGl0eS9QYXJpc21vbnkNCiogQUlDIChBa2Fpa2UgMTk3MykNCiogW0FrYWlrZSBpbmZvcm1hdGlvbiBjcml0ZXJpb24gKEFJQyldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0FrYWlrZV9pbmZvcm1hdGlvbl9jcml0ZXJpb24pPGJyLz4NCiogQklDIChTY2h3YXJ6IDE5NzgpDQoqIFtCYXllc2lhbiBpbmZvcm1hdGlvbiBjcml0ZXJpb24gKEJJQyldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JheWVzaWFuX2luZm9ybWF0aW9uX2NyaXRlcmlvbik8YnIvPg0KDQoNCiMgQ3Vyc2Ugb2YgZGltZW5zaW9uYWxpdHkNCg0KIyMgRmVhdHVyZSBCYWdnaW5nL1N1YnNwYWNlIE10ZXRob2RzDQoqIFtSYW5kb20gc3Vic3BhY2UgbWV0aG9kXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SYW5kb21fc3Vic3BhY2VfbWV0aG9kKQ0KKiBb4oCcQmFnZ2luZyBQcmVkaWN0b3JzLuKAnV0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9jb250ZW50L3BkZi8xMC4xMDA3L0JGMDAwNTg2NTUucGRmKSxCcmVpbWFuLCBMZW8uIDE5OTYuICwgTWFjaGluZSBMZWFybmluZyAyNDoxMjPigJMxNDAuDQoNCiMgUmFuZG9tIEZvcmVzdHMNCiogaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmFuZG9tX2ZvcmVzdCA8YnIvPg0KKiBbIlJBTkRPTSBGT1JFU1RTIl0oaHR0cHM6Ly93d3cuc3RhdC5iZXJrZWxleS5lZHUvfmJyZWltYW4vcmFuZG9tZm9yZXN0MjAwMS5wZGYpIExlbyBCcmVpbWFuLCBKYW51YXJ5IDIwMDENCiogWyJFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIHVzaW5nIFJhbmRvbSBGb3Jlc3RzIl0oaHR0cDovL3ptam9uZXMuY29tL3N0YXRpYy9wYXBlcnMvcmZzc19tYW51c2NyaXB0LnBkZikNCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCiNpbnN0YWxsLnBhY2thZ2VzKCdyYW5kb21Gb3Jlc3QnLCBkZXBlbmRlbmNpZXM9VCkNCg0KcF9sb2FkKHJhbmRvbUZvcmVzdCkNCg0KZm9yZXN0IDwtIHJhbmRvbUZvcmVzdChmb3JtdWxhLA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gQnJlYXN0Q2FuY2VyX3RyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICBsb2NhbEltcCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5vbWl0KQ0KcHJpbnQoZm9yZXN0KQ0KYGBgDQoNCmBgYHtyfQ0KDQpzZXQuc2VlZCgxMjMpDQpwX2xvYWQocmFuZG9tRm9yZXN0KQ0KDQpmb3Jlc3RfY3ZfbW9kZWwgPC0gdHJhaW4oeD1CcmVhc3RDYW5jZXJfdHJhaW5bLC1jKDEwKV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9YXMuZmFjdG9yKHBhc3RlMCgnT3V0Y29tZScsQnJlYXN0Q2FuY2VyX3RyYWluJHkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY2N0cmwxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiUk9DIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjdHVuZUdyaWQgPSBleHBhbmQuZ3JpZChhbHBoYSA9IDEsbGFtYmRhID0gZ2xtbmV0MV9jdiRsYW1iZGEuMXNlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQoNCnByaW50KGZvcmVzdF9jdl9tb2RlbCRyZXN1bHRzKQ0KDQoNCnBfbG9hZChwbG90Uk9DKQ0KY29uZGl0aW9uIDwtIGZvcmVzdF9jdl9tb2RlbCRwcmVkJG10cnk9PTUNCm91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnM1IDwtIGRhdGEuZnJhbWUoeV9oYXQ9Zm9yZXN0X2N2X21vZGVsJHByZWQkT3V0Y29tZTFbY29uZGl0aW9uXSAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9QnJlYXN0Q2FuY2VyX3RyYWluJHlbZm9yZXN0X2N2X21vZGVsJHByZWQkcm93SW5kZXhbY29uZGl0aW9uXV0gLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbD0iRm9yZXN0IikNCmJhc2ljcGxvdCA8LSBnZ3Bsb3QoYmluZF9yb3dzKG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRfb2Zfc2FtcGxlX3ByZWRpY3Rpb25zMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9vZl9zYW1wbGVfcHJlZGljdGlvbnMzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0X29mX3NhbXBsZV9wcmVkaWN0aW9uczQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRfb2Zfc2FtcGxlX3ByZWRpY3Rpb25zNSksDQogICAgICAgICAgICAgICAgICAgIGFlcyhkID0gYXMubnVtZXJpYyh5KSwgbSA9IHlfaGF0LCBjb2xvcj1tb2RlbCkpICsgZ2VvbV9yb2Mobi5jdXRzPTApICsgDQogICAgICAgICAgICAgICAgICAgIHN0eWxlX3JvYyh0aGVtZSA9IHRoZW1lX2dyZXksIHhsYWIgPSAiMSAtIFNwZWNpZmljaXR5IikgKyBnZ3RpdGxlKCJBVUMiKQ0KYmFzaWNwbG90DQoNCg0KYGBgDQoNCiMjIERlcHRoDQoNCltVbmRlcnN0YW5kaW5nIHJhbmRvbSBmb3Jlc3RzIHdpdGggcmFuZG9tRm9yZXN0RXhwbGFpbmVyXShodHRwczovL2NyYW4ucnN0dWRpby5jb20vd2ViL3BhY2thZ2VzL3JhbmRvbUZvcmVzdEV4cGxhaW5lci92aWduZXR0ZXMvcmFuZG9tRm9yZXN0RXhwbGFpbmVyLmh0bWwpLCBBbGVrc2FuZHJhIFBhbHVzennFhHNrYQ0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KI2RldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiTUkyRGF0YUxhYi9yYW5kb21Gb3Jlc3RFeHBsYWluZXIiKQ0KcF9sb2FkKHJhbmRvbUZvcmVzdEV4cGxhaW5lcikNCiNpbnN0YWxsLnBhY2thZ2VzKCdybGFuZycpDQoNCm1pbl9kZXB0aF9mcmFtZSA8LSBtaW5fZGVwdGhfZGlzdHJpYnV0aW9uKGZvcmVzdCkNCnNhdmUobWluX2RlcHRoX2ZyYW1lLCBmaWxlID0gIm1pbl9kZXB0aF9mcmFtZS5yZGEiKQ0KbG9hZCgibWluX2RlcHRoX2ZyYW1lLnJkYSIpDQpoZWFkKG1pbl9kZXB0aF9mcmFtZSwgbiA9IDEwKQ0KDQojIHBsb3RfbWluX2RlcHRoX2Rpc3RyaWJ1dGlvbihmb3Jlc3QpICMgZ2l2ZXMgdGhlIHNhbWUgcmVzdWx0IGFzIGJlbG93IGJ1dCB0YWtlcyBsb25nZXINCnBsb3RfbWluX2RlcHRoX2Rpc3RyaWJ1dGlvbihtaW5fZGVwdGhfZnJhbWUpDQoNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlDQpQYXkgcGFydGljdWxhciBhdHRlbnRpb24gdG8gImFjY3VyYWN5X2RlY3JlYXNlIiB3aGljaCBpcyB0aGUgZHJvcCBpbiB0aGUgY2xhc3NpZmllcidzIGFjY3VyYWN5IGlmIHRoYXQgdmFyaWFibGUgaXMgc2h1ZmZsZWQgZGVzdHJveWluZyBpdHMgaW5mb3JtYXRpb24uDQoNCmBgYHtyfQ0KaW1wb3J0YW5jZV9mcmFtZSA8LSBtZWFzdXJlX2ltcG9ydGFuY2UoZm9yZXN0KQ0KaW1wb3J0YW5jZV9mcmFtZQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdF9tdWx0aV93YXlfaW1wb3J0YW5jZShpbXBvcnRhbmNlX2ZyYW1lLCBzaXplX21lYXN1cmUgPSAibm9fb2Zfbm9kZXMiKQ0KYGBgDQoNCmBgYHtyfQ0KKHZhcnMgPC0gaW1wb3J0YW50X3ZhcmlhYmxlcyhpbXBvcnRhbmNlX2ZyYW1lLCBrID0gNSwgbWVhc3VyZXMgPSBjKCJtZWFuX21pbl9kZXB0aCIsICJub19vZl90cmVlcyIpKSkNCmludGVyYWN0aW9uc19mcmFtZSA8LSBtaW5fZGVwdGhfaW50ZXJhY3Rpb25zKGZvcmVzdCwgdmFycykNCmhlYWQoaW50ZXJhY3Rpb25zX2ZyYW1lW29yZGVyKGludGVyYWN0aW9uc19mcmFtZSRvY2N1cnJlbmNlcywgZGVjcmVhc2luZyA9IFRSVUUpLCBdKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdF9taW5fZGVwdGhfaW50ZXJhY3Rpb25zKGludGVyYWN0aW9uc19mcmFtZSkNCmBgYA0KDQpgYGB7ciwgZXZhbD1GfQ0KcGxvdF9wcmVkaWN0X2ludGVyYWN0aW9uKGZvcmVzdCwgQnJlYXN0Q2FuY2VyX3RyYWluWywtYygxMCldLCAiQ2VsbC5zaXplIiwgIkNsLnRoaWNrbmVzcyIpDQpgYGANCg0KQ2FuIGV2ZW4gZ2VuZXJhdGUgYW4gYXV0b21hdGVkIHJlcG9ydA0KYGBge3IsIGV2YWw9Rn0NCmV4cGxhaW5fZm9yZXN0KGZvcmVzdCwgaW50ZXJhY3Rpb25zID0gVFJVRSwgZGF0YSA9IEJyZWFzdENhbmNlcl90cmFpbikNCmBgYA0KDQojIENvbXBhcmUgb3V0IG9mIFNhbXBsZSBBY2N1cmFjeQ0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmRmX3ByZWRpY3Rpb25zIDwtIGRhdGEuZnJhbWUoeV90cnVlPUJyZWFzdENhbmNlcl90ZXN0JHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlfaGF0X2dsbT1zdGF0czo6cHJlZGljdC5nbG0oZ2xtMSwgbmV3ZGF0YT1CcmVhc3RDYW5jZXJfb25laG90X3Rlc3QsIHR5cGUgPSAicmVzcG9uc2UiICksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlfaGF0X2xhc3NvID0gcHJlZGljdChnbG1uZXQxX2N2LCBuZXd4PUJyZWFzdENhbmNlcl9vbmVob3RfdGVzdCAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KC15KSAlPiUgZGF0YS5tYXRyaXgoKSwgcz1jKCJsYW1iZGEuMXNlIikgLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKVssMV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5X2hhdF9sYXNzb190d293YXkgPC0gcHJlZGljdChnbG1uZXRfdHdvd2F5X2N2LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld3g9QnJlYXN0Q2FuY2VyX29uZWhvdF90ZXN0X3R3b3dheSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5tYXRyaXgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcz1jKCJsYW1iZGEuMXNlIikgLCB0eXBlID0gInJlc3BvbnNlIilbLDFdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5X2hhdF9zaW5nbGVfdHJlZSA9IHByZWRpY3Qoc2luZ2xlX2RlY2lzaW9uX3RyZWUsIG5ld2RhdGE9QnJlYXN0Q2FuY2VyX3Rlc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInByb2IiKSAlPiUgc2FwcGx5KHJiaW5kKSAlPiUgdCgpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoKSAlPiUgcHVsbChYMiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHlfaGF0X2ZvcmVzdCA9IHByZWRpY3QoZm9yZXN0LCBuZXdkYXRhPUJyZWFzdENhbmNlcl90ZXN0LCB0eXBlID0gInByb2IiKVssJzEnXSMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICN5X2hhdF9ubiA9IHByZWRpY3QoTk4sIG5ld2RhdGE9QnJlYXN0Q2FuY2VyX3Rlc3QsIHR5cGUgPSAicHJvYiIpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCmBgYA0KYGBge3J9DQpwX2xvYWQoTUxtZXRyaWNzKQ0KQVVDKGRmX3ByZWRpY3Rpb25zJHlfaGF0X2dsbSxkZl9wcmVkaWN0aW9ucyR5X3RydWUpICU+JSByb3VuZCgzKQ0KQVVDKGRmX3ByZWRpY3Rpb25zJHlfaGF0X2xhc3NvLGRmX3ByZWRpY3Rpb25zJHlfdHJ1ZSkgJT4lIHJvdW5kKDMpDQpBVUMoZGZfcHJlZGljdGlvbnMkeV9oYXRfbGFzc29fdHdvd2F5LGRmX3ByZWRpY3Rpb25zJHlfdHJ1ZSkgJT4lIHJvdW5kKDMpDQpBVUMoZGZfcHJlZGljdGlvbnMkeV9oYXRfc2luZ2xlX3RyZWUsZGZfcHJlZGljdGlvbnMkeV90cnVlKSAlPiUgcm91bmQoMykNCkFVQyhkZl9wcmVkaWN0aW9ucyR5X2hhdF9mb3Jlc3QsZGZfcHJlZGljdGlvbnMkeV90cnVlKSAlPiUgcm91bmQoMykNCg0KdGFibGUoZGZfcHJlZGljdGlvbnMkeV9oYXRfbGFzc28+LjUsDQogICAgICBkZl9wcmVkaWN0aW9ucyR5X3RydWUpDQoNCmBgYA0KDQoNCg0KIyBOZXVyYWwgTmV0d29ya3MNCiogWyJOZXVyYWwgTmV0d29ya3MsIE1hbmlmb2xkcywgYW5kIFRvcG9sb2d5Il0oaHR0cDovL2NvbGFoLmdpdGh1Yi5pby9wb3N0cy8yMDE0LTAzLU5OLU1hbmlmb2xkcy1Ub3BvbG9neS8pLCBDaHJpc3RvcGhlciBPbGFoDQoqIChETCkgRGVlcCBMZWFybmluZywgSWFuIEdvb2RmZWxsb3cgYW5kIFlvc2h1YSBCZW5naW8gYW5kIEFhcm9uIENvdXJ2aWxsZSwgMjAxNiwgaHR0cDovL3d3dy5kZWVwbGVhcm5pbmdib29rLm9yZy8gPGJyLz4NCiogKFBSTUwpICJDaGFwdGVyIDUgTmV1cmFsIE5ldHdvcmtzIg0KKiBbVGVuc29yZmxvdyBQbGF5Z3JvdW5kXShodHRwOi8vcGxheWdyb3VuZC50ZW5zb3JmbG93Lm9yZy8jYWN0aXZhdGlvbj10YW5oJmJhdGNoU2l6ZT0xMCZkYXRhc2V0PWNpcmNsZSZyZWdEYXRhc2V0PXJlZy1wbGFuZSZsZWFybmluZ1JhdGU9MC4wMyZyZWd1bGFyaXphdGlvblJhdGU9MCZub2lzZT0wJm5ldHdvcmtTaGFwZT00LDImc2VlZD0wLjQ3MDc3JnNob3dUZXN0RGF0YT1mYWxzZSZkaXNjcmV0aXplPWZhbHNlJnBlcmNUcmFpbkRhdGE9NTAmeD10cnVlJnk9dHJ1ZSZ4VGltZXNZPWZhbHNlJnhTcXVhcmVkPWZhbHNlJnlTcXVhcmVkPWZhbHNlJmNvc1g9ZmFsc2Umc2luWD1mYWxzZSZjb3NZPWZhbHNlJnNpblk9ZmFsc2UmY29sbGVjdFN0YXRzPWZhbHNlJnByb2JsZW09Y2xhc3NpZmljYXRpb24maW5pdFplcm89ZmFsc2UmaGlkZVRleHQ9ZmFsc2UpDQoqIFtDb252TmV0SlMgRGVlcCBMZWFybmluZyBpbiB5b3VyIGJyb3dzZXJdKGh0dHBzOi8vY3Muc3RhbmZvcmQuZWR1L3Blb3BsZS9rYXJwYXRoeS9jb252bmV0anMvKQ0KKiBbS2VyYXNKU10oaHR0cHM6Ly90cmFuc2NyYW5pYWwuZ2l0aHViLmlvL2tlcmFzLWpzLyMvKQ0KKiBbIlVuZGVyc3RhbmRpbmcgTFNUTSBOZXR3b3JrcywiXShodHRwOi8vY29sYWguZ2l0aHViLmlvL3Bvc3RzLzIwMTUtMDgtVW5kZXJzdGFuZGluZy1MU1RNcy8pLCBDaHJpc3RvcGhlciBPbGFoLCAgQXVndXN0IDI3LCAyMDE1LCAgPGJyLz4NCiogW1RoZSBCdWlsZGluZyBCbG9ja3Mgb2YgSW50ZXJwcmV0YWJpbGl0eV0oaHR0cHM6Ly9kaXN0aWxsLnB1Yi8yMDE4L2J1aWxkaW5nLWJsb2Nrcy8pLCBDaHJpcyBPbGFoLCBBcnZpbmQgU2F0eWFuYXJheWFuLCBJYW4gSm9obnNvbiwgU2hhbiBDYXJ0ZXIsIEx1ZHdpZyBTY2h1YmVydCwgS2F0aGVyaW5lIFllLCBBbGV4YW5kZXIgTW9yZHZpbnRzZXYsIDIwMTgsIERpc3RpbGwNCiogW0ZlYXR1cmUgVmlzdWFsaXphdGlvbiBIb3cgbmV1cmFsIG5ldHdvcmtzIGJ1aWxkIHVwIHRoZWlyIHVuZGVyc3RhbmRpbmcgb2YgaW1hZ2VzXShodHRwczovL2Rpc3RpbGwucHViLzIwMTcvZmVhdHVyZS12aXN1YWxpemF0aW9uLyksIENocmlzIE9sYWgsIEFsZXhhbmRlciBNb3JkdmludHNldiwgTHVkd2lnIFNjaHViZXJ0LCBOb3YuIDcsIDIwMTcsIERpc3RpbGwNCg0KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04fQ0KcF9sb2FkKCJuZXVyYWxuZXQiKQ0KDQpmb3JtdWxhX29uZWhvdF8yID0geSArIHlfbm90IH4gQ2wudGhpY2tuZXNzICsgQ2VsbC5zaXplICsgQ2VsbC5zaGFwZSArIE1hcmcuYWRoZXNpb24gKyBFcGl0aC5jLnNpemUgKyANCiAgICBCYXJlLm51Y2xlaV8xICsgQmFyZS5udWNsZWlfMTAgKyBCYXJlLm51Y2xlaV8yICsgQmFyZS5udWNsZWlfNCArIA0KICAgIEJhcmUubnVjbGVpXzMgKyBCYXJlLm51Y2xlaV85ICsgQmFyZS5udWNsZWlfNyArIEJhcmUubnVjbGVpXzUgKyANCiAgICBCYXJlLm51Y2xlaV84ICsgQmFyZS5udWNsZWlfNiArIEJsLmNyb21hdGluXzMgKyBCbC5jcm9tYXRpbl85ICsgDQogICAgQmwuY3JvbWF0aW5fMSArIEJsLmNyb21hdGluXzIgKyBCbC5jcm9tYXRpbl80ICsgQmwuY3JvbWF0aW5fNSArIA0KICAgIEJsLmNyb21hdGluXzcgKyBCbC5jcm9tYXRpbl84ICsgQmwuY3JvbWF0aW5fNiArIEJsLmNyb21hdGluXzEwICsgDQogICAgTm9ybWFsLm51Y2xlb2xpXzEgKyBOb3JtYWwubnVjbGVvbGlfMiArIE5vcm1hbC5udWNsZW9saV83ICsgDQogICAgTm9ybWFsLm51Y2xlb2xpXzQgKyBOb3JtYWwubnVjbGVvbGlfNSArIE5vcm1hbC5udWNsZW9saV8zICsgDQogICAgTm9ybWFsLm51Y2xlb2xpXzEwICsgTm9ybWFsLm51Y2xlb2xpXzYgKyBOb3JtYWwubnVjbGVvbGlfOSArIA0KICAgIE5vcm1hbC5udWNsZW9saV84ICsgTWl0b3Nlc18xICsgTWl0b3Nlc181ICsgTWl0b3Nlc180ICsgTWl0b3Nlc18yICsgDQogICAgTWl0b3Nlc18zICsgTWl0b3Nlc183ICsgTWl0b3Nlc18xMCArIE1pdG9zZXNfOCArIE1pdG9zZXNfNg0KDQpCcmVhc3RDYW5jZXJfb25laG90X3RyYWluXzIgPSBCcmVhc3RDYW5jZXJfb25laG90X3RyYWluDQpCcmVhc3RDYW5jZXJfb25laG90X3RyYWluXzIkeV9ub3QgPSBhcy5udW1lcmljKCFhcy5sb2dpY2FsKGFzLm51bWVyaWMoQnJlYXN0Q2FuY2VyX29uZWhvdF90cmFpbl8yJHkpLTEpKQ0KQnJlYXN0Q2FuY2VyX29uZWhvdF90ZXN0XzIgPSBCcmVhc3RDYW5jZXJfb25laG90X3Rlc3QNCkJyZWFzdENhbmNlcl9vbmVob3RfdGVzdF8yJHlfbm90ID0gYXMubnVtZXJpYyghYXMubG9naWNhbChhcy5udW1lcmljKEJyZWFzdENhbmNlcl9vbmVob3RfdGVzdF8yJHkpLTEpKQ0KdGFibGUoQnJlYXN0Q2FuY2VyX29uZWhvdF90ZXN0XzIkeV9ub3QsIEJyZWFzdENhbmNlcl9vbmVob3RfdGVzdF8yJHkpDQoNCk5OID0gbmV1cmFsbmV0KGZvcm11bGFfb25laG90XzIsDQogICAgICAgICAgICAgICBkYXRhPSBCcmVhc3RDYW5jZXJfb25laG90X3RyYWluXzIgJT4lIGRhdGEubWF0cml4KCksIA0KICAgICAgICAgICAgICAgaGlkZGVuID0gMTAgLCANCiAgICAgICAgICAgICAgIGxpbmVhci5vdXRwdXQgPSBGDQogICAgICAgICAgICAgICApDQoNCiMgcGxvdCBuZXVyYWwgbmV0d29yaw0KcGxvdChOTikNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCiMgVW5zdXBlcnZpc2VkIExlYXJuaW5nDQoNCiogKElTTFIpICJDaGFwdGVyIDEwIFVuc3VwZXJ2aXNlZCBMZWFybmluZyINCiogSU1MUiBbIkNoYXB0ZXIgNCBVbnN1cGVydmlzZWQgTGVhcm5pbmciXShodHRwczovL2xnYXR0by5naXRodWIuaW8vSW50cm9NYWNoaW5lTGVhcm5pbmdXaXRoUi91bnN1cGVydmlzZWQtbGVhcm5pbmcuaHRtbCkNCg0KIyMgRGltZW5zaW9uYWxpdHkgUmVkdWN0aW9uDQpbUHJpbmNpcGFsX2NvbXBvbmVudF9hbmFseXNpc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUHJpbmNpcGFsX2NvbXBvbmVudF9hbmFseXNpcykNCltNdWx0aXBsZSBjb3JyZXNwb25kZW5jZSBhbmFseXNpc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTXVsdGlwbGVfY29ycmVzcG9uZGVuY2VfYW5hbHlzaXMpDQoNCiMjIENsdXN0ZXJpbmcNCiogW0NsdXN0ZXIgYW5hbHlzaXNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NsdXN0ZXJfYW5hbHlzaXMgPGJyLz4pDQoqIFtLLW1lYW5zX2NsdXN0ZXJpbmddKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0stbWVhbnNfY2x1c3RlcmluZykNCiogWyJVbnN1cGVydmlzZWQgTWFjaGluZSBMZWFybmluZzogVGhlIGhjbHVzdCwgcHZjbHVzdCwgY2x1c3RlciwgbWNsdXN0LCBhbmQgbW9yZSwiXShodHRwczovL3F1YW50ZGV2LnNzcmkucHN1LmVkdS9zaXRlcy9xZGV2L2ZpbGVzL1Vuc3VwZXJ2aXNlZF9NYWNoaW5lX0xlYXJuaW5nX1RoZV9tY2x1c3RfUGFja2FnZV9hbmRfb3RoZXJzLmh0bWwpIDxici8+DQoNCiMgU3BlY2lhbCBUb3BpY3MNCg0KIyMgVGltZQ0KKiBbIkludmVzdGlnYXRpbmcgU2VxdWVuY2VzIGluIE9yZGluYWwgRGF0YTogQSBOZXcgQXBwcm9hY2ggV2l0aCBBZGFwdGVkIEV2b2x1dGlvbmFyeSBNb2RlbHMsIl0oaHR0cHM6Ly93d3cuY2FtYnJpZGdlLm9yZy9jb3JlL2pvdXJuYWxzL3BvbGl0aWNhbC1zY2llbmNlLXJlc2VhcmNoLWFuZC1tZXRob2RzL2FydGljbGUvaW52ZXN0aWdhdGluZy1zZXF1ZW5jZXMtaW4tb3JkaW5hbC1kYXRhLWEtbmV3LWFwcHJvYWNoLXdpdGgtYWRhcHRlZC1ldm9sdXRpb25hcnktbW9kZWxzL0YzNzQ3RDhBMTkwODkwMkJBN0YyNkM1RUUyOEFGQUVGKSxQYXRyaWsgTGluZGVuZm9ycywgRnJlZHJpayBKYW5zc29uLCBZaS10aW5nIFdhbmcgYW5kIFN0YWZmYW4gSS4gTGluZGJlcmcsIENocmlzdGlhbiBMb3BleiwgMDUgTWFyY2ggMjAxOCwNCg0KIyMgVGV4dA0KKiBbIlRleHQgTWluaW5nIHdpdGggUjogQSBUaWR5IEFwcHJvYWNoLCJdKGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS8pLCBKdWxpYSBTaWxnZSBhbmQgRGF2aWQgUm9iaW5zb24sIDIwMTgtMDQtMDIsICAgPGJyLz4NCiogWyJJbnRyb2R1Y2luZyBNb250ZSBDYXJsbyBNZXRob2RzIHdpdGggUiwiXShodHRwczovL3d3dy5zbGlkZXNoYXJlLm5ldC94aWFuYmxvZy9pbnRyb2R1Y2luZy1tb250ZS1jYXJsby1tZXRob2RzLXdpdGgtcikgPGJyLz4NCiogWyJUZXh0IGFzIERhdGEsIl0oaHR0cDovL3dlYi5zdGFuZm9yZC5lZHUvfmdlbnR6a293L3Jlc2VhcmNoL3RleHQtYXMtZGF0YS5wZGYpLCBNYXR0aGV3IEdlbnR6a293LCBCcnlhbiBULiBLZWxseSwgTWF0dCBUYWRkeSA8YnIvPg0KDQojIyBJbWFnZXMNCiogaHR0cHM6Ly9rZXJhcy5yc3R1ZGlvLmNvbS9hcnRpY2xlcy9leGFtcGxlcy9jaWZhcjEwX2Nubi5odG1sDQoNCiMgRXhhbXBsZXMNCiogWyJFeGFtaW5pbmcgRXhwbGFuYXRpb25zIGZvciBOdWNsZWFyIFByb2xpZmVyYXRpb24iXShodHRwczovL2RvaS5vcmcvMTAuMTA5My9pc3Evc3F2MDA3KSwgTWFyayBTLiBCZWxsLCBJbnRlcm5hdGlvbmFsIFN0dWRpZXMgUXVhcnRlcmx5LCBWb2x1bWUgNjAsIElzc3VlIDMsIDEgU2VwdGVtYmVyIDIwMTYsIFBhZ2VzIDUyMOKAkzUyOQ0KDQojIEV4dHJhcw0KDQojIyBHcmFkaWVudCBCb29zdGluZw0KKiBUZXJlbmNlIFBhcnIgYW5kIEplcmVteSBIb3dhcmQsICJIb3cgdG8gZXhwbGFpbiBncmFkaWVudCBib29zdGluZywiIGh0dHA6Ly9leHBsYWluZWQuYWkvZ3JhZGllbnQtYm9vc3RpbmcvaW5kZXguaHRtbA0KKiBbWEdCb29zdCBlWHRyZW1lIEdyYWRpZW50IEJvb3N0aW5nXShodHRwczovL2dpdGh1Yi5jb20vZG1sYy94Z2Jvb3N0KQ0KDQojIyBTVk0NCiogW1N1cHBvcnRfdmVjdG9yX21hY2hpbmVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1N1cHBvcnRfdmVjdG9yX21hY2hpbmUpDQoNCg0KIyMgTmVhcmVzdCBOZWlnaGJvcg0KKiBbSy1uZWFyZXN0X25laWdoYm9yc19hbGdvcml0aG1dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0stbmVhcmVzdF9uZWlnaGJvcnNfYWxnb3JpdGhtKSA8YnIvPg0KDQoNCiMjIEhvdyB0aGUgU2F1c2FnZSBpcyBNYWRlDQoqIFsiVHJvdWJsaW5nICBUcmVuZHMgIGluICBNYWNoaW5lICBMZWFybmluZyAgU2Nob2xhcnNoaXAiXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2FvN2MwOTBwOGJnMWhrMy9MaXB0b24lMjBhbmQlMjBTdGVpbmhhcmR0JTIwLSUyMFRyb3VibGluZyUyMFRyZW5kcyUyMGluJTIwTWFjaGluZSUyMExlYXJuaW5nJTIwU2Nob2xhcnNoaXAucGRmP2RsPTApLCBaYWNoYXJ5ICBDLiAgTGlwdG9u4oiXJiAgSmFjb2IgIFN0ZWluaGFyZHQsIEp1bHkgIDksICAyMDE4DQoNCg0K