Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here.
Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks.
# additional package required in hidden code chunks
library(purrr)
library(tidyr)
library(readr)
library(kableExtra)
# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())
Prerequisites
This chapter leverages the following packages:
# Helper packages
library(dplyr) # for data manipulation
library(ggplot2) # for awesome graphics
library(visdat) # for additional visualizations
# Feature engineering packages
library(caret) # for various ML tasks
library(recipes) # for feature engineering tasks
We’ll also continue working with the ames_train
data set:
ames <- AmesHousing::make_ames()
# Load and split the Ames housing data using stratified sampling
set.seed(123) # for reproducibility
split <- rsample::initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- rsample::training(split)
ames_test <- rsample::testing(split)
Target engineering
Figure 3.1:
models <- c("Non-log transformed model residuals",
"Log transformed model residuals")
list(
m1 = lm(Sale_Price ~ Year_Built, data = ames_train),
m2 = lm(log(Sale_Price) ~ Year_Built, data = ames_train)
) %>%
map2_dfr(models, ~ broom::augment(.x) %>% mutate(model = .y)) %>%
ggplot(aes(.resid)) +
geom_histogram(bins = 75) +
facet_wrap(~ model, scales = "free_x") +
ylab(NULL) +
xlab("Residuals")
transformed_response <- log(ames_train$Sale_Price)
# log transformation
ames_recipe <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_log(all_outcomes())
ames_recipe
Data Recipe
Inputs:
Operations:
Log transformation on all_outcomes
log(-0.5)
NaNs produced
[1] NaN
log1p(-0.5)
[1] -0.6931472
\[
\begin{equation}
y(\lambda) =
\begin{cases}
\frac{Y^\lambda-1}{\lambda}, & \text{if}\ \lambda \neq 0 \\
\log\left(Y\right), & \text{if}\ \lambda = 0.
\end{cases}
\end{equation}
\]
Figure 3.2
# Log transformation
train_log_y <- log(ames_train$Sale_Price)
test_log_y <- log(ames_train$Sale_Price)
# Box Cox transformation
lambda <- forecast::BoxCox.lambda(ames_train$Sale_Price)
train_bc_y <- forecast::BoxCox(ames_train$Sale_Price, lambda)
test_bc_y <- forecast::BoxCox(ames_test$Sale_Price, lambda)
# Plot differences
levs <- c("Normal", "Log_Transform", "BoxCox_Transform")
data.frame(
Normal = ames_train$Sale_Price,
Log_Transform = train_log_y,
BoxCox_Transform = train_bc_y
) %>%
gather(Transform, Value) %>%
mutate(Transform = factor(Transform, levels = levs)) %>%
ggplot(aes(Value, fill = Transform)) +
geom_histogram(show.legend = FALSE, bins = 40) +
facet_wrap(~ Transform, scales = "free_x")
# Log transform a value
y <- log(10)
# Undo log-transformation
exp(y)
[1] 10
# Box Cox transform a value
y <- forecast::BoxCox(10, lambda)
# Inverse Box Cox function
inv_box_cox <- function(x, lambda) {
# for Box-Cox, lambda = 0 --> log transform
if (lambda == 0) exp(x) else (lambda*x + 1)^(1/lambda)
}
# Undo Box Cox-transformation
inv_box_cox(y, lambda)
[1] 10
attr(,"lambda")
[1] -0.05425403
Dealing with missingness
sum(is.na(AmesHousing::ames_raw))
[1] 13997
Figure 3.3:
AmesHousing::ames_raw %>%
is.na() %>%
reshape2::melt() %>%
ggplot(aes(Var2, Var1, fill=value)) +
geom_raster() +
coord_flip() +
scale_y_continuous(NULL, expand = c(0, 0)) +
scale_fill_grey(name = "",
labels = c("Present",
"Missing")) +
xlab("Observation") +
theme(axis.text.y = element_text(size = 4))
AmesHousing::ames_raw %>%
filter(is.na(`Garage Type`)) %>%
select(`Garage Type`, `Garage Cars`, `Garage Area`)
Figure 3.4:
vis_miss(AmesHousing::ames_raw, cluster = TRUE)
Imputation
ames_recipe %>%
step_medianimpute(Gr_Liv_Area)
Data Recipe
Inputs:
Operations:
Log transformation on all_outcomes
Median Imputation for Gr_Liv_Area
ames_recipe %>%
step_knnimpute(all_predictors(), neighbors = 6)
Data Recipe
Inputs:
Operations:
Log transformation on all_outcomes
K-nearest neighbor imputation for all_predictors
ames_recipe %>%
step_bagimpute(all_predictors())
Data Recipe
Inputs:
Operations:
Log transformation on all_outcomes
Bagged tree imputation for all_predictors
Figure 3.5:
impute_ames <- ames_train
set.seed(123)
index <- sample(seq_along(impute_ames$Gr_Liv_Area), 50)
actuals <- ames_train[index, ]
impute_ames$Gr_Liv_Area[index] <- NA
p1 <- ggplot() +
geom_point(data = impute_ames, aes(Gr_Liv_Area, Sale_Price), alpha = .2) +
geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
scale_x_log10(limits = c(300, 5000)) +
scale_y_log10(limits = c(10000, 500000)) +
ggtitle("Actual values")
# Mean imputation
mean_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
step_meanimpute(Gr_Liv_Area) %>%
prep(training = impute_ames, retain = TRUE) %>%
juice()
mean_impute <- mean_juiced[index, ]
p2 <- ggplot() +
geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
geom_point(data = mean_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
scale_x_log10(limits = c(300, 5000)) +
scale_y_log10(limits = c(10000, 500000)) +
ggtitle("Mean Imputation")
# KNN imputation
knn_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
step_knnimpute(Gr_Liv_Area) %>%
prep(training = impute_ames, retain = TRUE) %>%
juice()
knn_impute <- knn_juiced[index, ]
p3 <- ggplot() +
geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
geom_point(data = knn_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
scale_x_log10(limits = c(300, 5000)) +
scale_y_log10(limits = c(10000, 500000)) +
ggtitle("KNN Imputation")
# Bagged imputation
bagged_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
step_bagimpute(Gr_Liv_Area) %>%
prep(training = impute_ames, retain = TRUE) %>%
juice()
bagged_impute <- bagged_juiced[index, ]
p4 <- ggplot() +
geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
geom_point(data = bagged_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
scale_x_log10(limits = c(300, 5000)) +
scale_y_log10(limits = c(10000, 500000)) +
ggtitle("Bagged Trees Imputation")
gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 2)
Feature filtering
Figure 3.6 (generated from benchmark data performed in the past):
model_results <- read_csv("data/feature-selection-impacts-results.csv") %>%
mutate(type = case_when(
model %in% c("lm", "pcr", "pls", "glmnet", "lasso") ~ "Linear models",
model %in% c("earth", "svmLinear", "nn") ~ "Non-linear Models",
TRUE ~ "Tree-based Models"
)) %>%
mutate(model = case_when(
model == "lm" ~ "Linear regression",
model == "earth" ~ "Multivariate adaptive regression splines",
model == "gbm" ~ "Gradient boosting machines",
model == "glmnet" ~ "Elastic net",
model == "lasso" ~ "Lasso",
model == "nn" ~ "Neural net",
model == "pcr" ~ "Principal component regression",
model == "pls" ~ "Partial least squares",
model == "ranger" ~ "Random forest",
TRUE ~ "Support vector machine"
))
Parsed with column specification:
cols(
model = [31mcol_character()[39m,
NIP = [32mcol_double()[39m,
RMSE = [32mcol_double()[39m,
time = [32mcol_double()[39m
)
ggplot(model_results, aes(NIP, RMSE, color = model, lty = model)) +
geom_line() +
geom_point() +
facet_wrap(~ type, nrow = 1) +
xlab("Number of additional non-informative predictors")
Figure 3.7 (generated from benchmark data performed in the past):
model_results %>%
group_by(model) %>%
mutate(
time_impact = time / first(time),
time_impact = time_impact - 1
) %>%
ggplot(aes(NIP, time_impact, color = model, lty = model)) +
geom_line() +
geom_point() +
facet_wrap(~ type, nrow = 1) +
scale_y_continuous("Percent increase in training time",
labels = scales::percent) +
xlab("Number of additional non-informative predictors")
caret::nearZeroVar(ames_train, saveMetrics = TRUE) %>%
tibble::rownames_to_column() %>%
filter(nzv)
Numeric feature engineering
# Normalize all numeric columns
recipe(Sale_Price ~ ., data = ames_train) %>%
step_YeoJohnson(all_numeric())
Data Recipe
Inputs:
Operations:
Yeo-Johnson transformation on all_numeric
Figure 3.8:
set.seed(123)
x1 <- tibble(
variable = "x1",
`Real value` = runif(25, min = -30, max = 5),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
set.seed(456)
x2 <- tibble(
variable = "x2",
`Real value` = rlnorm(25, log(25)),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
set.seed(789)
x3 <- tibble(
variable = "x3",
`Real value` = rnorm(25, 150, 15),
`Standardized value` = scale(`Real value`) %>% as.numeric()
)
x1 %>%
bind_rows(x2) %>%
bind_rows(x3) %>%
gather(key, value, -variable) %>%
mutate(variable = factor(variable, levels = c("x3", "x2", "x1"))) %>%
ggplot(aes(value, variable)) +
geom_point(alpha = .6) +
facet_wrap(~ key, scales = "free_x") +
ylab("Feature") +
xlab("Value")
ames_recipe %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes())
Data Recipe
Inputs:
Operations:
Log transformation on all_outcomes
Centering for all_numeric, -, all_outcomes()
Scaling for all_numeric, -, all_outcomes()
Categorical feature engineering
count(ames_train, Neighborhood) %>% arrange(n)
count(ames_train, Screen_Porch) %>% arrange(n)
# Lump levels for two features
lumping <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_other(Neighborhood, threshold = 0.01,
other = "other") %>%
step_other(Screen_Porch, threshold = 0.1,
other = ">0")
# Apply this blue print --> you will learn about this at
# the end of the chapter
apply_2_training <- prep(lumping, training = ames_train) %>%
bake(ames_train)
# New distribution of Neighborhood
count(apply_2_training, Neighborhood) %>% arrange(n)
# New distribution of Screen_Porch
count(apply_2_training, Screen_Porch) %>% arrange(n)
knitr::include_graphics("images/ohe-vs-dummy.png")
# Lump levels for two features
recipe(Sale_Price ~ ., data = ames_train) %>%
step_dummy(all_nominal(), one_hot = TRUE)
Data Recipe
Inputs:
Operations:
Dummy variables from all_nominal
# Original categories
count(ames_train, MS_SubClass)
# Label encoded
recipe(Sale_Price ~ ., data = ames_train) %>%
step_integer(MS_SubClass) %>%
prep(ames_train) %>%
bake(ames_train) %>%
count(MS_SubClass)
ames_train %>% select(contains("Qual"))
# Original categories
count(ames_train, Overall_Qual)
# Label encoded
recipe(Sale_Price ~ ., data = ames_train) %>%
step_integer(Overall_Qual) %>%
prep(ames_train) %>%
bake(ames_train) %>%
count(Overall_Qual)
ames_train %>%
group_by(Neighborhood) %>%
summarize(`Avg Sale_Price` = mean(Sale_Price, na.rm = TRUE)) %>%
head(10) %>%
kable(caption = "Example of target encoding the Neighborhood feature of the Ames housing data set.") %>%
kable_styling(bootstrap_options = "striped", full_width = TRUE)
Example of target encoding the Neighborhood feature of the Ames housing data set.
Neighborhood |
Avg Sale_Price |
North_Ames |
144562.7 |
College_Creek |
199831.7 |
Old_Town |
122736.7 |
Edwards |
130652.2 |
Somerset |
227379.6 |
Northridge_Heights |
323289.5 |
Gilbert |
192162.9 |
Sawyer |
136460.6 |
Northwest_Ames |
187328.2 |
Sawyer_West |
188644.6 |
ames_train %>%
count(Neighborhood) %>%
mutate(Proportion = n / sum(n)) %>%
select(-n) %>%
head(10) %>%
kable(caption = 'Example of categorical proportion encoding the Neighborhood feature of the Ames housing data set.') %>%
kable_styling(bootstrap_options = "striped", full_width = TRUE)
Example of categorical proportion encoding the Neighborhood feature of the Ames housing data set.
Neighborhood |
Proportion |
North_Ames |
0.1451534 |
College_Creek |
0.0910862 |
Old_Town |
0.0832927 |
Edwards |
0.0711154 |
Somerset |
0.0623478 |
Northridge_Heights |
0.0560156 |
Gilbert |
0.0565027 |
Sawyer |
0.0496834 |
Northwest_Ames |
0.0467608 |
Sawyer_West |
0.0414028 |
Dimension reduction
recipe(Sale_Price ~ ., data = ames_train) %>%
step_center(all_numeric()) %>%
step_scale(all_numeric()) %>%
step_pca(all_numeric(), threshold = .95)
Data Recipe
Inputs:
Operations:
Centering for all_numeric
Scaling for all_numeric
No PCA components were extracted.
Proper implementation
Figure 3.10:
knitr::include_graphics("images/minimize-leakage.png")
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_nzv(all_nominal()) %>%
step_integer(matches("Qual|Cond|QC|Qu")) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes()) %>%
step_pca(all_numeric(), -all_outcomes())
blueprint
Data Recipe
Inputs:
Operations:
Sparse, unbalanced variable filter on all_nominal
Integer encoding for matches, Qual|Cond|QC|Qu
Centering for all_numeric, -, all_outcomes()
Scaling for all_numeric, -, all_outcomes()
No PCA components were extracted.
prepare <- prep(blueprint, training = ames_train)
prepare
Data Recipe
Inputs:
Training data contained 2053 data points and no missing data.
Operations:
Sparse, unbalanced variable filter removed Street, Alley, Land_Contour, Utilities, ... [trained]
Integer encoding for Condition_1, Overall_Qual, Overall_Cond, Exter_Qual, Exter_Cond, ... [trained]
Centering for Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
Scaling for Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
PCA extraction with Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
baked_train <- bake(prepare, new_data = ames_train)
baked_test <- bake(prepare, new_data = ames_test)
baked_train
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_nzv(all_nominal()) %>%
step_integer(matches("Qual|Cond|QC|Qu")) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes()) %>%
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)
# Specify resampling plan
cv <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 5
)
# Construct grid of hyperparameter values
hyper_grid <- expand.grid(k = seq(2, 25, by = 1))
# Tune a knn model using grid search
knn_fit2 <- train(
blueprint,
data = ames_train,
method = "knn",
trControl = cv,
tuneGrid = hyper_grid,
metric = "RMSE"
)
# print model results
knn_fit2
k-Nearest Neighbors
2053 samples
80 predictor
Recipe steps: nzv, integer, center, scale, dummy
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 1848, 1849, 1848, 1847, 1848, 1848, ...
Resampling results across tuning parameters:
k RMSE Rsquared MAE
2 35939.89 0.8048642 22437.87
3 35010.19 0.8158644 21727.75
4 34397.34 0.8237016 21241.75
5 33930.03 0.8316443 20941.83
6 33546.26 0.8383965 20777.11
7 33336.67 0.8433273 20688.80
8 33132.85 0.8467156 20578.56
9 33054.25 0.8486616 20536.96
10 32948.08 0.8505378 20529.58
11 32902.66 0.8522363 20537.99
12 32812.52 0.8543514 20571.13
13 32835.43 0.8554652 20625.39
14 32859.76 0.8564118 20685.16
15 32890.51 0.8571297 20728.81
16 32939.40 0.8577373 20774.40
17 32982.90 0.8578918 20817.29
18 33040.08 0.8579879 20910.09
19 33113.72 0.8579053 20977.65
20 33210.68 0.8574767 21057.82
21 33274.76 0.8570757 21110.48
22 33296.28 0.8574598 21162.57
23 33397.05 0.8571769 21252.19
24 33421.53 0.8575578 21282.18
25 33461.60 0.8574388 21320.31
RMSE was used to select the optimal model using the smallest value.
The final value used for the model was k = 12.
# plot cross validation results
ggplot(knn_fit2)
LS0tCnRpdGxlOiAiQ2hhcHRlciAzOiBGZWF0dXJlICYgVGFyZ2V0IEVuZ2luZWVyaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIAoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rcy4KCmBgYHtyIHNldHVwfQojIGFkZGl0aW9uYWwgcGFja2FnZSByZXF1aXJlZCBpbiBoaWRkZW4gY29kZSBjaHVua3MKbGlicmFyeShwdXJycikKbGlicmFyeSh0aWR5cikKbGlicmFyeShyZWFkcikKbGlicmFyeShrYWJsZUV4dHJhKQoKIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKYGBgCgojIyBQcmVyZXF1aXNpdGVzCgpUaGlzIGNoYXB0ZXIgbGV2ZXJhZ2VzIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6CgpgYGB7ciBlbmdpbmVlcmluZy1wcmVyZXFzfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAjIGZvciBkYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KGdncGxvdDIpICAjIGZvciBhd2Vzb21lIGdyYXBoaWNzCmxpYnJhcnkodmlzZGF0KSAgICMgZm9yIGFkZGl0aW9uYWwgdmlzdWFsaXphdGlvbnMKIyBGZWF0dXJlIGVuZ2luZWVyaW5nIHBhY2thZ2VzCmxpYnJhcnkoY2FyZXQpICAgICMgZm9yIHZhcmlvdXMgTUwgdGFza3MKbGlicmFyeShyZWNpcGVzKSAgIyBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZyB0YXNrcwpgYGAKCldlJ2xsIGFsc28gY29udGludWUgd29ya2luZyB3aXRoIHRoZSBgYW1lc190cmFpbmAgZGF0YSBzZXQ6CgpgYGB7ciBlbmdpbmVlcmluZy1hbWVzLXRyYWluLCBlY2hvPVRSVUV9CmFtZXMgPC0gQW1lc0hvdXNpbmc6Om1ha2VfYW1lcygpCgojIExvYWQgYW5kIHNwbGl0IHRoZSBBbWVzIGhvdXNpbmcgZGF0YSB1c2luZyBzdHJhdGlmaWVkIHNhbXBsaW5nCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpzcGxpdCAgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHJzYW1wbGU6OnRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgICA8LSByc2FtcGxlOjp0ZXN0aW5nKHNwbGl0KQpgYGAKCiMjIFRhcmdldCBlbmdpbmVlcmluZwoKRmlndXJlIDMuMToKCmBgYHtyIGVuZ2luZWVyaW5nLXNrZXdlZC1yZXNpZHVhbHMsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTMsIGVjaG89VFJVRSwgZmlnLmNhcD0iVHJhbnNmb3JtaW5nIHRoZSByZXNwb25zZSB2YXJpYWJsZSB0byBtaW5pbWl6ZSBza2V3bmVzcyBjYW4gcmVzb2x2ZSBjb25jZXJucyB3aXRoIG5vbi1ub3JtYWxseSBkaXN0cmlidXRlZCBlcnJvcnMuIn0KbW9kZWxzIDwtIGMoIk5vbi1sb2cgdHJhbnNmb3JtZWQgbW9kZWwgcmVzaWR1YWxzIiwgCiAgICAgICAgICAgICJMb2cgdHJhbnNmb3JtZWQgbW9kZWwgcmVzaWR1YWxzIikKbGlzdCgKICBtMSA9IGxtKFNhbGVfUHJpY2UgfiBZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbiksCiAgbTIgPSBsbShsb2coU2FsZV9QcmljZSkgfiBZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbikKKSAlPiUKICBtYXAyX2Rmcihtb2RlbHMsIH4gYnJvb206OmF1Z21lbnQoLngpICU+JSBtdXRhdGUobW9kZWwgPSAueSkpICU+JQogIGdncGxvdChhZXMoLnJlc2lkKSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDc1KSArCiAgICBmYWNldF93cmFwKH4gbW9kZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICB5bGFiKE5VTEwpICsKICAgIHhsYWIoIlJlc2lkdWFscyIpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctZXhhbXBsZS1sb2csIGV2YWw9RkFMU0V9CnRyYW5zZm9ybWVkX3Jlc3BvbnNlIDwtIGxvZyhhbWVzX3RyYWluJFNhbGVfUHJpY2UpCmBgYAoKYGBge3IgZW5naW5lZXJpbmcteV9sb2d9CiMgbG9nIHRyYW5zZm9ybWF0aW9uCmFtZXNfcmVjaXBlIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfbG9nKGFsbF9vdXRjb21lcygpKQphbWVzX3JlY2lwZQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLW5lZy1sb2csIGVycm9yPVRSVUV9CmxvZygtMC41KQpsb2cxcCgtMC41KQpgYGAKCiQkCiBcYmVnaW57ZXF1YXRpb259IAogeShcbGFtYmRhKSA9ClxiZWdpbntjYXNlc30KICAgXGZyYWN7WV5cbGFtYmRhLTF9e1xsYW1iZGF9LCAmIFx0ZXh0e2lmfVwgXGxhbWJkYSBcbmVxIDAgXFwKICAgXGxvZ1xsZWZ0KFlccmlnaHQpLCAmIFx0ZXh0e2lmfVwgXGxhbWJkYSA9IDAuClxlbmR7Y2FzZXN9ClxlbmR7ZXF1YXRpb259CiQkCgpGaWd1cmUgMy4yCgpgYGB7ciBlbmdpbmVlcmluZy1kaXN0cmlidXRpb24tY29tcGFyaXNvbiwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuY2FwPSJSZXNwb25zZSB2YXJpYWJsZSB0cmFuc2Zvcm1hdGlvbnMuIiwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OX0KIyBMb2cgdHJhbnNmb3JtYXRpb24KdHJhaW5fbG9nX3kgPC0gbG9nKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKdGVzdF9sb2dfeSAgPC0gbG9nKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKCiMgQm94IENveCB0cmFuc2Zvcm1hdGlvbgpsYW1iZGEgIDwtIGZvcmVjYXN0OjpCb3hDb3gubGFtYmRhKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKdHJhaW5fYmNfeSA8LSBmb3JlY2FzdDo6Qm94Q294KGFtZXNfdHJhaW4kU2FsZV9QcmljZSwgbGFtYmRhKQp0ZXN0X2JjX3kgIDwtIGZvcmVjYXN0OjpCb3hDb3goYW1lc190ZXN0JFNhbGVfUHJpY2UsIGxhbWJkYSkKCiMgUGxvdCBkaWZmZXJlbmNlcwpsZXZzIDwtIGMoIk5vcm1hbCIsICJMb2dfVHJhbnNmb3JtIiwgIkJveENveF9UcmFuc2Zvcm0iKQpkYXRhLmZyYW1lKAogIE5vcm1hbCA9IGFtZXNfdHJhaW4kU2FsZV9QcmljZSwKICBMb2dfVHJhbnNmb3JtID0gdHJhaW5fbG9nX3ksCiAgQm94Q294X1RyYW5zZm9ybSA9IHRyYWluX2JjX3kKKSAlPiUKICBnYXRoZXIoVHJhbnNmb3JtLCBWYWx1ZSkgJT4lCiAgbXV0YXRlKFRyYW5zZm9ybSA9IGZhY3RvcihUcmFuc2Zvcm0sIGxldmVscyA9IGxldnMpKSAlPiUgCiAgZ2dwbG90KGFlcyhWYWx1ZSwgZmlsbCA9IFRyYW5zZm9ybSkpICsKICAgIGdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UsIGJpbnMgPSA0MCkgKwogICAgZmFjZXRfd3JhcCh+IFRyYW5zZm9ybSwgc2NhbGVzID0gImZyZWVfeCIpCmBgYAoKCmBgYHtyIGVuZ2luZWVyaW5nLWVuaW52ZXJzZS1iY30KIyBMb2cgdHJhbnNmb3JtIGEgdmFsdWUKeSA8LSBsb2coMTApCgojIFVuZG8gbG9nLXRyYW5zZm9ybWF0aW9uCmV4cCh5KQoKIyBCb3ggQ294IHRyYW5zZm9ybSBhIHZhbHVlCnkgPC0gZm9yZWNhc3Q6OkJveENveCgxMCwgbGFtYmRhKQoKIyBJbnZlcnNlIEJveCBDb3ggZnVuY3Rpb24KaW52X2JveF9jb3ggPC0gZnVuY3Rpb24oeCwgbGFtYmRhKSB7CiAgIyBmb3IgQm94LUNveCwgbGFtYmRhID0gMCAtLT4gbG9nIHRyYW5zZm9ybQogIGlmIChsYW1iZGEgPT0gMCkgZXhwKHgpIGVsc2UgKGxhbWJkYSp4ICsgMSleKDEvbGFtYmRhKSAKfQoKIyBVbmRvIEJveCBDb3gtdHJhbnNmb3JtYXRpb24KaW52X2JveF9jb3goeSwgbGFtYmRhKQpgYGAKCiMjIERlYWxpbmcgd2l0aCBtaXNzaW5nbmVzcwoKYGBge3IgZW5naW5lZXJpbmctYW1lcy1yYXctbWlzc2luZ30Kc3VtKGlzLm5hKEFtZXNIb3VzaW5nOjphbWVzX3JhdykpCmBgYAoKRmlndXJlIDMuMzoKCmBgYHtyIGVuZ2luZWVyaW5nLWhlYXQtbWFwLW1pc3NpbmduZXNzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwID0gIkhlYXQgbWFwIG9mIG1pc3NpbmcgdmFsdWVzIGluIHRoZSByYXcgQW1lcyBob3VzaW5nIGRhdGEuIn0KQW1lc0hvdXNpbmc6OmFtZXNfcmF3ICU+JQogIGlzLm5hKCkgJT4lCiAgcmVzaGFwZTI6Om1lbHQoKSAlPiUKICBnZ3Bsb3QoYWVzKFZhcjIsIFZhcjEsIGZpbGw9dmFsdWUpKSArIAogICAgZ2VvbV9yYXN0ZXIoKSArIAogICAgY29vcmRfZmxpcCgpICsKICAgIHNjYWxlX3lfY29udGludW91cyhOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgICBzY2FsZV9maWxsX2dyZXkobmFtZSA9ICIiLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJQcmVzZW50IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWlzc2luZyIpKSArCiAgICB4bGFiKCJPYnNlcnZhdGlvbiIpICsKICAgIHRoZW1lKGF4aXMudGV4dC55ICA9IGVsZW1lbnRfdGV4dChzaXplID0gNCkpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctbWlzc2luZ25lc3MtZ2FyYWdlc30KQW1lc0hvdXNpbmc6OmFtZXNfcmF3ICU+JSAKICBmaWx0ZXIoaXMubmEoYEdhcmFnZSBUeXBlYCkpICU+JSAKICBzZWxlY3QoYEdhcmFnZSBUeXBlYCwgYEdhcmFnZSBDYXJzYCwgYEdhcmFnZSBBcmVhYCkKYGBgCgpGaWd1cmUgMy40OgoKYGBge3IgZW5naW5lZXJpbmctbWlzc2luZ25lc3MtdmlzbmEsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEyLCBmaWcuY2FwPSJWaXN1YWxpemluZyBtaXNzaW5nIGRhdGEgcGF0dGVybnMgaW4gdGhlIHJhdyBBbWVzIGhvdXNpbmcgZGF0YS4ifQp2aXNfbWlzcyhBbWVzSG91c2luZzo6YW1lc19yYXcsIGNsdXN0ZXIgPSBUUlVFKQpgYGAKCiMjIyBJbXB1dGF0aW9uCgpgYGB7ciBlbmdpbmVlcmluZy1tZWFuLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9tZWRpYW5pbXB1dGUoR3JfTGl2X0FyZWEpCmBgYAoKYGBge3IgZW5naW5lZXJpbmcta25uLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9rbm5pbXB1dGUoYWxsX3ByZWRpY3RvcnMoKSwgbmVpZ2hib3JzID0gNikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1iYWdnaW5nLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9iYWdpbXB1dGUoYWxsX3ByZWRpY3RvcnMoKSkKYGBgCgpGaWd1cmUgMy41OgoKYGBge3IgZW5naW5lZXJpbmctaW1wdXRhdGlvbi1leGFtcGxlcywgZWNobz1UUlVFLCBmaWcuY2FwPSJDb21wYXJpc29uIG9mIHRocmVlIGRpZmZlcmVudCBpbXB1dGF0aW9uIG1ldGhvZHMuIFRoZSByZWQgcG9pbnRzIHJlcHJlc2VudCBhY3R1YWwgdmFsdWVzIHdoaWNoIHdlcmUgcmVtb3ZlZCBhbmQgbWFkZSBtaXNzaW5nIGFuZCB0aGUgYmx1ZSBwb2ludHMgcmVwcmVzZW50IHRoZSBpbXB1dGVkIHZhbHVlcy4gRXN0aW1hdGVkIHN0YXRpc3RpYyBpbXB1dGF0aW9uIG1ldGhvZHMgKGkuZS4gbWVhbiwgbWVkaWFuKSBtZXJlbHkgcHJlZGljdCB0aGUgc2FtZSB2YWx1ZSBmb3IgZWFjaCBvYnNlcnZhdGlvbiBhbmQgY2FuIHJlZHVjZSB0aGUgc2lnbmFsIGJldHdlZW4gYSBmZWF0dXJlIGFuZCB0aGUgcmVzcG9uc2U7IHdoZXJlYXMgS05OIGFuZCB0cmVlLWJhc2VkIHByb2NlZHVyZXMgdGVuZCB0byBtYWludGFpbiB0aGUgZmVhdHVyZSBkaXN0cmlidXRpb24gYW5kIHJlbGF0aW9uc2hpcC4ifQppbXB1dGVfYW1lcyA8LSBhbWVzX3RyYWluCnNldC5zZWVkKDEyMykKaW5kZXggPC0gc2FtcGxlKHNlcV9hbG9uZyhpbXB1dGVfYW1lcyRHcl9MaXZfQXJlYSksIDUwKQphY3R1YWxzIDwtIGFtZXNfdHJhaW5baW5kZXgsIF0KaW1wdXRlX2FtZXMkR3JfTGl2X0FyZWFbaW5kZXhdIDwtIE5BCgpwMSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gaW1wdXRlX2FtZXMsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGFscGhhID0gLjIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBhY3R1YWxzLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9sb2cxMChsaW1pdHMgPSBjKDMwMCwgNTAwMCkpICsKICBzY2FsZV95X2xvZzEwKGxpbWl0cyA9IGMoMTAwMDAsIDUwMDAwMCkpICsKICBnZ3RpdGxlKCJBY3R1YWwgdmFsdWVzIikKCiMgTWVhbiBpbXB1dGF0aW9uCm1lYW5fanVpY2VkIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGltcHV0ZV9hbWVzKSAlPiUKICBzdGVwX21lYW5pbXB1dGUoR3JfTGl2X0FyZWEpICU+JQogIHByZXAodHJhaW5pbmcgPSBpbXB1dGVfYW1lcywgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKQptZWFuX2ltcHV0ZSA8LSBtZWFuX2p1aWNlZFtpbmRleCwgXQogIApwMiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gYWN0dWFscywgYWVzKEdyX0xpdl9BcmVhLCBTYWxlX1ByaWNlKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1lYW5faW1wdXRlLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJibHVlIikgKwogIHNjYWxlX3hfbG9nMTAobGltaXRzID0gYygzMDAsIDUwMDApKSArCiAgc2NhbGVfeV9sb2cxMChsaW1pdHMgPSBjKDEwMDAwLCA1MDAwMDApKSArCiAgZ2d0aXRsZSgiTWVhbiBJbXB1dGF0aW9uIikKCiMgS05OIGltcHV0YXRpb24Ka25uX2p1aWNlZCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBpbXB1dGVfYW1lcykgJT4lCiAgc3RlcF9rbm5pbXB1dGUoR3JfTGl2X0FyZWEpICU+JQogIHByZXAodHJhaW5pbmcgPSBpbXB1dGVfYW1lcywgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKQprbm5faW1wdXRlIDwtIGtubl9qdWljZWRbaW5kZXgsIF0KICAKcDMgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGFjdHVhbHMsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBrbm5faW1wdXRlLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJibHVlIikgKwogIHNjYWxlX3hfbG9nMTAobGltaXRzID0gYygzMDAsIDUwMDApKSArCiAgc2NhbGVfeV9sb2cxMChsaW1pdHMgPSBjKDEwMDAwLCA1MDAwMDApKSArCiAgZ2d0aXRsZSgiS05OIEltcHV0YXRpb24iKQoKIyBCYWdnZWQgaW1wdXRhdGlvbgpiYWdnZWRfanVpY2VkIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGltcHV0ZV9hbWVzKSAlPiUKICBzdGVwX2JhZ2ltcHV0ZShHcl9MaXZfQXJlYSkgJT4lCiAgcHJlcCh0cmFpbmluZyA9IGltcHV0ZV9hbWVzLCByZXRhaW4gPSBUUlVFKSAlPiUKICBqdWljZSgpCmJhZ2dlZF9pbXB1dGUgPC0gYmFnZ2VkX2p1aWNlZFtpbmRleCwgXQogIApwNCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gYWN0dWFscywgYWVzKEdyX0xpdl9BcmVhLCBTYWxlX1ByaWNlKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGJhZ2dlZF9pbXB1dGUsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGNvbG9yID0gImJsdWUiKSArCiAgc2NhbGVfeF9sb2cxMChsaW1pdHMgPSBjKDMwMCwgNTAwMCkpICsKICBzY2FsZV95X2xvZzEwKGxpbWl0cyA9IGMoMTAwMDAsIDUwMDAwMCkpICsKICBnZ3RpdGxlKCJCYWdnZWQgVHJlZXMgSW1wdXRhdGlvbiIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbnJvdyA9IDIpCmBgYAoKIyMgRmVhdHVyZSBmaWx0ZXJpbmcgCgpGaWd1cmUgMy42IChnZW5lcmF0ZWQgZnJvbSBiZW5jaG1hcmsgZGF0YSBwZXJmb3JtZWQgaW4gdGhlIHBhc3QpOgoKYGBge3IgZW5naW5lZXJpbmctYWNjdXJhY3ktY29tcGFyaXNvbiwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJUZXN0IHNldCBSTVNFIHByb2ZpbGVzIHdoZW4gbm9uLWluZm9ybWF0aXZlIHByZWRpY3RvcnMgYXJlIGFkZGVkLiJ9Cm1vZGVsX3Jlc3VsdHMgPC0gcmVhZF9jc3YoImRhdGEvZmVhdHVyZS1zZWxlY3Rpb24taW1wYWN0cy1yZXN1bHRzLmNzdiIpICU+JQogIG11dGF0ZSh0eXBlID0gY2FzZV93aGVuKAogICAgbW9kZWwgJWluJSBjKCJsbSIsICJwY3IiLCAicGxzIiwgImdsbW5ldCIsICJsYXNzbyIpIH4gIkxpbmVhciBtb2RlbHMiLAogICAgbW9kZWwgJWluJSBjKCJlYXJ0aCIsICJzdm1MaW5lYXIiLCAibm4iKSB+ICJOb24tbGluZWFyIE1vZGVscyIsCiAgICBUUlVFIH4gIlRyZWUtYmFzZWQgTW9kZWxzIgogICkpICU+JQogIG11dGF0ZShtb2RlbCA9IGNhc2Vfd2hlbigKICAgIG1vZGVsID09ICJsbSIgfiAiTGluZWFyIHJlZ3Jlc3Npb24iLAogICAgbW9kZWwgPT0gImVhcnRoIiB+ICJNdWx0aXZhcmlhdGUgYWRhcHRpdmUgcmVncmVzc2lvbiBzcGxpbmVzIiwKICAgIG1vZGVsID09ICJnYm0iIH4gIkdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmVzIiwKICAgIG1vZGVsID09ICJnbG1uZXQiIH4gIkVsYXN0aWMgbmV0IiwKICAgIG1vZGVsID09ICJsYXNzbyIgfiAiTGFzc28iLAogICAgbW9kZWwgPT0gIm5uIiB+ICJOZXVyYWwgbmV0IiwKICAgIG1vZGVsID09ICJwY3IiIH4gIlByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbiIsCiAgICBtb2RlbCA9PSAicGxzIiB+ICJQYXJ0aWFsIGxlYXN0IHNxdWFyZXMiLAogICAgbW9kZWwgPT0gInJhbmdlciIgfiAiUmFuZG9tIGZvcmVzdCIsCiAgICBUUlVFIH4gIlN1cHBvcnQgdmVjdG9yIG1hY2hpbmUiCiAgKSkKCmdncGxvdChtb2RlbF9yZXN1bHRzLCBhZXMoTklQLCBSTVNFLCBjb2xvciA9IG1vZGVsLCBsdHkgPSBtb2RlbCkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH4gdHlwZSwgbnJvdyA9IDEpICsKICB4bGFiKCJOdW1iZXIgb2YgYWRkaXRpb25hbCBub24taW5mb3JtYXRpdmUgcHJlZGljdG9ycyIpCmBgYAoKRmlndXJlIDMuNyAoZ2VuZXJhdGVkIGZyb20gYmVuY2htYXJrIGRhdGEgcGVyZm9ybWVkIGluIHRoZSBwYXN0KToKCmBgYHtyIGVuZ2luZWVyaW5nLWltcGFjdC1vbi10aW1lLCBlY2hvPVRSVUUsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0zLjUsIGZpZy5jYXA9IkltcGFjdCBpbiBtb2RlbCB0cmFpbmluZyB0aW1lIGFzIG5vbi1pbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIGFyZSBhZGRlZC4ifQptb2RlbF9yZXN1bHRzICU+JQogIGdyb3VwX2J5KG1vZGVsKSAlPiUKICBtdXRhdGUoCiAgICB0aW1lX2ltcGFjdCA9IHRpbWUgLyBmaXJzdCh0aW1lKSwKICAgIHRpbWVfaW1wYWN0ID0gdGltZV9pbXBhY3QgLSAxCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKE5JUCwgdGltZV9pbXBhY3QsIGNvbG9yID0gbW9kZWwsIGx0eSA9IG1vZGVsKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGZhY2V0X3dyYXAofiB0eXBlLCBucm93ID0gMSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCJQZXJjZW50IGluY3JlYXNlIGluIHRyYWluaW5nIHRpbWUiLCAKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAgIHhsYWIoIk51bWJlciBvZiBhZGRpdGlvbmFsIG5vbi1pbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1uenZ9CmNhcmV0OjpuZWFyWmVyb1ZhcihhbWVzX3RyYWluLCBzYXZlTWV0cmljcyA9IFRSVUUpICU+JSAKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAKICBmaWx0ZXIobnp2KQpgYGAKCiMjIE51bWVyaWMgZmVhdHVyZSBlbmdpbmVlcmluZwoKYGBge3IgZW5naW5lZXJpbmctbm9ybWFsaXppbmd9CiMgTm9ybWFsaXplIGFsbCBudW1lcmljIGNvbHVtbnMKcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljKCkpICAgICAgICAgICAgICAgICAKYGBgCgpGaWd1cmUgMy44OgoKYGBge3IgZW5naW5lZXJpbmctc3RhbmRhcmRpemluZywgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IlN0YW5kYXJkaXppbmcgZmVhdHVyZXMgYWxsb3dzIGFsbCBmZWF0dXJlcyB0byBiZSBjb21wYXJlZCBvbiBhIGNvbW1vbiB2YWx1ZSBzY2FsZSByZWdhcmRsZXNzIG9mIHRoZWlyIHJlYWwgdmFsdWUgZGlmZmVyZW5jZXMuIn0Kc2V0LnNlZWQoMTIzKQp4MSA8LSB0aWJibGUoCiAgdmFyaWFibGUgPSAieDEiLAogIGBSZWFsIHZhbHVlYCA9IHJ1bmlmKDI1LCBtaW4gPSAtMzAsIG1heCA9IDUpLAogIGBTdGFuZGFyZGl6ZWQgdmFsdWVgID0gc2NhbGUoYFJlYWwgdmFsdWVgKSAlPiUgYXMubnVtZXJpYygpCikKCnNldC5zZWVkKDQ1NikKeDIgPC0gdGliYmxlKAogIHZhcmlhYmxlID0gIngyIiwKICBgUmVhbCB2YWx1ZWAgPSBybG5vcm0oMjUsIGxvZygyNSkpLAogIGBTdGFuZGFyZGl6ZWQgdmFsdWVgID0gc2NhbGUoYFJlYWwgdmFsdWVgKSAlPiUgYXMubnVtZXJpYygpCikKCnNldC5zZWVkKDc4OSkKeDMgPC0gdGliYmxlKAogIHZhcmlhYmxlID0gIngzIiwKICBgUmVhbCB2YWx1ZWAgPSBybm9ybSgyNSwgMTUwLCAxNSksCiAgYFN0YW5kYXJkaXplZCB2YWx1ZWAgPSBzY2FsZShgUmVhbCB2YWx1ZWApICU+JSBhcy5udW1lcmljKCkKKQoKeDEgJT4lCiAgYmluZF9yb3dzKHgyKSAlPiUKICBiaW5kX3Jvd3MoeDMpICU+JQogIGdhdGhlcihrZXksIHZhbHVlLCAtdmFyaWFibGUpICU+JQogIG11dGF0ZSh2YXJpYWJsZSA9IGZhY3Rvcih2YXJpYWJsZSwgbGV2ZWxzID0gYygieDMiLCAieDIiLCAieDEiKSkpICU+JQogIGdncGxvdChhZXModmFsdWUsIHZhcmlhYmxlKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IC42KSArCiAgICBmYWNldF93cmFwKH4ga2V5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgeWxhYigiRmVhdHVyZSIpICsKICAgIHhsYWIoIlZhbHVlIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGFuZGFyZGl6aW5nLXJlY2lwZXN9CmFtZXNfcmVjaXBlICU+JQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpCmBgYAoKCiMjIENhdGVnb3JpY2FsIGZlYXR1cmUgZW5naW5lZXJpbmcKCmBgYHtyIGVuZ2luZWVyaW5nLW92ZXJhbGwtcXVhbC1sZXZlbHN9CmNvdW50KGFtZXNfdHJhaW4sIE5laWdoYm9yaG9vZCkgJT4lIGFycmFuZ2UobikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zY3JlZW4tcG9yY2h9CmNvdW50KGFtZXNfdHJhaW4sIFNjcmVlbl9Qb3JjaCkgJT4lIGFycmFuZ2UobikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy10YmR9CiMgTHVtcCBsZXZlbHMgZm9yIHR3byBmZWF0dXJlcwpsdW1waW5nIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfb3RoZXIoTmVpZ2hib3Job29kLCB0aHJlc2hvbGQgPSAwLjAxLCAKICAgICAgICAgICAgIG90aGVyID0gIm90aGVyIikgJT4lCiAgc3RlcF9vdGhlcihTY3JlZW5fUG9yY2gsIHRocmVzaG9sZCA9IDAuMSwgCiAgICAgICAgICAgICBvdGhlciA9ICI+MCIpCgojIEFwcGx5IHRoaXMgYmx1ZSBwcmludCAtLT4geW91IHdpbGwgbGVhcm4gYWJvdXQgdGhpcyBhdCAKIyB0aGUgZW5kIG9mIHRoZSBjaGFwdGVyCmFwcGx5XzJfdHJhaW5pbmcgPC0gcHJlcChsdW1waW5nLCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pICU+JQogIGJha2UoYW1lc190cmFpbikKCiMgTmV3IGRpc3RyaWJ1dGlvbiBvZiBOZWlnaGJvcmhvb2QKY291bnQoYXBwbHlfMl90cmFpbmluZywgTmVpZ2hib3Job29kKSAlPiUgYXJyYW5nZShuKQoKIyBOZXcgZGlzdHJpYnV0aW9uIG9mIFNjcmVlbl9Qb3JjaApjb3VudChhcHBseV8yX3RyYWluaW5nLCBTY3JlZW5fUG9yY2gpICU+JSBhcnJhbmdlKG4pCmBgYAoKYGBge3IgZW5naW5lZXJpbmctb25lLWhvdCwgZWNobz1UUlVFLCBmaWcuY2FwPSdFaWdodCBvYnNlcnZhdGlvbnMgY29udGFpbmluZyBhIGNhdGVnb3JpY2FsIGZlYXR1cmUgWCBhbmQgdGhlIGRpZmZlcmVuY2UgaW4gaG93IG9uZS1ob3QgYW5kIGR1bW15IGVuY29kaW5nIHRyYW5zZm9ybXMgdGhpcyBmZWF0dXJlLicsIG91dC5oZWlnaHQ9Ijk5JSIsIG91dC53aWR0aD0iOTklIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9vaGUtdnMtZHVtbXkucG5nIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy10YmQyfQojIEx1bXAgbGV2ZWxzIGZvciB0d28gZmVhdHVyZXMKcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCBvbmVfaG90ID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1sYWJlbC1lbmNvZGluZ30KIyBPcmlnaW5hbCBjYXRlZ29yaWVzCmNvdW50KGFtZXNfdHJhaW4sIE1TX1N1YkNsYXNzKQoKIyBMYWJlbCBlbmNvZGVkCnJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfaW50ZWdlcihNU19TdWJDbGFzcykgJT4lCiAgcHJlcChhbWVzX3RyYWluKSAlPiUKICBiYWtlKGFtZXNfdHJhaW4pICU+JQogIGNvdW50KE1TX1N1YkNsYXNzKQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLXF1YWwtdmFyaWFibGVzfQphbWVzX3RyYWluICU+JSBzZWxlY3QoY29udGFpbnMoIlF1YWwiKSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1vcmRpbmFsLWVuY29kaW5nfQojIE9yaWdpbmFsIGNhdGVnb3JpZXMKY291bnQoYW1lc190cmFpbiwgT3ZlcmFsbF9RdWFsKQoKIyBMYWJlbCBlbmNvZGVkCnJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfaW50ZWdlcihPdmVyYWxsX1F1YWwpICU+JQogIHByZXAoYW1lc190cmFpbikgJT4lCiAgYmFrZShhbWVzX3RyYWluKSAlPiUKICBjb3VudChPdmVyYWxsX1F1YWwpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctdGFyZ2V0LWVuY29kaW5nLCBlY2hvPVRSVUUsIGZpZy5jYXA9J0V4YW1wbGUgb2YgdGFyZ2V0IGVuY29kaW5nIHRoZSBOZWlnaGJvcmhvb2QgZmVhdHVyZSBvZiB0aGUgQW1lcyBob3VzaW5nIGRhdGEgc2V0Lid9CmFtZXNfdHJhaW4gJT4lCiAgZ3JvdXBfYnkoTmVpZ2hib3Job29kKSAlPiUKICBzdW1tYXJpemUoYEF2ZyBTYWxlX1ByaWNlYCA9IG1lYW4oU2FsZV9QcmljZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgaGVhZCgxMCkgJT4lCiAga2FibGUoY2FwdGlvbiA9ICJFeGFtcGxlIG9mIHRhcmdldCBlbmNvZGluZyB0aGUgTmVpZ2hib3Job29kIGZlYXR1cmUgb2YgdGhlIEFtZXMgaG91c2luZyBkYXRhIHNldC4iKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1wcm9wb3J0aW9uLWVuY29kaW5nLCBlY2hvPVRSVUUsIGZpZy5jYXA9J0V4YW1wbGUgb2YgY2F0ZWdvcmljYWwgcHJvcG9ydGlvbiBlbmNvZGluZyB0aGUgTmVpZ2hib3Job29kIGZlYXR1cmUgb2YgdGhlIEFtZXMgaG91c2luZyBkYXRhIHNldC4nfQphbWVzX3RyYWluICU+JQogIGNvdW50KE5laWdoYm9yaG9vZCkgJT4lCiAgbXV0YXRlKFByb3BvcnRpb24gPSBuIC8gc3VtKG4pKSAlPiUKICBzZWxlY3QoLW4pICU+JQogIGhlYWQoMTApICU+JQogIGthYmxlKGNhcHRpb24gPSAnRXhhbXBsZSBvZiBjYXRlZ29yaWNhbCBwcm9wb3J0aW9uIGVuY29kaW5nIHRoZSBOZWlnaGJvcmhvb2QgZmVhdHVyZSBvZiB0aGUgQW1lcyBob3VzaW5nIGRhdGEgc2V0LicpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUUlVFKQpgYGAKCiMjIERpbWVuc2lvbiByZWR1Y3Rpb24KCmBgYHtyIGVuZ2luZWVyaW5nLXBjYX0KcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpKSAlPiUKICBzdGVwX3BjYShhbGxfbnVtZXJpYygpLCB0aHJlc2hvbGQgPSAuOTUpCmBgYAoKIyMgUHJvcGVyIGltcGxlbWVudGF0aW9uCgpGaWd1cmUgMy4xMDoKCmBgYHtyIGVuZ2luZWVyaW5nLW1pbmltaXplLWxlYWthZ2UsIGVjaG89VFJVRSwgZmlnLmNhcD0iUGVyZm9ybWluZyBmZWF0dXJlIGVuZ2luZWVyaW5nIHByZXByb2Nlc3Npbmcgd2l0aGluIGVhY2ggcmVzYW1wbGUgaGVscHMgdG8gbWluaW1pemUgZGF0YSBsZWFrYWdlLiIsIG91dC53aWR0aD0nOTAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9taW5pbWl6ZS1sZWFrYWdlLnBuZyIpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctc3RlcDEtcmVjaXBlfQpibHVlcHJpbnQgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgICU+JQogIHN0ZXBfaW50ZWdlcihtYXRjaGVzKCJRdWFsfENvbmR8UUN8UXUiKSkgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9wY2EoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKQogIApibHVlcHJpbnQKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGVwMi1wcmVwYXJlfQpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pCnByZXBhcmUKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGVwMy1iYWtlfQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQpiYWtlZF90cmFpbgpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLWtubi1ibHVlcHJpbnR9CmJsdWVwcmludCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzX3RyYWluKSAlPiUKICBzdGVwX256dihhbGxfbm9taW5hbCgpKSAlPiUKICBzdGVwX2ludGVnZXIobWF0Y2hlcygiUXVhbHxDb25kfFFDfFF1IikpICU+JQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpLCBvbmVfaG90ID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1rbm4td2l0aC1ibHVlcHJpbnR9CiMgU3BlY2lmeSByZXNhbXBsaW5nIHBsYW4KY3YgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgCiAgbnVtYmVyID0gMTAsIAogIHJlcGVhdHMgPSA1CikKCiMgQ29uc3RydWN0IGdyaWQgb2YgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQoayA9IHNlcSgyLCAyNSwgYnkgPSAxKSkKCiMgVHVuZSBhIGtubiBtb2RlbCB1c2luZyBncmlkIHNlYXJjaAprbm5fZml0MiA8LSB0cmFpbigKICBibHVlcHJpbnQsIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAia25uIiwgCiAgdHJDb250cm9sID0gY3YsIAogIHR1bmVHcmlkID0gaHlwZXJfZ3JpZCwKICBtZXRyaWMgPSAiUk1TRSIKKQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLWtubi13aXRoLWJsdWVwcmludC1hc3Nlc3MsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iUmVzdWx0cyBmcm9tIHRoZSBzYW1lIGdyaWQgc2VhcmNoIHBlcmZvcm1lZCBpbiBTZWN0aW9uIDIuNyBidXQgd2l0aCBmZWF0dXJlIGVuZ2luZWVyaW5nIHBlcmZvcm1lZCB3aXRoaW4gZWFjaCByZXNhbXBsZS4ifQojIHByaW50IG1vZGVsIHJlc3VsdHMKa25uX2ZpdDIKIyBwbG90IGNyb3NzIHZhbGlkYXRpb24gcmVzdWx0cwpnZ3Bsb3Qoa25uX2ZpdDIpCmBgYA==