# 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.
> [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.
> (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.
> (Intercept) Hits Walks CAtBat
> 79.4509472 1.2833513 3.2274264 -0.3752350
> CHits CHmRun DivisionW PutOuts
> 1.4957073 1.4420538 -129.9866432 0.2366813
> (Intercept) AtBat Hits Walks
> 109.7873062 -1.9588851 7.4498772 4.9131401
> CRBI CWalks DivisionW PutOuts
> 0.8537622 -0.3053070 -127.1223928 0.2533404
> (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.
> [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)
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()
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())
- Brandon M. Greenwell and Bradley C. Boehmke (2020). Variable Importance Plots—An Introduction to the vip Package. The R Journal, 12(1), 343–366. URL https://doi.org/10.32614/RJ-2020-013.
- David Robinson, Alex Hayes and Simon Couch (2022). broom: Convert Statistical Objects into Tidy Tibbles. R package version 0.7.12. https://CRAN.R-project.org/package=broom
- Davis Vaughan (2022). workflows: Modeling Workflows. R package version 0.2.6. https://CRAN.R-project.org/package=workflows
- Douglas Bates and Martin Maechler (2021). Matrix: Sparse and Dense Matrix Classes and Methods. R package version 1.4-0. https://CRAN.R-project.org/package=Matrix
- Gareth James, Daniela Witten, Trevor Hastie and Rob Tibshirani (2022). ISLR2: Introduction to Statistical Learning, Second Edition. R package version 1.3-1. https://CRAN.R-project.org/package=ISLR2
- Garrick Aden-Buie and Matthew T. Warkentin (2022). xaringanExtra: Extras And Extensions for Xaringan Slides. R package version 0.5.5. https://github.com/gadenbuie/xaringanExtra
- Greg Lin (2020). reactable: Interactive Data Tables Based on ‘React Table’. R package version 0.2.3. https://CRAN.R-project.org/package=reactable
- H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016.
- Hadley Wickham (2021). forcats: Tools for Working with Categorical Variables (Factors). R package version 0.5.1. https://CRAN.R-project.org/package=forcats
- Hadley Wickham and Dana Seidel (2020). scales: Scale Functions for Visualization. R package version 1.1.1. https://CRAN.R-project.org/package=scales
- Hadley Wickham and Maximilian Girlich (2022). tidyr: Tidy Messy Data. R package version 1.2.0. https://CRAN.R-project.org/package=tidyr
- Hadley Wickham, Romain François, Lionel Henry and Kirill Müller (2022). dplyr: A Grammar of Data Manipulation. R package version 1.0.8. https://CRAN.R-project.org/package=dplyr
- Jerome Friedman, Trevor Hastie, Robert Tibshirani (2010). Regularization Paths for Generalized Linear Models via Coordinate Descent. Journal of Statistical Software, 33(1), 1-22. URL https://www.jstatsoft.org/v33/i01/.
- JJ Allaire and Yihui Xie and Jonathan McPherson and Javier Luraschi and Kevin Ushey and Aron Atkins and Hadley Wickham and Joe Cheng and Winston Chang and Richard Iannone (2022). rmarkdown: Dynamic Documents for R. R package version 2.13. URL https://rmarkdown.rstudio.com.
- Joe Cheng, Carson Sievert, Barret Schloerke, Winston Chang, Yihui Xie and Jeff Allen (2021). htmltools: Tools for HTML. R package version 0.5.2. https://CRAN.R-project.org/package=htmltools
- Julia Silge, Fanny Chow, Max Kuhn and Hadley Wickham (2021). rsample: General Resampling Infrastructure. R package version 0.1.1. https://CRAN.R-project.org/package=rsample
- Kirill Müller and Lorenz Walthert (2022). styler: Non-Invasive Pretty Printing of R Code. R package version 1.7.0. https://CRAN.R-project.org/package=styler
- Makowski, D., Ben-Shachar, M.S., Patil, I. & Lüdecke, D. (2020). Automated Results Reporting as a Practical Tool to Improve Reproducibility and Methodological Best Practices Adoption. CRAN. Available from https://github.com/easystats/report. doi: .
- Max Kuhn (2022). tune: Tidy Tuning Tools. R package version 0.2.0. https://CRAN.R-project.org/package=tune
- Max Kuhn and Davis Vaughan (2021). yardstick: Tidy Characterizations of Model Performance. R package version 0.0.9. https://CRAN.R-project.org/package=yardstick
- Max Kuhn and Davis Vaughan (2022). parsnip: A Common API to Modeling and Analysis Functions. R package version 0.2.1. https://CRAN.R-project.org/package=parsnip
- Max Kuhn and Hadley Wickham (2022). recipes: Preprocessing and Feature Engineering Steps for Modeling. R package version 0.2.0. https://CRAN.R-project.org/package=recipes
- Max Kuhn and Hannah Frick (2022). dials: Tools for Creating Tuning Parameter Values. R package version 0.1.0. https://CRAN.R-project.org/package=dials
- Microsoft and Steve Weston (2022). foreach: Provides Foreach Looping Construct. R package version 1.5.2. https://CRAN.R-project.org/package=foreach
- Microsoft Corporation and Steve Weston (2022). doParallel: Foreach Parallel Adaptor for the ‘parallel’ Package. R package version 1.0.17. https://CRAN.R-project.org/package=doParallel
- R Core Team (2022). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. URL https://www.R-project.org/.
- Revolution Analytics and Steve Weston (2022). iterators: Provides Iterator Construct. R package version 1.0.14. https://CRAN.R-project.org/package=iterators
- Rohart F, Gautier B, Singh A, and Le Cao K-A (2017) mixOmics: An R package for ’omics feature selection and multiple data integration. PLoS computational biology 13(11):e1005752
- Sarkar, Deepayan (2008) Lattice: Multivariate Data Visualization with R. Springer, New York. ISBN 978-0-387-75968-5
- Silge J, Robinson D (2016). “tidytext: Text Miningand Analysis Using Tidy Data Principles in R.”JOSS, 1(3). doi: 10.21105/joss.00037 (URL:https://doi.org/10.21105/joss.00037), <URL:http://dx.doi.org/10.21105/joss.00037>.
- Stefan Milton Bache and Hadley Wickham (2022). magrittr: A Forward-Pipe Operator for R. R package version 2.0.2. https://CRAN.R-project.org/package=magrittr
- Thomas Lumley based on Fortran code by Alan Miller (2020). leaps: Regression Subset Selection. R package version 3.1. https://CRAN.R-project.org/package=leaps
- Venables, W. N. & Ripley, B. D. (2002) Modern Applied Statistics with S. Fourth Edition. Springer, New York. ISBN 0-387-95457-0
- Yihui Xie (2022). knitr: A General-Purpose Package for Dynamic Report Generation in R. R package version 1.38.
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==