# Rmarkdown stuff
library(rmarkdown, quietly = TRUE)
library(knitr, quietly = TRUE)
library(htmltools, quietly = TRUE)
library(styler, quietly = TRUE)
library(xaringanExtra, quietly = TRUE)

# Dataset
library(ISLR2, quietly = TRUE)

# Session info and package reporting
library(report, quietly = TRUE)

# Data wrangling

library(dplyr, quietly = TRUE)
library(tidyr, quietly = TRUE)
library(magrittr, quietly = TRUE)

# Create interactive tables
library(reactable, quietly = TRUE)

# For plotting

library(forcats, quietly = TRUE)
library(scales, quietly = TRUE)
library(tidytext, quietly = TRUE)
library(ggplot2, quietly = TRUE)

# For parallel processing for tuning
library(foreach, quietly = TRUE)
library(doParallel, quietly = TRUE)

# For applying tidymodels

library(broom, quietly = TRUE)
library(rsample, quietly = TRUE)
library(parsnip, quietly = TRUE)
library(recipes, quietly = TRUE)
library(dials, quietly = TRUE)
library(tune, quietly = TRUE)
library(workflows, quietly = TRUE)
library(yardstick, quietly = TRUE)

# For subset selection
library(leaps, quietly = TRUE)

# For ridge regression and lasso
library(glmnet, quietly = TRUE)

# For partial least square regression
# In recipes::step_pls
# Warning message:
#  `step_pls()` failed: Error in loadNamespace(x) : there is no package called ‘mixOmics’
# Install from Bioconductor
library(mixOmics, quietly = TRUE)

# For variable importance
library(vip, quietly = TRUE)

summary(report::report(sessionInfo()))

The analysis was done using the R Statistical language (v4.1.3; R Core Team, 2022) on Windows 10 x64, using the packages vip (v0.3.2), broom (v0.7.12), workflows (v0.2.6), Matrix (v1.4.0), ISLR2 (v1.3.1), xaringanExtra (v0.5.5), reactable (v0.2.3), ggplot2 (v3.3.5), forcats (v0.5.1), scales (v1.1.1), tidyr (v1.2.0), dplyr (v1.0.8), glmnet (v4.1.3), rmarkdown (v2.13), htmltools (v0.5.2), rsample (v0.1.1), styler (v1.7.0), report (v0.5.1), tune (v0.2.0), yardstick (v0.0.9), parsnip (v0.2.1), recipes (v0.2.0), dials (v0.1.0), foreach (v1.5.2), doParallel (v1.0.17), iterators (v1.0.14), mixOmics (v6.18.1), lattice (v0.20.45), tidytext (v0.3.2), magrittr (v2.0.2), leaps (v3.1), MASS (v7.3.55) and knitr (v1.38).

Subset Selection Methods

Best Subset Selection

We will be using the Hitters data set from the ISLR2 package. We wish to predict the baseball players Salary based on several different characteristics which are included in the data set.

Remove all rows with missing data from that column.

Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

Hitters %>%
  reactable::reactable(
    defaultPageSize = 5,
    filterable = TRUE
  )

Use leaps::regsubsets function to performs best subset selection.

An asterisk indicates that a given variable is included in the corresponding model. For instance, this output indicates that the best two-variable model contains only Hits and CRBI.

regfit.full <- leaps::regsubsets(
  x = Salary ~ .,
  data = Hitters,
  nvmax = 8 # default
)

summary(regfit.full)
> Subset selection object
> Call: regsubsets.formula(x = Salary ~ ., data = Hitters, nvmax = 8)
> 19 Variables  (and intercept)
>            Forced in Forced out
> AtBat          FALSE      FALSE
> Hits           FALSE      FALSE
> HmRun          FALSE      FALSE
> Runs           FALSE      FALSE
> RBI            FALSE      FALSE
> Walks          FALSE      FALSE
> Years          FALSE      FALSE
> CAtBat         FALSE      FALSE
> CHits          FALSE      FALSE
> CHmRun         FALSE      FALSE
> CRuns          FALSE      FALSE
> CRBI           FALSE      FALSE
> CWalks         FALSE      FALSE
> LeagueN        FALSE      FALSE
> DivisionW      FALSE      FALSE
> PutOuts        FALSE      FALSE
> Assists        FALSE      FALSE
> Errors         FALSE      FALSE
> NewLeagueN     FALSE      FALSE
> 1 subsets of each size up to 8
> Selection Algorithm: exhaustive
>          AtBat Hits HmRun Runs RBI Walks Years CAtBat
> 1  ( 1 ) " "   " "  " "   " "  " " " "   " "   " "   
> 2  ( 1 ) " "   "*"  " "   " "  " " " "   " "   " "   
> 3  ( 1 ) " "   "*"  " "   " "  " " " "   " "   " "   
> 4  ( 1 ) " "   "*"  " "   " "  " " " "   " "   " "   
> 5  ( 1 ) "*"   "*"  " "   " "  " " " "   " "   " "   
> 6  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   " "   
> 7  ( 1 ) " "   "*"  " "   " "  " " "*"   " "   "*"   
> 8  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   " "   
>          CHits CHmRun CRuns CRBI CWalks LeagueN
> 1  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 2  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 3  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 4  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 5  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 6  ( 1 ) " "   " "    " "   "*"  " "    " "    
> 7  ( 1 ) "*"   "*"    " "   " "  " "    " "    
> 8  ( 1 ) " "   "*"    "*"   " "  "*"    " "    
>          DivisionW PutOuts Assists Errors NewLeagueN
> 1  ( 1 ) " "       " "     " "     " "    " "       
> 2  ( 1 ) " "       " "     " "     " "    " "       
> 3  ( 1 ) " "       "*"     " "     " "    " "       
> 4  ( 1 ) "*"       "*"     " "     " "    " "       
> 5  ( 1 ) "*"       "*"     " "     " "    " "       
> 6  ( 1 ) "*"       "*"     " "     " "    " "       
> 7  ( 1 ) "*"       "*"     " "     " "    " "       
> 8  ( 1 ) "*"       "*"     " "     " "    " "

Here we fit up to a 19-variable model.

regfit.full <- leaps::regsubsets(
  x = Salary ~ .,
  data = Hitters,
  nvmax = 19
)

The summary function also returns \(R^2\), \(RSS\), adjusted \(R^2\), \(C_p\), and \(BIC\). We can examine these to try to select the best overall model.

reg.summary <- regfit.full %>%
  summary()

names(reg.summary)
> [1] "which"  "rsq"    "rss"    "adjr2"  "cp"     "bic"   
> [7] "outmat" "obj"

For instance, we see that the \(R^2\) statistic increases from 32 %, when only one variable is included in the model, to almost 55 %, when all variables are included. As expected, the \(R^2\) statistic increases monotonically as more variables are included.

reg.summary$rsq
>  [1] 0.3214501 0.4252237 0.4514294 0.4754067 0.4908036
>  [6] 0.5087146 0.5141227 0.5285569 0.5346124 0.5404950
> [11] 0.5426153 0.5436302 0.5444570 0.5452164 0.5454692
> [16] 0.5457656 0.5459518 0.5460945 0.5461159

The leaps::regsubsets function has a built-in plot command which can be used to display the selected variables for the best model with a given number of predictors, ranked according to the \(BIC\), \(C_p\), adjusted \(R^2\), or \(AIC\). To find out more about this function, type ?plot.regsubsets.

The top row of each plot contains a black square for each variable selected according to the optimal model associated with that statistic. For instance, we see that several models share a \(BIC\) close to \(−150\). However, the model with the lowest \(BIC\) is the six-variable model that contains only AtBat, Hits, Walks, CRBI, DivisionW, and PutOuts.

plot(regfit.full, scale = "bic")

We can use the coef function to see the coefficient estimates associated with this model.

coef(regfit.full, 6)
>  (Intercept)        AtBat         Hits        Walks 
>   91.5117981   -1.8685892    7.6043976    3.6976468 
>         CRBI    DivisionW      PutOuts 
>    0.6430169 -122.9515338    0.2643076

Forward and Backward Stepwise Selection

We can also use the leaps::regsubsets function to perform forward stepwise selection using the argument method = "forward"

regfit.fwd <- leaps::regsubsets(
  x = Salary ~ .,
  data = Hitters,
  nvmax = 19,
  method = "forward"
)

summary(regfit.fwd)
> Subset selection object
> Call: regsubsets.formula(x = Salary ~ ., data = Hitters, nvmax = 19, 
>     method = "forward")
> 19 Variables  (and intercept)
>            Forced in Forced out
> AtBat          FALSE      FALSE
> Hits           FALSE      FALSE
> HmRun          FALSE      FALSE
> Runs           FALSE      FALSE
> RBI            FALSE      FALSE
> Walks          FALSE      FALSE
> Years          FALSE      FALSE
> CAtBat         FALSE      FALSE
> CHits          FALSE      FALSE
> CHmRun         FALSE      FALSE
> CRuns          FALSE      FALSE
> CRBI           FALSE      FALSE
> CWalks         FALSE      FALSE
> LeagueN        FALSE      FALSE
> DivisionW      FALSE      FALSE
> PutOuts        FALSE      FALSE
> Assists        FALSE      FALSE
> Errors         FALSE      FALSE
> NewLeagueN     FALSE      FALSE
> 1 subsets of each size up to 19
> Selection Algorithm: forward
>           AtBat Hits HmRun Runs RBI Walks Years CAtBat
> 1  ( 1 )  " "   " "  " "   " "  " " " "   " "   " "   
> 2  ( 1 )  " "   "*"  " "   " "  " " " "   " "   " "   
> 3  ( 1 )  " "   "*"  " "   " "  " " " "   " "   " "   
> 4  ( 1 )  " "   "*"  " "   " "  " " " "   " "   " "   
> 5  ( 1 )  "*"   "*"  " "   " "  " " " "   " "   " "   
> 6  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 7  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 8  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 9  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 10  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 11  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 12  ( 1 ) "*"   "*"  " "   "*"  " " "*"   " "   "*"   
> 13  ( 1 ) "*"   "*"  " "   "*"  " " "*"   " "   "*"   
> 14  ( 1 ) "*"   "*"  "*"   "*"  " " "*"   " "   "*"   
> 15  ( 1 ) "*"   "*"  "*"   "*"  " " "*"   " "   "*"   
> 16  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   " "   "*"   
> 17  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   " "   "*"   
> 18  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   "*"   "*"   
> 19  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   "*"   "*"   
>           CHits CHmRun CRuns CRBI CWalks LeagueN
> 1  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 2  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 3  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 4  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 5  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 6  ( 1 )  " "   " "    " "   "*"  " "    " "    
> 7  ( 1 )  " "   " "    " "   "*"  "*"    " "    
> 8  ( 1 )  " "   " "    "*"   "*"  "*"    " "    
> 9  ( 1 )  " "   " "    "*"   "*"  "*"    " "    
> 10  ( 1 ) " "   " "    "*"   "*"  "*"    " "    
> 11  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 12  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 13  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 14  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 15  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 16  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 17  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 18  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 19  ( 1 ) "*"   "*"    "*"   "*"  "*"    "*"    
>           DivisionW PutOuts Assists Errors NewLeagueN
> 1  ( 1 )  " "       " "     " "     " "    " "       
> 2  ( 1 )  " "       " "     " "     " "    " "       
> 3  ( 1 )  " "       "*"     " "     " "    " "       
> 4  ( 1 )  "*"       "*"     " "     " "    " "       
> 5  ( 1 )  "*"       "*"     " "     " "    " "       
> 6  ( 1 )  "*"       "*"     " "     " "    " "       
> 7  ( 1 )  "*"       "*"     " "     " "    " "       
> 8  ( 1 )  "*"       "*"     " "     " "    " "       
> 9  ( 1 )  "*"       "*"     " "     " "    " "       
> 10  ( 1 ) "*"       "*"     "*"     " "    " "       
> 11  ( 1 ) "*"       "*"     "*"     " "    " "       
> 12  ( 1 ) "*"       "*"     "*"     " "    " "       
> 13  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 14  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 15  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 16  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 17  ( 1 ) "*"       "*"     "*"     "*"    "*"       
> 18  ( 1 ) "*"       "*"     "*"     "*"    "*"       
> 19  ( 1 ) "*"       "*"     "*"     "*"    "*"

To perform backward stepwise selection, use the argument method = "backward"

regfit.bwd <- leaps::regsubsets(
  x = Salary ~ .,
  data = Hitters,
  nvmax = 19,
  method = "backward"
)

summary(regfit.bwd)
> Subset selection object
> Call: regsubsets.formula(x = Salary ~ ., data = Hitters, nvmax = 19, 
>     method = "backward")
> 19 Variables  (and intercept)
>            Forced in Forced out
> AtBat          FALSE      FALSE
> Hits           FALSE      FALSE
> HmRun          FALSE      FALSE
> Runs           FALSE      FALSE
> RBI            FALSE      FALSE
> Walks          FALSE      FALSE
> Years          FALSE      FALSE
> CAtBat         FALSE      FALSE
> CHits          FALSE      FALSE
> CHmRun         FALSE      FALSE
> CRuns          FALSE      FALSE
> CRBI           FALSE      FALSE
> CWalks         FALSE      FALSE
> LeagueN        FALSE      FALSE
> DivisionW      FALSE      FALSE
> PutOuts        FALSE      FALSE
> Assists        FALSE      FALSE
> Errors         FALSE      FALSE
> NewLeagueN     FALSE      FALSE
> 1 subsets of each size up to 19
> Selection Algorithm: backward
>           AtBat Hits HmRun Runs RBI Walks Years CAtBat
> 1  ( 1 )  " "   " "  " "   " "  " " " "   " "   " "   
> 2  ( 1 )  " "   "*"  " "   " "  " " " "   " "   " "   
> 3  ( 1 )  " "   "*"  " "   " "  " " " "   " "   " "   
> 4  ( 1 )  "*"   "*"  " "   " "  " " " "   " "   " "   
> 5  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 6  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 7  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 8  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   " "   
> 9  ( 1 )  "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 10  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 11  ( 1 ) "*"   "*"  " "   " "  " " "*"   " "   "*"   
> 12  ( 1 ) "*"   "*"  " "   "*"  " " "*"   " "   "*"   
> 13  ( 1 ) "*"   "*"  " "   "*"  " " "*"   " "   "*"   
> 14  ( 1 ) "*"   "*"  "*"   "*"  " " "*"   " "   "*"   
> 15  ( 1 ) "*"   "*"  "*"   "*"  " " "*"   " "   "*"   
> 16  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   " "   "*"   
> 17  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   " "   "*"   
> 18  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   "*"   "*"   
> 19  ( 1 ) "*"   "*"  "*"   "*"  "*" "*"   "*"   "*"   
>           CHits CHmRun CRuns CRBI CWalks LeagueN
> 1  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 2  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 3  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 4  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 5  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 6  ( 1 )  " "   " "    "*"   " "  " "    " "    
> 7  ( 1 )  " "   " "    "*"   " "  "*"    " "    
> 8  ( 1 )  " "   " "    "*"   "*"  "*"    " "    
> 9  ( 1 )  " "   " "    "*"   "*"  "*"    " "    
> 10  ( 1 ) " "   " "    "*"   "*"  "*"    " "    
> 11  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 12  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 13  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 14  ( 1 ) " "   " "    "*"   "*"  "*"    "*"    
> 15  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 16  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 17  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 18  ( 1 ) "*"   " "    "*"   "*"  "*"    "*"    
> 19  ( 1 ) "*"   "*"    "*"   "*"  "*"    "*"    
>           DivisionW PutOuts Assists Errors NewLeagueN
> 1  ( 1 )  " "       " "     " "     " "    " "       
> 2  ( 1 )  " "       " "     " "     " "    " "       
> 3  ( 1 )  " "       "*"     " "     " "    " "       
> 4  ( 1 )  " "       "*"     " "     " "    " "       
> 5  ( 1 )  " "       "*"     " "     " "    " "       
> 6  ( 1 )  "*"       "*"     " "     " "    " "       
> 7  ( 1 )  "*"       "*"     " "     " "    " "       
> 8  ( 1 )  "*"       "*"     " "     " "    " "       
> 9  ( 1 )  "*"       "*"     " "     " "    " "       
> 10  ( 1 ) "*"       "*"     "*"     " "    " "       
> 11  ( 1 ) "*"       "*"     "*"     " "    " "       
> 12  ( 1 ) "*"       "*"     "*"     " "    " "       
> 13  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 14  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 15  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 16  ( 1 ) "*"       "*"     "*"     "*"    " "       
> 17  ( 1 ) "*"       "*"     "*"     "*"    "*"       
> 18  ( 1 ) "*"       "*"     "*"     "*"    "*"       
> 19  ( 1 ) "*"       "*"     "*"     "*"    "*"

For this data, the best seven-variable models identified by forward stepwise selection, backward stepwise selection, and best subset selection are different.

coef(regfit.full, 7)
>  (Intercept)         Hits        Walks       CAtBat 
>   79.4509472    1.2833513    3.2274264   -0.3752350 
>        CHits       CHmRun    DivisionW      PutOuts 
>    1.4957073    1.4420538 -129.9866432    0.2366813
coef(regfit.fwd, 7)
>  (Intercept)        AtBat         Hits        Walks 
>  109.7873062   -1.9588851    7.4498772    4.9131401 
>         CRBI       CWalks    DivisionW      PutOuts 
>    0.8537622   -0.3053070 -127.1223928    0.2533404
coef(regfit.bwd, 7)
>  (Intercept)        AtBat         Hits        Walks 
>  105.6487488   -1.9762838    6.7574914    6.0558691 
>        CRuns       CWalks    DivisionW      PutOuts 
>    1.1293095   -0.7163346 -116.1692169    0.3028847

Choosing Among Models Using the Validation-Set Approach

We split the samples into training set and a test set

set.seed(1234)

Hitters_split <- Hitters %>%
  dplyr::mutate(
    isTrainingSet = sample(
      x = c(TRUE, FALSE),
      size = nrow(Hitters),
      replace = TRUE
    )
  ) %>%
  dplyr::relocate(.data[["isTrainingSet"]])

full_train <- Hitters_split %>%
  dplyr::filter(.data[["isTrainingSet"]] == TRUE) %>%
  dplyr::select(-c("isTrainingSet"))

test <- Hitters_split %>%
  dplyr::filter(.data[["isTrainingSet"]] == FALSE) %>%
  dplyr::select(-c("isTrainingSet"))

train_split <- full_train %>%
  dplyr::mutate(
    isValidationSet = sample(
      x = c(TRUE, FALSE),
      size = nrow(full_train),
      replace = TRUE
    )
  ) %>%
  dplyr::relocate(.data[["isValidationSet"]])

validate <- train_split %>%
  dplyr::filter(.data[["isValidationSet"]] == TRUE) %>%
  dplyr::select(-c("isValidationSet"))

train <- train_split %>%
  dplyr::filter(.data[["isValidationSet"]] == FALSE) %>%
  dplyr::select(-c("isValidationSet"))

Now, we apply leaps::regsubsets to the training set in order to perform best subset selection and compute the the validation set error for the best model of each model size.

regfit.best <- leaps::regsubsets(Salary ~ ., data = train, nvmax = 19)

validate.mat <- stats::model.matrix(Salary ~ ., data = validate)

val.errors <- rep(NA, 19)

for (i in 1:19) {
  coefi <- stats::coef(regfit.best, id = i)
  pred <- validate.mat[, names(coefi)] %*% coefi
  val.errors[i] <- mean((validate$Salary - pred)^2)
}

val.errors
>  [1] 152197.4 176717.6 175668.1 189872.0 188283.5
>  [6] 139431.4 140402.2 144188.3 137154.9 130012.8
> [11] 128254.4 136305.3 175777.6 171918.5 159903.7
> [16] 150711.9 156424.0 153931.9 158701.9

We find that the best model is the one that contains 11 variables.

which.min(val.errors)
> [1] 11
regfit.best <- leaps::regsubsets(Salary ~ ., data = full_train, nvmax = 19)

coef(regfit.best, which.min(val.errors))
> (Intercept)       AtBat        Hits       Walks 
> 374.6296081  -3.2269892   8.7256272   9.3417318 
>       Years      CHmRun       CRuns      CWalks 
> -23.7454571   1.9340683   1.4336789  -1.2611593 
>   DivisionW     PutOuts     Assists      Errors 
> -94.2091001   0.2327136   0.8373956 -11.1969894

We now create a predict function

# A predict method for leaps::regsubsets
predict.regsubsets <- function(object, newdata, id, ...) {
  form <- as.formula(object$call[[2]])
  mat <- model.matrix(form, newdata)
  coefi <- coef(object, id = id)
  xvars <- names(coefi)
  mat[, xvars] %*% coefi
}

And apply it on the test.

pred_train <- stats::predict(regfit.best, full_train, id = which.min(val.errors))
pred_test <- stats::predict(regfit.best, test, id = which.min(val.errors))

Here is the \(MSE\) for train and test data

mean((full_train$Salary - pred_train)^2)
> [1] 96679.82
mean((test$Salary - pred_test)^2)
> [1] 105683.6

Choosing Among Models Using Cross-Validation

We now try to choose among the models of different sizes using cross validation. Create a vector that allocates each observation to one of k = 10 folds.

set.seed(1)

Hitters_split <- Hitters %>%
  dplyr::mutate(
    isTrainingSet = sample(
      x = c(TRUE, FALSE),
      size = nrow(Hitters),
      replace = TRUE
    )
  ) %>%
  dplyr::relocate(.data[["isTrainingSet"]])

train <- Hitters_split %>%
  dplyr::filter(.data[["isTrainingSet"]] == TRUE) %>%
  dplyr::select(-c("isTrainingSet"))

test <- Hitters_split %>%
  dplyr::filter(.data[["isTrainingSet"]] == FALSE) %>%
  dplyr::select(-c("isTrainingSet"))

k <- 10
n <- nrow(train)
folds <- sample(rep(1:k, length = n))

We write a for loop that performs cross-validation

# A predict method for leaps::regsubsets
predict.regsubsets <- function(object, newdata, id, ...) {
  form <- as.formula(object$call[[2]])
  mat <- model.matrix(form, newdata)
  coefi <- coef(object, id = id)
  xvars <- names(coefi)
  mat[, xvars] %*% coefi
}

cv.errors <- matrix(NA, k, 19,
  dimnames = list(NULL, paste(1:19))
)
for (j in 1:k) {
  # For each cross validation fold data,
  # get the best i variable model using subset selection
  best.fit <- leaps::regsubsets(Salary ~ .,
    data = train[folds != j, ],
    nvmax = 19
  )

  for (i in 1:19) {
    # Compute the cross validation error for each
    # best i variable model
    pred <- stats::predict(best.fit, train[folds == j, ], id = i)
    cv.errors[j, i] <- mean((train$Salary[folds == j] - pred)^2)
  }
}

This has given us a \(10\) by \(19\) matrix, of which the \((j, i)\)th element corresponds to the cross validation \(MSE\) for the \(j\)th cross-validation fold for the best \(i\)-variable model.

We use the apply function to average over the columns of this apply matrix in order to obtain a vector for which the \(i\)th element is the cross validation error for the \(i\)-variable model.

mean.cv.errors <- apply(cv.errors, 2, mean)

plot(mean.cv.errors, type = "b")

We see that cross-validation selects a 11-variable model. So we retrain the model

regfit.best <- leaps::regsubsets(Salary ~ ., data = train, nvmax = 19)
coef(regfit.best, which.min(mean.cv.errors))
>  (Intercept)        AtBat         Hits        HmRun 
>   27.1490022   -1.4275486    5.8330210  -10.8599653 
>        Walks       CAtBat        CRuns         CRBI 
>    8.1700662   -0.1884247    1.8806606    0.7803514 
>       CWalks      LeagueN    DivisionW      PutOuts 
>   -0.9328753   76.1994969 -108.1186878    0.2263419

And apply it on the test.

pred_train <- stats::predict(regfit.best, train, id = which.min(mean.cv.errors))
pred_test <- stats::predict(regfit.best, test, id = which.min(mean.cv.errors))

Here is the \(MSE\) for train and test data

mean((train$Salary - pred_train)^2)
> [1] 67248.67
mean((test$Salary - pred_test)^2)
> [1] 136963.3

Ridge Regression

Introduction to ridge regression

Use parsnip::linear_reg, parsnip::set_mode and parsnip::set_engine to create the model.

Set mixture to 0 to indicate Ridge Regression. For now, we set penalty/lambda as 0.

Use parsnip::translate to better understand the model created.

Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

ridge_spec <- parsnip::linear_reg(
  mixture = 0,
  penalty = 0
) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("glmnet")

ridge_spec %>%
  parsnip::translate()
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = 0
>   mixture = 0
> 
> Computational engine: glmnet 
> 
> Model fit template:
> glmnet::glmnet(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
>     alpha = 0, family = "gaussian")

Once the specification is created we can fit it to our data using parsnip::fit. We will use all the predictors.

ridge_fit <- ridge_spec %>%
  parsnip::fit(
    formula = Salary ~ .,
    data = Hitters
  )

We can see the parameter estimate for different values of penalty/lambda with parsnip::tidy. Notice how the estimates are decreasing when the amount of penalty goes up

parsnip::tidy(ridge_fit) %>%
  reactable::reactable(defaultPageSize = 5)
parsnip::tidy(ridge_fit, penalty = 705) %>%
  reactable::reactable(defaultPageSize = 5)
parsnip::tidy(ridge_fit, penalty = 11498) %>%
  reactable::reactable(defaultPageSize = 5)

Using parsnip::extract_fit_engine we can visualize how the magnitude of the coefficients are being regularized towards zero as the penalty goes up.

# Arrange figure margin
par(mar = c(5, 5, 5, 1))
ridge_fit %>%
  parsnip::extract_fit_engine() %>%
  plot(xvar = "lambda")

It would be nice if we could find the “best” value of the penalty/lambda. We can do this using the tune::tune_grid

Need we need three things in order to use the tune::tune_grid

  • a resample object containing the resamples the workflow should be fitted within,

  • a workflow object containing the model and preprocessor,and

  • a tibble penalty_grid containing the parameter values to be evaluated.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

set.seed(1234)
Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

Hitters_split <- rsample::initial_split(Hitters, strata = "Salary")

Hitters_train <- rsample::training(Hitters_split)
Hitters_test <- rsample::testing(Hitters_split)

Hitters_fold <- rsample::vfold_cv(Hitters_train, v = 10)

Create the preprocessor

We use the recipes package to create the preprocessing steps. However, the order of the preprocessing step is actually important. See the Ordering of steps vignette

We create a basic recipe with recipes::recipe.

Apply recipes::prep and recipes::bake to compute the preprocessing step.

ridge_recipe <- recipes::recipe(
  formula = Salary ~ .,
  data = Hitters_train
)

rec <- recipes::prep(x = ridge_recipe, training = Hitters_train)

rec %>%
  summary() %>%
  reactable::reactable(defaultPageSize = 5)
rec %>%
  recipes::bake(new_data = Hitters_train) %>%
  reactable::reactable(defaultPageSize = 5)

Do preprocessing on the factor variables recipes::all_nominal_predictors. recipes::step_novel and recipes::step_dummy was used.

ridge_recipe <- ridge_recipe %>%
  # Step Novel is important.
  # If test set has a new factor not found in training, it will be treated as "new" and not NA
  # This will prevent the model from giving an error
  # https://blog.datascienceheroes.com/how-to-use-recipes-package-for-one-hot-encoding/
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  # Create Dummy variables
  recipes::step_dummy(recipes::all_nominal_predictors())

rec <- recipes::prep(x = ridge_recipe, training = Hitters_train)

rec %>%
  summary() %>%
  reactable::reactable(defaultPageSize = 5)
rec %>%
  recipes::bake(new_data = Hitters_train) %>%
  reactable::reactable(defaultPageSize = 5)

Do normalisation step on all predictors recipes::all_predictors(). recipes::step_zv and recipes::step_normalize was used.

ridge_recipe <- ridge_recipe %>%
  # Remove predictors with zero variation
  recipes::step_zv(recipes::all_predictors()) %>%
  # Standardise all variable
  recipes::step_normalize(recipes::all_predictors())

rec <- recipes::prep(x = ridge_recipe, training = Hitters_train)

rec %>%
  recipes::bake(new_data = Hitters_train) %>%
  reactable::reactable(defaultPageSize = 5)

Specify the model

The model specification will look very similar to what we have seen earlier, but we will set penalty = tune::tune. This tells tune::tune_grid that the penalty parameter should be tuned using tune::tune.

ridge_spec <-
  parsnip::linear_reg(penalty = tune::tune(), mixture = 0) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("glmnet")

ridge_spec %>%
  parsnip::translate()
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = tune()
>   mixture = 0
> 
> Computational engine: glmnet 
> 
> Model fit template:
> glmnet::glmnet(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
>     alpha = 0, family = "gaussian")

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

ridge_workflow <- workflows::workflow() %>%
  workflows::add_recipe(ridge_recipe) %>%
  workflows::add_model(ridge_spec)

ridge_workflow
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 4 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = tune::tune()
>   mixture = 0
> 
> Computational engine: glmnet

Create the penalty/lambda grid

A penalty/lambda grid of \(50\) numbers from \(0.00001\) (\(10^{-5}\)) to \(10000\) (\(10^5\)) is created.

Regular grid is created using dials::grid_regular, dials::penalty and scales::log10_trans

penalty_grid <- dials::grid_regular(
  x = dials::penalty(
    range = c(-5, 5),
    trans = scales::log10_trans()
  ),
  levels = 50
)

penalty_grid %>%
  reactable::reactable(defaultPageSize = 5)

Ridge regression model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = ridge_workflow,
  resamples = Hitters_fold,
  grid = penalty_grid
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 x 4
>    splits           id     .metrics           .notes  
>    <list>           <chr>  <list>             <list>  
>  1 <split [176/20]> Fold01 <tibble [100 x 5]> <tibble>
>  2 <split [176/20]> Fold02 <tibble [100 x 5]> <tibble>
>  3 <split [176/20]> Fold03 <tibble [100 x 5]> <tibble>
>  4 <split [176/20]> Fold04 <tibble [100 x 5]> <tibble>
>  5 <split [176/20]> Fold05 <tibble [100 x 5]> <tibble>
>  6 <split [176/20]> Fold06 <tibble [100 x 5]> <tibble>
>  7 <split [177/19]> Fold07 <tibble [100 x 5]> <tibble>
>  8 <split [177/19]> Fold08 <tibble [100 x 5]> <tibble>
>  9 <split [177/19]> Fold09 <tibble [100 x 5]> <tibble>
> 10 <split [177/19]> Fold10 <tibble [100 x 5]> <tibble>

Here we see that the amount of regularization affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["penalty"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::scale_x_log10() +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The penalty/lambda value is 569 for metric rsq since it gives the highest value. Do note that using a different seed will give a different best penalty/lambda value.

top_penalty <- tune::show_best(tune_res, metric = "rsq", n = 5)
top_penalty %>%
  reactable::reactable(defaultPageSize = 5)
best_penalty <- tune::select_best(tune_res, metric = "rsq")
best_penalty %>%
  reactable::reactable(defaultPageSize = 5)

Ridge regression model with optimised penalty/lambda value

We create the ridge regression workflow with the best penalty score using tune::finalize_workflow.

ridge_final <- tune::finalize_workflow(
  x = ridge_workflow,
  parameters = best_penalty
)

ridge_final
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 4 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = 568.98660290183
>   mixture = 0
> 
> Computational engine: glmnet

We now train the ridge regression model with the training data using parsnip::fit

ridge_final_fit <- parsnip::fit(object = ridge_final, data = Hitters_train)

We can see the coefficients using tune::extract_fit_parsnip and parsnip::tidy

ridge_final_fit %>%
  tune::extract_fit_parsnip() %>%
  parsnip::tidy() %>%
  reactable::reactable(defaultPageSize = 5)

Variable Importance

While we’re at it, let’s see what the most important variables are using the vip package and workflows::extract_fit_parsnip.

vip_table <- ridge_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi(lambda = best_penalty$penalty)

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
vip_table %>%
  dplyr::mutate(
    Importance = abs(.data[["Importance"]]),
    Variable = forcats::fct_reorder(
      .f = .data[["Variable"]],
      .x = .data[["Importance"]]
    )
  ) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Importance"]],
    y = .data[["Variable"]],
    fill = .data[["Sign"]]
  )) +
  ggplot2::geom_col() +
  ggplot2::scale_x_continuous(expand = c(0, 0)) +
  ggplot2::labs(y = NULL)

Ridge regression model on test data

This ridge regression model can now be applied on our testing data set to validate its performance. For regression models, a .pred column is added when parsnip::augment is used.

test_results <- parsnip::augment(x = ridge_final_fit, new_data = Hitters_test)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the Salary using yardstick::rsq.

test_results %>%
  yardstick::rsq(truth = .data[["Salary"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Alternatively, we can use tune::last_fit and tune::collect_metrics.

test_rs <- tune::last_fit(
  object = ridge_final_fit,
  split = Hitters_split
)

test_rs %>%
  tune::collect_metrics() %>%
  reactable::reactable(defaultPageSize = 5)

Use tune::collect_predictions, to see only the actual and predicted values of the test data.

test_rs %>%
  tune::collect_predictions() %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_rs %>%
  tune::collect_predictions() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Salary"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

The Lasso

The following procedure will be very similar to what we saw in the ridge regression section. The resampling step is the same.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

set.seed(1234)
Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

Hitters_split <- rsample::initial_split(Hitters, strata = "Salary")

Hitters_train <- rsample::training(Hitters_split)
Hitters_test <- rsample::testing(Hitters_split)

Hitters_fold <- rsample::vfold_cv(Hitters_train, v = 10)

Create the preprocessor

The following procedure will be very similar to what we saw in the ridge regression section. The preprocessing steps needed are the same using recipes::recipe, recipes::all_nominal_predictors, recipes::all_predictors(), recipes::step_novel, recipes::step_dummy, recipes::step_zv and recipes::step_normalize.

lasso_recipe <-
  recipes::recipe(formula = Salary ~ ., data = Hitters_train) %>%
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  recipes::step_dummy(recipes::all_nominal_predictors()) %>%
  recipes::step_zv(recipes::all_predictors()) %>%
  recipes::step_normalize(recipes::all_predictors())

Specify the model

Again we set penalty = tune::tune. This tells tune::tune_grid that the penalty parameter should be tuned using tune::tune.

This time, it is mixture=1 in parsnip::linear_reg

lasso_spec <-
  parsnip::linear_reg(penalty = tune::tune(), mixture = 1) %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("glmnet")

lasso_spec %>%
  parsnip::translate()
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = tune()
>   mixture = 1
> 
> Computational engine: glmnet 
> 
> Model fit template:
> glmnet::glmnet(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
>     alpha = 1, family = "gaussian")

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

lasso_workflow <- workflows::workflow() %>%
  workflows::add_recipe(lasso_recipe) %>%
  workflows::add_model(lasso_spec)

lasso_workflow
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 4 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = tune::tune()
>   mixture = 1
> 
> Computational engine: glmnet

Create the penalty/lambda grid

A penalty/lambda grid of \(50\) numbers from \(0.01\) (\(10^{-2}\)) to \(100\) (\(10^2\)) is created.

Regular grid is created using dials::grid_regular, dials::penalty and scales::log10_trans

penalty_grid <- dials::grid_regular(
  x = dials::penalty(
    range = c(-2, 2),
    trans = scales::log10_trans()
  ),
  levels = 50
)

penalty_grid %>%
  reactable::reactable(defaultPageSize = 5)

Lasso model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = lasso_workflow,
  resamples = Hitters_fold,
  grid = penalty_grid
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 x 4
>    splits           id     .metrics           .notes  
>    <list>           <chr>  <list>             <list>  
>  1 <split [176/20]> Fold01 <tibble [100 x 5]> <tibble>
>  2 <split [176/20]> Fold02 <tibble [100 x 5]> <tibble>
>  3 <split [176/20]> Fold03 <tibble [100 x 5]> <tibble>
>  4 <split [176/20]> Fold04 <tibble [100 x 5]> <tibble>
>  5 <split [176/20]> Fold05 <tibble [100 x 5]> <tibble>
>  6 <split [176/20]> Fold06 <tibble [100 x 5]> <tibble>
>  7 <split [177/19]> Fold07 <tibble [100 x 5]> <tibble>
>  8 <split [177/19]> Fold08 <tibble [100 x 5]> <tibble>
>  9 <split [177/19]> Fold09 <tibble [100 x 5]> <tibble>
> 10 <split [177/19]> Fold10 <tibble [100 x 5]> <tibble>

Here we see that the amount of regularization affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["penalty"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::scale_x_log10() +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The penalty/lambda value is 22.2 for metric rsq since it gives the highest value. Do note that using a different seed will give a different best penalty/lambda value.

top_penalty <- tune::show_best(tune_res, metric = "rsq", n = 5)
top_penalty %>%
  reactable::reactable(defaultPageSize = 5)
best_penalty <- tune::select_best(tune_res, metric = "rsq")
best_penalty %>%
  reactable::reactable(defaultPageSize = 5)

Lasso model with optimised penalty/lambda value

We create the lasso regression workflow with the best penalty score using tune::finalize_workflow.

lasso_final <- tune::finalize_workflow(
  x = lasso_workflow,
  parameters = best_penalty
)

lasso_final
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 4 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Main Arguments:
>   penalty = 22.2299648252619
>   mixture = 1
> 
> Computational engine: glmnet

We now train the lasso regression model with the training data using parsnip::fit

lasso_final_fit <- parsnip::fit(object = lasso_final, data = Hitters_train)

We can see the coefficients using tune::extract_fit_parsnip and parsnip::tidy

lasso_final_fit %>%
  tune::extract_fit_parsnip() %>%
  parsnip::tidy() %>%
  reactable::reactable(defaultPageSize = 5)

Variable Importance

While we’re at it, let’s see what the most important variables are using the vip package and workflows::extract_fit_parsnip.

vip_table <- lasso_final_fit %>%
  workflows::extract_fit_parsnip() %>%
  vip::vi(lambda = best_penalty$penalty)

vip_table %>%
  reactable::reactable(defaultPageSize = 5)
vip_table %>%
  dplyr::mutate(
    Importance = abs(.data[["Importance"]]),
    Variable = forcats::fct_reorder(
      .f = .data[["Variable"]],
      .x = .data[["Importance"]]
    )
  ) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Importance"]],
    y = .data[["Variable"]],
    fill = .data[["Sign"]]
  )) +
  ggplot2::geom_col() +
  ggplot2::scale_x_continuous(expand = c(0, 0)) +
  ggplot2::labs(y = NULL)

Lasso regression model on test data

This lasso regression model can now be applied on our testing data set to validate its performance. For regression models, a .pred column is added when parsnip::augment is used.

test_results <- parsnip::augment(x = lasso_final_fit, new_data = Hitters_test)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the Salary using yardstick::rsq.

test_results %>%
  yardstick::rsq(truth = .data[["Salary"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Alternatively, we can use tune::last_fit and tune::collect_metrics.

test_rs <- tune::last_fit(
  object = lasso_final_fit,
  split = Hitters_split
)

test_rs %>%
  tune::collect_metrics() %>%
  reactable::reactable(defaultPageSize = 5)

Use tune::collect_predictions, to see only the actual and predicted values of the test data.

test_rs %>%
  tune::collect_predictions() %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_rs %>%
  tune::collect_predictions() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Salary"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Principal Components Regression

The principal component regression is a linear model with pca transformed data. Hence, the major changes will be on the preprocessing steps.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

set.seed(1234)
Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

Hitters_split <- rsample::initial_split(Hitters, strata = "Salary")

Hitters_train <- rsample::training(Hitters_split)
Hitters_test <- rsample::testing(Hitters_split)

Hitters_fold <- rsample::vfold_cv(Hitters_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe, recipes::all_nominal_predictors, recipes::all_predictors(), recipes::step_novel, recipes::step_dummy, recipes::step_zv and recipes::step_normalize.

We add recipes::step_pca this time to perform principal component analysis on all the predictors.

pca_recipe <-
  recipes::recipe(formula = Salary ~ ., data = Hitters_train) %>%
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  recipes::step_dummy(recipes::all_nominal_predictors()) %>%
  recipes::step_zv(recipes::all_predictors()) %>%
  recipes::step_normalize(recipes::all_predictors()) %>%
  recipes::step_pca(recipes::all_predictors(), id = "pca")

Apply recipes::prep and recipes::bake to compute the preprocessing step.

rec <- recipes::prep(x = pca_recipe, training = Hitters_train)

rec %>%
  summary() %>%
  reactable::reactable(defaultPageSize = 5)
rec %>%
  recipes::bake(new_data = Hitters_train) %>%
  reactable::reactable(defaultPageSize = 5)

PCA exploration

We can explore the results of the PCA using the recipes::prep and parsnip::tidy. We can see that pca is done at step number 5

recipes::prep(x = pca_recipe, training = Hitters_train) %>%
  parsnip::tidy() %>%
  reactable::reactable(defaultPageSize = 5)

As such, we extract the results of step number 5 or the pca step.

tidied_pca_loadings <- recipes::prep(x = pca_recipe, training = Hitters_train) %>%
  parsnip::tidy(id = "pca", type = "coef")


tidied_pca_loadings %>%
  reactable::reactable(defaultPageSize = 5)

We make a visualization to see what the first four components look like

tidied_pca_loadings %>%
  dplyr::filter(.data[["component"]] %in% c("PC1", "PC2", "PC3", "PC4")) %>%
  dplyr::mutate(component = forcats::fct_inorder(.data[["component"]])) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["value"]],
    y = .data[["terms"]],
    fill = .data[["terms"]]
  )) +
  ggplot2::geom_col(show.legend = FALSE) +
  ggplot2::facet_wrap(facets = ggplot2::vars(.data[["component"]])) +
  ggplot2::labs(y = NULL)

Let us take a closer look at the top 6 variables that contribute to the first four components

tidied_pca_loadings %>%
  dplyr::filter(.data[["component"]] %in% c("PC1", "PC2", "PC3", "PC4")) %>%
  dplyr::group_by(.data[["component"]]) %>%
  dplyr::top_n(6, abs(.data[["value"]])) %>%
  dplyr::ungroup() %>%
  dplyr::mutate(
    terms = tidytext::reorder_within(
      x = .data[["terms"]],
      by = abs(.data[["value"]]),
      within = .data[["component"]]
    )
  ) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = abs(.data[["value"]]),
    y = .data[["terms"]],
    fill = .data[["value"]] > 0
  )) +
  ggplot2::geom_col() +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[["component"]]),
    scales = "free_y"
  ) +
  tidytext::scale_y_reordered() +
  ggplot2::labs(
    x = "Absolute value of contribution to PCA component",
    y = NULL,
    fill = "Positive?"
  )

How much variation are we capturing for each component?

Here is how we can see the variance statistics but it is in a long form

tidied_pca_variance <- recipes::prep(x = pca_recipe, training = Hitters_train) %>%
  broom::tidy(id = "pca", type = "variance")

tidied_pca_variance %>%
  reactable::reactable(defaultPageSize = 5)

Here is how we can see the variance statistics in its wide form

tidied_pca_variance %>%
  tidyr::pivot_wider(
    names_from = .data[["component"]],
    values_from = .data[["value"]],
    names_prefix = "PC"
  ) %>%
  dplyr::select(-dplyr::all_of("id")) %>%
  reactable::reactable(defaultPageSize = 5)

Here is a simple plot to show the variance captured for each component

# Get the variance
percent_variation <- tidied_pca_variance %>%
  dplyr::filter(.data[["terms"]] == "percent variance") %>%
  dplyr::pull(.data[["value"]])

percent_variation <- percent_variation / 100

# I use [1:4] to select the first four components
dplyr::tibble(
  component = unique(tidied_pca_loadings$component)[1:4],
  percent_var = percent_variation[1:4]
) %>%
  dplyr::mutate(component = forcats::fct_inorder(.data[["component"]])) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["component"]],
    y = .data[["percent_var"]]
  )) +
  ggplot2::geom_col() +
  ggplot2::scale_y_continuous(labels = scales::percent_format()) +
  ggplot2::labs(
    x = NULL,
    y = "Percent variance explained by each PCA component"
  )

Threshold is the fraction of the total variance that should be covered by the components. For example, threshold = .75 means that step_pca should generate enough components to capture 75 percent of the variability in the variables. Note: using this argument will override and reset any value given to num_comp.

We will now try to find the best threshold value using the tune::tune()

pca_recipe <-
  recipes::recipe(formula = Salary ~ ., data = Hitters_train) %>%
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  recipes::step_dummy(recipes::all_nominal_predictors()) %>%
  recipes::step_zv(recipes::all_predictors()) %>%
  recipes::step_normalize(recipes::all_predictors()) %>%
  recipes::step_pca(recipes::all_predictors(), threshold = tune::tune())

Specify the model

We use a linear model

lm_spec <-
  parsnip::linear_reg() %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("lm")

lm_spec %>%
  parsnip::translate()
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm 
> 
> Model fit template:
> stats::lm(formula = missing_arg(), data = missing_arg(), weights = missing_arg())

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

pca_workflow <- workflows::workflow() %>%
  workflows::add_recipe(pca_recipe) %>%
  workflows::add_model(lm_spec)

pca_workflow
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 5 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> * step_pca()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm

Create the threshold grid

A threshold grid of \(10\) numbers from \(0\) to \(1\) is created.

Threshold grid is created using dials::grid_regular and dials::threshold

threshold_grid <- dials::grid_regular(
  x = dials::threshold(range = c(0, 1)),
  levels = 10
)

threshold_grid %>%
  reactable::reactable(defaultPageSize = 5)

Principal component regression model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = pca_workflow,
  resamples = Hitters_fold,
  grid = threshold_grid
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 x 4
>    splits           id     .metrics          .notes  
>    <list>           <chr>  <list>            <list>  
>  1 <split [176/20]> Fold01 <tibble [20 x 5]> <tibble>
>  2 <split [176/20]> Fold02 <tibble [20 x 5]> <tibble>
>  3 <split [176/20]> Fold03 <tibble [20 x 5]> <tibble>
>  4 <split [176/20]> Fold04 <tibble [20 x 5]> <tibble>
>  5 <split [176/20]> Fold05 <tibble [20 x 5]> <tibble>
>  6 <split [176/20]> Fold06 <tibble [20 x 5]> <tibble>
>  7 <split [177/19]> Fold07 <tibble [20 x 5]> <tibble>
>  8 <split [177/19]> Fold08 <tibble [20 x 5]> <tibble>
>  9 <split [177/19]> Fold09 <tibble [20 x 5]> <tibble>
> 10 <split [177/19]> Fold10 <tibble [20 x 5]> <tibble>

Here we see that the amount of regularization affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["threshold"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The threshold value is 0.889 for metric rsme since it gives the lowest value. Do note that using a different seed will give a different best number of threshold value.

top_threshold <- tune::show_best(tune_res, metric = "rmse", n = 5)
top_threshold %>%
  reactable::reactable(defaultPageSize = 5)
best_threshold <- tune::select_best(tune_res, metric = "rmse")
best_threshold %>%
  reactable::reactable(defaultPageSize = 5)

Principal component regression model with optimised threshold value

We create the principal component regression workflow with the best threshold using tune::finalize_workflow.

pca_final <- tune::finalize_workflow(
  x = pca_workflow,
  parameters = best_threshold
)

pca_final
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 5 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> * step_pca()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm

We now train the principal component regression model with the training data using parsnip::fit

pca_final_fit <- parsnip::fit(object = pca_final, data = Hitters_train)

We can see the coefficients and statistics using tune::extract_fit_parsnip, broom::tidy and broom::glance for lm class objects

pca_final_fit %>%
  tune::extract_fit_parsnip() %>%
  broom::tidy() %>%
  reactable::reactable(defaultPageSize = 5)
pca_final_fit %>%
  tune::extract_fit_parsnip() %>%
  broom::glance() %>%
  reactable::reactable(defaultPageSize = 5)

Principal component regression model on test data

This principal component regression model can now be applied on our testing data set to validate its performance. For regression models, a .pred column is added when parsnip::augment is used.

test_results <- parsnip::augment(x = pca_final_fit, new_data = Hitters_test)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the Salary using yardstick::rsq.

test_results %>%
  yardstick::rsq(truth = .data[["Salary"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Alternatively, we can use tune::last_fit and tune::collect_metrics.

test_rs <- tune::last_fit(
  object = pca_final_fit,
  split = Hitters_split
)

test_rs %>%
  tune::collect_metrics() %>%
  reactable::reactable(defaultPageSize = 5)

Use tune::collect_predictions, to see only the actual and predicted values of the test data.

test_rs %>%
  tune::collect_predictions() %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_rs %>%
  tune::collect_predictions() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Salary"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Partial Least Square

The partial least square regression is a linear model with pls transformed data. Hence, the major changes will be on the preprocessing steps.

Create the resample object

First, we split the samples into a training set and a test set. From the training set, we create a 10-fold cross-validation data set from the training set.

This is done with rsample::initial_split, rsample::training, rsample::testing and rsample::vfold_cv.

set.seed(1234)
Hitters <- dplyr::as_tibble(ISLR2::Hitters) %>%
  tidyr::drop_na()

Hitters_split <- rsample::initial_split(Hitters, strata = "Salary")

Hitters_train <- rsample::training(Hitters_split)
Hitters_test <- rsample::testing(Hitters_split)

Hitters_fold <- rsample::vfold_cv(Hitters_train, v = 10)

Create the preprocessor

We create a recipe with recipes::recipe, recipes::all_nominal_predictors, recipes::all_predictors(), recipes::step_novel, recipes::step_dummy, recipes::step_zv and recipes::step_normalize.

We add recipes::step_pls this time to perform partial least square calculation on all the predictors. num_comp is the number of partial least square components to retain as new predictors. outcome is the response variable for partial least square regression to use.

pls_recipe <-
  recipes::recipe(formula = Salary ~ ., data = Hitters_train) %>%
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  recipes::step_dummy(recipes::all_nominal_predictors()) %>%
  recipes::step_zv(recipes::all_predictors()) %>%
  recipes::step_normalize(recipes::all_predictors()) %>%
  recipes::step_pls(recipes::all_predictors(), num_comp = 19, outcome = "Salary")

Apply recipes::prep and recipes::bake to compute the preprocessing step.

rec <- recipes::prep(x = pls_recipe, training = Hitters_train)

rec %>%
  summary() %>%
  reactable::reactable(defaultPageSize = 5)
rec %>%
  recipes::bake(new_data = Hitters_train) %>%
  reactable::reactable(defaultPageSize = 5)

PLS exploration

We can explore the results of the PCA using the recipes::prep and parsnip::tidy. We can see that pca is done at step number 5

recipes::prep(x = pls_recipe, training = Hitters_train) %>%
  parsnip::tidy() %>%
  reactable::reactable(defaultPageSize = 5)

As such, we extract the results of step number 5 or the pca step.

tidied_pls <- recipes::prep(x = pls_recipe, training = Hitters_train) %>%
  parsnip::tidy(5)


tidied_pls %>%
  reactable::reactable(defaultPageSize = 5)

We make a visualization to see what the first four components look like

tidied_pls %>%
  dplyr::filter(.data[["component"]] %in% c("PLS1", "PLS2", "PLS3", "PLS4")) %>%
  dplyr::mutate(component = forcats::fct_inorder(.data[["component"]])) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["value"]],
    y = .data[["terms"]],
    fill = .data[["terms"]]
  )) +
  ggplot2::geom_col(show.legend = FALSE) +
  ggplot2::facet_wrap(facets = ggplot2::vars(.data[["component"]])) +
  ggplot2::labs(y = NULL)

Let us take a closer look at the top 6 variables that contribute to the first four components

tidied_pls %>%
  dplyr::filter(.data[["component"]] %in% c("PLS1", "PLS2", "PLS3", "PLS4")) %>%
  dplyr::group_by(.data[["component"]]) %>%
  dplyr::top_n(6, abs(.data[["value"]])) %>%
  dplyr::ungroup() %>%
  dplyr::mutate(
    terms = tidytext::reorder_within(
      x = .data[["terms"]],
      by = abs(.data[["value"]]),
      within = .data[["component"]]
    )
  ) %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = abs(.data[["value"]]),
    y = .data[["terms"]],
    fill = .data[["value"]] > 0
  )) +
  ggplot2::geom_col() +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[["component"]]),
    scales = "free_y"
  ) +
  tidytext::scale_y_reordered() +
  ggplot2::labs(
    x = "Absolute value of contribution to PLS component",
    y = NULL,
    fill = "Positive?"
  )

num_comp is the number of partial least square components to retain as new predictors. We will now try to find the best number of component value using the tune::tune()

pls_recipe <-
  recipes::recipe(formula = Salary ~ ., data = Hitters_train) %>%
  recipes::step_novel(recipes::all_nominal_predictors()) %>%
  recipes::step_dummy(recipes::all_nominal_predictors()) %>%
  recipes::step_zv(recipes::all_predictors()) %>%
  recipes::step_normalize(recipes::all_predictors()) %>%
  recipes::step_pls(recipes::all_predictors(), num_comp = tune::tune(), outcome = "Salary")

Specify the model

We use a linear model

lm_spec <-
  parsnip::linear_reg() %>%
  parsnip::set_mode("regression") %>%
  parsnip::set_engine("lm")

lm_spec %>%
  parsnip::translate()
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm 
> 
> Model fit template:
> stats::lm(formula = missing_arg(), data = missing_arg(), weights = missing_arg())

Create the workflow

workflows::workflow, workflows::add_recipe and workflows::add_model are used.

pls_workflow <- workflows::workflow() %>%
  workflows::add_recipe(pls_recipe) %>%
  workflows::add_model(lm_spec)

pls_workflow
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 5 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> * step_pls()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm

Create the number of components grid

A number of components grid of \(10\) numbers from \(1\) to \(20\) is created.

Number of components grid is created using dials::grid_regular and dials::num_comp

num_comp_grid <- dials::grid_regular(
  x = dials::num_comp(range = c(1, 20)),
  levels = 10
)

num_comp_grid %>%
  reactable::reactable(defaultPageSize = 5)

Partial least square regression model fitting on cross validated data

Now we have everything we need and we can fit all the models on the cross validated data with tune::tune_grid. Note that this process may take some time.

doParallel::registerDoParallel()
foreach::getDoParWorkers()
> [1] 3
tune_res <- tune::tune_grid(
  object = pls_workflow,
  resamples = Hitters_fold,
  grid = num_comp_grid
)

tune_res
> # Tuning results
> # 10-fold cross-validation 
> # A tibble: 10 x 4
>    splits           id     .metrics          .notes  
>    <list>           <chr>  <list>            <list>  
>  1 <split [176/20]> Fold01 <tibble [20 x 5]> <tibble>
>  2 <split [176/20]> Fold02 <tibble [20 x 5]> <tibble>
>  3 <split [176/20]> Fold03 <tibble [20 x 5]> <tibble>
>  4 <split [176/20]> Fold04 <tibble [20 x 5]> <tibble>
>  5 <split [176/20]> Fold05 <tibble [20 x 5]> <tibble>
>  6 <split [176/20]> Fold06 <tibble [20 x 5]> <tibble>
>  7 <split [177/19]> Fold07 <tibble [20 x 5]> <tibble>
>  8 <split [177/19]> Fold08 <tibble [20 x 5]> <tibble>
>  9 <split [177/19]> Fold09 <tibble [20 x 5]> <tibble>
> 10 <split [177/19]> Fold10 <tibble [20 x 5]> <tibble>

Here we see that the amount of regularization affects the performance metrics differently using tune::autoplot. Do note that using a different seed will give a different plot

# Note that a different seed will give different plots
tune::autoplot(tune_res)

We can also see the raw metrics that created this chart by calling tune::collect_metrics().

tune::collect_metrics(tune_res) %>%
  reactable::reactable(defaultPageSize = 5)

Here is the ggplot way should tune::autoplot fails

tune_res %>%
  tune::collect_metrics() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["num_comp"]],
    y = .data[["mean"]],
    colour = .data[[".metric"]]
  )) +
  ggplot2::geom_errorbar(
    mapping = ggplot2::aes(
      ymin = .data[["mean"]] - .data[["std_err"]],
      ymax = .data[["mean"]] + .data[["std_err"]]
    ),
    alpha = 0.5
  ) +
  ggplot2::geom_line(size = 1.5) +
  ggplot2::facet_wrap(
    facets = ggplot2::vars(.data[[".metric"]]),
    scales = "free",
    nrow = 2
  ) +
  ggplot2::theme(legend.position = "none")

Use tune::show_best to see the top few values for a given metric.

The “best” values can be selected using tune::select_best, this function requires you to specify a metric that it should select against. The number of components value is 1 for metric rsme since it gives the lowest value. Do note that using a different seed will give a different best number of components value.

top_num_comp <- tune::show_best(tune_res, metric = "rmse", n = 5)
top_num_comp %>%
  reactable::reactable(defaultPageSize = 5)
best_num_comp <- tune::select_best(tune_res, metric = "rmse")
best_num_comp %>%
  reactable::reactable(defaultPageSize = 5)

Partial least square model with optimised threshold value

We create the partial least square regression workflow with the best threshold using tune::finalize_workflow.

pls_final <- tune::finalize_workflow(
  x = pls_workflow,
  parameters = best_num_comp
)

pls_final
> == Workflow =============================================
> Preprocessor: Recipe
> Model: linear_reg()
> 
> -- Preprocessor -----------------------------------------
> 5 Recipe Steps
> 
> * step_novel()
> * step_dummy()
> * step_zv()
> * step_normalize()
> * step_pls()
> 
> -- Model ------------------------------------------------
> Linear Regression Model Specification (regression)
> 
> Computational engine: lm

We now train the partial least square regression model with the training data using parsnip::fit

pls_final_fit <- parsnip::fit(object = pls_final, data = Hitters_train)

We can see the coefficients and statistics using tune::extract_fit_parsnip, broom::tidy and broom::glance for lm class objects

pls_final_fit %>%
  tune::extract_fit_parsnip() %>%
  broom::tidy() %>%
  reactable::reactable(defaultPageSize = 5)
pls_final_fit %>%
  tune::extract_fit_parsnip() %>%
  broom::glance() %>%
  reactable::reactable(defaultPageSize = 5)

Partial least square regression model on test data

This partial least square regression model can now be applied on our testing data set to validate its performance. For regression models, a .pred column is added when parsnip::augment is used.

test_results <- parsnip::augment(x = pls_final_fit, new_data = Hitters_test)

test_results %>%
  reactable::reactable(defaultPageSize = 5)

We check how well the .pred column matches the Salary using yardstick::rsq.

test_results %>%
  yardstick::rsq(truth = .data[["Salary"]], estimate = .data[[".pred"]]) %>%
  reactable::reactable(defaultPageSize = 5)

Alternatively, we can use tune::last_fit and tune::collect_metrics.

test_rs <- tune::last_fit(
  object = pls_final_fit,
  split = Hitters_split
)

test_rs %>%
  tune::collect_metrics() %>%
  reactable::reactable(defaultPageSize = 5)

Use tune::collect_predictions, to see only the actual and predicted values of the test data.

test_rs %>%
  tune::collect_predictions() %>%
  reactable::reactable(defaultPageSize = 5)

Let us take a closer look at the predicted and actual response as a scatter plot.

test_rs %>%
  tune::collect_predictions() %>%
  ggplot2::ggplot(mapping = ggplot2::aes(
    x = .data[["Salary"]],
    y = .data[[".pred"]]
  )) +
  ggplot2::geom_abline(slope = 1, lty = 2, color = "gray50", alpha = 0.5) +
  ggplot2::geom_point(alpha = 0.6, color = "midnightblue") +
  ggplot2::coord_fixed()

Rmarkdown Template

This Rmarkdown template is created by the Reality Bending Lab. The template can be download from the lab’s github repository. For more information about the motivation behind creating this template, check out Dr. Dominique Makowski’s blog post

Blog References

  • Emil Hvitfeldt’s ISLR tidymodels Labs

  • Julia Silge’s blog titled “LASSO regression using tidymodels and #TidyTuesday data for The Office”

  • Julia Silge’s blog titled “PCA and the #TidyTuesday best hip hop songs ever”

Package References

report::cite_packages(sessionInfo())
LS0tDQp0aXRsZTogJyoqQ2hhcHRlciA2IExhYioqJw0KYXV0aG9yOiAiSmVyZW15IFNlbHZhIg0Kc3VidGl0bGU6IFRoaXMgZG9jdW1lbnQgd2FzIHByZXBhcmVkIG9uIGByIGZvcm1hdChTeXMuRGF0ZSgpKWAuDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiBweWdtZW50cw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNlbGZfY29udGFpbmVkOiBmYWxzZQ0KICAgIGxpYl9kaXI6ICJkb2NzL3JtYXJrZG93bl9saWJzIg0KICBybWFya2Rvd246Omh0bWxfdmlnbmV0dGU6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCmVkaXRvcl9vcHRpb25zOg0KICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQ0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQo8IS0tIA0KISEhISBJTVBPUlRBTlQ6IHJ1biBgc291cmNlKCJ1dGlscy9yZW5kZXIuUiIpYCB0byBwdWJsaXNoIGluc3RlYWQgb2YgY2xpY2tpbmcgb24gJ0tuaXQnDQojIFNlZSBodHRwczovL3lpaHVpLm9yZy9rbml0ci9vcHRpb25zLw0KLS0+DQoNCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPVRSVUUsIGluY2x1ZGU9RkFMU0V9DQojIFNldCB1cCB0aGUgZW52aXJvbm1lbnQNCg0KIyBPcHRpb25zIHJlbGF0aXZlIHRvIGZpZ3VyZSBzaXplDQojIDEuNjE4IGlzIHRoZSBnb2xkZW4gcmF0aW8NCmZpZ2hlaWdodCA8LSA0DQpmaWd3aWR0aCA8LSA0ICogMS42MTggDQoNCiMgR2VuZXJhbCBvcHRpb25zDQpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gIiIsDQogICAgICAgIG5zbWFsbCA9IDMsDQogICAgICAgIHRpZHl2ZXJzZS5xdWlldCA9IFRSVUUNCiAgICAgICAgKQ0KaG9va19vdXRwdXQgPC0ga25pdHI6OmtuaXRfaG9va3MkZ2V0KCdvdXRwdXQnKQ0KDQprbml0cjo6a25pdF9ob29rcyRzZXQoDQogIG91dHB1dCA9IGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsNCiAgICBpZiAoIWlzLm51bGwob3B0aW9ucyRtYXguaGVpZ2h0KSkgew0KICAgICAgb3B0aW9ucyRhdHRyLm91dHB1dCA8LSBjKG9wdGlvbnMkYXR0ci5vdXRwdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50Zignc3R5bGU9Im1heC1oZWlnaHQ6ICVzOyInLCBvcHRpb25zJG1heC5oZWlnaHQpKQ0KICAgIH0NCiAgICBob29rX291dHB1dCh4LCBvcHRpb25zKQ0KICAgIH0NCiAgKQ0KDQojIENodW5rIG9wdGlvbnMgKHNlZSBodHRwczovL3lpaHVpLm9yZy9rbml0ci9vcHRpb25zLyNjaHVua19vcHRpb25zKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBjb21tZW50ID0gIj4iLCAgIyBUaGUgcHJlZml4IHRvIGJlIGFkZGVkIGJlZm9yZSBlYWNoIGxpbmUgb2YgdGhlIHRleHQgb3V0cHV0Lg0KICBkcGkgPSA2MDAsDQogIGZpZy5wYXRoID0gImRvY3MvZmlndXJlcy8iLA0KICBmaWcuaGVpZ2h0ID0gZmlnaGVpZ2h0LA0KICBmaWcud2lkdGggPSBmaWd3aWR0aCwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsDQogICMgU2VlIGh0dHBzOi8vY29tbXVuaXR5LnJzdHVkaW8uY29tL3QvY2VudGVyaW5nLWltYWdlcy1pbi1ibG9nZG93bi1wb3N0LzIwOTYyDQogICMgdG8gbGVhcm4gaG93IHRvIGNlbnRlciBpbWFnZXMNCiAgIyBTZWUgaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLWNvb2tib29rL29wdHMtdGlkeS5odG1sDQogICMgU2VlIGh0dHBzOi8vd3d3LnpvdGVyby5vcmcvc3R5bGVzIGZvciBjaXRhdGlvbiBzdHlsZSByZXNwb3NpdG9yeQ0KICB0aWR5PSdzdHlsZXInLA0KICB0aWR5Lm9wdHM9bGlzdChzdHJpY3Q9VFJVRSkNCikNCg0KaHRtbHRvb2xzOjp0YWdMaXN0KA0KICB4YXJpbmdhbkV4dHJhOjp1c2VfY2xpcGJvYXJkKA0KICAgIGJ1dHRvbl90ZXh0ID0gIjxpIGNsYXNzPVwiZmEgZmEtY2xpcGJvYXJkXCI+PC9pPiBDb3B5IENvZGUiLA0KICAgIHN1Y2Nlc3NfdGV4dCA9ICI8aSBjbGFzcz1cImZhIGZhLWNoZWNrXCIgc3R5bGU9XCJjb2xvcjogIzkwQkU2RFwiPjwvaT4gQ29waWVkISIsDQogICksDQogIHJtYXJrZG93bjo6aHRtbF9kZXBlbmRlbmN5X2ZvbnRfYXdlc29tZSgpDQopDQpgYGANCg0KYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0nYXNpcycsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KIyBSbWFya2Rvd24gc3R1ZmYNCmxpYnJhcnkocm1hcmtkb3duLCBxdWlldGx5PVRSVUUpDQpsaWJyYXJ5KGtuaXRyLCBxdWlldGx5PVRSVUUpDQpsaWJyYXJ5KGh0bWx0b29scywgcXVpZXRseT1UUlVFKQ0KbGlicmFyeShzdHlsZXIsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkoeGFyaW5nYW5FeHRyYSwgcXVpZXRseT1UUlVFKQ0KDQojIERhdGFzZXQNCmxpYnJhcnkoSVNMUjIsIHF1aWV0bHk9VFJVRSkNCg0KIyBTZXNzaW9uIGluZm8gYW5kIHBhY2thZ2UgcmVwb3J0aW5nDQpsaWJyYXJ5KHJlcG9ydCwgcXVpZXRseT1UUlVFKQ0KDQojIERhdGEgd3JhbmdsaW5nDQoNCmxpYnJhcnkoZHBseXIsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkodGlkeXIsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkobWFncml0dHIsIHF1aWV0bHk9VFJVRSkNCg0KIyBDcmVhdGUgaW50ZXJhY3RpdmUgdGFibGVzDQpsaWJyYXJ5KHJlYWN0YWJsZSwgcXVpZXRseT1UUlVFKQ0KDQojIEZvciBwbG90dGluZw0KDQpsaWJyYXJ5KGZvcmNhdHMsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkoc2NhbGVzLCBxdWlldGx5PVRSVUUpDQpsaWJyYXJ5KHRpZHl0ZXh0LCBxdWlldGx5PVRSVUUpDQpsaWJyYXJ5KGdncGxvdDIsIHF1aWV0bHk9VFJVRSkNCg0KIyBGb3IgcGFyYWxsZWwgcHJvY2Vzc2luZyBmb3IgdHVuaW5nDQpsaWJyYXJ5KGZvcmVhY2gsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkoZG9QYXJhbGxlbCwgcXVpZXRseT1UUlVFKQ0KDQojIEZvciBhcHBseWluZyB0aWR5bW9kZWxzDQoNCmxpYnJhcnkoYnJvb20sIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkocnNhbXBsZSwgcXVpZXRseT1UUlVFKQ0KbGlicmFyeShwYXJzbmlwLCBxdWlldGx5PVRSVUUpDQpsaWJyYXJ5KHJlY2lwZXMsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkoZGlhbHMsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkodHVuZSwgcXVpZXRseT1UUlVFKQ0KbGlicmFyeSh3b3JrZmxvd3MsIHF1aWV0bHk9VFJVRSkNCmxpYnJhcnkoeWFyZHN0aWNrLCBxdWlldGx5PVRSVUUpDQoNCiMgRm9yIHN1YnNldCBzZWxlY3Rpb24NCmxpYnJhcnkobGVhcHMsIHF1aWV0bHk9VFJVRSkNCg0KIyBGb3IgcmlkZ2UgcmVncmVzc2lvbiBhbmQgbGFzc28NCmxpYnJhcnkoZ2xtbmV0LCBxdWlldGx5PVRSVUUpDQoNCiMgRm9yIHBhcnRpYWwgbGVhc3Qgc3F1YXJlIHJlZ3Jlc3Npb24NCiMgSW4gcmVjaXBlczo6c3RlcF9wbHMNCiMgV2FybmluZyBtZXNzYWdlOg0KIyAgYHN0ZXBfcGxzKClgIGZhaWxlZDogRXJyb3IgaW4gbG9hZE5hbWVzcGFjZSh4KSA6IHRoZXJlIGlzIG5vIHBhY2thZ2UgY2FsbGVkIOKAmG1peE9taWNz4oCZDQojIEluc3RhbGwgZnJvbSBCaW9jb25kdWN0b3INCmxpYnJhcnkobWl4T21pY3MsIHF1aWV0bHk9VFJVRSkNCg0KIyBGb3IgdmFyaWFibGUgaW1wb3J0YW5jZQ0KbGlicmFyeSh2aXAsIHF1aWV0bHk9VFJVRSkNCg0Kc3VtbWFyeShyZXBvcnQ6OnJlcG9ydChzZXNzaW9uSW5mbygpKSkNCmBgYA0KDQojIFN1YnNldCBTZWxlY3Rpb24gTWV0aG9kcw0KDQojIyBCZXN0IFN1YnNldCBTZWxlY3Rpb24NCg0KYGBge3IsIGVjaG89RkFMU0V9DQpzZXQuc2VlZCgxMjM0KQ0KYGBgDQoNCldlIHdpbGwgYmUgdXNpbmcgdGhlIFtgSGl0dGVyc2BdKGh0dHBzOi8vcmRyci5pby9jcmFuL0lTTFIyL21hbi9IaXR0ZXJzLmh0bWwpIGRhdGEgc2V0IGZyb20gdGhlIGBJU0xSMmAgcGFja2FnZS4gV2Ugd2lzaA0KdG8gcHJlZGljdCB0aGUgYmFzZWJhbGwgcGxheWVycyBgU2FsYXJ5YCBiYXNlZCBvbiBzZXZlcmFsIGRpZmZlcmVudA0KY2hhcmFjdGVyaXN0aWNzIHdoaWNoIGFyZSBpbmNsdWRlZCBpbiB0aGUgZGF0YSBzZXQuDQoNClJlbW92ZSBhbGwgcm93cyB3aXRoIG1pc3NpbmcgZGF0YSBmcm9tIHRoYXQgY29sdW1uLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KSGl0dGVycyA8LSBkcGx5cjo6YXNfdGliYmxlKElTTFIyOjpIaXR0ZXJzKSAlPiUNCiAgdGlkeXI6OmRyb3BfbmEoKQ0KDQpIaXR0ZXJzICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXJhYmxlID0gVFJVRSkNCmBgYA0KDQpVc2UgYGxlYXBzOjpyZWdzdWJzZXRzYCBmdW5jdGlvbiB0byBwZXJmb3JtcyBiZXN0IHN1YnNldCBzZWxlY3Rpb24uDQoNCkFuIGFzdGVyaXNrIGluZGljYXRlcyB0aGF0IGEgZ2l2ZW4gdmFyaWFibGUgaXMgaW5jbHVkZWQgaW4gdGhlDQpjb3JyZXNwb25kaW5nIG1vZGVsLiBGb3IgaW5zdGFuY2UsIHRoaXMgb3V0cHV0IGluZGljYXRlcyB0aGF0IHRoZSBiZXN0DQp0d28tdmFyaWFibGUgbW9kZWwgY29udGFpbnMgb25seSBIaXRzIGFuZCBDUkJJLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCBtYXguaGVpZ2h0ID0gJzE1MHB4J30NCg0KcmVnZml0LmZ1bGwgPC0gbGVhcHM6OnJlZ3N1YnNldHMoDQogIHggPSBTYWxhcnkgfiAuLCANCiAgZGF0YSA9IEhpdHRlcnMsDQogIG52bWF4ID0gOCAjZGVmYXVsdA0KICApDQoNCnN1bW1hcnkocmVnZml0LmZ1bGwpDQpgYGANCg0KSGVyZSB3ZSBmaXQgdXAgdG8gYSAxOS12YXJpYWJsZSBtb2RlbC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJlZ2ZpdC5mdWxsIDwtIGxlYXBzOjpyZWdzdWJzZXRzKA0KICB4ID0gU2FsYXJ5IH4gLiwgDQogIGRhdGEgPSBIaXR0ZXJzLA0KICBudm1heCA9IDE5DQogICkNCmBgYA0KDQpUaGUgYHN1bW1hcnlgIGZ1bmN0aW9uIGFsc28gcmV0dXJucyAkUl4yJCwgJFJTUyQsIGFkanVzdGVkICRSXjIkLCAkQ19wJCwNCmFuZCAkQklDJC4gV2UgY2FuIGV4YW1pbmUgdGhlc2UgdG8gdHJ5IHRvIHNlbGVjdCB0aGUgYmVzdCBvdmVyYWxsIG1vZGVsLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcmVnLnN1bW1hcnkgPC0gcmVnZml0LmZ1bGwgJT4lDQogIHN1bW1hcnkoKQ0KDQpuYW1lcyhyZWcuc3VtbWFyeSkNCmBgYA0KDQpGb3IgaW5zdGFuY2UsIHdlIHNlZSB0aGF0IHRoZSAkUl4yJCBzdGF0aXN0aWMgaW5jcmVhc2VzIGZyb20gMzIgJSwgd2hlbg0Kb25seSBvbmUgdmFyaWFibGUgaXMgaW5jbHVkZWQgaW4gdGhlIG1vZGVsLCB0byBhbG1vc3QgNTUgJSwgd2hlbiBhbGwNCnZhcmlhYmxlcyBhcmUgaW5jbHVkZWQuIEFzIGV4cGVjdGVkLCB0aGUgJFJeMiQgc3RhdGlzdGljIGluY3JlYXNlcw0KbW9ub3RvbmljYWxseSBhcyBtb3JlIHZhcmlhYmxlcyBhcmUgaW5jbHVkZWQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpyZWcuc3VtbWFyeSRyc3ENCmBgYA0KDQpUaGUgYGxlYXBzOjpyZWdzdWJzZXRzYCBmdW5jdGlvbiBoYXMgYSBidWlsdC1pbiBgcGxvdGAgY29tbWFuZCB3aGljaCBjYW4NCmJlIHVzZWQgdG8gZGlzcGxheSB0aGUgc2VsZWN0ZWQgdmFyaWFibGVzIGZvciB0aGUgYmVzdCBtb2RlbCB3aXRoIGENCmdpdmVuIG51bWJlciBvZiBwcmVkaWN0b3JzLCByYW5rZWQgYWNjb3JkaW5nIHRvIHRoZSAkQklDJCwgJENfcCQsDQphZGp1c3RlZCAkUl4yJCwgb3IgJEFJQyQuIFRvIGZpbmQgb3V0IG1vcmUgYWJvdXQgdGhpcyBmdW5jdGlvbiwgdHlwZQ0KYD9wbG90LnJlZ3N1YnNldHNgLg0KDQpUaGUgdG9wIHJvdyBvZiBlYWNoIHBsb3QgY29udGFpbnMgYSBibGFjayBzcXVhcmUgZm9yIGVhY2ggdmFyaWFibGUNCnNlbGVjdGVkIGFjY29yZGluZyB0byB0aGUgb3B0aW1hbCBtb2RlbCBhc3NvY2lhdGVkIHdpdGggdGhhdCBzdGF0aXN0aWMuDQpGb3IgaW5zdGFuY2UsIHdlIHNlZSB0aGF0IHNldmVyYWwgbW9kZWxzIHNoYXJlIGEgJEJJQyQgY2xvc2UgdG8gJOKIkjE1MCQuDQpIb3dldmVyLCB0aGUgbW9kZWwgd2l0aCB0aGUgbG93ZXN0ICRCSUMkIGlzIHRoZSBzaXgtdmFyaWFibGUgbW9kZWwgdGhhdA0KY29udGFpbnMgb25seSBgQXRCYXRgLCBgSGl0c2AsIGBXYWxrc2AsIGBDUkJJYCwgYERpdmlzaW9uV2AsIGFuZA0KYFB1dE91dHNgLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnBsb3QocmVnZml0LmZ1bGwgLCBzY2FsZSA9ICJiaWMiKQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIGBjb2VmYCBmdW5jdGlvbiB0byBzZWUgdGhlIGNvZWZmaWNpZW50IGVzdGltYXRlcw0KYXNzb2NpYXRlZCB3aXRoIHRoaXMgbW9kZWwuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KY29lZihyZWdmaXQuZnVsbCAsIDYpDQpgYGANCg0KIyMgRm9yd2FyZCBhbmQgQmFja3dhcmQgU3RlcHdpc2UgU2VsZWN0aW9uDQoNCldlIGNhbiBhbHNvIHVzZSB0aGUgYGxlYXBzOjpyZWdzdWJzZXRzYCBmdW5jdGlvbiB0byBwZXJmb3JtIGZvcndhcmQNCnN0ZXB3aXNlIHNlbGVjdGlvbiB1c2luZyB0aGUgYXJndW1lbnQgYG1ldGhvZCA9ICJmb3J3YXJkImANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCnJlZ2ZpdC5md2QgPC0gbGVhcHM6OnJlZ3N1YnNldHMoDQogIHggPSBTYWxhcnkgfiAuLCANCiAgZGF0YSA9IEhpdHRlcnMgLA0KICBudm1heCA9IDE5LCANCiAgbWV0aG9kID0gImZvcndhcmQiDQopDQoNCnN1bW1hcnkocmVnZml0LmZ3ZCkNCmBgYA0KDQpUbyBwZXJmb3JtIGJhY2t3YXJkIHN0ZXB3aXNlIHNlbGVjdGlvbiwgdXNlIHRoZSBhcmd1bWVudA0KYG1ldGhvZCA9ICJiYWNrd2FyZCJgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQpyZWdmaXQuYndkIDwtIGxlYXBzOjpyZWdzdWJzZXRzKA0KICB4ID0gU2FsYXJ5IH4gLiwgDQogIGRhdGEgPSBIaXR0ZXJzICwNCiAgbnZtYXggPSAxOSwgDQogIG1ldGhvZCA9ICJiYWNrd2FyZCINCikNCg0Kc3VtbWFyeShyZWdmaXQuYndkKQ0KYGBgDQoNCkZvciB0aGlzIGRhdGEsIHRoZSBiZXN0IHNldmVuLXZhcmlhYmxlIG1vZGVscyBpZGVudGlmaWVkIGJ5IGZvcndhcmQNCnN0ZXB3aXNlIHNlbGVjdGlvbiwgYmFja3dhcmQgc3RlcHdpc2Ugc2VsZWN0aW9uLCBhbmQgYmVzdCBzdWJzZXQNCnNlbGVjdGlvbiBhcmUgZGlmZmVyZW50Lg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KY29lZihyZWdmaXQuZnVsbCw3KQ0KY29lZihyZWdmaXQuZndkLDcpDQpjb2VmKHJlZ2ZpdC5id2QsNykNCg0KYGBgDQoNCiMjIENob29zaW5nIEFtb25nIE1vZGVscyBVc2luZyB0aGUgVmFsaWRhdGlvbi1TZXQgQXBwcm9hY2gNCg0KV2Ugc3BsaXQgdGhlIHNhbXBsZXMgaW50byB0cmFpbmluZyBzZXQgYW5kIGEgdGVzdCBzZXQNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnNldC5zZWVkKDEyMzQpDQoNCkhpdHRlcnNfc3BsaXQgPC0gSGl0dGVycyAlPiUNCiAgZHBseXI6Om11dGF0ZSgNCiAgICBpc1RyYWluaW5nU2V0ID0gc2FtcGxlKHggPSBjKFRSVUUgLCBGQUxTRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gbnJvdyhIaXR0ZXJzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcGxhY2UgPSBUUlVFKQ0KICApICU+JQ0KICBkcGx5cjo6cmVsb2NhdGUoLmRhdGFbWyJpc1RyYWluaW5nU2V0Il1dKQ0KDQpmdWxsX3RyYWluIDwtIEhpdHRlcnNfc3BsaXQgJT4lDQogIGRwbHlyOjpmaWx0ZXIoLmRhdGFbWyJpc1RyYWluaW5nU2V0Il1dID09IFRSVUUpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1jKCJpc1RyYWluaW5nU2V0IikpDQoNCnRlc3QgPC0gSGl0dGVyc19zcGxpdCAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImlzVHJhaW5pbmdTZXQiXV09PSBGQUxTRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWMoImlzVHJhaW5pbmdTZXQiKSkNCg0KdHJhaW5fc3BsaXQgPC0gZnVsbF90cmFpbiAlPiUNCiAgZHBseXI6Om11dGF0ZSgNCiAgICBpc1ZhbGlkYXRpb25TZXQgPSBzYW1wbGUoeCA9IGMoVFJVRSAsIEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSBucm93KGZ1bGxfdHJhaW4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUpDQogICkgJT4lDQogIGRwbHlyOjpyZWxvY2F0ZSguZGF0YVtbImlzVmFsaWRhdGlvblNldCJdXSkNCg0KdmFsaWRhdGUgPC0gdHJhaW5fc3BsaXQgJT4lDQogIGRwbHlyOjpmaWx0ZXIoLmRhdGFbWyJpc1ZhbGlkYXRpb25TZXQiXV0gPT0gVFJVRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWMoImlzVmFsaWRhdGlvblNldCIpKQ0KDQp0cmFpbiA8LSB0cmFpbl9zcGxpdCAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImlzVmFsaWRhdGlvblNldCJdXSA9PSBGQUxTRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWMoImlzVmFsaWRhdGlvblNldCIpKQ0KDQoNCmBgYA0KDQpOb3csIHdlIGFwcGx5IGBsZWFwczo6cmVnc3Vic2V0c2AgdG8gdGhlIHRyYWluaW5nIHNldCBpbiBvcmRlciB0bw0KcGVyZm9ybSBiZXN0IHN1YnNldCBzZWxlY3Rpb24gYW5kIGNvbXB1dGUgdGhlIHRoZSB2YWxpZGF0aW9uIHNldCBlcnJvcg0KZm9yIHRoZSBiZXN0IG1vZGVsIG9mIGVhY2ggbW9kZWwgc2l6ZS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJlZ2ZpdC5iZXN0IDwtIGxlYXBzOjpyZWdzdWJzZXRzKFNhbGFyeSB+IC4sIGRhdGEgPSB0cmFpbiwgbnZtYXggPSAxOSkNCg0KdmFsaWRhdGUubWF0IDwtIHN0YXRzOjptb2RlbC5tYXRyaXgoU2FsYXJ5IH4gLiwgZGF0YSA9IHZhbGlkYXRlKQ0KDQp2YWwuZXJyb3JzIDwtIHJlcChOQSwgMTkpDQoNCmZvciAoaSBpbiAxOjE5KSB7DQogIGNvZWZpIDwtIHN0YXRzOjpjb2VmKHJlZ2ZpdC5iZXN0ICwgaWQgPSBpKQ0KICBwcmVkIDwtIHZhbGlkYXRlLm1hdFssIG5hbWVzKGNvZWZpKV0gJSolIGNvZWZpDQogIHZhbC5lcnJvcnNbaV0gPC0gbWVhbiAoKCB2YWxpZGF0ZSRTYWxhcnkgLSBwcmVkKV4yKQ0KfQ0KDQp2YWwuZXJyb3JzDQpgYGANCg0KV2UgZmluZCB0aGF0IHRoZSBiZXN0IG1vZGVsIGlzIHRoZSBvbmUgdGhhdCBjb250YWlucyBgciB3aGljaC5taW4odmFsLmVycm9ycylgIHZhcmlhYmxlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp3aGljaC5taW4odmFsLmVycm9ycykNCnJlZ2ZpdC5iZXN0IDwtIGxlYXBzOjpyZWdzdWJzZXRzKFNhbGFyeSB+IC4sIGRhdGEgPSBmdWxsX3RyYWluLCBudm1heCA9IDE5KQ0KDQpjb2VmKHJlZ2ZpdC5iZXN0ICwgd2hpY2gubWluKHZhbC5lcnJvcnMpKQ0KYGBgDQoNCldlIG5vdyBjcmVhdGUgYSBwcmVkaWN0IGZ1bmN0aW9uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQojIEEgcHJlZGljdCBtZXRob2QgZm9yIGxlYXBzOjpyZWdzdWJzZXRzDQpwcmVkaWN0LnJlZ3N1YnNldHMgPC0gZnVuY3Rpb24ob2JqZWN0ICwgbmV3ZGF0YSAsIGlkLCAuLi4pIHsNCiAgZm9ybSA8LSBhcy5mb3JtdWxhKG9iamVjdCRjYWxsIFtbMl1dKQ0KICBtYXQgPC0gbW9kZWwubWF0cml4KGZvcm0gLCBuZXdkYXRhKQ0KICBjb2VmaSA8LSBjb2VmKG9iamVjdCAsIGlkID0gaWQpDQogIHh2YXJzIDwtIG5hbWVzKGNvZWZpKQ0KICBtYXRbLCB4dmFyc10gJSolIGNvZWZpDQp9DQpgYGANCg0KQW5kIGFwcGx5IGl0IG9uIHRoZSB0ZXN0Lg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnByZWRfdHJhaW4gPC0gc3RhdHM6OnByZWRpY3QocmVnZml0LmJlc3QsIGZ1bGxfdHJhaW4sIGlkID0gd2hpY2gubWluKHZhbC5lcnJvcnMpKQ0KcHJlZF90ZXN0IDwtIHN0YXRzOjpwcmVkaWN0KHJlZ2ZpdC5iZXN0LCB0ZXN0LCBpZCA9IHdoaWNoLm1pbih2YWwuZXJyb3JzKSkNCmBgYA0KDQpIZXJlIGlzIHRoZSAkTVNFJCBmb3IgdHJhaW4gYW5kIHRlc3QgZGF0YQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCm1lYW4oKCBmdWxsX3RyYWluJFNhbGFyeSAtIHByZWRfdHJhaW4gKV4yKQ0KbWVhbigoIHRlc3QkU2FsYXJ5IC0gcHJlZF90ZXN0ICleMikNCmBgYA0KDQojIyBDaG9vc2luZyBBbW9uZyBNb2RlbHMgVXNpbmcgQ3Jvc3MtVmFsaWRhdGlvbg0KDQpXZSBub3cgdHJ5IHRvIGNob29zZSBhbW9uZyB0aGUgbW9kZWxzIG9mIGRpZmZlcmVudCBzaXplcyB1c2luZyBjcm9zcw0KdmFsaWRhdGlvbi4gQ3JlYXRlIGEgdmVjdG9yIHRoYXQgYWxsb2NhdGVzIGVhY2ggb2JzZXJ2YXRpb24gdG8gb25lIG9mIGsNCj0gMTAgZm9sZHMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMSkNCg0KSGl0dGVyc19zcGxpdCA8LSBIaXR0ZXJzICU+JQ0KICBkcGx5cjo6bXV0YXRlKA0KICAgIGlzVHJhaW5pbmdTZXQgPSBzYW1wbGUoeCA9IGMoVFJVRSAsIEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSBucm93KEhpdHRlcnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZSA9IFRSVUUpDQogICkgJT4lDQogIGRwbHlyOjpyZWxvY2F0ZSguZGF0YVtbImlzVHJhaW5pbmdTZXQiXV0pDQoNCnRyYWluIDwtIEhpdHRlcnNfc3BsaXQgJT4lDQogIGRwbHlyOjpmaWx0ZXIoLmRhdGFbWyJpc1RyYWluaW5nU2V0Il1dID09IFRSVUUpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1jKCJpc1RyYWluaW5nU2V0IikpDQoNCnRlc3QgPC0gSGl0dGVyc19zcGxpdCAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImlzVHJhaW5pbmdTZXQiXV09PSBGQUxTRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWMoImlzVHJhaW5pbmdTZXQiKSkNCg0KayA8LSAxMA0KbiA8LSBucm93KHRyYWluKQ0KZm9sZHMgPC0gc2FtcGxlKHJlcCAoMTprLCBsZW5ndGggPSBuKSkNCg0KYGBgDQoNCldlIHdyaXRlIGEgZm9yIGxvb3AgdGhhdCBwZXJmb3JtcyBjcm9zcy12YWxpZGF0aW9uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQojIEEgcHJlZGljdCBtZXRob2QgZm9yIGxlYXBzOjpyZWdzdWJzZXRzDQpwcmVkaWN0LnJlZ3N1YnNldHMgPC0gZnVuY3Rpb24ob2JqZWN0ICwgbmV3ZGF0YSAsIGlkLCAuLi4pIHsNCiAgZm9ybSA8LSBhcy5mb3JtdWxhKG9iamVjdCRjYWxsIFtbMl1dKQ0KICBtYXQgPC0gbW9kZWwubWF0cml4KGZvcm0gLCBuZXdkYXRhKQ0KICBjb2VmaSA8LSBjb2VmKG9iamVjdCAsIGlkID0gaWQpDQogIHh2YXJzIDwtIG5hbWVzKGNvZWZpKQ0KICBtYXRbLCB4dmFyc10gJSolIGNvZWZpDQogIH0NCg0KY3YuZXJyb3JzIDwtIG1hdHJpeChOQSwgaywgMTksDQogICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChOVUxMICwgcGFzdGUgKDE6MTkpKSkNCmZvciAoaiBpbiAxOmspIHsNCiAgIyBGb3IgZWFjaCBjcm9zcyB2YWxpZGF0aW9uIGZvbGQgZGF0YSwNCiAgIyBnZXQgdGhlIGJlc3QgaSB2YXJpYWJsZSBtb2RlbCB1c2luZyBzdWJzZXQgc2VsZWN0aW9uDQogIGJlc3QuZml0IDwtIGxlYXBzOjpyZWdzdWJzZXRzKFNhbGFyeSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbltmb2xkcyAhPSBqLF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG52bWF4ID0gMTkpDQogIA0KICBmb3IgKGkgaW4gMToxOSkgew0KICAgICMgQ29tcHV0ZSB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBlcnJvciBmb3IgZWFjaA0KICAgICMgYmVzdCBpIHZhcmlhYmxlIG1vZGVsDQogICAgcHJlZCA8LSBzdGF0czo6cHJlZGljdChiZXN0LmZpdCwgdHJhaW5bZm9sZHMgPT0gaiwgXSwgaWQgPSBpKQ0KICAgIGN2LmVycm9yc1tqLCBpXSA8LSBtZWFuKCggdHJhaW4kU2FsYXJ5W2ZvbGRzID09IGpdIC0gcHJlZCleMikNCiAgfQ0KICB9DQoNCmBgYA0KDQpUaGlzIGhhcyBnaXZlbiB1cyBhICQxMCQgYnkgJDE5JCBtYXRyaXgsIG9mIHdoaWNoIHRoZSAkKGosIGkpJHRoIGVsZW1lbnQNCmNvcnJlc3BvbmRzIHRvIHRoZSBjcm9zcyB2YWxpZGF0aW9uICRNU0UkIGZvciB0aGUgJGokdGggY3Jvc3MtdmFsaWRhdGlvbiBmb2xkIGZvcg0KdGhlIGJlc3QgJGkkLXZhcmlhYmxlIG1vZGVsLg0KDQpXZSB1c2UgdGhlIGBhcHBseWAgZnVuY3Rpb24gdG8gYXZlcmFnZSBvdmVyIHRoZSBjb2x1bW5zIG9mIHRoaXMgYGFwcGx5YA0KbWF0cml4IGluIG9yZGVyIHRvIG9idGFpbiBhIHZlY3RvciBmb3Igd2hpY2ggdGhlICRpJHRoIGVsZW1lbnQgaXMgdGhlDQpjcm9zcyB2YWxpZGF0aW9uIGVycm9yIGZvciB0aGUgJGkkLXZhcmlhYmxlIG1vZGVsLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KbWVhbi5jdi5lcnJvcnMgPC0gYXBwbHkoY3YuZXJyb3JzICwgMiwgbWVhbikNCg0KcGxvdChtZWFuLmN2LmVycm9ycyAsIHR5cGUgPSAiYiIpDQoNCmBgYA0KDQpXZSBzZWUgdGhhdCBjcm9zcy12YWxpZGF0aW9uIHNlbGVjdHMgYSBgciB3aGljaC5taW4obWVhbi5jdi5lcnJvcnMpYC12YXJpYWJsZSBtb2RlbC4gU28gd2UgcmV0cmFpbiB0aGUgbW9kZWwNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpyZWdmaXQuYmVzdCA8LSBsZWFwczo6cmVnc3Vic2V0cyhTYWxhcnkgfiAuLCBkYXRhID0gdHJhaW4gLCBudm1heCA9IDE5KQ0KY29lZihyZWdmaXQuYmVzdCAsIHdoaWNoLm1pbihtZWFuLmN2LmVycm9ycykpDQpgYGANCg0KQW5kIGFwcGx5IGl0IG9uIHRoZSB0ZXN0Lg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnByZWRfdHJhaW4gPC0gc3RhdHM6OnByZWRpY3QocmVnZml0LmJlc3QsIHRyYWluLCBpZCA9IHdoaWNoLm1pbihtZWFuLmN2LmVycm9ycykpDQpwcmVkX3Rlc3QgPC0gc3RhdHM6OnByZWRpY3QocmVnZml0LmJlc3QsIHRlc3QsIGlkID0gd2hpY2gubWluKG1lYW4uY3YuZXJyb3JzKSkNCmBgYA0KDQpIZXJlIGlzIHRoZSAkTVNFJCBmb3IgdHJhaW4gYW5kIHRlc3QgZGF0YQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCm1lYW4oKCB0cmFpbiRTYWxhcnkgLSBwcmVkX3RyYWluICleMikNCm1lYW4oKCB0ZXN0JFNhbGFyeSAtIHByZWRfdGVzdCApXjIpDQpgYGANCg0KIyBSaWRnZSBSZWdyZXNzaW9uDQoNCiMjIEludHJvZHVjdGlvbiB0byByaWRnZSByZWdyZXNzaW9uDQoNClVzZQ0KW2BwYXJzbmlwOjpsaW5lYXJfcmVnYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9saW5lYXJfcmVnLmh0bWwpLA0KW2BwYXJzbmlwOjpzZXRfbW9kZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2V0X2FyZ3MuaHRtbCkNCmFuZA0KW2BwYXJzbmlwOjpzZXRfZW5naW5lYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zZXRfZW5naW5lLmh0bWwpDQp0byBjcmVhdGUgdGhlIG1vZGVsLg0KDQpTZXQgbWl4dHVyZSB0byAwIHRvIGluZGljYXRlIFJpZGdlIFJlZ3Jlc3Npb24uIEZvciBub3csIHdlIHNldA0KcGVuYWx0eS9sYW1iZGEgYXMgMC4NCg0KVXNlDQpbYHBhcnNuaXA6OnRyYW5zbGF0ZWBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHJhbnNsYXRlLmh0bWwpDQp0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgbW9kZWwgY3JlYXRlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpIaXR0ZXJzIDwtIGRwbHlyOjphc190aWJibGUoSVNMUjI6OkhpdHRlcnMpICU+JQ0KICB0aWR5cjo6ZHJvcF9uYSgpDQoNCnJpZGdlX3NwZWMgPC0gcGFyc25pcDo6bGluZWFyX3JlZyhtaXh0dXJlID0gMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZW5hbHR5ID0gMCkgJT4lDQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgJT4lDQogIHBhcnNuaXA6OnNldF9lbmdpbmUoImdsbW5ldCIpDQoNCnJpZGdlX3NwZWMgJT4lDQogIHBhcnNuaXA6OnRyYW5zbGF0ZSgpDQpgYGANCg0KT25jZSB0aGUgc3BlY2lmaWNhdGlvbiBpcyBjcmVhdGVkIHdlIGNhbiBmaXQgaXQgdG8gb3VyIGRhdGEgdXNpbmcNCltgcGFyc25pcDo6Zml0YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maXQuaHRtbCkuIFdlDQp3aWxsIHVzZSBhbGwgdGhlIHByZWRpY3RvcnMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KcmlkZ2VfZml0IDwtIHJpZGdlX3NwZWMgJT4lDQogIHBhcnNuaXA6OmZpdChmb3JtdWxhID0gU2FsYXJ5IH4gLiwgDQogICAgICAgICAgICAgICBkYXRhID0gSGl0dGVycykNCg0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIHBhcmFtZXRlciBlc3RpbWF0ZSBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiBwZW5hbHR5L2xhbWJkYQ0Kd2l0aA0KW2BwYXJzbmlwOjp0aWR5YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90aWR5Lm1vZGVsX2ZpdC5odG1sKS4NCk5vdGljZSBob3cgdGhlIGVzdGltYXRlcyBhcmUgZGVjcmVhc2luZyB3aGVuIHRoZSBhbW91bnQgb2YgcGVuYWx0eSBnb2VzDQp1cA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KcGFyc25pcDo6dGlkeShyaWRnZV9maXQpICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KcGFyc25pcDo6dGlkeShyaWRnZV9maXQsIHBlbmFsdHkgPSA3MDUpICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KcGFyc25pcDo6dGlkeShyaWRnZV9maXQsIHBlbmFsdHkgPSAxMTQ5OCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KVXNpbmcNCltgcGFyc25pcDo6ZXh0cmFjdF9maXRfZW5naW5lYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXBhcnNuaXAuaHRtbCkNCndlIGNhbiB2aXN1YWxpemUgaG93IHRoZSBtYWduaXR1ZGUgb2YgdGhlIGNvZWZmaWNpZW50cyBhcmUgYmVpbmcNCnJlZ3VsYXJpemVkIHRvd2FyZHMgemVybyBhcyB0aGUgcGVuYWx0eSBnb2VzIHVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCiMgQXJyYW5nZSBmaWd1cmUgbWFyZ2luDQpwYXIobWFyPWMoNSw1LDUsMSkpDQpyaWRnZV9maXQgJT4lDQogIHBhcnNuaXA6OmV4dHJhY3RfZml0X2VuZ2luZSgpICU+JQ0KICBwbG90KHh2YXIgPSAibGFtYmRhIikNCmBgYA0KDQpJdCB3b3VsZCBiZSBuaWNlIGlmIHdlIGNvdWxkIGZpbmQgdGhlICJiZXN0IiB2YWx1ZSBvZiB0aGUNCnBlbmFsdHkvbGFtYmRhLiBXZSBjYW4gZG8gdGhpcyB1c2luZyB0aGUNCltgdHVuZTo6dHVuZV9ncmlkYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lX2dyaWQuaHRtbCkNCg0KTmVlZCB3ZSBuZWVkIHRocmVlIHRoaW5ncyBpbiBvcmRlciB0byB1c2UgdGhlDQpbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpDQoNCi0gICBhIGByZXNhbXBsZWAgb2JqZWN0IGNvbnRhaW5pbmcgdGhlIHJlc2FtcGxlcyB0aGUgYHdvcmtmbG93YCBzaG91bGQNCiAgICBiZSBmaXR0ZWQgd2l0aGluLA0KDQotICAgYSBgd29ya2Zsb3dgIG9iamVjdCBjb250YWluaW5nIHRoZSBtb2RlbCBhbmQgcHJlcHJvY2Vzc29yLGFuZA0KDQotICAgYSB0aWJibGUgYHBlbmFsdHlfZ3JpZGAgY29udGFpbmluZyB0aGUgcGFyYW1ldGVyIHZhbHVlcyB0byBiZQ0KICAgIGV2YWx1YXRlZC4NCg0KIyMgQ3JlYXRlIHRoZSByZXNhbXBsZSBvYmplY3QNCg0KRmlyc3QsIHdlIHNwbGl0IHRoZSBzYW1wbGVzIGludG8gYSB0cmFpbmluZyBzZXQgYW5kIGEgdGVzdCBzZXQuIEZyb20gdGhlIHRyYWluaW5nIHNldCwgd2UgY3JlYXRlIGEgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGRhdGEgc2V0IGZyb20gdGhlIHRyYWluaW5nIHNldC4NCg0KVGhpcyBpcyBkb25lIHdpdGggW2Byc2FtcGxlOjppbml0aWFsX3NwbGl0YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9pbml0aWFsX3NwbGl0Lmh0bWwpLA0KW2Byc2FtcGxlOjp0cmFpbmluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwNCltgcnNhbXBsZTo6dGVzdGluZ2BdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKQ0KYW5kDQpbYHJzYW1wbGU6OnZmb2xkX2N2YF0oaHR0cHM6Ly9yc2FtcGxlLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS92Zm9sZF9jdi5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpzZXQuc2VlZCgxMjM0KQ0KSGl0dGVycyA8LSBkcGx5cjo6YXNfdGliYmxlKElTTFIyOjpIaXR0ZXJzKSAlPiUNCiAgdGlkeXI6OmRyb3BfbmEoKQ0KDQpIaXR0ZXJzX3NwbGl0IDwtIHJzYW1wbGU6OmluaXRpYWxfc3BsaXQoSGl0dGVycywgc3RyYXRhID0gIlNhbGFyeSIpDQoNCkhpdHRlcnNfdHJhaW4gPC0gcnNhbXBsZTo6dHJhaW5pbmcoSGl0dGVyc19zcGxpdCkNCkhpdHRlcnNfdGVzdCA8LSByc2FtcGxlOjp0ZXN0aW5nKEhpdHRlcnNfc3BsaXQpDQoNCkhpdHRlcnNfZm9sZCA8LSByc2FtcGxlOjp2Zm9sZF9jdihIaXR0ZXJzX3RyYWluLCB2ID0gMTApDQoNCmBgYA0KDQojIyBDcmVhdGUgdGhlIHByZXByb2Nlc3Nvcg0KDQpXZSB1c2UgdGhlIGByZWNpcGVzYCBwYWNrYWdlIHRvIGNyZWF0ZSB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwcy4gSG93ZXZlciwNCnRoZSBvcmRlciBvZiB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwIGlzIGFjdHVhbGx5IGltcG9ydGFudC4gU2VlIHRoZQ0KW09yZGVyaW5nIG9mIHN0ZXBzDQp2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JlY2lwZXMvdmlnbmV0dGVzL09yZGVyaW5nLmh0bWwpDQoNCldlIGNyZWF0ZSBhIGJhc2ljIHJlY2lwZSB3aXRoDQpbYHJlY2lwZXM6OnJlY2lwZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcmVjaXBlLmh0bWwpLg0KDQpBcHBseQ0KW2ByZWNpcGVzOjpwcmVwYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9wcmVwLmh0bWwpDQphbmQNCltgcmVjaXBlczo6YmFrZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYmFrZS5odG1sKSB0bw0KY29tcHV0ZSB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcmlkZ2VfcmVjaXBlIDwtIHJlY2lwZXM6OnJlY2lwZShmb3JtdWxhID0gU2FsYXJ5IH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IEhpdHRlcnNfdHJhaW4pDQoNCnJlYyA8LSByZWNpcGVzOjpwcmVwKHggPSByaWRnZV9yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikNCiAgDQpyZWMgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCg0KcmVjICU+JQ0KICByZWNpcGVzOjpiYWtlKG5ld19kYXRhID0gSGl0dGVyc190cmFpbikgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQpEbyBwcmVwcm9jZXNzaW5nIG9uIHRoZSBmYWN0b3IgdmFyaWFibGVzDQpbYHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnNgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2hhc19yb2xlLmh0bWwpLg0KW2ByZWNpcGVzOjpzdGVwX25vdmVsYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX25vdmVsLmh0bWwpDQphbmQNCltgcmVjaXBlczo6c3RlcF9kdW1teWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9kdW1teS5odG1sKQ0Kd2FzIHVzZWQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpyaWRnZV9yZWNpcGUgPC0gcmlkZ2VfcmVjaXBlICU+JQ0KICAjIFN0ZXAgTm92ZWwgaXMgaW1wb3J0YW50Lg0KICAjIElmIHRlc3Qgc2V0IGhhcyBhIG5ldyBmYWN0b3Igbm90IGZvdW5kIGluIHRyYWluaW5nLCBpdCB3aWxsIGJlIHRyZWF0ZWQgYXMgIm5ldyIgYW5kIG5vdCBOQQ0KICAjIFRoaXMgd2lsbCBwcmV2ZW50IHRoZSBtb2RlbCBmcm9tIGdpdmluZyBhbiBlcnJvcg0KICAjIGh0dHBzOi8vYmxvZy5kYXRhc2NpZW5jZWhlcm9lcy5jb20vaG93LXRvLXVzZS1yZWNpcGVzLXBhY2thZ2UtZm9yLW9uZS1ob3QtZW5jb2RpbmcvDQogIHJlY2lwZXM6OnN0ZXBfbm92ZWwocmVjaXBlczo6YWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUNCiAgIyBDcmVhdGUgRHVtbXkgdmFyaWFibGVzDQogIHJlY2lwZXM6OnN0ZXBfZHVtbXkocmVjaXBlczo6YWxsX25vbWluYWxfcHJlZGljdG9ycygpKQ0KDQpyZWMgPC0gcmVjaXBlczo6cHJlcCh4ID0gcmlkZ2VfcmVjaXBlLCB0cmFpbmluZyA9IEhpdHRlcnNfdHJhaW4pDQogIA0KcmVjICU+JQ0KICBzdW1tYXJ5KCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCnJlYyAlPiUNCiAgcmVjaXBlczo6YmFrZShuZXdfZGF0YSA9IEhpdHRlcnNfdHJhaW4pICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KDQpgYGANCg0KRG8gbm9ybWFsaXNhdGlvbiBzdGVwIG9uIGFsbCBwcmVkaWN0b3JzDQpbYHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKClgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2hhc19yb2xlLmh0bWwpLg0KW2ByZWNpcGVzOjpzdGVwX3p2YF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX3p2Lmh0bWwpDQphbmQNCltgcmVjaXBlczo6c3RlcF9ub3JtYWxpemVgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3N0ZXBfbm9ybWFsaXplLmh0bWwpDQp3YXMgdXNlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJpZGdlX3JlY2lwZSA8LSByaWRnZV9yZWNpcGUgJT4lDQogICMgUmVtb3ZlIHByZWRpY3RvcnMgd2l0aCB6ZXJvIHZhcmlhdGlvbg0KICByZWNpcGVzOjpzdGVwX3p2KHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCkpICU+JQ0KICAjIFN0YW5kYXJkaXNlIGFsbCB2YXJpYWJsZQ0KICByZWNpcGVzOjpzdGVwX25vcm1hbGl6ZShyZWNpcGVzOjphbGxfcHJlZGljdG9ycygpKQ0KDQpyZWMgPC0gcmVjaXBlczo6cHJlcCh4ID0gcmlkZ2VfcmVjaXBlLCB0cmFpbmluZyA9IEhpdHRlcnNfdHJhaW4pDQogIA0KcmVjICU+JQ0KICByZWNpcGVzOjpiYWtlKG5ld19kYXRhID0gSGl0dGVyc190cmFpbikgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCg0KYGBgDQoNCiMjIFNwZWNpZnkgdGhlIG1vZGVsDQoNClRoZSBtb2RlbCBzcGVjaWZpY2F0aW9uIHdpbGwgbG9vayB2ZXJ5IHNpbWlsYXIgdG8gd2hhdCB3ZSBoYXZlIHNlZW4NCmVhcmxpZXIsIGJ1dCB3ZSB3aWxsIHNldCBgcGVuYWx0eSA9IHR1bmU6OnR1bmVgLiBUaGlzIHRlbGxzDQpbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpDQp0aGF0IHRoZSBwZW5hbHR5IHBhcmFtZXRlciBzaG91bGQgYmUgdHVuZWQgdXNpbmcNCltgdHVuZTo6dHVuZWBdKGh0dHBzOi8vaGFyZGhhdC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZS5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJpZGdlX3NwZWMgPC0gDQogIHBhcnNuaXA6OmxpbmVhcl9yZWcocGVuYWx0eSA9IHR1bmU6OnR1bmUoKSwgbWl4dHVyZSA9IDApICU+JSANCiAgcGFyc25pcDo6c2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgDQogIHBhcnNuaXA6OnNldF9lbmdpbmUoImdsbW5ldCIpDQoNCnJpZGdlX3NwZWMgJT4lDQogIHBhcnNuaXA6OnRyYW5zbGF0ZSgpDQpgYGANCg0KIyMgQ3JlYXRlIHRoZSB3b3JrZmxvdw0KDQpbYHdvcmtmbG93czo6d29ya2Zsb3dgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvd29ya2Zsb3cuaHRtbCksDQpbYHdvcmtmbG93czo6YWRkX3JlY2lwZWBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfcmVjaXBlLmh0bWwpDQphbmQNCltgd29ya2Zsb3dzOjphZGRfbW9kZWxgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX21vZGVsLmh0bWwpDQphcmUgdXNlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KcmlkZ2Vfd29ya2Zsb3cgPC0gIHdvcmtmbG93czo6d29ya2Zsb3coKSAlPiUgDQogIHdvcmtmbG93czo6YWRkX3JlY2lwZShyaWRnZV9yZWNpcGUpICU+JSANCiAgd29ya2Zsb3dzOjphZGRfbW9kZWwocmlkZ2Vfc3BlYykNCg0KcmlkZ2Vfd29ya2Zsb3cNCmBgYA0KDQojIyBDcmVhdGUgdGhlIHBlbmFsdHkvbGFtYmRhIGdyaWQNCg0KQSBwZW5hbHR5L2xhbWJkYSBncmlkIG9mICQ1MCQgbnVtYmVycyBmcm9tICQwLjAwMDAxJCAoJDEwXnstNX0kKSB0bw0KJDEwMDAwJCAoJDEwXjUkKSBpcyBjcmVhdGVkLg0KDQpSZWd1bGFyIGdyaWQgaXMgY3JlYXRlZCB1c2luZw0KW2BkaWFsczo6Z3JpZF9yZWd1bGFyYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZ3JpZF9yZWd1bGFyLmh0bWwpLA0KW2BkaWFsczo6cGVuYWx0eWBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3BlbmFsdHkuaHRtbCkNCmFuZA0KW2BzY2FsZXM6OmxvZzEwX3RyYW5zYF0oaHR0cHM6Ly9zY2FsZXMuci1saWIub3JnL3JlZmVyZW5jZS9sb2dfdHJhbnMuaHRtbCkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnBlbmFsdHlfZ3JpZCA8LSBkaWFsczo6Z3JpZF9yZWd1bGFyKHggPSBkaWFsczo6cGVuYWx0eShyYW5nZSA9IGMoLTUsIDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYW5zID0gc2NhbGVzOjpsb2cxMF90cmFucygpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSA1MCkNCg0KcGVuYWx0eV9ncmlkICAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KIyMgUmlkZ2UgcmVncmVzc2lvbiBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhDQoNCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUNCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgNCltgdHVuZTo6dHVuZV9ncmlkYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lX2dyaWQuaHRtbCkuDQpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQ0KZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KdHVuZV9yZXMgPC0gdHVuZTo6dHVuZV9ncmlkKA0KICBvYmplY3QgPSByaWRnZV93b3JrZmxvdywNCiAgcmVzYW1wbGVzID0gSGl0dGVyc19mb2xkLCANCiAgZ3JpZCA9IHBlbmFsdHlfZ3JpZA0KKQ0KDQp0dW5lX3Jlcw0KYGBgDQoNCkhlcmUgd2Ugc2VlIHRoYXQgdGhlIGFtb3VudCBvZiByZWd1bGFyaXphdGlvbiBhZmZlY3RzIHRoZSBwZXJmb3JtYW5jZQ0KbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZw0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLg0KRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGEgZGlmZmVyZW50IHBsb3QNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIE5vdGUgdGhhdCBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBkaWZmZXJlbnQgcGxvdHMNCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZw0KW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KdHVuZTo6Y29sbGVjdF9tZXRyaWNzKHR1bmVfcmVzKSAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZA0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpDQpmYWlscw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KDQp0dW5lX3JlcyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInBlbmFsdHkiXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIm1lYW4iXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG91ciA9IC5kYXRhW1siLm1ldHJpYyJdXSkpICsNCiAgZ2dwbG90Mjo6Z2VvbV9lcnJvcmJhcihtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHltaW4gPSAuZGF0YVtbIm1lYW4iXV0gLSAuZGF0YVtbInN0ZF9lcnIiXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gLmRhdGFbWyJtZWFuIl1dICsgLmRhdGFbWyJzdGRfZXJyIl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2xpbmUoc2l6ZSA9IDEuNSkgKw0KICBnZ3Bsb3QyOjpmYWNldF93cmFwKGZhY2V0cyA9IGdncGxvdDI6OnZhcnMoLmRhdGFbWyIubWV0cmljIl1dKSwgDQogICAgICAgICAgICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCANCiAgICAgICAgICAgICAgICAgICAgICBucm93ID0gMikgKw0KICBnZ3Bsb3QyOjpzY2FsZV94X2xvZzEwKCkgKw0KICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpgYGANCg0KVXNlDQpbYHR1bmU6OnNob3dfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpDQp0byBzZWUgdGhlIHRvcCBmZXcgdmFsdWVzIGZvciBhIGdpdmVuIG1ldHJpYy4NCg0KVGhlICJiZXN0IiB2YWx1ZXMgY2FuIGJlIHNlbGVjdGVkIHVzaW5nDQpbYHR1bmU6OnNlbGVjdF9iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCksDQp0aGlzIGZ1bmN0aW9uIHJlcXVpcmVzIHlvdSB0byBzcGVjaWZ5IGEgbWV0cmljIHRoYXQgaXQgc2hvdWxkIHNlbGVjdA0KYWdhaW5zdC4gVGhlIHBlbmFsdHkvbGFtYmRhIHZhbHVlIGlzIDU2OSBmb3IgbWV0cmljIGByc3FgIHNpbmNlIGl0IGdpdmVzDQp0aGUgaGlnaGVzdCB2YWx1ZS4gRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGENCmRpZmZlcmVudCBiZXN0IHBlbmFsdHkvbGFtYmRhIHZhbHVlLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KDQp0b3BfcGVuYWx0eSA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJyc3EiLCBuID0gNSkNCnRvcF9wZW5hbHR5ICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KDQpiZXN0X3BlbmFsdHkgPC0gdHVuZTo6c2VsZWN0X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJyc3EiKQ0KYmVzdF9wZW5hbHR5ICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KDQpgYGANCg0KIyMgUmlkZ2UgcmVncmVzc2lvbiBtb2RlbCB3aXRoIG9wdGltaXNlZCBwZW5hbHR5L2xhbWJkYSB2YWx1ZQ0KDQpXZSBjcmVhdGUgdGhlIHJpZGdlIHJlZ3Jlc3Npb24gd29ya2Zsb3cgd2l0aCB0aGUgYmVzdCBwZW5hbHR5IHNjb3JlDQp1c2luZw0KW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnJpZGdlX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KHggPSByaWRnZV93b3JrZmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gYmVzdF9wZW5hbHR5KQ0KDQpyaWRnZV9maW5hbA0KYGBgDQoNCldlIG5vdyB0cmFpbiB0aGUgcmlkZ2UgcmVncmVzc2lvbiBtb2RlbCB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nDQpbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpyaWRnZV9maW5hbF9maXQgPC0gcGFyc25pcDo6Zml0KG9iamVjdCA9IHJpZGdlX2ZpbmFsLCBkYXRhID0gSGl0dGVyc190cmFpbikNCmBgYA0KDQpXZSBjYW4gc2VlIHRoZSBjb2VmZmljaWVudHMgdXNpbmcNCltgdHVuZTo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC10dW5lLmh0bWwpIGFuZCBbYHBhcnNuaXA6OnRpZHlgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RpZHkubW9kZWxfZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpyaWRnZV9maW5hbF9maXQgJT4lDQogIHR1bmU6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUNCiAgcGFyc25pcDo6dGlkeSgpICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KV2hpbGUgd2UncmUgYXQgaXQsIGxldCdzIHNlZSB3aGF0IHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgYXJlIHVzaW5nDQp0aGUgYHZpcGAgcGFja2FnZSBhbmQNCltgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwYF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3Qtd29ya2Zsb3cuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQp2aXBfdGFibGUgPC0gcmlkZ2VfZmluYWxfZml0ICU+JQ0KICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUNCiAgdmlwOjp2aShsYW1iZGEgPSBiZXN0X3BlbmFsdHkkcGVuYWx0eSkgDQoNCnZpcF90YWJsZSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnZpcF90YWJsZSAlPiUgDQogIGRwbHlyOjptdXRhdGUoDQogICAgSW1wb3J0YW5jZSA9IGFicyguZGF0YVtbIkltcG9ydGFuY2UiXV0pLA0KICAgIFZhcmlhYmxlID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoLmYgPSAuZGF0YVtbIlZhcmlhYmxlIl1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC54ID0gLmRhdGFbWyJJbXBvcnRhbmNlIl1dKQ0KICApICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJJbXBvcnRhbmNlIl1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siVmFyaWFibGUiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gLmRhdGFbWyJTaWduIl1dKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2NvbCgpICsNCiAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgZ2dwbG90Mjo6bGFicyh5ID0gTlVMTCkNCmBgYA0KDQojIyBSaWRnZSByZWdyZXNzaW9uIG1vZGVsIG9uIHRlc3QgZGF0YQ0KDQpUaGlzIHJpZGdlIHJlZ3Jlc3Npb24gbW9kZWwgY2FuIG5vdyBiZSBhcHBsaWVkIG9uIG91ciB0ZXN0aW5nIGRhdGEgc2V0DQp0byB2YWxpZGF0ZSBpdHMgcGVyZm9ybWFuY2UuIEZvciByZWdyZXNzaW9uIG1vZGVscywgYSBgLnByZWRgIGNvbHVtbiBpcw0KYWRkZWQgd2hlbg0KW2BwYXJzbmlwOjphdWdtZW50YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hdWdtZW50Lmh0bWwpDQppcyB1c2VkLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGVzdF9yZXN1bHRzIDwtIHBhcnNuaXA6OmF1Z21lbnQoeCA9IHJpZGdlX2ZpbmFsX2ZpdCwgbmV3X2RhdGEgPSBIaXR0ZXJzX3Rlc3QpDQogIA0KdGVzdF9yZXN1bHRzICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KICANCmBgYA0KDQpXZSBjaGVjayBob3cgd2VsbCB0aGUgYC5wcmVkYCBjb2x1bW4gbWF0Y2hlcyB0aGUgU2FsYXJ5IHVzaW5nDQpbYHlhcmRzdGljazo6cnNxYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JzcS5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0ZXN0X3Jlc3VsdHMgJT4lDQogIHlhcmRzdGljazo6cnNxKHRydXRoID0gLmRhdGFbWyJTYWxhcnkiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KQWx0ZXJuYXRpdmVseSwgd2UgY2FuIHVzZQ0KW2B0dW5lOjpsYXN0X2ZpdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGFzdF9maXQuaHRtbCkNCmFuZA0KW2B0dW5lOjpjb2xsZWN0X21ldHJpY3NgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2NvbGxlY3RfcHJlZGljdGlvbnMuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQp0ZXN0X3JzIDwtIHR1bmU6Omxhc3RfZml0KG9iamVjdCA9IHJpZGdlX2ZpbmFsX2ZpdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gSGl0dGVyc19zcGxpdCkNCiAgDQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCiAgDQpgYGANCg0KVXNlIFtgdHVuZTo6Y29sbGVjdF9wcmVkaWN0aW9uc2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKSwgdG8gc2VlIG9ubHkgdGhlIGFjdHVhbCBhbmQgcHJlZGljdGVkIHZhbHVlcyBvZiB0aGUgdGVzdCBkYXRhLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnRlc3RfcnMgJT4lDQogIHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbIlNhbGFyeSJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siLnByZWQiXV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgICAgICAgICAgKSArDQogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgbHR5ID0gMiwgY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjUpICsNCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAibWlkbmlnaHRibHVlIikgKw0KICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpDQpgYGANCg0KIyBUaGUgTGFzc28NCg0KVGhlIGZvbGxvd2luZyBwcm9jZWR1cmUgd2lsbCBiZSB2ZXJ5IHNpbWlsYXIgdG8gd2hhdCB3ZSBzYXcgaW4gdGhlIHJpZGdlDQpyZWdyZXNzaW9uIHNlY3Rpb24uIFRoZSByZXNhbXBsaW5nIHN0ZXAgaXMgdGhlIHNhbWUuDQoNCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0DQoNCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuDQoNClRoaXMgaXMgZG9uZSB3aXRoIFtgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdGBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwNCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksDQpbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkNCmFuZA0KW2Byc2FtcGxlOjp2Zm9sZF9jdmBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdmZvbGRfY3YuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzNCkNCkhpdHRlcnMgPC0gZHBseXI6OmFzX3RpYmJsZShJU0xSMjo6SGl0dGVycykgJT4lDQogIHRpZHlyOjpkcm9wX25hKCkNCg0KSGl0dGVyc19zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KEhpdHRlcnMsIHN0cmF0YSA9ICJTYWxhcnkiKQ0KDQpIaXR0ZXJzX3RyYWluIDwtIHJzYW1wbGU6OnRyYWluaW5nKEhpdHRlcnNfc3BsaXQpDQpIaXR0ZXJzX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhIaXR0ZXJzX3NwbGl0KQ0KDQpIaXR0ZXJzX2ZvbGQgPC0gcnNhbXBsZTo6dmZvbGRfY3YoSGl0dGVyc190cmFpbiwgdiA9IDEwKQ0KDQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3INCg0KVGhlIGZvbGxvd2luZyBwcm9jZWR1cmUgd2lsbCBiZSB2ZXJ5IHNpbWlsYXIgdG8gd2hhdCB3ZSBzYXcgaW4gdGhlIHJpZGdlDQpyZWdyZXNzaW9uIHNlY3Rpb24uIFRoZSBwcmVwcm9jZXNzaW5nIHN0ZXBzIG5lZWRlZCBhcmUgdGhlIHNhbWUgdXNpbmcNCltgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCksDQpbYHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnNgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2hhc19yb2xlLmh0bWwpLA0KW2ByZWNpcGVzOjphbGxfcHJlZGljdG9ycygpYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9oYXNfcm9sZS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9ub3ZlbGBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9ub3ZlbC5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9kdW1teWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9kdW1teS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF96dmBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF96di5odG1sKQ0KYW5kDQpbYHJlY2lwZXM6OnN0ZXBfbm9ybWFsaXplYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX25vcm1hbGl6ZS5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCmxhc3NvX3JlY2lwZSA8LSANCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBTYWxhcnkgfiAuLCBkYXRhID0gSGl0dGVyc190cmFpbikgJT4lIA0KICByZWNpcGVzOjpzdGVwX25vdmVsKHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX2R1bW15KHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX3p2KHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcmVjaXBlczo6c3RlcF9ub3JtYWxpemUocmVjaXBlczo6YWxsX3ByZWRpY3RvcnMoKSkNCmBgYA0KDQojIyBTcGVjaWZ5IHRoZSBtb2RlbA0KDQpBZ2FpbiB3ZSBzZXQgYHBlbmFsdHkgPSB0dW5lOjp0dW5lYC4gVGhpcyB0ZWxscw0KW2B0dW5lOjp0dW5lX2dyaWRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmVfZ3JpZC5odG1sKQ0KdGhhdCB0aGUgcGVuYWx0eSBwYXJhbWV0ZXIgc2hvdWxkIGJlIHR1bmVkIHVzaW5nDQpbYHR1bmU6OnR1bmVgXShodHRwczovL2hhcmRoYXQudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmUuaHRtbCkuDQoNClRoaXMgdGltZSwgaXQgaXMgYG1peHR1cmU9MWAgaW4NCltgcGFyc25pcDo6bGluZWFyX3JlZ2BdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGluZWFyX3JlZy5odG1sKQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KbGFzc29fc3BlYyA8LSANCiAgcGFyc25pcDo6bGluZWFyX3JlZyhwZW5hbHR5ID0gdHVuZTo6dHVuZSgpLCBtaXh0dXJlID0gMSkgJT4lIA0KICBwYXJzbmlwOjpzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSANCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgiZ2xtbmV0IikNCg0KbGFzc29fc3BlYyAlPiUNCiAgcGFyc25pcDo6dHJhbnNsYXRlKCkNCg0KYGBgDQoNCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cNCg0KW2B3b3JrZmxvd3M6OndvcmtmbG93YF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3dvcmtmbG93Lmh0bWwpLA0KW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQ0KYW5kDQpbYHdvcmtmbG93czo6YWRkX21vZGVsYF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2FkZF9tb2RlbC5odG1sKQ0KYXJlIHVzZWQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCmxhc3NvX3dvcmtmbG93IDwtICB3b3JrZmxvd3M6OndvcmtmbG93KCkgJT4lIA0KICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUobGFzc29fcmVjaXBlKSAlPiUgDQogIHdvcmtmbG93czo6YWRkX21vZGVsKGxhc3NvX3NwZWMpDQoNCmxhc3NvX3dvcmtmbG93DQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBwZW5hbHR5L2xhbWJkYSBncmlkDQoNCkEgcGVuYWx0eS9sYW1iZGEgZ3JpZCBvZiAkNTAkIG51bWJlcnMgZnJvbSAkMC4wMSQgKCQxMF57LTJ9JCkgdG8gJDEwMCQNCigkMTBeMiQpIGlzIGNyZWF0ZWQuDQoNClJlZ3VsYXIgZ3JpZCBpcyBjcmVhdGVkIHVzaW5nDQpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCksDQpbYGRpYWxzOjpwZW5hbHR5YF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcGVuYWx0eS5odG1sKQ0KYW5kDQpbYHNjYWxlczo6bG9nMTBfdHJhbnNgXShodHRwczovL3NjYWxlcy5yLWxpYi5vcmcvcmVmZXJlbmNlL2xvZ190cmFucy5odG1sKQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcGVuYWx0eV9ncmlkIDwtIGRpYWxzOjpncmlkX3JlZ3VsYXIoeCA9IGRpYWxzOjpwZW5hbHR5KHJhbmdlID0gYygtMiwgMiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnMgPSBzY2FsZXM6OmxvZzEwX3RyYW5zKCkpLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSA1MCkNCg0KcGVuYWx0eV9ncmlkICAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KIyMgTGFzc28gbW9kZWwgZml0dGluZyBvbiBjcm9zcyB2YWxpZGF0ZWQgZGF0YQ0KDQpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlDQpjcm9zcyB2YWxpZGF0ZWQgZGF0YSB3aXRoDQpbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLg0KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkNCmZvcmVhY2g6OmdldERvUGFyV29ya2VycygpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCnR1bmVfcmVzIDwtIHR1bmU6OnR1bmVfZ3JpZCgNCiAgb2JqZWN0ID0gbGFzc29fd29ya2Zsb3csDQogIHJlc2FtcGxlcyA9IEhpdHRlcnNfZm9sZCwgDQogIGdyaWQgPSBwZW5hbHR5X2dyaWQNCikNCg0KdHVuZV9yZXMNCmBgYA0KDQpIZXJlIHdlIHNlZSB0aGF0IHRoZSBhbW91bnQgb2YgcmVndWxhcml6YXRpb24gYWZmZWN0cyB0aGUgcGVyZm9ybWFuY2UNCm1ldHJpY3MgZGlmZmVyZW50bHkgdXNpbmcNCltgdHVuZTo6YXV0b3Bsb3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2F1dG9wbG90LnR1bmVfcmVzdWx0cy5odG1sKS4NCkRvIG5vdGUgdGhhdCB1c2luZyBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBhIGRpZmZlcmVudCBwbG90DQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBOb3RlIHRoYXQgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgZGlmZmVyZW50IHBsb3RzDQp0dW5lOjphdXRvcGxvdCh0dW5lX3JlcykNCmBgYA0KDQpXZSBjYW4gYWxzbyBzZWUgdGhlIHJhdyBtZXRyaWNzIHRoYXQgY3JlYXRlZCB0aGlzIGNoYXJ0IGJ5IGNhbGxpbmcNCltgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKClgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2NvbGxlY3RfcHJlZGljdGlvbnMuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnR1bmU6OmNvbGxlY3RfbWV0cmljcyh0dW5lX3JlcykgJT4lIA0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCkhlcmUgaXMgdGhlIGBnZ3Bsb3RgIHdheSBzaG91bGQNCltgdHVuZTo6YXV0b3Bsb3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2F1dG9wbG90LnR1bmVfcmVzdWx0cy5odG1sKQ0KZmFpbHMNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KdHVuZV9yZXMgJT4lDQogIHR1bmU6OmNvbGxlY3RfbWV0cmljcygpICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJwZW5hbHR5Il1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJtZWFuIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSAuZGF0YVtbIi5tZXRyaWMiXV0pKSArDQogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5kYXRhW1sibWVhbiJdXSArIC5kYXRhW1sic3RkX2VyciJdXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsNCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siLm1ldHJpYyJdXSksIA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIiwgDQogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsNCiAgZ2dwbG90Mjo6c2NhbGVfeF9sb2cxMCgpICsNCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNClVzZQ0KW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQ0KdG8gc2VlIHRoZSB0b3AgZmV3IHZhbHVlcyBmb3IgYSBnaXZlbiBtZXRyaWMuDQoNClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZw0KW2B0dW5lOjpzZWxlY3RfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpLA0KdGhpcyBmdW5jdGlvbiByZXF1aXJlcyB5b3UgdG8gc3BlY2lmeSBhIG1ldHJpYyB0aGF0IGl0IHNob3VsZCBzZWxlY3QNCmFnYWluc3QuIFRoZSBwZW5hbHR5L2xhbWJkYSB2YWx1ZSBpcyAyMi4yIGZvciBtZXRyaWMgYHJzcWAgc2luY2UgaXQNCmdpdmVzIHRoZSBoaWdoZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQ0KZGlmZmVyZW50IGJlc3QgcGVuYWx0eS9sYW1iZGEgdmFsdWUuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnRvcF9wZW5hbHR5IDwtIHR1bmU6OnNob3dfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gInJzcSIsIG4gPSA1KQ0KdG9wX3BlbmFsdHkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmJlc3RfcGVuYWx0eSA8LSB0dW5lOjpzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gInJzcSIpDQpiZXN0X3BlbmFsdHkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQojIyBMYXNzbyBtb2RlbCB3aXRoIG9wdGltaXNlZCBwZW5hbHR5L2xhbWJkYSB2YWx1ZQ0KDQpXZSBjcmVhdGUgdGhlIGxhc3NvIHJlZ3Jlc3Npb24gd29ya2Zsb3cgd2l0aCB0aGUgYmVzdCBwZW5hbHR5IHNjb3JlDQp1c2luZw0KW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCmxhc3NvX2ZpbmFsIDwtIHR1bmU6OmZpbmFsaXplX3dvcmtmbG93KHggPSBsYXNzb193b3JrZmxvdywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gYmVzdF9wZW5hbHR5KQ0KDQpsYXNzb19maW5hbA0KYGBgDQoNCldlIG5vdyB0cmFpbiB0aGUgbGFzc28gcmVncmVzc2lvbiBtb2RlbCB3aXRoIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nDQpbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpsYXNzb19maW5hbF9maXQgPC0gcGFyc25pcDo6Zml0KG9iamVjdCA9IGxhc3NvX2ZpbmFsLCBkYXRhID0gSGl0dGVyc190cmFpbikNCmBgYA0KDQpXZSBjYW4gc2VlIHRoZSBjb2VmZmljaWVudHMgdXNpbmcNCltgdHVuZTo6ZXh0cmFjdF9maXRfcGFyc25pcGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZXh0cmFjdC10dW5lLmh0bWwpIGFuZCBbYHBhcnNuaXA6OnRpZHlgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3RpZHkubW9kZWxfZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpsYXNzb19maW5hbF9maXQgJT4lDQogIHR1bmU6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUNCiAgcGFyc25pcDo6dGlkeSgpICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCiMjIFZhcmlhYmxlIEltcG9ydGFuY2UNCg0KV2hpbGUgd2UncmUgYXQgaXQsIGxldCdzIHNlZSB3aGF0IHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgYXJlIHVzaW5nDQp0aGUgYHZpcGAgcGFja2FnZSBhbmQNCltgd29ya2Zsb3dzOjpleHRyYWN0X2ZpdF9wYXJzbmlwYF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2V4dHJhY3Qtd29ya2Zsb3cuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQp2aXBfdGFibGUgPC0gbGFzc29fZmluYWxfZml0ICU+JQ0KICB3b3JrZmxvd3M6OmV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUNCiAgdmlwOjp2aShsYW1iZGEgPSBiZXN0X3BlbmFsdHkkcGVuYWx0eSkgDQoNCnZpcF90YWJsZSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnZpcF90YWJsZSAlPiUgDQogIGRwbHlyOjptdXRhdGUoDQogICAgSW1wb3J0YW5jZSA9IGFicyguZGF0YVtbIkltcG9ydGFuY2UiXV0pLA0KICAgIFZhcmlhYmxlID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoLmYgPSAuZGF0YVtbIlZhcmlhYmxlIl1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC54ID0gLmRhdGFbWyJJbXBvcnRhbmNlIl1dKQ0KICApICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJJbXBvcnRhbmNlIl1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siVmFyaWFibGUiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gLmRhdGFbWyJTaWduIl1dKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2NvbCgpICsNCiAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgZ2dwbG90Mjo6bGFicyh5ID0gTlVMTCkNCmBgYA0KDQojIyBMYXNzbyByZWdyZXNzaW9uIG1vZGVsIG9uIHRlc3QgZGF0YQ0KDQpUaGlzIGxhc3NvIHJlZ3Jlc3Npb24gbW9kZWwgY2FuIG5vdyBiZSBhcHBsaWVkIG9uIG91ciB0ZXN0aW5nIGRhdGEgc2V0DQp0byB2YWxpZGF0ZSBpdHMgcGVyZm9ybWFuY2UuIEZvciByZWdyZXNzaW9uIG1vZGVscywgYSAucHJlZCBjb2x1bW4gaXMNCmFkZGVkIHdoZW4NCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQ0KaXMgdXNlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBsYXNzb19maW5hbF9maXQsIG5ld19kYXRhID0gSGl0dGVyc190ZXN0KQ0KICANCnRlc3RfcmVzdWx0cyAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCiAgDQpgYGANCg0KV2UgY2hlY2sgaG93IHdlbGwgdGhlIGAucHJlZGAgY29sdW1uIG1hdGNoZXMgdGhlIFNhbGFyeSB1c2luZw0KW2B5YXJkc3RpY2s6OnJzcWBdKGh0dHBzOi8veWFyZHN0aWNrLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yc3EuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KdGVzdF9yZXN1bHRzICU+JQ0KICB5YXJkc3RpY2s6OnJzcSh0cnV0aCA9IC5kYXRhW1siU2FsYXJ5Il1dLCBlc3RpbWF0ZSA9IC5kYXRhW1siLnByZWQiXV0pICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCkFsdGVybmF0aXZlbHksIHdlIGNhbiB1c2UNCltgdHVuZTo6bGFzdF9maXRgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2xhc3RfZml0Lmh0bWwpDQphbmQNCltgdHVuZTo6Y29sbGVjdF9tZXRyaWNzYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGVzdF9ycyA8LSB0dW5lOjpsYXN0X2ZpdChvYmplY3QgPSBsYXNzb19maW5hbF9maXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdCA9IEhpdHRlcnNfc3BsaXQpDQogIA0KdGVzdF9ycyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQogIA0KYGBgDQoNClVzZSBbYHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnNgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2NvbGxlY3RfcHJlZGljdGlvbnMuaHRtbCksIHRvIHNlZSBvbmx5IHRoZSBhY3R1YWwgYW5kIHByZWRpY3RlZCB2YWx1ZXMgb2YgdGhlIHRlc3QgZGF0YS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KTGV0IHVzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgcHJlZGljdGVkIGFuZCBhY3R1YWwgcmVzcG9uc2UgYXMgYSBzY2F0dGVyIHBsb3QuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KdGVzdF9ycyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9wcmVkaWN0aW9ucygpICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJTYWxhcnkiXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbIi5wcmVkIl1dDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCiAgICAgICAgICAgICAgICAgICkgKw0KICBnZ3Bsb3QyOjpnZW9tX2FibGluZShzbG9wZSA9IDEsIGx0eSA9IDIsIGNvbG9yID0gImdyYXk1MCIsIGFscGhhID0gMC41KSArDQogIGdncGxvdDI6Omdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gIm1pZG5pZ2h0Ymx1ZSIpICsNCiAgZ2dwbG90Mjo6Y29vcmRfZml4ZWQoKQ0KYGBgDQoNCiMgUHJpbmNpcGFsIENvbXBvbmVudHMgUmVncmVzc2lvbg0KDQpUaGUgcHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIGlzIGEgbGluZWFyIG1vZGVsIHdpdGggcGNhDQp0cmFuc2Zvcm1lZCBkYXRhLiBIZW5jZSwgdGhlIG1ham9yIGNoYW5nZXMgd2lsbCBiZSBvbiB0aGUgcHJlcHJvY2Vzc2luZw0Kc3RlcHMuDQoNCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0DQoNCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuDQoNClRoaXMgaXMgZG9uZSB3aXRoIFtgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdGBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwNCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksDQpbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkNCmFuZA0KW2Byc2FtcGxlOjp2Zm9sZF9jdmBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdmZvbGRfY3YuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzNCkNCkhpdHRlcnMgPC0gZHBseXI6OmFzX3RpYmJsZShJU0xSMjo6SGl0dGVycykgJT4lDQogIHRpZHlyOjpkcm9wX25hKCkNCg0KSGl0dGVyc19zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KEhpdHRlcnMsIHN0cmF0YSA9ICJTYWxhcnkiKQ0KDQpIaXR0ZXJzX3RyYWluIDwtIHJzYW1wbGU6OnRyYWluaW5nKEhpdHRlcnNfc3BsaXQpDQpIaXR0ZXJzX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhIaXR0ZXJzX3NwbGl0KQ0KDQpIaXR0ZXJzX2ZvbGQgPC0gcnNhbXBsZTo6dmZvbGRfY3YoSGl0dGVyc190cmFpbiwgdiA9IDEwKQ0KDQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3INCg0KV2UgY3JlYXRlIGEgcmVjaXBlIHdpdGgNCltgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCksDQpbYHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnNgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2hhc19yb2xlLmh0bWwpLA0KW2ByZWNpcGVzOjphbGxfcHJlZGljdG9ycygpYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9oYXNfcm9sZS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9ub3ZlbGBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9ub3ZlbC5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9kdW1teWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9kdW1teS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF96dmBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF96di5odG1sKQ0KYW5kDQpbYHJlY2lwZXM6OnN0ZXBfbm9ybWFsaXplYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX25vcm1hbGl6ZS5odG1sKS4NCg0KV2UgYWRkDQpbYHJlY2lwZXM6OnN0ZXBfcGNhYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX3BjYS5odG1sKQ0KdGhpcyB0aW1lIHRvIHBlcmZvcm0gcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyBvbiBhbGwgdGhlIHByZWRpY3RvcnMuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpwY2FfcmVjaXBlIDwtIA0KICByZWNpcGVzOjpyZWNpcGUoZm9ybXVsYSA9IFNhbGFyeSB+IC4sIGRhdGEgPSBIaXR0ZXJzX3RyYWluKSAlPiUgDQogIHJlY2lwZXM6OnN0ZXBfbm92ZWwocmVjaXBlczo6YWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgDQogIHJlY2lwZXM6OnN0ZXBfZHVtbXkocmVjaXBlczo6YWxsX25vbWluYWxfcHJlZGljdG9ycygpKSAlPiUgDQogIHJlY2lwZXM6OnN0ZXBfenYocmVjaXBlczo6YWxsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX25vcm1hbGl6ZShyZWNpcGVzOjphbGxfcHJlZGljdG9ycygpKSAlPiUNCiAgcmVjaXBlczo6c3RlcF9wY2EocmVjaXBlczo6YWxsX3ByZWRpY3RvcnMoKSwgaWQgPSAicGNhIikNCmBgYA0KDQpBcHBseQ0KW2ByZWNpcGVzOjpwcmVwYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9wcmVwLmh0bWwpDQphbmQNCltgcmVjaXBlczo6YmFrZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYmFrZS5odG1sKSB0bw0KY29tcHV0ZSB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcmVjIDwtIHJlY2lwZXM6OnByZXAoeCA9IHBjYV9yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikNCiAgDQpyZWMgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCg0KcmVjICU+JQ0KICByZWNpcGVzOjpiYWtlKG5ld19kYXRhID0gSGl0dGVyc190cmFpbikgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQojIyBQQ0EgZXhwbG9yYXRpb24NCg0KV2UgY2FuIGV4cGxvcmUgdGhlIHJlc3VsdHMgb2YgdGhlIFBDQSB1c2luZyB0aGUgW2ByZWNpcGVzOjpwcmVwYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9wcmVwLmh0bWwpIGFuZA0KW2BwYXJzbmlwOjp0aWR5YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90aWR5Lm1vZGVsX2ZpdC5odG1sKS4gV2UgY2FuIHNlZSB0aGF0IHBjYSBpcyBkb25lIGF0IHN0ZXAgbnVtYmVyIDUNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJlY2lwZXM6OnByZXAoeCA9IHBjYV9yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikgJT4lDQogIHBhcnNuaXA6OnRpZHkoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpBcyBzdWNoLCB3ZSBleHRyYWN0IHRoZSByZXN1bHRzIG9mIHN0ZXAgbnVtYmVyIDUgb3IgdGhlIHBjYSBzdGVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGlkaWVkX3BjYV9sb2FkaW5ncyA8LSByZWNpcGVzOjpwcmVwKHggPSBwY2FfcmVjaXBlLCB0cmFpbmluZyA9IEhpdHRlcnNfdHJhaW4pICU+JQ0KICBwYXJzbmlwOjp0aWR5KGlkID0gInBjYSIsIHR5cGUgPSAiY29lZiIpDQoNCg0KdGlkaWVkX3BjYV9sb2FkaW5ncyAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCg0KYGBgDQoNCldlIG1ha2UgYSB2aXN1YWxpemF0aW9uIHRvIHNlZSB3aGF0IHRoZSBmaXJzdCBmb3VyIGNvbXBvbmVudHMgbG9vayBsaWtlDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gOH0NCg0KdGlkaWVkX3BjYV9sb2FkaW5ncyAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImNvbXBvbmVudCJdXSAlaW4lIGMoIlBDMSIsICJQQzIiLCAiUEMzIiwgIlBDNCIpKSAlPiUNCiAgZHBseXI6Om11dGF0ZShjb21wb25lbnQgPSBmb3JjYXRzOjpmY3RfaW5vcmRlciguZGF0YVtbImNvbXBvbmVudCJdXSkpICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gLmRhdGFbWyJ2YWx1ZSJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbInRlcm1zIl1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IC5kYXRhW1sidGVybXMiXV0pKSArDQogIGdncGxvdDI6Omdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siY29tcG9uZW50Il1dKSkgKw0KICBnZ3Bsb3QyOjpsYWJzKHkgPSBOVUxMKQ0KDQpgYGANCg0KTGV0IHVzIHRha2UgYSBjbG9zZXIgbG9vayBhdCB0aGUgdG9wIDYgdmFyaWFibGVzIHRoYXQgY29udHJpYnV0ZSB0byB0aGUNCmZpcnN0IGZvdXIgY29tcG9uZW50cw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGlkaWVkX3BjYV9sb2FkaW5ncyAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImNvbXBvbmVudCJdXSAlaW4lIGMoIlBDMSIsICJQQzIiLCAiUEMzIiwgIlBDNCIpKSAlPiUNCiAgZHBseXI6Omdyb3VwX2J5KC5kYXRhW1siY29tcG9uZW50Il1dKSAlPiUNCiAgZHBseXI6OnRvcF9uKDYsIGFicyguZGF0YVtbInZhbHVlIl1dKSkgJT4lDQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lDQogIGRwbHlyOjptdXRhdGUoDQogICAgdGVybXMgPSB0aWR5dGV4dDo6cmVvcmRlcl93aXRoaW4oeCA9IC5kYXRhW1sidGVybXMiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYWJzKC5kYXRhW1sidmFsdWUiXV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aXRoaW4gPSAuZGF0YVtbImNvbXBvbmVudCJdXSkNCiAgICApICU+JQ0KICBnZ3Bsb3QyOjpnZ3Bsb3QobWFwcGluZyA9IGdncGxvdDI6OmFlcyh4ID0gYWJzKC5kYXRhW1sidmFsdWUiXV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1sidGVybXMiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gLmRhdGFbWyJ2YWx1ZSJdXSA+IDApDQogICAgICAgICAgICAgICAgICApICsNCiAgZ2dwbG90Mjo6Z2VvbV9jb2woKSArDQogIGdncGxvdDI6OmZhY2V0X3dyYXAoZmFjZXRzID0gZ2dwbG90Mjo6dmFycyguZGF0YVtbImNvbXBvbmVudCJdXSksIA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIHRpZHl0ZXh0OjpzY2FsZV95X3Jlb3JkZXJlZCgpICsNCiAgZ2dwbG90Mjo6bGFicygNCiAgICB4ID0gIkFic29sdXRlIHZhbHVlIG9mIGNvbnRyaWJ1dGlvbiB0byBQQ0EgY29tcG9uZW50IiwNCiAgICB5ID0gTlVMTCwgDQogICAgZmlsbCA9ICJQb3NpdGl2ZT8iDQogICkNCg0KYGBgDQoNCkhvdyBtdWNoIHZhcmlhdGlvbiBhcmUgd2UgY2FwdHVyaW5nIGZvciBlYWNoIGNvbXBvbmVudD8NCg0KSGVyZSBpcyBob3cgd2UgY2FuIHNlZSB0aGUgdmFyaWFuY2Ugc3RhdGlzdGljcyBidXQgaXQgaXMgaW4gYSBsb25nIGZvcm0NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0aWRpZWRfcGNhX3ZhcmlhbmNlIDwtIHJlY2lwZXM6OnByZXAoeCA9IHBjYV9yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikgJT4lDQogIGJyb29tOjp0aWR5KGlkID0gInBjYSIsIHR5cGUgPSAidmFyaWFuY2UiKQ0KDQp0aWRpZWRfcGNhX3ZhcmlhbmNlICU+JSANCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpIZXJlIGlzIGhvdyB3ZSBjYW4gc2VlIHRoZSB2YXJpYW5jZSBzdGF0aXN0aWNzIGluIGl0cyB3aWRlIGZvcm0NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0aWRpZWRfcGNhX3ZhcmlhbmNlICU+JSANCiAgdGlkeXI6OnBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAuZGF0YVtbImNvbXBvbmVudCJdXSwgDQogICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IC5kYXRhW1sidmFsdWUiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgbmFtZXNfcHJlZml4ID0gIlBDIikgJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1kcGx5cjo6YWxsX29mKCJpZCIpKSAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KSGVyZSBpcyBhIHNpbXBsZSBwbG90IHRvIHNob3cgdGhlIHZhcmlhbmNlIGNhcHR1cmVkIGZvciBlYWNoIGNvbXBvbmVudA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCiMgR2V0IHRoZSB2YXJpYW5jZQ0KcGVyY2VudF92YXJpYXRpb24gPC0gdGlkaWVkX3BjYV92YXJpYW5jZSAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbInRlcm1zIl1dID09ICJwZXJjZW50IHZhcmlhbmNlIikgJT4lIA0KICBkcGx5cjo6cHVsbCguZGF0YVtbInZhbHVlIl1dKQ0KDQpwZXJjZW50X3ZhcmlhdGlvbiA8LSBwZXJjZW50X3ZhcmlhdGlvbi8xMDANCg0KIyBJIHVzZSBbMTo0XSB0byBzZWxlY3QgdGhlIGZpcnN0IGZvdXIgY29tcG9uZW50cw0KZHBseXI6OnRpYmJsZShjb21wb25lbnQgPSB1bmlxdWUodGlkaWVkX3BjYV9sb2FkaW5ncyRjb21wb25lbnQpWzE6NF0sIA0KICAgICAgICAgICAgICBwZXJjZW50X3ZhciA9IHBlcmNlbnRfdmFyaWF0aW9uWzE6NF0pICU+JQ0KICBkcGx5cjo6bXV0YXRlKGNvbXBvbmVudCA9IGZvcmNhdHM6OmZjdF9pbm9yZGVyKC5kYXRhW1siY29tcG9uZW50Il1dKSkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbImNvbXBvbmVudCJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAuZGF0YVtbInBlcmNlbnRfdmFyIl1dKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2NvbCgpICsNCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSkgKw0KICBnZ3Bsb3QyOjpsYWJzKHggPSBOVUxMLCANCiAgICAgICAgICAgICAgICB5ID0gIlBlcmNlbnQgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggUENBIGNvbXBvbmVudCIpDQoNCmBgYA0KDQpUaHJlc2hvbGQgaXMgdGhlIGZyYWN0aW9uIG9mIHRoZSB0b3RhbCB2YXJpYW5jZSB0aGF0IHNob3VsZCBiZSBjb3ZlcmVkDQpieSB0aGUgY29tcG9uZW50cy4gRm9yIGV4YW1wbGUsIGB0aHJlc2hvbGQgPSAuNzVgIG1lYW5zIHRoYXQgc3RlcF9wY2ENCnNob3VsZCBnZW5lcmF0ZSBlbm91Z2ggY29tcG9uZW50cyB0byBjYXB0dXJlIDc1IHBlcmNlbnQgb2YgdGhlDQp2YXJpYWJpbGl0eSBpbiB0aGUgdmFyaWFibGVzLiBOb3RlOiB1c2luZyB0aGlzIGFyZ3VtZW50IHdpbGwgb3ZlcnJpZGUNCmFuZCByZXNldCBhbnkgdmFsdWUgZ2l2ZW4gdG8gYG51bV9jb21wYC4NCg0KV2Ugd2lsbCBub3cgdHJ5IHRvIGZpbmQgdGhlIGJlc3QgdGhyZXNob2xkIHZhbHVlIHVzaW5nIHRoZQ0KW2B0dW5lOjp0dW5lKClgXShodHRwczovL2hhcmRoYXQudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3R1bmUuaHRtbCkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnBjYV9yZWNpcGUgPC0gDQogIHJlY2lwZXM6OnJlY2lwZShmb3JtdWxhID0gU2FsYXJ5IH4gLiwgZGF0YSA9IEhpdHRlcnNfdHJhaW4pICU+JSANCiAgcmVjaXBlczo6c3RlcF9ub3ZlbChyZWNpcGVzOjphbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcmVjaXBlczo6c3RlcF9kdW1teShyZWNpcGVzOjphbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcmVjaXBlczo6c3RlcF96dihyZWNpcGVzOjphbGxfcHJlZGljdG9ycygpKSAlPiUgDQogIHJlY2lwZXM6OnN0ZXBfbm9ybWFsaXplKHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCkpICU+JQ0KICByZWNpcGVzOjpzdGVwX3BjYShyZWNpcGVzOjphbGxfcHJlZGljdG9ycygpLCB0aHJlc2hvbGQgPSB0dW5lOjp0dW5lKCkpDQpgYGANCg0KIyMgU3BlY2lmeSB0aGUgbW9kZWwNCg0KV2UgdXNlIGEgbGluZWFyIG1vZGVsDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpsbV9zcGVjIDwtIA0KICBwYXJzbmlwOjpsaW5lYXJfcmVnKCkgJT4lIA0KICBwYXJzbmlwOjpzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSANCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgibG0iKQ0KDQpsbV9zcGVjICU+JQ0KICBwYXJzbmlwOjp0cmFuc2xhdGUoKQ0KDQpgYGANCg0KIyMgQ3JlYXRlIHRoZSB3b3JrZmxvdw0KDQpbYHdvcmtmbG93czo6d29ya2Zsb3dgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvd29ya2Zsb3cuaHRtbCksDQpbYHdvcmtmbG93czo6YWRkX3JlY2lwZWBdKGh0dHBzOi8vd29ya2Zsb3dzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9hZGRfcmVjaXBlLmh0bWwpDQphbmQNCltgd29ya2Zsb3dzOjphZGRfbW9kZWxgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX21vZGVsLmh0bWwpDQphcmUgdXNlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KcGNhX3dvcmtmbG93IDwtICB3b3JrZmxvd3M6OndvcmtmbG93KCkgJT4lIA0KICB3b3JrZmxvd3M6OmFkZF9yZWNpcGUocGNhX3JlY2lwZSkgJT4lIA0KICB3b3JrZmxvd3M6OmFkZF9tb2RlbChsbV9zcGVjKQ0KDQpwY2Ffd29ya2Zsb3cNCmBgYA0KDQojIyBDcmVhdGUgdGhlIHRocmVzaG9sZCBncmlkDQoNCkEgdGhyZXNob2xkIGdyaWQgb2YgJDEwJCBudW1iZXJzIGZyb20gJDAkIHRvICQxJCBpcyBjcmVhdGVkLg0KDQpUaHJlc2hvbGQgZ3JpZCBpcyBjcmVhdGVkIHVzaW5nDQpbYGRpYWxzOjpncmlkX3JlZ3VsYXJgXShodHRwczovL2RpYWxzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9ncmlkX3JlZ3VsYXIuaHRtbCkNCmFuZA0KW2BkaWFsczo6dGhyZXNob2xkYF0oaHR0cHM6Ly9kaWFscy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdGhyZXNob2xkLmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KdGhyZXNob2xkX2dyaWQgIDwtIGRpYWxzOjpncmlkX3JlZ3VsYXIoeCA9IGRpYWxzOjp0aHJlc2hvbGQocmFuZ2UgPSBjKDAsIDEpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAxMCkNCg0KdGhyZXNob2xkX2dyaWQgJT4lIA0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCiMjIFByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbiBtb2RlbCBmaXR0aW5nIG9uIGNyb3NzIHZhbGlkYXRlZCBkYXRhDQoNCk5vdyB3ZSBoYXZlIGV2ZXJ5dGhpbmcgd2UgbmVlZCBhbmQgd2UgY2FuIGZpdCBhbGwgdGhlIG1vZGVscyBvbiB0aGUNCmNyb3NzIHZhbGlkYXRlZCBkYXRhIHdpdGgNCltgdHVuZTo6dHVuZV9ncmlkYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90dW5lX2dyaWQuaHRtbCkuDQpOb3RlIHRoYXQgdGhpcyBwcm9jZXNzIG1heSB0YWtlIHNvbWUgdGltZS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQ0KZm9yZWFjaDo6Z2V0RG9QYXJXb3JrZXJzKCkNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KdHVuZV9yZXMgPC0gdHVuZTo6dHVuZV9ncmlkKA0KICBvYmplY3QgPSBwY2Ffd29ya2Zsb3csDQogIHJlc2FtcGxlcyA9IEhpdHRlcnNfZm9sZCwgDQogIGdyaWQgPSB0aHJlc2hvbGRfZ3JpZA0KKQ0KDQp0dW5lX3Jlcw0KYGBgDQoNCkhlcmUgd2Ugc2VlIHRoYXQgdGhlIGFtb3VudCBvZiByZWd1bGFyaXphdGlvbiBhZmZlY3RzIHRoZSBwZXJmb3JtYW5jZQ0KbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZw0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLg0KRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGEgZGlmZmVyZW50IHBsb3QNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIE5vdGUgdGhhdCBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBkaWZmZXJlbnQgcGxvdHMNCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZw0KW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KdHVuZTo6Y29sbGVjdF9tZXRyaWNzKHR1bmVfcmVzKSAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZA0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpDQpmYWlscw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KDQp0dW5lX3JlcyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbInRocmVzaG9sZCJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1sibWVhbiJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gLmRhdGFbWyIubWV0cmljIl1dKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2Vycm9yYmFyKG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeW1pbiA9IC5kYXRhW1sibWVhbiJdXSAtIC5kYXRhW1sic3RkX2VyciJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHltYXggPSAuZGF0YVtbIm1lYW4iXV0gKyAuZGF0YVtbInN0ZF9lcnIiXV0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArDQogIGdncGxvdDI6Omdlb21fbGluZShzaXplID0gMS41KSArDQogIGdncGxvdDI6OmZhY2V0X3dyYXAoZmFjZXRzID0gZ2dwbG90Mjo6dmFycyguZGF0YVtbIi5tZXRyaWMiXV0pLCANCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIA0KICAgICAgICAgICAgICAgICAgICAgIG5yb3cgPSAyKSArDQogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQpVc2UNCltgdHVuZTo6c2hvd19iZXN0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zaG93X2Jlc3QuaHRtbCkNCnRvIHNlZSB0aGUgdG9wIGZldyB2YWx1ZXMgZm9yIGEgZ2l2ZW4gbWV0cmljLg0KDQpUaGUgImJlc3QiIHZhbHVlcyBjYW4gYmUgc2VsZWN0ZWQgdXNpbmcNCltgdHVuZTo6c2VsZWN0X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKSwNCnRoaXMgZnVuY3Rpb24gcmVxdWlyZXMgeW91IHRvIHNwZWNpZnkgYSBtZXRyaWMgdGhhdCBpdCBzaG91bGQgc2VsZWN0DQphZ2FpbnN0LiBUaGUgdGhyZXNob2xkIHZhbHVlIGlzIDAuODg5IGZvciBtZXRyaWMgYHJzbWVgIHNpbmNlIGl0IGdpdmVzDQp0aGUgbG93ZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQ0KZGlmZmVyZW50IGJlc3QgbnVtYmVyIG9mIHRocmVzaG9sZCB2YWx1ZS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KdG9wX3RocmVzaG9sZCA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJybXNlIiwgbiA9IDUpDQp0b3BfdGhyZXNob2xkICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KDQpiZXN0X3RocmVzaG9sZCA8LSB0dW5lOjpzZWxlY3RfYmVzdCh0dW5lX3JlcywgbWV0cmljID0gInJtc2UiKQ0KYmVzdF90aHJlc2hvbGQgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQojIyBQcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBvcHRpbWlzZWQgdGhyZXNob2xkIHZhbHVlDQoNCldlIGNyZWF0ZSB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIHdvcmtmbG93IHdpdGggdGhlIGJlc3QNCnRocmVzaG9sZCB1c2luZw0KW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnBjYV9maW5hbCA8LSB0dW5lOjpmaW5hbGl6ZV93b3JrZmxvdyh4ID0gcGNhX3dvcmtmbG93LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gYmVzdF90aHJlc2hvbGQpDQoNCnBjYV9maW5hbA0KYGBgDQoNCldlIG5vdyB0cmFpbiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIHRyYWluaW5nDQpkYXRhIHVzaW5nDQpbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpwY2FfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSBwY2FfZmluYWwsIGRhdGEgPSBIaXR0ZXJzX3RyYWluKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGNvZWZmaWNpZW50cyBhbmQgc3RhdGlzdGljcyB1c2luZw0KW2B0dW5lOjpleHRyYWN0X2ZpdF9wYXJzbmlwYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXR1bmUuaHRtbCksIFtgYnJvb206OnRpZHlgXShodHRwczovL2Jyb29tLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90aWR5LmxtLmh0bWwpIGFuZCBbYGJyb29tOjpnbGFuY2VgXShodHRwczovL2Jyb29tLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9nbGFuY2UubG0uaHRtbCkgZm9yIGBsbWAgY2xhc3Mgb2JqZWN0cw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnBjYV9maW5hbF9maXQgICU+JQ0KICB0dW5lOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lDQogIGJyb29tOjp0aWR5KCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpwY2FfZmluYWxfZml0ICAlPiUNCiAgdHVuZTo6ZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JQ0KICBicm9vbTo6Z2xhbmNlKCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KIyMgUHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIG1vZGVsIG9uIHRlc3QgZGF0YQ0KDQpUaGlzIHByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbiBtb2RlbCBjYW4gbm93IGJlIGFwcGxpZWQgb24gb3VyDQp0ZXN0aW5nIGRhdGEgc2V0IHRvIHZhbGlkYXRlIGl0cyBwZXJmb3JtYW5jZS4gRm9yIHJlZ3Jlc3Npb24gbW9kZWxzLCBhDQpgLnByZWRgIGNvbHVtbiBpcyBhZGRlZCB3aGVuDQpbYHBhcnNuaXA6OmF1Z21lbnRgXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2F1Z21lbnQuaHRtbCkNCmlzIHVzZWQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQp0ZXN0X3Jlc3VsdHMgPC0gcGFyc25pcDo6YXVnbWVudCh4ID0gcGNhX2ZpbmFsX2ZpdCwgbmV3X2RhdGEgPSBIaXR0ZXJzX3Rlc3QpDQogIA0KdGVzdF9yZXN1bHRzICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KICANCmBgYA0KDQpXZSBjaGVjayBob3cgd2VsbCB0aGUgYC5wcmVkYCBjb2x1bW4gbWF0Y2hlcyB0aGUgU2FsYXJ5IHVzaW5nDQpbYHlhcmRzdGljazo6cnNxYF0oaHR0cHM6Ly95YXJkc3RpY2sudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3JzcS5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0ZXN0X3Jlc3VsdHMgJT4lDQogIHlhcmRzdGljazo6cnNxKHRydXRoID0gLmRhdGFbWyJTYWxhcnkiXV0sIGVzdGltYXRlID0gLmRhdGFbWyIucHJlZCJdXSkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KQWx0ZXJuYXRpdmVseSwgd2UgY2FuIHVzZQ0KW2B0dW5lOjpsYXN0X2ZpdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbGFzdF9maXQuaHRtbCkgYW5kIFtgdHVuZTo6Y29sbGVjdF9tZXRyaWNzYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGVzdF9ycyA8LSB0dW5lOjpsYXN0X2ZpdChvYmplY3QgPSBwY2FfZmluYWxfZml0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXQgPSBIaXR0ZXJzX3NwbGl0KQ0KDQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpVc2UgW2B0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9jb2xsZWN0X3ByZWRpY3Rpb25zLmh0bWwpLCB0byBzZWUgb25seSB0aGUgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgdmFsdWVzIG9mIHRoZSB0ZXN0IGRhdGEuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KdGVzdF9ycyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9wcmVkaWN0aW9ucygpICU+JQ0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCkxldCB1cyB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIHByZWRpY3RlZCBhbmQgYWN0dWFsIHJlc3BvbnNlIGFzIGEgc2NhdHRlciBwbG90Lg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnRlc3RfcnMgJT4lDQogIHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUNCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1siU2FsYXJ5Il1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyIucHJlZCJdXQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICAgICApICsNCiAgZ2dwbG90Mjo6Z2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBsdHkgPSAyLCBjb2xvciA9ICJncmF5NTAiLCBhbHBoYSA9IDAuNSkgKw0KICBnZ3Bsb3QyOjpnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJtaWRuaWdodGJsdWUiKSArDQogIGdncGxvdDI6OmNvb3JkX2ZpeGVkKCkNCmBgYA0KDQojIFBhcnRpYWwgTGVhc3QgU3F1YXJlDQoNClRoZSBwYXJ0aWFsIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIGlzIGEgbGluZWFyIG1vZGVsIHdpdGggcGxzDQp0cmFuc2Zvcm1lZCBkYXRhLiBIZW5jZSwgdGhlIG1ham9yIGNoYW5nZXMgd2lsbCBiZSBvbiB0aGUgcHJlcHJvY2Vzc2luZw0Kc3RlcHMuDQoNCiMjIENyZWF0ZSB0aGUgcmVzYW1wbGUgb2JqZWN0DQoNCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlcyBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LiBGcm9tIHRoZSB0cmFpbmluZyBzZXQsIHdlIGNyZWF0ZSBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBkYXRhIHNldCBmcm9tIHRoZSB0cmFpbmluZyBzZXQuDQoNClRoaXMgaXMgZG9uZSB3aXRoIFtgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdGBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvaW5pdGlhbF9zcGxpdC5odG1sKSwNCltgcnNhbXBsZTo6dHJhaW5pbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCksDQpbYHJzYW1wbGU6OnRlc3RpbmdgXShodHRwczovL3JzYW1wbGUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2luaXRpYWxfc3BsaXQuaHRtbCkNCmFuZA0KW2Byc2FtcGxlOjp2Zm9sZF9jdmBdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdmZvbGRfY3YuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzNCkNCkhpdHRlcnMgPC0gZHBseXI6OmFzX3RpYmJsZShJU0xSMjo6SGl0dGVycykgJT4lDQogIHRpZHlyOjpkcm9wX25hKCkNCg0KSGl0dGVyc19zcGxpdCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KEhpdHRlcnMsIHN0cmF0YSA9ICJTYWxhcnkiKQ0KDQpIaXR0ZXJzX3RyYWluIDwtIHJzYW1wbGU6OnRyYWluaW5nKEhpdHRlcnNfc3BsaXQpDQpIaXR0ZXJzX3Rlc3QgPC0gcnNhbXBsZTo6dGVzdGluZyhIaXR0ZXJzX3NwbGl0KQ0KDQpIaXR0ZXJzX2ZvbGQgPC0gcnNhbXBsZTo6dmZvbGRfY3YoSGl0dGVyc190cmFpbiwgdiA9IDEwKQ0KDQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBwcmVwcm9jZXNzb3INCg0KV2UgY3JlYXRlIGEgcmVjaXBlIHdpdGgNCltgcmVjaXBlczo6cmVjaXBlYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCksDQpbYHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnNgXShodHRwczovL3JlY2lwZXMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2hhc19yb2xlLmh0bWwpLA0KW2ByZWNpcGVzOjphbGxfcHJlZGljdG9ycygpYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9oYXNfcm9sZS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9ub3ZlbGBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9ub3ZlbC5odG1sKSwNCltgcmVjaXBlczo6c3RlcF9kdW1teWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF9kdW1teS5odG1sKSwNCltgcmVjaXBlczo6c3RlcF96dmBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc3RlcF96di5odG1sKQ0KYW5kDQpbYHJlY2lwZXM6OnN0ZXBfbm9ybWFsaXplYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX25vcm1hbGl6ZS5odG1sKS4NCg0KV2UgYWRkDQpbYHJlY2lwZXM6OnN0ZXBfcGxzYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9zdGVwX3Bscy5odG1sKQ0KdGhpcyB0aW1lIHRvIHBlcmZvcm0gcGFydGlhbCBsZWFzdCBzcXVhcmUgY2FsY3VsYXRpb24gb24gYWxsIHRoZQ0KcHJlZGljdG9ycy4gYG51bV9jb21wYCBpcyB0aGUgbnVtYmVyIG9mIHBhcnRpYWwgbGVhc3Qgc3F1YXJlIGNvbXBvbmVudHMNCnRvIHJldGFpbiBhcyBuZXcgcHJlZGljdG9ycy4gYG91dGNvbWVgIGlzIHRoZSByZXNwb25zZSB2YXJpYWJsZSBmb3INCnBhcnRpYWwgbGVhc3Qgc3F1YXJlIHJlZ3Jlc3Npb24gdG8gdXNlLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcGxzX3JlY2lwZSA8LSANCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBTYWxhcnkgfiAuLCBkYXRhID0gSGl0dGVyc190cmFpbikgJT4lIA0KICByZWNpcGVzOjpzdGVwX25vdmVsKHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX2R1bW15KHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX3p2KHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcmVjaXBlczo6c3RlcF9ub3JtYWxpemUocmVjaXBlczo6YWxsX3ByZWRpY3RvcnMoKSkgJT4lDQogIHJlY2lwZXM6OnN0ZXBfcGxzKHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCksIG51bV9jb21wID0gMTkgLG91dGNvbWUgPSAiU2FsYXJ5IikNCmBgYA0KDQpBcHBseQ0KW2ByZWNpcGVzOjpwcmVwYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9wcmVwLmh0bWwpDQphbmQNCltgcmVjaXBlczo6YmFrZWBdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYmFrZS5odG1sKSB0bw0KY29tcHV0ZSB0aGUgcHJlcHJvY2Vzc2luZyBzdGVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcmVjIDwtIHJlY2lwZXM6OnByZXAoeCA9IHBsc19yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikNCiAgDQpyZWMgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCg0KcmVjICU+JQ0KICByZWNpcGVzOjpiYWtlKG5ld19kYXRhID0gSGl0dGVyc190cmFpbikgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQojIyBQTFMgZXhwbG9yYXRpb24NCg0KV2UgY2FuIGV4cGxvcmUgdGhlIHJlc3VsdHMgb2YgdGhlIFBDQSB1c2luZyB0aGUgW2ByZWNpcGVzOjpwcmVwYF0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9wcmVwLmh0bWwpIGFuZA0KW2BwYXJzbmlwOjp0aWR5YF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90aWR5Lm1vZGVsX2ZpdC5odG1sKS4gV2UgY2FuIHNlZSB0aGF0IHBjYSBpcyBkb25lIGF0IHN0ZXAgbnVtYmVyIDUNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnJlY2lwZXM6OnByZXAoeCA9IHBsc19yZWNpcGUsIHRyYWluaW5nID0gSGl0dGVyc190cmFpbikgJT4lDQogIHBhcnNuaXA6OnRpZHkoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpBcyBzdWNoLCB3ZSBleHRyYWN0IHRoZSByZXN1bHRzIG9mIHN0ZXAgbnVtYmVyIDUgb3IgdGhlIHBjYSBzdGVwLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KdGlkaWVkX3BscyA8LSByZWNpcGVzOjpwcmVwKHggPSBwbHNfcmVjaXBlLCB0cmFpbmluZyA9IEhpdHRlcnNfdHJhaW4pICU+JQ0KICBwYXJzbmlwOjp0aWR5KDUpDQoNCg0KdGlkaWVkX3BscyAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCg0KYGBgDQoNCldlIG1ha2UgYSB2aXN1YWxpemF0aW9uIHRvIHNlZSB3aGF0IHRoZSBmaXJzdCBmb3VyIGNvbXBvbmVudHMgbG9vayBsaWtlDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0ID0gOH0NCg0KdGlkaWVkX3BscyAlPiUNCiAgZHBseXI6OmZpbHRlciguZGF0YVtbImNvbXBvbmVudCJdXSAlaW4lIGMoIlBMUzEiLCAiUExTMiIsICJQTFMzIiwgIlBMUzQiKSkgJT4lDQogIGRwbHlyOjptdXRhdGUoY29tcG9uZW50ID0gZm9yY2F0czo6ZmN0X2lub3JkZXIoLmRhdGFbWyJjb21wb25lbnQiXV0pKSAlPiUNCiAgZ2dwbG90Mjo6Z2dwbG90KG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoeCA9IC5kYXRhW1sidmFsdWUiXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJ0ZXJtcyJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAuZGF0YVtbInRlcm1zIl1dKSkgKw0KICBnZ3Bsb3QyOjpnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdncGxvdDI6OmZhY2V0X3dyYXAoZmFjZXRzID0gZ2dwbG90Mjo6dmFycyguZGF0YVtbImNvbXBvbmVudCJdXSkpICsNCiAgZ2dwbG90Mjo6bGFicyh5ID0gTlVMTCkNCg0KYGBgDQoNCkxldCB1cyB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIHRvcCA2IHZhcmlhYmxlcyB0aGF0IGNvbnRyaWJ1dGUgdG8gdGhlDQpmaXJzdCBmb3VyIGNvbXBvbmVudHMNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnRpZGllZF9wbHMgJT4lDQogIGRwbHlyOjpmaWx0ZXIoLmRhdGFbWyJjb21wb25lbnQiXV0gJWluJSBjKCJQTFMxIiwgIlBMUzIiLCAiUExTMyIsICJQTFM0IikpICU+JQ0KICBkcGx5cjo6Z3JvdXBfYnkoLmRhdGFbWyJjb21wb25lbnQiXV0pICU+JQ0KICBkcGx5cjo6dG9wX24oNiwgYWJzKC5kYXRhW1sidmFsdWUiXV0pKSAlPiUNCiAgZHBseXI6OnVuZ3JvdXAoKSAlPiUNCiAgZHBseXI6Om11dGF0ZSgNCiAgICB0ZXJtcyA9IHRpZHl0ZXh0OjpyZW9yZGVyX3dpdGhpbih4ID0gLmRhdGFbWyJ0ZXJtcyJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBhYnMoLmRhdGFbWyJ2YWx1ZSJdXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpdGhpbiA9IC5kYXRhW1siY29tcG9uZW50Il1dKQ0KICAgICkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSBhYnMoLmRhdGFbWyJ2YWx1ZSJdXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJ0ZXJtcyJdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAuZGF0YVtbInZhbHVlIl1dID4gMCkNCiAgICAgICAgICAgICAgICAgICkgKw0KICBnZ3Bsb3QyOjpnZW9tX2NvbCgpICsNCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siY29tcG9uZW50Il1dKSwgDQogICAgICAgICAgICAgICAgICAgICAgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgdGlkeXRleHQ6OnNjYWxlX3lfcmVvcmRlcmVkKCkgKw0KICBnZ3Bsb3QyOjpsYWJzKA0KICAgIHggPSAiQWJzb2x1dGUgdmFsdWUgb2YgY29udHJpYnV0aW9uIHRvIFBMUyBjb21wb25lbnQiLA0KICAgIHkgPSBOVUxMLCANCiAgICBmaWxsID0gIlBvc2l0aXZlPyINCiAgKQ0KDQpgYGANCg0KYG51bV9jb21wYCBpcyB0aGUgbnVtYmVyIG9mIHBhcnRpYWwgbGVhc3Qgc3F1YXJlIGNvbXBvbmVudHMgdG8gcmV0YWluIGFzDQpuZXcgcHJlZGljdG9ycy4gV2Ugd2lsbCBub3cgdHJ5IHRvIGZpbmQgdGhlIGJlc3QgbnVtYmVyIG9mIGNvbXBvbmVudA0KdmFsdWUgdXNpbmcgdGhlDQpbYHR1bmU6OnR1bmUoKWBdKGh0dHBzOi8vaGFyZGhhdC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZS5odG1sKQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KcGxzX3JlY2lwZSA8LSANCiAgcmVjaXBlczo6cmVjaXBlKGZvcm11bGEgPSBTYWxhcnkgfiAuLCBkYXRhID0gSGl0dGVyc190cmFpbikgJT4lIA0KICByZWNpcGVzOjpzdGVwX25vdmVsKHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX2R1bW15KHJlY2lwZXM6OmFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIA0KICByZWNpcGVzOjpzdGVwX3p2KHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCkpICU+JSANCiAgcmVjaXBlczo6c3RlcF9ub3JtYWxpemUocmVjaXBlczo6YWxsX3ByZWRpY3RvcnMoKSkgJT4lDQogIHJlY2lwZXM6OnN0ZXBfcGxzKHJlY2lwZXM6OmFsbF9wcmVkaWN0b3JzKCksIG51bV9jb21wID0gdHVuZTo6dHVuZSgpICwgb3V0Y29tZSA9ICJTYWxhcnkiKQ0KYGBgDQoNCiMjIFNwZWNpZnkgdGhlIG1vZGVsDQoNCldlIHVzZSBhIGxpbmVhciBtb2RlbA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCg0KbG1fc3BlYyA8LSANCiAgcGFyc25pcDo6bGluZWFyX3JlZygpICU+JSANCiAgcGFyc25pcDo6c2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgDQogIHBhcnNuaXA6OnNldF9lbmdpbmUoImxtIikNCg0KbG1fc3BlYyAlPiUNCiAgcGFyc25pcDo6dHJhbnNsYXRlKCkNCg0KYGBgDQoNCiMjIENyZWF0ZSB0aGUgd29ya2Zsb3cNCg0KW2B3b3JrZmxvd3M6OndvcmtmbG93YF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3dvcmtmbG93Lmh0bWwpLA0KW2B3b3JrZmxvd3M6OmFkZF9yZWNpcGVgXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYWRkX3JlY2lwZS5odG1sKQ0KYW5kDQpbYHdvcmtmbG93czo6YWRkX21vZGVsYF0oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2FkZF9tb2RlbC5odG1sKQ0KYXJlIHVzZWQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnBsc193b3JrZmxvdyA8LSAgd29ya2Zsb3dzOjp3b3JrZmxvdygpICU+JSANCiAgd29ya2Zsb3dzOjphZGRfcmVjaXBlKHBsc19yZWNpcGUpICU+JSANCiAgd29ya2Zsb3dzOjphZGRfbW9kZWwobG1fc3BlYykNCg0KcGxzX3dvcmtmbG93DQpgYGANCg0KIyMgQ3JlYXRlIHRoZSBudW1iZXIgb2YgY29tcG9uZW50cyBncmlkDQoNCkEgbnVtYmVyIG9mIGNvbXBvbmVudHMgZ3JpZCBvZiAkMTAkIG51bWJlcnMgZnJvbSAkMSQgdG8gJDIwJCBpcyBjcmVhdGVkLg0KDQpOdW1iZXIgb2YgY29tcG9uZW50cyBncmlkIGlzIGNyZWF0ZWQgdXNpbmcNCltgZGlhbHM6OmdyaWRfcmVndWxhcmBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL2dyaWRfcmVndWxhci5odG1sKQ0KYW5kDQpbYGRpYWxzOjpudW1fY29tcGBdKGh0dHBzOi8vZGlhbHMudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL251bV9jb21wLmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KbnVtX2NvbXBfZ3JpZCAgPC0gZGlhbHM6OmdyaWRfcmVndWxhcih4ID0gZGlhbHM6Om51bV9jb21wKHJhbmdlID0gYygxLCAyMCkpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMTApDQoNCm51bV9jb21wX2dyaWQgJT4lIA0KICByZWFjdGFibGU6OnJlYWN0YWJsZShkZWZhdWx0UGFnZVNpemUgPSA1KQ0KYGBgDQoNCiMjIFBhcnRpYWwgbGVhc3Qgc3F1YXJlIHJlZ3Jlc3Npb24gbW9kZWwgZml0dGluZyBvbiBjcm9zcyB2YWxpZGF0ZWQgZGF0YQ0KDQpOb3cgd2UgaGF2ZSBldmVyeXRoaW5nIHdlIG5lZWQgYW5kIHdlIGNhbiBmaXQgYWxsIHRoZSBtb2RlbHMgb24gdGhlDQpjcm9zcyB2YWxpZGF0ZWQgZGF0YSB3aXRoDQpbYHR1bmU6OnR1bmVfZ3JpZGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvdHVuZV9ncmlkLmh0bWwpLg0KTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyBtYXkgdGFrZSBzb21lIHRpbWUuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkNCmZvcmVhY2g6OmdldERvUGFyV29ya2VycygpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCnR1bmVfcmVzIDwtIHR1bmU6OnR1bmVfZ3JpZCgNCiAgb2JqZWN0ID0gcGxzX3dvcmtmbG93LA0KICByZXNhbXBsZXMgPSBIaXR0ZXJzX2ZvbGQsIA0KICBncmlkID0gbnVtX2NvbXBfZ3JpZA0KKQ0KDQp0dW5lX3Jlcw0KYGBgDQoNCkhlcmUgd2Ugc2VlIHRoYXQgdGhlIGFtb3VudCBvZiByZWd1bGFyaXphdGlvbiBhZmZlY3RzIHRoZSBwZXJmb3JtYW5jZQ0KbWV0cmljcyBkaWZmZXJlbnRseSB1c2luZw0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpLg0KRG8gbm90ZSB0aGF0IHVzaW5nIGEgZGlmZmVyZW50IHNlZWQgd2lsbCBnaXZlIGEgZGlmZmVyZW50IHBsb3QNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQojIE5vdGUgdGhhdCBhIGRpZmZlcmVudCBzZWVkIHdpbGwgZ2l2ZSBkaWZmZXJlbnQgcGxvdHMNCnR1bmU6OmF1dG9wbG90KHR1bmVfcmVzKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHNlZSB0aGUgcmF3IG1ldHJpY3MgdGhhdCBjcmVhdGVkIHRoaXMgY2hhcnQgYnkgY2FsbGluZw0KW2B0dW5lOjpjb2xsZWN0X21ldHJpY3MoKWBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIG1heC5oZWlnaHQ9JzE1MHB4J30NCg0KdHVuZTo6Y29sbGVjdF9tZXRyaWNzKHR1bmVfcmVzKSAlPiUgDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KSGVyZSBpcyB0aGUgYGdncGxvdGAgd2F5IHNob3VsZA0KW2B0dW5lOjphdXRvcGxvdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXV0b3Bsb3QudHVuZV9yZXN1bHRzLmh0bWwpDQpmYWlscw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgbWF4LmhlaWdodD0nMTUwcHgnfQ0KDQp0dW5lX3JlcyAlPiUNCiAgdHVuZTo6Y29sbGVjdF9tZXRyaWNzKCkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbIm51bV9jb21wIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gLmRhdGFbWyJtZWFuIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSAuZGF0YVtbIi5tZXRyaWMiXV0pKSArDQogIGdncGxvdDI6Omdlb21fZXJyb3JiYXIobWFwcGluZyA9IGdncGxvdDI6OmFlcyh5bWluID0gLmRhdGFbWyJtZWFuIl1dIC0gLmRhdGFbWyJzdGRfZXJyIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5kYXRhW1sibWVhbiJdXSArIC5kYXRhW1sic3RkX2VyciJdXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgZ2dwbG90Mjo6Z2VvbV9saW5lKHNpemUgPSAxLjUpICsNCiAgZ2dwbG90Mjo6ZmFjZXRfd3JhcChmYWNldHMgPSBnZ3Bsb3QyOjp2YXJzKC5kYXRhW1siLm1ldHJpYyJdXSksIA0KICAgICAgICAgICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIiwgDQogICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IDIpICsNCiAgZ2dwbG90Mjo6dGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNClVzZQ0KW2B0dW5lOjpzaG93X2Jlc3RgXShodHRwczovL3R1bmUudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3Nob3dfYmVzdC5odG1sKQ0KdG8gc2VlIHRoZSB0b3AgZmV3IHZhbHVlcyBmb3IgYSBnaXZlbiBtZXRyaWMuDQoNClRoZSAiYmVzdCIgdmFsdWVzIGNhbiBiZSBzZWxlY3RlZCB1c2luZw0KW2B0dW5lOjpzZWxlY3RfYmVzdGBdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvc2hvd19iZXN0Lmh0bWwpLA0KdGhpcyBmdW5jdGlvbiByZXF1aXJlcyB5b3UgdG8gc3BlY2lmeSBhIG1ldHJpYyB0aGF0IGl0IHNob3VsZCBzZWxlY3QNCmFnYWluc3QuIFRoZSBudW1iZXIgb2YgY29tcG9uZW50cyB2YWx1ZSBpcyAxIGZvciBtZXRyaWMgYHJzbWVgIHNpbmNlIGl0DQpnaXZlcyB0aGUgbG93ZXN0IHZhbHVlLiBEbyBub3RlIHRoYXQgdXNpbmcgYSBkaWZmZXJlbnQgc2VlZCB3aWxsIGdpdmUgYQ0KZGlmZmVyZW50IGJlc3QgbnVtYmVyIG9mIGNvbXBvbmVudHMgdmFsdWUuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnRvcF9udW1fY29tcCA8LSB0dW5lOjpzaG93X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJybXNlIiwgbiA9IDUpDQp0b3BfbnVtX2NvbXAgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmJlc3RfbnVtX2NvbXAgPC0gdHVuZTo6c2VsZWN0X2Jlc3QodHVuZV9yZXMsIG1ldHJpYyA9ICJybXNlIikNCmJlc3RfbnVtX2NvbXAgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQoNCmBgYA0KDQojIyBQYXJ0aWFsIGxlYXN0IHNxdWFyZSBtb2RlbCB3aXRoIG9wdGltaXNlZCB0aHJlc2hvbGQgdmFsdWUNCg0KV2UgY3JlYXRlIHRoZSBwYXJ0aWFsIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIHdvcmtmbG93IHdpdGggdGhlIGJlc3QNCnRocmVzaG9sZCB1c2luZw0KW2B0dW5lOjpmaW5hbGl6ZV93b3JrZmxvd2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZmluYWxpemVfbW9kZWwuaHRtbCkuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBtYXguaGVpZ2h0PScxNTBweCd9DQoNCnBsc19maW5hbCA8LSB0dW5lOjpmaW5hbGl6ZV93b3JrZmxvdyh4ID0gcGxzX3dvcmtmbG93LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gYmVzdF9udW1fY29tcCkNCg0KcGxzX2ZpbmFsDQpgYGANCg0KV2Ugbm93IHRyYWluIHRoZSBwYXJ0aWFsIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIG1vZGVsIHdpdGggdGhlIHRyYWluaW5nDQpkYXRhIHVzaW5nDQpbYHBhcnNuaXA6OmZpdGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvZml0Lmh0bWwpDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KDQpwbHNfZmluYWxfZml0IDwtIHBhcnNuaXA6OmZpdChvYmplY3QgPSBwbHNfZmluYWwsIGRhdGEgPSBIaXR0ZXJzX3RyYWluKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGNvZWZmaWNpZW50cyBhbmQgc3RhdGlzdGljcyB1c2luZw0KW2B0dW5lOjpleHRyYWN0X2ZpdF9wYXJzbmlwYF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9leHRyYWN0LXR1bmUuaHRtbCksIFtgYnJvb206OnRpZHlgXShodHRwczovL2Jyb29tLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS90aWR5LmxtLmh0bWwpIGFuZCBbYGJyb29tOjpnbGFuY2VgXShodHRwczovL2Jyb29tLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9nbGFuY2UubG0uaHRtbCkgZm9yIGBsbWAgY2xhc3Mgb2JqZWN0cw0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnBsc19maW5hbF9maXQgICU+JQ0KICB0dW5lOjpleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lDQogIGJyb29tOjp0aWR5KCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpwbHNfZmluYWxfZml0ICAlPiUNCiAgdHVuZTo6ZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JQ0KICBicm9vbTo6Z2xhbmNlKCkgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQpgYGANCg0KIyMgUGFydGlhbCBsZWFzdCBzcXVhcmUgcmVncmVzc2lvbiBtb2RlbCBvbiB0ZXN0IGRhdGENCg0KVGhpcyBwYXJ0aWFsIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIG1vZGVsIGNhbiBub3cgYmUgYXBwbGllZCBvbiBvdXINCnRlc3RpbmcgZGF0YSBzZXQgdG8gdmFsaWRhdGUgaXRzIHBlcmZvcm1hbmNlLiBGb3IgcmVncmVzc2lvbiBtb2RlbHMsIGENCmAucHJlZGAgY29sdW1uIGlzIGFkZGVkIHdoZW4NCltgcGFyc25pcDo6YXVnbWVudGBdKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvYXVnbWVudC5odG1sKQ0KaXMgdXNlZC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnRlc3RfcmVzdWx0cyA8LSBwYXJzbmlwOjphdWdtZW50KHggPSBwbHNfZmluYWxfZml0LCBuZXdfZGF0YSA9IEhpdHRlcnNfdGVzdCkNCiAgDQp0ZXN0X3Jlc3VsdHMgJT4lDQogIHJlYWN0YWJsZTo6cmVhY3RhYmxlKGRlZmF1bHRQYWdlU2l6ZSA9IDUpDQogIA0KYGBgDQoNCldlIGNoZWNrIGhvdyB3ZWxsIHRoZSBgLnByZWRgIGNvbHVtbiBtYXRjaGVzIHRoZSBTYWxhcnkgdXNpbmcNCltgeWFyZHN0aWNrOjpyc3FgXShodHRwczovL3lhcmRzdGljay50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvcnNxLmh0bWwpLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnRlc3RfcmVzdWx0cyAlPiUNCiAgeWFyZHN0aWNrOjpyc3EodHJ1dGggPSAuZGF0YVtbIlNhbGFyeSJdXSwgZXN0aW1hdGUgPSAuZGF0YVtbIi5wcmVkIl1dKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpBbHRlcm5hdGl2ZWx5LCB3ZSBjYW4gdXNlDQpbYHR1bmU6Omxhc3RfZml0YF0oaHR0cHM6Ly90dW5lLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9sYXN0X2ZpdC5odG1sKQ0KYW5kDQpbYHR1bmU6OmNvbGxlY3RfbWV0cmljc2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQoNCnRlc3RfcnMgPC0gdHVuZTo6bGFzdF9maXQob2JqZWN0ID0gcGxzX2ZpbmFsX2ZpdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gSGl0dGVyc19zcGxpdCkNCiAgDQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCiAgDQpgYGANCg0KVXNlIFtgdHVuZTo6Y29sbGVjdF9wcmVkaWN0aW9uc2BdKGh0dHBzOi8vdHVuZS50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvY29sbGVjdF9wcmVkaWN0aW9ucy5odG1sKSwgdG8gc2VlIG9ubHkgdGhlIGFjdHVhbCBhbmQgcHJlZGljdGVkIHZhbHVlcyBvZiB0aGUgdGVzdCBkYXRhLg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnRlc3RfcnMgJT4lDQogIHR1bmU6OmNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUNCiAgcmVhY3RhYmxlOjpyZWFjdGFibGUoZGVmYXVsdFBhZ2VTaXplID0gNSkNCmBgYA0KDQpMZXQgdXMgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBwcmVkaWN0ZWQgYW5kIGFjdHVhbCByZXNwb25zZSBhcyBhIHNjYXR0ZXIgcGxvdC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQp0ZXN0X3JzICU+JQ0KICB0dW5lOjpjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lDQogIGdncGxvdDI6OmdncGxvdChtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKHggPSAuZGF0YVtbIlNhbGFyeSJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IC5kYXRhW1siLnByZWQiXV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgICAgICAgICAgKSArDQogIGdncGxvdDI6Omdlb21fYWJsaW5lKHNsb3BlID0gMSwgbHR5ID0gMiwgY29sb3IgPSAiZ3JheTUwIiwgYWxwaGEgPSAwLjUpICsNCiAgZ2dwbG90Mjo6Z2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAibWlkbmlnaHRibHVlIikgKw0KICBnZ3Bsb3QyOjpjb29yZF9maXhlZCgpDQpgYGANCg0KIyBSbWFya2Rvd24gVGVtcGxhdGUNCg0KVGhpcyBSbWFya2Rvd24gdGVtcGxhdGUgaXMgY3JlYXRlZCBieSB0aGUgW1JlYWxpdHkgQmVuZGluZw0KTGFiXShodHRwczovL3JlYWxpdHliZW5kaW5nLmdpdGh1Yi5pby8pLiBUaGUgdGVtcGxhdGUgY2FuIGJlIGRvd25sb2FkDQpmcm9tIHRoZSBsYWIncw0KW2dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL1JlYWxpdHlCZW5kaW5nL1RlbXBsYXRlUmVzdWx0cykgcmVwb3NpdG9yeS4NCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBtb3RpdmF0aW9uIGJlaGluZCBjcmVhdGluZyB0aGlzIHRlbXBsYXRlLA0KY2hlY2sgb3V0IFtEci4gRG9taW5pcXVlIE1ha293c2tpJ3MgYmxvZw0KcG9zdF0oaHR0cHM6Ly9kb21pbmlxdWVtYWtvd3NraS5naXRodWIuaW8vcG9zdC8yMDIxLTAyLTEwLXRlbXBsYXRlX3Jlc3VsdHMvKQ0KDQojIEJsb2cgUmVmZXJlbmNlcw0KDQotICAgRW1pbCBIdml0ZmVsZHQncyBbSVNMUiB0aWR5bW9kZWxzDQogICAgTGFic10oaHR0cHM6Ly9lbWlsaHZpdGZlbGR0LmdpdGh1Yi5pby9JU0xSLXRpZHltb2RlbHMtbGFicy9saW5lYXItbW9kZWwtc2VsZWN0aW9uLWFuZC1yZWd1bGFyaXphdGlvbi5odG1sKQ0KDQotICAgSnVsaWEgU2lsZ2Uncw0KICAgIFtibG9nXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvbGFzc28tdGhlLW9mZmljZS5odG1sKSB0aXRsZWQNCiAgICAiTEFTU08gcmVncmVzc2lvbiB1c2luZyB0aWR5bW9kZWxzIGFuZCAjVGlkeVR1ZXNkYXkgZGF0YSBmb3IgVGhlDQogICAgT2ZmaWNlIg0KDQotICAgSnVsaWEgU2lsZ2UncyBbYmxvZ10oaHR0cHM6Ly9qdWxpYXNpbGdlLmNvbS9ibG9nL2Jlc3QtaGlwLWhvcC5odG1sKQ0KICAgIHRpdGxlZCAiUENBIGFuZCB0aGUgI1RpZHlUdWVzZGF5IGJlc3QgaGlwIGhvcCBzb25ncyBldmVyIg0KDQojIFBhY2thZ2UgUmVmZXJlbmNlcw0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQ0KcmVwb3J0OjpjaXRlX3BhY2thZ2VzKHNlc3Npb25JbmZvKCkpDQpgYGANCg==