Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks.
Data splitting
Figure 2.2:
knitr::include_graphics("images/data_split.png")
Simple random sampling
# Using base R
set.seed(123) # for reproducibility
index_1 <- sample(1:nrow(ames), round(nrow(ames) * 0.7))
train_1 <- ames[index_1, ]
test_1 <- ames[-index_1, ]
# Using caret package
set.seed(123) # for reproducibility
index_2 <- createDataPartition(ames$Sale_Price, p = 0.7,
list = FALSE)
train_2 <- ames[index_2, ]
test_2 <- ames[-index_2, ]
# Using rsample package
set.seed(123) # for reproducibility
split_1 <- initial_split(ames, prop = 0.7)
train_3 <- training(split_1)
test_3 <- testing(split_1)
# Using h2o package
split_2 <- h2o.splitFrame(ames.h2o, ratios = 0.7,
seed = 123)
train_4 <- split_2[[1]]
test_4 <- split_2[[2]]
Figure 2.3:
p1 <- ggplot(train_1, aes(x = Sale_Price)) +
geom_density(trim = TRUE) +
geom_density(data = test_1, trim = TRUE, col = "red") +
ggtitle("Base R")
p2 <- ggplot(train_2, aes(x = Sale_Price)) +
geom_density(trim = TRUE) +
geom_density(data = test_2, trim = TRUE, col = "red") +
theme(axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank()) +
ggtitle("caret")
p3 <- ggplot(train_3, aes(x = Sale_Price)) +
geom_density(trim = TRUE) +
geom_density(data = test_3, trim = TRUE, col = "red") +
theme(axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank()) +
ggtitle("rsample")
p4 <- ggplot(as.data.frame(train_4), aes(x = Sale_Price)) +
geom_density(trim = TRUE) +
geom_density(data = as.data.frame(test_4), trim = TRUE, col = "red") +
theme(axis.title.y = element_blank(),
axis.ticks.y = element_blank(),
axis.text.y = element_blank()) +
ggtitle("h2o")
# Side-by-side plots
gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 1)
# clean up
rm(p1, p2, p3, p4)
Stratified sampling
# orginal response distribution
table(churn$Attrition) %>% prop.table()
No Yes
0.8387755 0.1612245
# stratified sampling with the rsample package
set.seed(123)
split_strat <- initial_split(churn, prop = 0.7,
strata = "Attrition")
train_strat <- training(split_strat)
test_strat <- testing(split_strat)
# consistent response ratio between train & test
table(train_strat$Attrition) %>% prop.table()
No Yes
0.838835 0.161165
table(test_strat$Attrition) %>% prop.table()
No Yes
0.8386364 0.1613636
Many engines
lm_lm <- lm(Sale_Price ~ ., data = ames)
lm_glm <- glm(Sale_Price ~ ., data = ames, family = gaussian)
lm_caret <- train(Sale_Price ~ ., data = ames, method = "lm")
Table 1:
Table 1: Syntax for computing predicted class probabilities with direct engines.
Linear discriminant analysis |
MASS |
predict(obj) |
Generalized linear model |
stats |
predict(obj, type = "response") |
Mixture discriminant analysis |
mda |
predict(obj, type = "posterior") |
Decision tree |
rpart |
predict(obj, type = "prob") |
Random Forest |
ranger |
predict(obj)$predictions |
Gradient boosting machine |
gbm |
predict(obj, type = "response", n.trees) |
Resampling methods
k-fold cross validation
Figure 2.4:
knitr::include_graphics("images/cv.png")
Figure 2.5:
cv <- vfold_cv(mtcars, 10)
cv_plot <- cv$splits %>%
purrr::map2_dfr(seq_along(cv$splits), ~ mtcars %>% mutate(
Resample = paste0("Fold_", stringr::str_pad(.y, 2, pad = 0)),
ID = row_number(),
Data = ifelse(ID %in% .x$in_id, "Training", "Validation"))
) %>%
ggplot(aes(Resample, ID, fill = Data)) +
geom_tile() +
scale_fill_manual(values = c("#f2f2f2", "#AAAAAA")) +
scale_y_reverse("Observation ID", breaks = 1:nrow(mtcars), expand = c(0, 0)) +
scale_x_discrete(NULL, expand = c(0, 0)) +
theme_classic() +
theme(legend.title=element_blank())
cv_plot
This code chunk is for illustrative purposes only; it is not designed to execute.
# Example using h2o
h2o.cv <- h2o.glm(
x = x,
y = y,
training_frame = ames.h2o,
nfolds = 10 # perform 10-fold CV
)
Bootstrapping
Figure 2.6:
knitr::include_graphics("images/bootstrap-scheme.png")
Figure 2.7:
boots <- rsample::bootstraps(mtcars, 10)
boots_plot <- boots$splits %>%
purrr::map2_dfr(seq_along(boots$splits), ~ mtcars %>%
mutate(
Resample = paste0("Bootstrap_", stringr::str_pad(.y, 2, pad = 0)),
ID = row_number()
) %>%
group_by(ID) %>%
mutate(Replicates = factor(sum(ID == .x$in_id)))) %>%
ggplot(aes(Resample, ID, fill = Replicates)) +
geom_tile() +
scale_fill_manual(values = c("#FFFFFF", "#F5F5F5", "#C8C8C8", "#A0A0A0", "#707070", "#505050", "#000000")) +
scale_y_reverse("Observation ID", breaks = 1:nrow(mtcars), expand = c(0, 0)) +
scale_x_discrete(NULL, expand = c(0, 0)) +
theme_classic() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
ggtitle("Bootstrap sampling")
cv_plot <- cv_plot +
ggtitle("10-fold cross validation") +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
cowplot::plot_grid(boots_plot, cv_plot, align = "h", nrow = 1)
# clean up
rm(boots, boots_plot, cv_plot)
Bias variance trade-off
Bias
Figure 2.8:
# Simulate some nonlinear monotonic data
set.seed(123) # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
filter(x < 4.5)
# Single model fit
bias_model <- lm(y ~ I(x^3), data = df)
df$predictions <- predict(bias_model, df)
p1 <- ggplot(df, aes(x, y)) +
geom_point(alpha = .3) +
geom_line(aes(x, predictions), size = 1.5, color = "dodgerblue") +
scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
ggtitle("Single biased model fit")
# Bootstrapped model fit
bootstrap_n <- 25
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
set.seed(i) # for reproducibility
index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
df_sim <- df[index, ]
fit <- lm(y ~ I(x^3), data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("model", i)
df_sim$ob <- index
bootstrap_results <- rbind(bootstrap_results, df_sim)
}
p2 <- ggplot(bootstrap_results, aes(x, predictions, color = model)) +
geom_line(show.legend = FALSE, size = .5) +
scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
ggtitle("25 biased models fit to bootstrap samples")
gridExtra::grid.arrange(p1, p2, nrow = 1)
Variance
Figure 2.9:
# Simulate some nonlinear monotonic data
set.seed(123) # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
filter(x < 4.5)
# Single model fit
variance_model <- knnreg(y ~ x, k = 3, data = df)
df$predictions <- predict(variance_model, df)
p1 <- ggplot(df, aes(x, y)) +
geom_point(alpha = .3) +
geom_line(aes(x, predictions), size = 1.5, color = "dodgerblue") +
scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
ggtitle("Single high variance model fit")
# Bootstrapped model fit
bootstrap_n <- 25
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
set.seed(i) # for reproducibility
index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
df_sim <- df[index, ]
fit <- knnreg(y ~ x, k = 3, data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("model", i)
df_sim$ob <- index
bootstrap_results <- rbind(bootstrap_results, df_sim)
}
p2 <- ggplot(bootstrap_results, aes(x, predictions, color = model)) +
geom_line(show.legend = FALSE) +
scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
ggtitle("25 high variance models fit to bootstrap samples")
gridExtra::grid.arrange(p1, p2, nrow = 1)
Hyperparameter tuning
Figure 2.10:
k_results <- NULL
k <- c(2, 5, 10, 20, 50, 150)
# Fit many different models
for(i in seq_along(k)) {
df_sim <- df
fit <- knnreg(y ~ x, k = k[i], data = df_sim)
df_sim$predictions <- predict(fit, df_sim)
df_sim$model <- paste0("k = ", stringr::str_pad(k[i], 3, pad = " "))
k_results <- rbind(k_results, df_sim)
}
ggplot() +
geom_point(data = df, aes(x, y), alpha = .3) +
geom_line(data = k_results, aes(x, predictions), color = "dodgerblue", size = 1.5) +
scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
facet_wrap(~ model)
Figure 2.11:
cv <- trainControl(method = "repeatedcv", number = 10, repeats = 10, returnResamp = "all")
hyper_grid <- expand.grid(k = seq(2, 150, by = 2))
knn_fit <- train(x ~ y, data = df, method = "knn", trControl = cv, tuneGrid = hyper_grid)
ggplot() +
geom_line(data = knn_fit$results, aes(k, RMSE)) +
geom_point(data = knn_fit$results, aes(k, RMSE)) +
geom_point(data = filter(knn_fit$results, k == as.numeric(knn_fit$bestTune)),
aes(k, RMSE),
shape = 21,
fill = "yellow",
color = "black",
stroke = 1,
size = 2) +
scale_y_continuous("Error (RMSE)")
# clean up
rm(cv, hyper_grid, knn_fit)
Classification models
Figure 2.12
knitr::include_graphics("images/confusion-matrix.png")
Figure 2.13:
knitr::include_graphics("images/confusion-matrix2.png")
Figure 2.14:
library(plotROC)
# Generate data
set.seed(123)
response <- rbinom(200, size = 1, prob = .5)
set.seed(123)
curve1 <- rnorm(200, mean = response, sd = .40)
set.seed(123)
curve2 <- rnorm(200, mean = response, sd = .75)
set.seed(123)
curve3 <- rnorm(200, mean = response, sd = 2.0)
df <- tibble(response, curve1, curve2, curve3)
ggplot(df) +
geom_roc(aes(d = response, m = curve1), n.cuts = 0, size = .5, color = "#1E56F9") +
geom_roc(aes(d = response, m = curve2), n.cuts = 0, size = .5, color = "#7194F9") +
geom_roc(aes(d = response, m = curve3), n.cuts = 0, size = .5, color = "#B6C7F9") +
geom_abline(lty = 'dashed') +
annotate("text", x = .48, y = .46, label = c("No better than guessing"),
vjust = 1, angle = 34) +
annotate("text", x = .3, y = .6, label = c("Ok"),
vjust = 1, angle = 33, color = "#B6C7F9") +
annotate("text", x = .20, y = .75, label = c("Better"),
vjust = 1, angle = 33, color = "#7194F9") +
annotate("text", x = .10, y = .96, label = c("Best"),
vjust = 1, angle = 33, color = "#1E56F9") +
xlab("False positive rate") +
ylab("True positive rate")
Putting the processes together
# Stratified sampling with the rsample package
set.seed(123)
split <- initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- training(split)
ames_test <- testing(split)
This grid search takes approximately 3.5 minutes:
# Specify resampling strategy
cv <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 5
)
# Create grid of hyperparameter values
hyper_grid <- expand.grid(k = seq(2, 25, by = 1))
# Tune a knn model using grid search
knn_fit <- train(
Sale_Price ~ .,
data = ames_train,
method = "knn",
trControl = cv,
tuneGrid = hyper_grid,
metric = "RMSE"
)
Figure 2.15:
# Print and plot the CV results
knn_fit
k-Nearest Neighbors
2053 samples
80 predictor
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 5 times)
Summary of sample sizes: 1848, 1848, 1848, 1847, 1849, 1847, ...
Resampling results across tuning parameters:
k RMSE Rsquared MAE
2 47844.53 0.6538046 31002.72
3 45875.79 0.6769848 29784.69
4 44529.50 0.6949240 28992.48
5 43944.65 0.7026947 28738.66
6 43645.76 0.7079683 28553.50
7 43439.07 0.7129916 28617.80
8 43658.35 0.7123254 28769.16
9 43799.74 0.7128924 28905.50
10 44058.76 0.7108900 29061.68
11 44304.91 0.7091949 29197.78
12 44565.82 0.7073437 29320.81
13 44798.10 0.7056491 29475.33
14 44966.27 0.7051474 29561.70
15 45188.86 0.7036000 29731.56
16 45376.09 0.7027152 29860.67
17 45557.94 0.7016254 29974.44
18 45666.30 0.7021351 30018.59
19 45836.33 0.7013026 30105.50
20 46044.44 0.6997198 30235.80
21 46242.59 0.6983978 30367.95
22 46441.87 0.6969620 30481.48
23 46651.66 0.6953968 30611.48
24 46788.22 0.6948738 30681.97
25 46980.13 0.6928159 30777.25
RMSE was used to select the optimal model using the smallest value.
The final value used for the model was k = 7.
ggplot(knn_fit)
Shutdown H2O and clean up
h2o.shutdown(prompt = FALSE)
[1] TRUE
rm(list = ls())
LS0tCnRpdGxlOiAiQ2hhcHRlciAyOiBNb2RlbGluZyBQcm9jZXNzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIAoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rcy4KCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgbWVzc2FnZSA9IEZBTFNFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBjb2xsYXBzZSA9IFRSVUUsCiAgY2FjaGUgPSBGQUxTRQopCgojIHVuZGVybHlpbmcgY29kZSBkZXBlbmRlbmNpZXMKbGlicmFyeSh0aWR5dmVyc2UpCgojIFNldCB0aGUgZ3JhcGhpY2FsIHRoZW1lCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQpgYGAKCkZpZ3VyZSAyLjE6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW1vZGVsaW5nLXByb2Nlc3MsIGVjaG89VFJVRSwgb3V0LmhlaWdodD0iOTAlIiwgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwPSJHZW5lcmFsIHByZWRpY3RpdmUgbWFjaGluZSBsZWFybmluZyBwcm9jZXNzLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvbW9kZWxpbmdfcHJvY2Vzcy5wbmciKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKClRoaXMgY2hhcHRlciBsZXZlcmFnZXMgdGhlIGZvbGxvd2luZyBwYWNrYWdlcy4KCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtcGtnLXByZXJlcXMsIHJlc3VsdHM9ImhpZGUifQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAgIyBmb3IgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAgICMgZm9yIGF3ZXNvbWUgZ3JhcGhpY3MKCiMgTW9kZWxpbmcgcHJvY2VzcyBwYWNrYWdlcwpsaWJyYXJ5KHJzYW1wbGUpICAgIyBmb3IgcmVzYW1wbGluZyBwcm9jZWR1cmVzCmxpYnJhcnkoY2FyZXQpICAgICAjIGZvciByZXNhbXBsaW5nIGFuZCBtb2RlbCB0cmFpbmluZwpsaWJyYXJ5KGgybykgICAgICAgIyBmb3IgcmVzYW1wbGluZyBhbmQgbW9kZWwgdHJhaW5pbmcKCiMgaDJvIHNldC11cCAKaDJvLm5vX3Byb2dyZXNzKCkgICMgdHVybiBvZmYgaDJvIHByb2dyZXNzIGJhcnMKaDJvLmluaXQoKSAgICAgICAgICMgbGF1bmNoIGgybwpgYGAKCkRhdGEgdXNlZDoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtbG9hZC1kYXRhfQojIEFtZXMgaG91c2luZyBkYXRhCmFtZXMgPC0gQW1lc0hvdXNpbmc6Om1ha2VfYW1lcygpCmFtZXMuaDJvIDwtIGFzLmgybyhhbWVzKQoKIyBKb2IgYXR0cml0aW9uIGRhdGEKY2h1cm4gPC0gcnNhbXBsZTo6YXR0cml0aW9uICU+JSAKICBtdXRhdGVfaWYoaXMub3JkZXJlZCwgLmZ1bnMgPSBmYWN0b3IsIG9yZGVyZWQgPSBGQUxTRSkKY2h1cm4uaDJvIDwtIGFzLmgybyhjaHVybikKYGBgCgoKIyMgRGF0YSBzcGxpdHRpbmcKCkZpZ3VyZSAyLjI6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXNwbGl0LCBlY2hvPVRSVUUsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iU3BsaXR0aW5nIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzLiIsIG91dC5oZWlnaHQ9IjMwJSIsIG91dC53aWR0aD0iMzAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9kYXRhX3NwbGl0LnBuZyIpCmBgYAoKIyMjIFNpbXBsZSByYW5kb20gc2FtcGxpbmcKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mtc3BsaXR0aW5nLWFwcGxpZWR9CiMgVXNpbmcgYmFzZSBSCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQppbmRleF8xIDwtIHNhbXBsZSgxOm5yb3coYW1lcyksIHJvdW5kKG5yb3coYW1lcykgKiAwLjcpKQp0cmFpbl8xIDwtIGFtZXNbaW5kZXhfMSwgXQp0ZXN0XzEgIDwtIGFtZXNbLWluZGV4XzEsIF0KCiMgVXNpbmcgY2FyZXQgcGFja2FnZQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKaW5kZXhfMiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGFtZXMkU2FsZV9QcmljZSwgcCA9IDAuNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UpCnRyYWluXzIgPC0gYW1lc1tpbmRleF8yLCBdCnRlc3RfMiAgPC0gYW1lc1staW5kZXhfMiwgXQoKIyBVc2luZyByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNwbGl0XzEgIDwtIGluaXRpYWxfc3BsaXQoYW1lcywgcHJvcCA9IDAuNykKdHJhaW5fMyAgPC0gdHJhaW5pbmcoc3BsaXRfMSkKdGVzdF8zICAgPC0gdGVzdGluZyhzcGxpdF8xKQoKIyBVc2luZyBoMm8gcGFja2FnZQpzcGxpdF8yIDwtIGgyby5zcGxpdEZyYW1lKGFtZXMuaDJvLCByYXRpb3MgPSAwLjcsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAxMjMpCnRyYWluXzQgPC0gc3BsaXRfMltbMV1dCnRlc3RfNCAgPC0gc3BsaXRfMltbMl1dCmBgYAoKRmlndXJlIDIuMzoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtZGlzdHJpYnV0aW9ucywgZWNobz1UUlVFLCBmaWcuY2FwPSJUcmFpbmluZyAoYmxhY2spIHZzLiB0ZXN0IChyZWQpIHJlc3BvbnNlIGRpc3RyaWJ1dGlvbi4iLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD05fQpwMSA8LSBnZ3Bsb3QodHJhaW5fMSwgYWVzKHggPSBTYWxlX1ByaWNlKSkgKyAKICAgIGdlb21fZGVuc2l0eSh0cmltID0gVFJVRSkgKyAKICAgIGdlb21fZGVuc2l0eShkYXRhID0gdGVzdF8xLCB0cmltID0gVFJVRSwgY29sID0gInJlZCIpICsKICBnZ3RpdGxlKCJCYXNlIFIiKQoKcDIgPC0gZ2dwbG90KHRyYWluXzIsIGFlcyh4ID0gU2FsZV9QcmljZSkpICsgCiAgICBnZW9tX2RlbnNpdHkodHJpbSA9IFRSVUUpICsgCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHRlc3RfMiwgdHJpbSA9IFRSVUUsIGNvbCA9ICJyZWQiKSArCiAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZ2d0aXRsZSgiY2FyZXQiKSAKCnAzIDwtIGdncGxvdCh0cmFpbl8zLCBhZXMoeCA9IFNhbGVfUHJpY2UpKSArIAogICAgZ2VvbV9kZW5zaXR5KHRyaW0gPSBUUlVFKSArIAogICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSB0ZXN0XzMsIHRyaW0gPSBUUlVFLCBjb2wgPSAicmVkIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIGdndGl0bGUoInJzYW1wbGUiKQoKcDQgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHJhaW5fNCksIGFlcyh4ID0gU2FsZV9QcmljZSkpICsgCiAgICBnZW9tX2RlbnNpdHkodHJpbSA9IFRSVUUpICsgCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IGFzLmRhdGEuZnJhbWUodGVzdF80KSwgdHJpbSA9IFRSVUUsIGNvbCA9ICJyZWQiKSArCiAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZ2d0aXRsZSgiaDJvIikKCiMgU2lkZS1ieS1zaWRlIHBsb3RzCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBucm93ID0gMSkKCiMgY2xlYW4gdXAKcm0ocDEsIHAyLCBwMywgcDQpCmBgYAoKIyMjIFN0cmF0aWZpZWQgc2FtcGxpbmcKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mtc3RyYXRpZmllZC1zYW1wbGluZ30KIyBvcmdpbmFsIHJlc3BvbnNlIGRpc3RyaWJ1dGlvbgp0YWJsZShjaHVybiRBdHRyaXRpb24pICU+JSBwcm9wLnRhYmxlKCkKCiMgc3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdF9zdHJhdCAgPC0gaW5pdGlhbF9zcGxpdChjaHVybiwgcHJvcCA9IDAuNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmF0YSA9ICJBdHRyaXRpb24iKQp0cmFpbl9zdHJhdCAgPC0gdHJhaW5pbmcoc3BsaXRfc3RyYXQpCnRlc3Rfc3RyYXQgICA8LSB0ZXN0aW5nKHNwbGl0X3N0cmF0KQoKIyBjb25zaXN0ZW50IHJlc3BvbnNlIHJhdGlvIGJldHdlZW4gdHJhaW4gJiB0ZXN0CnRhYmxlKHRyYWluX3N0cmF0JEF0dHJpdGlvbikgJT4lIHByb3AudGFibGUoKQp0YWJsZSh0ZXN0X3N0cmF0JEF0dHJpdGlvbikgJT4lIHByb3AudGFibGUoKQpgYGAKCgojIyMgTWFueSBmb3JtdWxhIGludGVyZmFjZXMKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLiAKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtZm9ybXVsYS1pbnRlcmZhY2UsIGV2YWw9RkFMU0V9CiMgU2FsZSBwcmljZSBhcyBmdW5jdGlvbiBvZiBuZWlnaGJvcmhvb2QgYW5kIHllYXIgc29sZAptb2RlbF9mbihTYWxlX1ByaWNlIH4gTmVpZ2hib3Job29kICsgWWVhcl9Tb2xkLCAKICAgICAgICAgZGF0YSA9IGFtZXMpCgojIFZhcmlhYmxlcyArIGludGVyYWN0aW9ucwptb2RlbF9mbihTYWxlX1ByaWNlIH4gTmVpZ2hib3Job29kICsgWWVhcl9Tb2xkICsgCiAgICAgICAgICAgTmVpZ2hib3Job29kOlllYXJfU29sZCwgZGF0YSA9IGFtZXMpCgojIFNob3J0aGFuZCBmb3IgYWxsIHByZWRpY3RvcnMKbW9kZWxfZm4oU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzKQoKIyBJbmxpbmUgZnVuY3Rpb25zIC8gdHJhbnNmb3JtYXRpb25zCm1vZGVsX2ZuKGxvZzEwKFNhbGVfUHJpY2UpIH4gbnMoTG9uZ2l0dWRlLCBkZiA9IDMpICsgCiAgICAgICAgICAgbnMoTGF0aXR1ZGUsIGRmID0gMyksIGRhdGEgPSBhbWVzKQpgYGAKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1ub24tZm9ydW1hbC1pbnRlcmZhY2UsIGV2YWw9RkFMU0V9CiMgVXNlIHNlcGFyYXRlIGlucHV0cyBmb3IgWCBhbmQgWQpmZWF0dXJlcyA8LSBjKCJZZWFyX1NvbGQiLCAiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIikKbW9kZWxfZm4oeCA9IGFtZXNbLCBmZWF0dXJlc10sIHkgPSBhbWVzJFNhbGVfUHJpY2UpCmBgYAoKVGhpcyBjb2RlIGNodW5rIGlzIGZvciBpbGx1c3RyYXRpdmUgcHVycG9zZXMgb25seTsgaXQgaXMgbm90IGRlc2lnbmVkIHRvIGV4ZWN1dGUuCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW5hbWUtaW50ZXJmYWNlLCBldmFsPUZBTFNFfQptb2RlbF9mbigKICB4ID0gYygiWWVhcl9Tb2xkIiwgIkxvbmdpdHVkZSIsICJMYXRpdHVkZSIpLAogIHkgPSAiU2FsZV9QcmljZSIsCiAgZGF0YSA9IGFtZXMuaDJvCikKYGBgCgojIyMgTWFueSBlbmdpbmVzCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW1hbnktZW5naW5lcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbG1fbG0gPC0gbG0oU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzKQpsbV9nbG0gPC0gZ2xtKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lcywgZmFtaWx5ID0gZ2F1c3NpYW4pCmxtX2NhcmV0IDwtIHRyYWluKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lcywgbWV0aG9kID0gImxtIikKYGBgCgpUYWJsZSAxOgoKfCBBbGdvcml0aG0gfCBQYWNrYWdlIHwgQ29kZSB8CnwgLS0tLS0tLS0tIHwgLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0gfAp8IExpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgfCBfX01BU1NfXyB8IGBwcmVkaWN0KG9iailgIHwKfCBHZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwgfAlfX3N0YXRzX18JfCBgcHJlZGljdChvYmosIHR5cGUgPSAicmVzcG9uc2UiKWAgfAp8IE1peHR1cmUgZGlzY3JpbWluYW50IGFuYWx5c2lzIHwJX19tZGFfXwl8CWBwcmVkaWN0KG9iaiwgdHlwZSA9ICJwb3N0ZXJpb3IiKWAgfAp8IERlY2lzaW9uIHRyZWUgfAlfX3JwYXJ0X18JfAlgcHJlZGljdChvYmosIHR5cGUgPSAicHJvYiIpYCB8CnwgUmFuZG9tIEZvcmVzdCB8CV9fcmFuZ2VyX18gfAlgcHJlZGljdChvYmopJHByZWRpY3Rpb25zYCB8CnwgR3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSB8CV9fZ2JtX18gfAlgcHJlZGljdChvYmosIHR5cGUgPSAicmVzcG9uc2UiLCBuLnRyZWVzKWAgfAoKVGFibGU6IFRhYmxlIDE6IFN5bnRheCBmb3IgY29tcHV0aW5nIHByZWRpY3RlZCBjbGFzcyBwcm9iYWJpbGl0aWVzIHdpdGggZGlyZWN0IGVuZ2luZXMuCgoKIyMgUmVzYW1wbGluZyBtZXRob2RzCgojIyMgX2tfLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbgoKRmlndXJlIDIuNDoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY3YtZGlhZ3JhbSwgZWNobz1UUlVFLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gb2YgdGhlIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHByb2Nlc3MuIiwgb3V0LndpZHRoPSc5MCUnLCBvdXQuaGVpZ2h0PSc5MCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2N2LnBuZyIpCmBgYAoKRmlndXJlIDIuNToKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY3YsIGVjaG89VFJVRSwgZmlnLmNhcD0iMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG9uIDMyIG9ic2VydmF0aW9ucy4gRWFjaCBvYnNlcnZhdGlvbiBpcyB1c2VkIG9uY2UgZm9yIHZhbGlkYXRpb24gYW5kIG5pbmUgdGltZXMgZm9yIHRyYWluaW5nLiJ9CmN2IDwtIHZmb2xkX2N2KG10Y2FycywgMTApCmN2X3Bsb3QgPC0gY3Ykc3BsaXRzICU+JQogIHB1cnJyOjptYXAyX2RmcihzZXFfYWxvbmcoY3Ykc3BsaXRzKSwgfiBtdGNhcnMgJT4lIG11dGF0ZSgKICAgIFJlc2FtcGxlID0gcGFzdGUwKCJGb2xkXyIsIHN0cmluZ3I6OnN0cl9wYWQoLnksIDIsIHBhZCA9IDApKSwKICAgIElEID0gcm93X251bWJlcigpLAogICAgRGF0YSA9IGlmZWxzZShJRCAlaW4lIC54JGluX2lkLCAiVHJhaW5pbmciLCAiVmFsaWRhdGlvbiIpKQogICAgKSAlPiUKICBnZ3Bsb3QoYWVzKFJlc2FtcGxlLCBJRCwgZmlsbCA9IERhdGEpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNmMmYyZjIiLCAiI0FBQUFBQSIpKSArCiAgc2NhbGVfeV9yZXZlcnNlKCJPYnNlcnZhdGlvbiBJRCIsIGJyZWFrcyA9IDE6bnJvdyhtdGNhcnMpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKQoKY3ZfcGxvdApgYGAKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1kaXJlY3QtY3YsIGV2YWw9RkFMU0V9CiMgRXhhbXBsZSB1c2luZyBoMm8KaDJvLmN2IDwtIGgyby5nbG0oCiAgeCA9IHgsIAogIHkgPSB5LCAKICB0cmFpbmluZ19mcmFtZSA9IGFtZXMuaDJvLAogIG5mb2xkcyA9IDEwICAjIHBlcmZvcm0gMTAtZm9sZCBDVgopCmBgYAoKIyMjIEJvb3RzdHJhcHBpbmcKCkZpZ3VyZSAyLjY6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWJvb3RzdHJhcHNjaGVtZSwgZWNobz1UUlVFLCBvdXQud2lkdGg9JzcwJScsIG91dC5oZWlnaHQ9JzcwJScsIGZpZy5jYXA9IklsbHVzdHJhdGlvbiBvZiB0aGUgYm9vdHN0cmFwcGluZyBwcm9jZXNzLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvYm9vdHN0cmFwLXNjaGVtZS5wbmciKQpgYGAKCkZpZ3VyZSAyLjc6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXNhbXBsaW5nLWNvbXBhcmlzb24sIGVjaG89VFJVRSwgZmlnLmNhcD0iQm9vdHN0cmFwIHNhbXBsaW5nIChsZWZ0KSB2ZXJzdXMgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIChyaWdodCkgb24gMzIgb2JzZXJ2YXRpb25zLiBGb3IgYm9vdHN0cmFwIHNhbXBsaW5nLCB3YXJuaW5nPUZBTFNFLCB0aGUgb2JzZXJ2YXRpb25zIHRoYXQgaGF2ZSB6ZXJvIHJlcGxpY2F0aW9ucyAod2hpdGUpIGFyZSB0aGUgb3V0LW9mLWJhZyBvYnNlcnZhdGlvbnMgdXNlZCBmb3IgdmFsaWRhdGlvbi4iLCBtZXNzYWdlPUZBTFNFfQpib290cyA8LSByc2FtcGxlOjpib290c3RyYXBzKG10Y2FycywgMTApCmJvb3RzX3Bsb3QgPC0gYm9vdHMkc3BsaXRzICU+JQogIHB1cnJyOjptYXAyX2RmcihzZXFfYWxvbmcoYm9vdHMkc3BsaXRzKSwgfiBtdGNhcnMgJT4lIAogICAgICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgICBSZXNhbXBsZSA9IHBhc3RlMCgiQm9vdHN0cmFwXyIsIHN0cmluZ3I6OnN0cl9wYWQoLnksIDIsIHBhZCA9IDApKSwKICAgICAgICAgICAgICAgSUQgPSByb3dfbnVtYmVyKCkKICAgICAgICAgICAgICkgJT4lCiAgICAgICAgICAgICBncm91cF9ieShJRCkgJT4lCiAgICAgICAgICAgICBtdXRhdGUoUmVwbGljYXRlcyA9IGZhY3RvcihzdW0oSUQgPT0gLngkaW5faWQpKSkpICU+JQogIGdncGxvdChhZXMoUmVzYW1wbGUsIElELCBmaWxsID0gUmVwbGljYXRlcykpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0ZGRkZGRiIsICIjRjVGNUY1IiwgIiNDOEM4QzgiLCAiI0EwQTBBMCIsICIjNzA3MDcwIiwgIiM1MDUwNTAiLCAiIzAwMDAwMCIpKSArCiAgc2NhbGVfeV9yZXZlcnNlKCJPYnNlcnZhdGlvbiBJRCIsIGJyZWFrcyA9IDE6bnJvdyhtdGNhcnMpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArCiAgZ2d0aXRsZSgiQm9vdHN0cmFwIHNhbXBsaW5nIikgCgpjdl9wbG90IDwtIGN2X3Bsb3QgKyAKICBnZ3RpdGxlKCIxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24iKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKCmNvd3Bsb3Q6OnBsb3RfZ3JpZChib290c19wbG90LCBjdl9wbG90LCBhbGlnbiA9ICJoIiwgbnJvdyA9IDEpCgojIGNsZWFuIHVwCnJtKGJvb3RzLCBib290c19wbG90LCBjdl9wbG90KQpgYGAKCiMjIEJpYXMgdmFyaWFuY2UgdHJhZGUtb2ZmCgojIyMgQmlhcwoKRmlndXJlIDIuODoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtYmlhcy1tb2RlbCwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMCwgZmlnLmNhcD0iQSBiaWFzZWQgcG9seW5vbWlhbCBtb2RlbCBmaXQgdG8gYSBzaW5nbGUgZGF0YSBzZXQgZG9lcyBub3QgY2FwdHVyZSB0aGUgdW5kZXJseWluZyBub24tbGluZWFyLCBub24tbW9ub3RvbmljIGRhdGEgc3RydWN0dXJlIChsZWZ0KS4gIE1vZGVscyBmaXQgdG8gMjUgYm9vdHN0cmFwcGVkIHJlcGxpY2F0ZXMgb2YgdGhlIGRhdGEgYXJlIHVuZGVydGVycmVkIGJ5IHRoZSBub2lzZSBhbmQgZ2VuZXJhdGVzIHNpbWlsYXIsIHlldCBzdGlsbCBiaWFzZWQsIHByZWRpY3Rpb25zIChyaWdodCkuIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTaW11bGF0ZSBzb21lIG5vbmxpbmVhciBtb25vdG9uaWMgZGF0YQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKeCA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMiAqIHBpLCBsZW5ndGggPSA1MDApCnkgPC0gc2luKHgpICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IDAuMykKZGYgPC0gZGF0YS5mcmFtZSh4LCB5KSAlPiUKICBmaWx0ZXIoeCA8IDQuNSkKCiMgU2luZ2xlIG1vZGVsIGZpdApiaWFzX21vZGVsIDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZikKZGYkcHJlZGljdGlvbnMgPC0gcHJlZGljdChiaWFzX21vZGVsLCBkZikKcDEgPC0gZ2dwbG90KGRmLCBhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjMpICsKICBnZW9tX2xpbmUoYWVzKHgsIHByZWRpY3Rpb25zKSwgc2l6ZSA9IDEuNSwgY29sb3IgPSAiZG9kZ2VyYmx1ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMS43NSwgMS43NSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA0LjUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiU2luZ2xlIGJpYXNlZCBtb2RlbCBmaXQiKQoKIyBCb290c3RyYXBwZWQgbW9kZWwgZml0CmJvb3RzdHJhcF9uIDwtIDI1CmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICBzZXQuc2VlZChpKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CiAgaW5kZXggPC0gc2FtcGxlKHNlcV9sZW4obnJvdyhkZikpLCBucm93KGRmKSwgcmVwbGFjZSA9IFRSVUUpCiAgZGZfc2ltIDwtIGRmW2luZGV4LCBdCiAgZml0IDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAyIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zLCBjb2xvciA9IG1vZGVsKSkgKwogIGdlb21fbGluZShzaG93LmxlZ2VuZCA9IEZBTFNFLCBzaXplID0gLjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMS43NSwgMS43NSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA0LjUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiMjUgYmlhc2VkIG1vZGVscyBmaXQgdG8gYm9vdHN0cmFwIHNhbXBsZXMiKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBucm93ID0gMSkKYGBgCgojIyMgVmFyaWFuY2UKCkZpZ3VyZSAyLjk6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXZhcmlhbmNlLW1vZGVsLCBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCBmaWcuY2FwPSJBIGhpZ2ggdmFyaWFuY2UgX2tfLW5lYXJlc3QgbmVpZ2hib3IgbW9kZWwgZml0IHRvIGEgc2luZ2xlIGRhdGEgc2V0IGNhcHR1cmVzIHRoZSB1bmRlcmx5aW5nIG5vbi1saW5lYXIsIG5vbi1tb25vdG9uaWMgZGF0YSBzdHJ1Y3R1cmUgd2VsbCBidXQgYWxzbyBvdmVyZml0cyB0byBpbmRpdmlkdWFsIGRhdGEgcG9pbnRzIChsZWZ0KS4gIE1vZGVscyBmaXQgdG8gMjUgYm9vdHN0cmFwcGVkIHJlcGxpY2F0ZXMgb2YgdGhlIGRhdGEgYXJlIGRldGVycmVkIGJ5IHRoZSBub2lzZSBhbmQgZ2VuZXJhdGUgaGlnaGx5IHZhcmlhYmxlIHByZWRpY3Rpb25zIChyaWdodCkuIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTaW11bGF0ZSBzb21lIG5vbmxpbmVhciBtb25vdG9uaWMgZGF0YQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKeCA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMiAqIHBpLCBsZW5ndGggPSA1MDApCnkgPC0gc2luKHgpICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IDAuMykKZGYgPC0gZGF0YS5mcmFtZSh4LCB5KSAlPiUKICBmaWx0ZXIoeCA8IDQuNSkKCiMgU2luZ2xlIG1vZGVsIGZpdAp2YXJpYW5jZV9tb2RlbCA8LSBrbm5yZWcoeSB+IHgsIGsgPSAzLCBkYXRhID0gZGYpCmRmJHByZWRpY3Rpb25zIDwtIHByZWRpY3QodmFyaWFuY2VfbW9kZWwsIGRmKQpwMSA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMykgKwogIGdlb21fbGluZShhZXMoeCwgcHJlZGljdGlvbnMpLCBzaXplID0gMS41LCBjb2xvciA9ICJkb2RnZXJibHVlIikgKwogIHNjYWxlX3lfY29udGludW91cygiUmVzcG9uc2UiLCBsaW1pdHMgPSBjKC0xLjc1LCAxLjc1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDQuNSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBnZ3RpdGxlKCJTaW5nbGUgaGlnaCB2YXJpYW5jZSBtb2RlbCBmaXQiKQoKIyBCb290c3RyYXBwZWQgbW9kZWwgZml0CmJvb3RzdHJhcF9uIDwtIDI1CmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICBzZXQuc2VlZChpKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CiAgaW5kZXggPC0gc2FtcGxlKHNlcV9sZW4obnJvdyhkZikpLCBucm93KGRmKSwgcmVwbGFjZSA9IFRSVUUpCiAgZGZfc2ltIDwtIGRmW2luZGV4LCBdCiAgZml0IDwtIGtubnJlZyh5IH4geCwgayA9IDMsIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAyIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zLCBjb2xvciA9IG1vZGVsKSkgKwogIGdlb21fbGluZShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJSZXNwb25zZSIsIGxpbWl0cyA9IGMoLTEuNzUsIDEuNzUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNC41KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIjI1IGhpZ2ggdmFyaWFuY2UgbW9kZWxzIGZpdCB0byBib290c3RyYXAgc2FtcGxlcyIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCiMjIyBIeXBlcnBhcmFtZXRlciB0dW5pbmcKCkZpZ3VyZSAyLjEwOgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1rbm4tb3B0aW9ucywgZmlnLndpZHRoPTEwLCBlY2hvPVRSVUUsIGZpZy5jYXA9Il9rXy1uZWFyZXN0IG5laWdoYm9yIG1vZGVsIHdpdGggZGlmZmVyaW5nIHZhbHVlcyBmb3IgX2tfLiIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmtfcmVzdWx0cyA8LSBOVUxMCmsgPC0gYygyLCA1LCAxMCwgMjAsIDUwLCAxNTApCgojIEZpdCBtYW55IGRpZmZlcmVudCBtb2RlbHMKZm9yKGkgaW4gc2VxX2Fsb25nKGspKSB7CiAgZGZfc2ltIDwtIGRmCiAgZml0IDwtIGtubnJlZyh5IH4geCwgayA9IGtbaV0sIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgiayA9ICIsIHN0cmluZ3I6OnN0cl9wYWQoa1tpXSwgMywgcGFkID0gIiAiKSkKICBrX3Jlc3VsdHMgPC0gcmJpbmQoa19yZXN1bHRzLCBkZl9zaW0pCn0KCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkZiwgYWVzKHgsIHkpLCBhbHBoYSA9IC4zKSArCiAgZ2VvbV9saW5lKGRhdGEgPSBrX3Jlc3VsdHMsIGFlcyh4LCBwcmVkaWN0aW9ucyksIGNvbG9yID0gImRvZGdlcmJsdWUiLCBzaXplID0gMS41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJSZXNwb25zZSIsIGxpbWl0cyA9IGMoLTEuNzUsIDEuNzUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNC41KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGZhY2V0X3dyYXAofiBtb2RlbCkKYGBgCgpGaWd1cmUgMi4xMToKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mta25uLXR1bmUsIGZpZy5oZWlnaHQ9MywgZWNobz1UUlVFLCBmaWcuY2FwPSJSZXN1bHRzIGZyb20gYSBncmlkIHNlYXJjaCBmb3IgYSBfa18tbmVhcmVzdCBuZWlnaGJvciBtb2RlbCBhc3Nlc3NpbmcgdmFsdWVzIGZvciBfa18gcmFuZ2luZyBmcm9tIDItMTUwLiAgV2Ugc2VlIGhpZ2ggZXJyb3IgdmFsdWVzIGR1ZSB0byBoaWdoIG1vZGVsIHZhcmlhbmNlIHdoZW4gX2tfIGlzIHNtYWxsIGFuZCB3ZSBhbHNvIHNlZSBoaWdoIGVycm9ycyB2YWx1ZXMgZHVlIHRvIGhpZ2ggbW9kZWwgYmlhcyB3aGVuIF9rXyBpcyBsYXJnZS4gIFRoZSBvcHRpbWFsIG1vZGVsIGlzIGZvdW5kIGF0IF9rXyA9IDQ2LiJ9CmN2IDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMTAsIHJldHVyblJlc2FtcCA9ICJhbGwiKQpoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMiwgMTUwLCBieSA9IDIpKQprbm5fZml0IDwtIHRyYWluKHggfiB5LCBkYXRhID0gZGYsIG1ldGhvZCA9ICJrbm4iLCB0ckNvbnRyb2wgPSBjdiwgdHVuZUdyaWQgPSBoeXBlcl9ncmlkKQoKZ2dwbG90KCkgKwogIGdlb21fbGluZShkYXRhID0ga25uX2ZpdCRyZXN1bHRzLCBhZXMoaywgUk1TRSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBrbm5fZml0JHJlc3VsdHMsIGFlcyhrLCBSTVNFKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihrbm5fZml0JHJlc3VsdHMsIGsgPT0gYXMubnVtZXJpYyhrbm5fZml0JGJlc3RUdW5lKSksCiAgICAgICAgICAgICBhZXMoaywgUk1TRSksCiAgICAgICAgICAgICBzaGFwZSA9IDIxLAogICAgICAgICAgICAgZmlsbCA9ICJ5ZWxsb3ciLAogICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICAgc3Ryb2tlID0gMSwKICAgICAgICAgICAgIHNpemUgPSAyKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJFcnJvciAoUk1TRSkiKQoKIyBjbGVhbiB1cApybShjdiwgaHlwZXJfZ3JpZCwga25uX2ZpdCkKYGBgCgojIyMgQ2xhc3NpZmljYXRpb24gbW9kZWxzCgpGaWd1cmUgMi4xMgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1jb25mdXNpb24tbWF0cml4MSwgZWNobz1UUlVFLCBvdXQuaGVpZ2h0PSIxMDAlIiwgb3V0LndpZHRoPSIxMDAlIiwgZmlnLmNhcD0iQ29uZnVzaW9uIG1hdHJpeCBhbmQgcmVsYXRpb25zaGlwcyB0byB0ZXJtcyBzdWNoIGFzIHRydWUtcG9zaXRpdmUgYW5kIGZhbHNlLW5lZ2F0aXZlLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvY29uZnVzaW9uLW1hdHJpeC5wbmciKQpgYGAKCgpGaWd1cmUgMi4xMzoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY29uZnVzaW9uLW1hdHJpeDIsIGVjaG89VFJVRSwgb3V0LmhlaWdodD0iNTAlIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJFeGFtcGxlIGNvbmZ1c2lvbiBtYXRyaXguIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9jb25mdXNpb24tbWF0cml4Mi5wbmciKQpgYGAKCkZpZ3VyZSAyLjE0OgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1yb2MsIGVjaG89VFJVRSwgZmlnLmNhcD0iUk9DIGN1cnZlLiIsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NX0KbGlicmFyeShwbG90Uk9DKQoKIyBHZW5lcmF0ZSBkYXRhCnNldC5zZWVkKDEyMykKcmVzcG9uc2UgPC0gcmJpbm9tKDIwMCwgc2l6ZSA9IDEsIHByb2IgPSAuNSkKCnNldC5zZWVkKDEyMykKY3VydmUxICAgPC0gcm5vcm0oMjAwLCBtZWFuID0gcmVzcG9uc2UsIHNkID0gLjQwKQoKc2V0LnNlZWQoMTIzKQpjdXJ2ZTIgICA8LSBybm9ybSgyMDAsIG1lYW4gPSByZXNwb25zZSwgc2QgPSAuNzUpCgpzZXQuc2VlZCgxMjMpCmN1cnZlMyAgIDwtIHJub3JtKDIwMCwgbWVhbiA9IHJlc3BvbnNlLCBzZCA9IDIuMCkKCmRmIDwtIHRpYmJsZShyZXNwb25zZSwgY3VydmUxLCBjdXJ2ZTIsIGN1cnZlMykKCmdncGxvdChkZikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUxKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjMUU1NkY5IikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUyKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjNzE5NEY5IikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUzKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjQjZDN0Y5IikgKwogIGdlb21fYWJsaW5lKGx0eSA9ICdkYXNoZWQnKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gLjQ4LCB5ID0gLjQ2LCBsYWJlbCA9IGMoIk5vIGJldHRlciB0aGFuIGd1ZXNzaW5nIiksIAogICAgICAgICAgIHZqdXN0ID0gMSwgYW5nbGUgPSAzNCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4zLCB5ID0gLjYsIGxhYmVsID0gYygiT2siKSwgCiAgICAgICAgICAgdmp1c3QgPSAxLCBhbmdsZSA9IDMzLCBjb2xvciA9ICIjQjZDN0Y5IikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4yMCwgeSA9IC43NSwgbGFiZWwgPSBjKCJCZXR0ZXIiKSwgCiAgICAgICAgICAgdmp1c3QgPSAxLCBhbmdsZSA9IDMzLCBjb2xvciA9ICIjNzE5NEY5IikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4xMCwgeSA9IC45NiwgbGFiZWwgPSBjKCJCZXN0IiksIAogICAgICAgICAgIHZqdXN0ID0gMSwgYW5nbGUgPSAzMywgY29sb3IgPSAiIzFFNTZGOSIpICsKICB4bGFiKCJGYWxzZSBwb3NpdGl2ZSByYXRlIikgKwogIHlsYWIoIlRydWUgcG9zaXRpdmUgcmF0ZSIpCmBgYAoKIyMgUHV0dGluZyB0aGUgcHJvY2Vzc2VzIHRvZ2V0aGVyCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWV4YW1wbGUtcHJvY2Vzcy1zcGxpdHRpbmd9CiMgU3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdCA8LSBpbml0aWFsX3NwbGl0KGFtZXMsIHByb3AgPSAwLjcsIHN0cmF0YSA9ICJTYWxlX1ByaWNlIikKYW1lc190cmFpbiAgPC0gdHJhaW5pbmcoc3BsaXQpCmFtZXNfdGVzdCAgIDwtIHRlc3Rpbmcoc3BsaXQpCmBgYAoKVGhpcyBncmlkIHNlYXJjaCB0YWtlcyBhcHByb3hpbWF0ZWx5IDMuNSBtaW51dGVzOgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1leGFtcGxlLXByb2Nlc3MtdHJhaW5pbmd9CiMgU3BlY2lmeSByZXNhbXBsaW5nIHN0cmF0ZWd5CmN2IDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsIAogIG51bWJlciA9IDEwLCAKICByZXBlYXRzID0gNQopCgojIENyZWF0ZSBncmlkIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcwpoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMiwgMjUsIGJ5ID0gMSkpCgojIFR1bmUgYSBrbm4gbW9kZWwgdXNpbmcgZ3JpZCBzZWFyY2gKa25uX2ZpdCA8LSB0cmFpbigKICBTYWxlX1ByaWNlIH4gLiwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJrbm4iLCAKICB0ckNvbnRyb2wgPSBjdiwgCiAgdHVuZUdyaWQgPSBoeXBlcl9ncmlkLAogIG1ldHJpYyA9ICJSTVNFIgopCmBgYAoKRmlndXJlIDIuMTU6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWV4YW1wbGUtcHJvY2Vzcy1hc3Nlc3MsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iUmVzdWx0cyBmcm9tIGEgZ3JpZCBzZWFyY2ggZm9yIGEgX2tfLW5lYXJlc3QgbmVpZ2hib3IgbW9kZWwgb24gdGhlIEFtZXMgaG91c2luZyBkYXRhIGFzc2Vzc2luZyB2YWx1ZXMgZm9yIF9rXyByYW5naW5nIGZyb20gMi0yNS4ifQojIFByaW50IGFuZCBwbG90IHRoZSBDViByZXN1bHRzCmtubl9maXQKZ2dwbG90KGtubl9maXQpCmBgYAoKU2h1dGRvd24gSDJPIGFuZCBjbGVhbiB1cAoKYGBge3J9Cmgyby5zaHV0ZG93bihwcm9tcHQgPSBGQUxTRSkKcm0obGlzdCA9IGxzKCkpCmBgYAoK