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:
# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())
# Set global knitr chunk options
knitr::opts_chunk$set(
fig.align = "center",
fig.height = 3.5
)
library(tidyverse)
ames <- AmesHousing::make_ames()
Prerequisites
This chapter leverages the following packages:
# Helper packages
library(recipes) # for feature engineering
# Modeling packages
library(glmnet) # for implementing regularized regression
library(caret) # for automating the tuning process
# Model interpretability packages
library(vip) # for variable importance
To illustrate various regularization concepts we’ll continue working with the ames_train
and ames_test
data sets:
library(rsample)
# Stratified sampling with the rsample package
set.seed(123) # for reproducibility
split <- initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train <- training(split)
ames_test <- testing(split)
Why regularize?
Figure 6.1:
ames_sub <- ames_train %>%
filter(Gr_Liv_Area > 1000 & Gr_Liv_Area < 3000) %>%
sample_frac(.5)
model1 <- lm(Sale_Price ~ Gr_Liv_Area, data = ames_sub)
model1 %>%
broom::augment() %>%
ggplot(aes(Gr_Liv_Area, Sale_Price)) +
geom_segment(aes(x = Gr_Liv_Area, y = Sale_Price,
xend = Gr_Liv_Area, yend = .fitted),
alpha = 0.3) +
geom_point(size = 1, color = "red") +
geom_smooth(se = FALSE, method = "lm") +
scale_y_continuous(labels = scales::dollar)
Ridge penalty
Figure 6.2:
boston_train_x <- model.matrix(cmedv ~ ., pdp::boston)[, -1]
boston_train_y <- pdp::boston$cmedv
# model
boston_ridge <- glmnet::glmnet(
x = boston_train_x,
y = boston_train_y,
alpha = 0
)
lam <- boston_ridge$lambda %>%
as.data.frame() %>%
mutate(penalty = boston_ridge$a0 %>% names()) %>%
rename(lambda = ".")
results <- boston_ridge$beta %>%
as.matrix() %>%
as.data.frame() %>%
rownames_to_column() %>%
gather(penalty, coefficients, -rowname) %>%
left_join(lam)
result_labels <- results %>%
group_by(rowname) %>%
filter(lambda == min(lambda)) %>%
ungroup() %>%
top_n(5, wt = abs(coefficients)) %>%
mutate(var = paste0("x", 1:5))
ggplot() +
geom_line(data = results, aes(lambda, coefficients, group = rowname, color = rowname), show.legend = FALSE) +
scale_x_log10() +
geom_text(data = result_labels, aes(lambda, coefficients, label = var, color = rowname), nudge_x = -.06, show.legend = FALSE)
Lasso penalty
Figure 6.3:
boston_train_x <- model.matrix(cmedv ~ ., pdp::boston)[, -1]
boston_train_y <- pdp::boston$cmedv
# model
boston_lasso <- glmnet::glmnet(
x = boston_train_x,
y = boston_train_y,
alpha = 1
)
lam <- boston_lasso$lambda %>%
as.data.frame() %>%
mutate(penalty = boston_lasso$a0 %>% names()) %>%
rename(lambda = ".")
results <- boston_lasso$beta %>%
as.matrix() %>%
as.data.frame() %>%
rownames_to_column() %>%
gather(penalty, coefficients, -rowname) %>%
left_join(lam)
result_labels <- results %>%
group_by(rowname) %>%
filter(lambda == min(lambda)) %>%
ungroup() %>%
top_n(5, wt = abs(coefficients)) %>%
mutate(var = paste0("x", 1:5))
ggplot() +
geom_line(data = results, aes(lambda, coefficients, group = rowname, color = rowname), show.legend = FALSE) +
scale_x_log10() +
geom_text(data = result_labels, aes(lambda, coefficients, label = var, color = rowname), nudge_x = -.05, show.legend = FALSE)
Elastic nets
Figure 6.4:
# model
boston_elastic <- glmnet::glmnet(
x = boston_train_x,
y = boston_train_y,
alpha = .2
)
lam <- boston_elastic$lambda %>%
as.data.frame() %>%
mutate(penalty = boston_elastic$a0 %>% names()) %>%
rename(lambda = ".")
results <- boston_elastic$beta %>%
as.matrix() %>%
as.data.frame() %>%
rownames_to_column() %>%
gather(penalty, coefficients, -rowname) %>%
left_join(lam)
result_labels <- results %>%
group_by(rowname) %>%
filter(lambda == min(lambda)) %>%
ungroup() %>%
top_n(5, wt = abs(coefficients)) %>%
mutate(var = paste0("x", 1:5))
ggplot() +
geom_line(data = results, aes(lambda, coefficients, group = rowname, color = rowname), show.legend = FALSE) +
scale_x_log10() +
geom_text(data = result_labels, aes(lambda, coefficients, label = var, color = rowname), nudge_x = -.05, show.legend = FALSE)
Implementation
# Create training feature matrices
# we use model.matrix(...)[, -1] to discard the intercept
X <- model.matrix(Sale_Price ~ ., ames_train)[, -1]
# transform y with log transformation
Y <- log(ames_train$Sale_Price)
# Apply ridge regression to ames data
ridge <- glmnet(
x = X,
y = Y,
alpha = 0
)
plot(ridge, xvar = "lambda")
# lambdas applied to penalty parameter
ridge$lambda %>% head()
[1] 285.8055 260.4153 237.2807 216.2014 196.9946 179.4942
# small lambda results in large coefficients
coef(ridge)[c("Latitude", "Overall_QualVery_Excellent"), 100]
Latitude Overall_QualVery_Excellent
0.4048216 0.1423770
# large lambda results in small coefficients
coef(ridge)[c("Latitude", "Overall_QualVery_Excellent"), 1]
Latitude Overall_QualVery_Excellent
6.382385e-36 9.838114e-37
Tuning
# Apply CV ridge regression to Ames data
ridge <- cv.glmnet(
x = X,
y = Y,
alpha = 0
)
# Apply CV lasso regression to Ames data
lasso <- cv.glmnet(
x = X,
y = Y,
alpha = 1
)
# plot results
par(mfrow = c(1, 2))
plot(ridge, main = "Ridge penalty\n\n")
plot(lasso, main = "Lasso penalty\n\n")
# Ridge model
min(ridge$cvm) # minimum MSE
[1] 0.01797102
ridge$lambda.min # lambda for this min MSE
[1] 0.1266296
ridge$cvm[ridge$lambda == ridge$lambda.1se] # 1-SE rule
[1] 0.02022124
ridge$lambda.1se # lambda for this MSE
[1] 0.5112058
# Lasso model
min(lasso$cvm) # minimum MSE
[1] 0.01866453
lasso$lambda.min # lambda for this min MSE
[1] 0.002994143
lasso$cvm[lasso$lambda == lasso$lambda.1se] # 1-SE rule
[1] 0.02211018
lasso$lambda.1se # lambda for this MSE
[1] 0.01455933
# Ridge model
ridge_min <- glmnet(
x = X,
y = Y,
alpha = 0
)
# Lasso model
lasso_min <- glmnet(
x = X,
y = Y,
alpha = 1
)
par(mfrow = c(1, 2))
# plot ridge model
plot(ridge_min, xvar = "lambda", main = "Ridge penalty\n\n")
abline(v = log(ridge$lambda.min), col = "red", lty = "dashed")
abline(v = log(ridge$lambda.1se), col = "blue", lty = "dashed")
# plot lasso model
plot(lasso_min, xvar = "lambda", main = "Lasso penalty\n\n")
abline(v = log(lasso$lambda.min), col = "red", lty = "dashed")
abline(v = log(lasso$lambda.1se), col = "blue", lty = "dashed")
Figure 6.8:
lasso <- glmnet(X, Y, alpha = 1.0)
elastic1 <- glmnet(X, Y, alpha = 0.25)
elastic2 <- glmnet(X, Y, alpha = 0.75)
ridge <- glmnet(X, Y, alpha = 0.0)
par(mfrow = c(2, 2), mar = c(6, 4, 6, 2) + 0.1)
plot(lasso, xvar = "lambda", main = "Lasso (Alpha = 1)\n\n\n")
plot(elastic1, xvar = "lambda", main = "Elastic Net (Alpha = .25)\n\n\n")
plot(elastic2, xvar = "lambda", main = "Elastic Net (Alpha = .75)\n\n\n")
plot(ridge, xvar = "lambda", main = "Ridge (Alpha = 0)\n\n\n")
# for reproducibility
set.seed(123)
# grid search across
cv_glmnet <- train(
x = X,
y = Y,
method = "glmnet",
preProc = c("zv", "center", "scale"),
trControl = trainControl(method = "cv", number = 10),
tuneLength = 10
)
# model with lowest RMSE
cv_glmnet$bestTune
# plot cross-validated RMSE
ggplot(cv_glmnet)
# predict sales price on training data
pred <- predict(cv_glmnet, X)
# compute RMSE of transformed predicted
RMSE(exp(pred), exp(Y))
[1] 19905.05
Feature interpretation
vip(cv_glmnet, num_features = 20, bar = FALSE)
Figure 6.11:
p1 <- pdp::partial(cv_glmnet, pred.var = "Gr_Liv_Area", grid.resolution = 20) %>%
mutate(yhat = exp(yhat)) %>%
ggplot(aes(Gr_Liv_Area, yhat)) +
geom_line() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p2 <- pdp::partial(cv_glmnet, pred.var = "Overall_QualExcellent") %>%
mutate(
yhat = exp(yhat),
Overall_QualExcellent = factor(Overall_QualExcellent)
) %>%
ggplot(aes(Overall_QualExcellent, yhat)) +
geom_boxplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p3 <- pdp::partial(cv_glmnet, pred.var = "First_Flr_SF", grid.resolution = 20) %>%
mutate(yhat = exp(yhat)) %>%
ggplot(aes(First_Flr_SF, yhat)) +
geom_line() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p4 <- pdp::partial(cv_glmnet, pred.var = "Garage_Cars") %>%
mutate(yhat = exp(yhat)) %>%
ggplot(aes(Garage_Cars, yhat)) +
geom_line() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
grid.arrange(p1, p2, p3, p4, nrow = 2)
Figure 6.12:
pdp::partial(cv_glmnet, pred.var = "Overall_QualPoor") %>%
mutate(
yhat = exp(yhat),
Overall_QualPoor = factor(Overall_QualPoor)
) %>%
ggplot(aes(Overall_QualPoor, yhat)) +
geom_boxplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
Attrition data
df <- attrition %>% mutate_if(is.ordered, factor, ordered = FALSE)
# Create training (70%) and test (30%) sets for the
# rsample::attrition data. Use set.seed for reproducibility
set.seed(123)
churn_split <- initial_split(df, prop = .7, strata = "Attrition")
train <- training(churn_split)
test <- testing(churn_split)
# train logistic regression model
set.seed(123)
glm_mod <- train(
Attrition ~ .,
data = train,
method = "glm",
family = "binomial",
preProc = c("zv", "center", "scale"),
trControl = trainControl(method = "cv", number = 10)
)
# train regularized logistic regression model
set.seed(123)
penalized_mod <- train(
Attrition ~ .,
data = train,
method = "glmnet",
family = "binomial",
preProc = c("zv", "center", "scale"),
trControl = trainControl(method = "cv", number = 10),
tuneLength = 10
)
# extract out of sample performance measures
summary(resamples(list(
logistic_model = glm_mod,
penalized_model = penalized_mod
)))$statistics$Accuracy
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
logistic_model 0.8365385 0.8495146 0.8792476 0.8757893 0.8907767 0.9313725 0
penalized_model 0.8446602 0.8759280 0.8834951 0.8835759 0.8915469 0.9411765 0
LS0tCnRpdGxlOiAiUmVndWxhcml6ZWQgUmVncmVzc2lvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKCiMgU2V0IGdsb2JhbCBrbml0ciBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICBmaWcuaGVpZ2h0ID0gMy41CikKCmxpYnJhcnkodGlkeXZlcnNlKQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKClRoaXMgY2hhcHRlciBsZXZlcmFnZXMgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCmBgYHtyfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KHJlY2lwZXMpICAjIGZvciBmZWF0dXJlIGVuZ2luZWVyaW5nCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkoZ2xtbmV0KSAgICMgZm9yIGltcGxlbWVudGluZyByZWd1bGFyaXplZCByZWdyZXNzaW9uCmxpYnJhcnkoY2FyZXQpICAgICMgZm9yIGF1dG9tYXRpbmcgdGhlIHR1bmluZyBwcm9jZXNzCgojIE1vZGVsIGludGVycHJldGFiaWxpdHkgcGFja2FnZXMKbGlicmFyeSh2aXApICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZQpgYGAKClRvIGlsbHVzdHJhdGUgdmFyaW91cyByZWd1bGFyaXphdGlvbiBjb25jZXB0cyB3ZSdsbCBjb250aW51ZSB3b3JraW5nIHdpdGggdGhlIGBhbWVzX3RyYWluYCBhbmQgYGFtZXNfdGVzdGAgZGF0YSBzZXRzOgoKYGBge3IgMDYtYW1lcy10cmFpbiwgZWNobz1UUlVFfQpsaWJyYXJ5KHJzYW1wbGUpCgojIFN0cmF0aWZpZWQgc2FtcGxpbmcgd2l0aCB0aGUgcnNhbXBsZSBwYWNrYWdlCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgICA8LSB0ZXN0aW5nKHNwbGl0KQpgYGAKCiMjIFdoeSByZWd1bGFyaXplPyAKCkZpZ3VyZSA2LjE6CgpgYGB7ciBoeXBlcnBsYW5lLCBlY2hvPVRSVUUsIGZpZy5jYXA9IkZpdHRlZCByZWdyZXNzaW9uIGxpbmUgdXNpbmcgT3JkaW5hcnkgTGVhc3QgU3F1YXJlcy4ifQphbWVzX3N1YiA8LSBhbWVzX3RyYWluICU+JQogIGZpbHRlcihHcl9MaXZfQXJlYSA+IDEwMDAgJiBHcl9MaXZfQXJlYSA8IDMwMDApICU+JQogIHNhbXBsZV9mcmFjKC41KQptb2RlbDEgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhLCBkYXRhID0gYW1lc19zdWIpCgptb2RlbDEgJT4lCiAgYnJvb206OmF1Z21lbnQoKSAlPiUKICBnZ3Bsb3QoYWVzKEdyX0xpdl9BcmVhLCBTYWxlX1ByaWNlKSkgKyAKICBnZW9tX3NlZ21lbnQoYWVzKHggPSBHcl9MaXZfQXJlYSwgeSA9IFNhbGVfUHJpY2UsCiAgICAgICAgICAgICAgICAgICB4ZW5kID0gR3JfTGl2X0FyZWEsIHllbmQgPSAuZml0dGVkKSwgCiAgICAgICAgICAgICAgIGFscGhhID0gMC4zKSArCiAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIG1ldGhvZCA9ICJsbSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpCmBgYAoKCiMjIyBSaWRnZSBwZW5hbHR5CgpGaWd1cmUgNi4yOgoKYGBge3IgcmlkZ2UtY29lZi1leGFtcGxlLCBlY2hvPVRSVUUsIGZpZy5jYXA9IlJpZGdlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGZvciAxNSBleGVtcGxhciBwcmVkaWN0b3IgdmFyaWFibGVzIGFzICRcXGxhbWJkYSQgZ3Jvd3MgZnJvbSAgJDAgXFxyaWdodGFycm93IFxcaW5mdHkkLiBBcyAkXFxsYW1iZGEkIGdyb3dzIGxhcmdlciwgb3VyIGNvZWZmaWNpZW50IG1hZ25pdHVkZXMgYXJlIG1vcmUgY29uc3RyYWluZWQuIiwgZmlnLmhlaWdodD0zLjUsIGZpZy53aWR0aD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpib3N0b25fdHJhaW5feCA8LSBtb2RlbC5tYXRyaXgoY21lZHYgfiAuLCBwZHA6OmJvc3RvbilbLCAtMV0KYm9zdG9uX3RyYWluX3kgPC0gcGRwOjpib3N0b24kY21lZHYKCiMgbW9kZWwKYm9zdG9uX3JpZGdlIDwtIGdsbW5ldDo6Z2xtbmV0KAogIHggPSBib3N0b25fdHJhaW5feCwKICB5ID0gYm9zdG9uX3RyYWluX3ksCiAgYWxwaGEgPSAwCikKCmxhbSA8LSBib3N0b25fcmlkZ2UkbGFtYmRhICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKHBlbmFsdHkgPSBib3N0b25fcmlkZ2UkYTAgJT4lIG5hbWVzKCkpICU+JQogIHJlbmFtZShsYW1iZGEgPSAiLiIpCgpyZXN1bHRzIDwtIGJvc3Rvbl9yaWRnZSRiZXRhICU+JSAKICBhcy5tYXRyaXgoKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigpICU+JQogIGdhdGhlcihwZW5hbHR5LCBjb2VmZmljaWVudHMsIC1yb3duYW1lKSAlPiUKICBsZWZ0X2pvaW4obGFtKQoKcmVzdWx0X2xhYmVscyA8LSByZXN1bHRzICU+JQogIGdyb3VwX2J5KHJvd25hbWUpICU+JQogIGZpbHRlcihsYW1iZGEgPT0gbWluKGxhbWJkYSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICB0b3Bfbig1LCB3dCA9IGFicyhjb2VmZmljaWVudHMpKSAlPiUKICBtdXRhdGUodmFyID0gcGFzdGUwKCJ4IiwgMTo1KSkKCmdncGxvdCgpICsKICBnZW9tX2xpbmUoZGF0YSA9IHJlc3VsdHMsIGFlcyhsYW1iZGEsIGNvZWZmaWNpZW50cywgZ3JvdXAgPSByb3duYW1lLCBjb2xvciA9IHJvd25hbWUpLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBnZW9tX3RleHQoZGF0YSA9IHJlc3VsdF9sYWJlbHMsIGFlcyhsYW1iZGEsIGNvZWZmaWNpZW50cywgbGFiZWwgPSB2YXIsIGNvbG9yID0gcm93bmFtZSksIG51ZGdlX3ggPSAtLjA2LCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpgYGAKCiMjIyBMYXNzbyBwZW5hbHR5CgpGaWd1cmUgNi4zOgoKYGBge3IgbGFzc28tY29lZi1leGFtcGxlLCBlY2hvPVRSVUUsIGZpZy5jYXA9Ikxhc3NvIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGFzICRcXGxhbWJkYSQgZ3Jvd3MgZnJvbSAgJDAgXFxyaWdodGFycm93IFxcaW5mdHkkLiIsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYm9zdG9uX3RyYWluX3ggPC0gbW9kZWwubWF0cml4KGNtZWR2IH4gLiwgcGRwOjpib3N0b24pWywgLTFdCmJvc3Rvbl90cmFpbl95IDwtIHBkcDo6Ym9zdG9uJGNtZWR2CgojIG1vZGVsCmJvc3Rvbl9sYXNzbyA8LSBnbG1uZXQ6OmdsbW5ldCgKICB4ID0gYm9zdG9uX3RyYWluX3gsCiAgeSA9IGJvc3Rvbl90cmFpbl95LAogIGFscGhhID0gMQopCgpsYW0gPC0gYm9zdG9uX2xhc3NvJGxhbWJkYSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZShwZW5hbHR5ID0gYm9zdG9uX2xhc3NvJGEwICU+JSBuYW1lcygpKSAlPiUKICByZW5hbWUobGFtYmRhID0gIi4iKQoKcmVzdWx0cyA8LSBib3N0b25fbGFzc28kYmV0YSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBnYXRoZXIocGVuYWx0eSwgY29lZmZpY2llbnRzLCAtcm93bmFtZSkgJT4lCiAgbGVmdF9qb2luKGxhbSkKCnJlc3VsdF9sYWJlbHMgPC0gcmVzdWx0cyAlPiUKICBncm91cF9ieShyb3duYW1lKSAlPiUKICBmaWx0ZXIobGFtYmRhID09IG1pbihsYW1iZGEpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdG9wX24oNSwgd3QgPSBhYnMoY29lZmZpY2llbnRzKSkgJT4lCiAgbXV0YXRlKHZhciA9IHBhc3RlMCgieCIsIDE6NSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGRhdGEgPSByZXN1bHRzLCBhZXMobGFtYmRhLCBjb2VmZmljaWVudHMsIGdyb3VwID0gcm93bmFtZSwgY29sb3IgPSByb3duYW1lKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSByZXN1bHRfbGFiZWxzLCBhZXMobGFtYmRhLCBjb2VmZmljaWVudHMsIGxhYmVsID0gdmFyLCBjb2xvciA9IHJvd25hbWUpLCBudWRnZV94ID0gLS4wNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKYGBgCgojIyMgRWxhc3RpYyBuZXRzIAoKRmlndXJlIDYuNDoKCmBgYHtyIGVsYXN0aWMtbmV0LWNvZWYtZXhhbXBsZSwgZWNobz1UUlVFLCBmaWcuY2FwPSJFbGFzdGljIG5ldCBjb2VmZmljaWVudHMgYXMgJFxcbGFtYmRhJCBncm93cyBmcm9tICAkMCBcXHJpZ2h0YXJyb3cgXFxpbmZ0eSQuIiwgZmlnLmhlaWdodD0zLjUsIGZpZy53aWR0aD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIG1vZGVsCmJvc3Rvbl9lbGFzdGljIDwtIGdsbW5ldDo6Z2xtbmV0KAogIHggPSBib3N0b25fdHJhaW5feCwKICB5ID0gYm9zdG9uX3RyYWluX3ksCiAgYWxwaGEgPSAuMgopCgpsYW0gPC0gYm9zdG9uX2VsYXN0aWMkbGFtYmRhICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKHBlbmFsdHkgPSBib3N0b25fZWxhc3RpYyRhMCAlPiUgbmFtZXMoKSkgJT4lCiAgcmVuYW1lKGxhbWJkYSA9ICIuIikKCnJlc3VsdHMgPC0gYm9zdG9uX2VsYXN0aWMkYmV0YSAlPiUgCiAgYXMubWF0cml4KCkgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBnYXRoZXIocGVuYWx0eSwgY29lZmZpY2llbnRzLCAtcm93bmFtZSkgJT4lCiAgbGVmdF9qb2luKGxhbSkKCnJlc3VsdF9sYWJlbHMgPC0gcmVzdWx0cyAlPiUKICBncm91cF9ieShyb3duYW1lKSAlPiUKICBmaWx0ZXIobGFtYmRhID09IG1pbihsYW1iZGEpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdG9wX24oNSwgd3QgPSBhYnMoY29lZmZpY2llbnRzKSkgJT4lCiAgbXV0YXRlKHZhciA9IHBhc3RlMCgieCIsIDE6NSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGRhdGEgPSByZXN1bHRzLCBhZXMobGFtYmRhLCBjb2VmZmljaWVudHMsIGdyb3VwID0gcm93bmFtZSwgY29sb3IgPSByb3duYW1lKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSByZXN1bHRfbGFiZWxzLCBhZXMobGFtYmRhLCBjb2VmZmljaWVudHMsIGxhYmVsID0gdmFyLCBjb2xvciA9IHJvd25hbWUpLCBudWRnZV94ID0gLS4wNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKYGBgCgojIyBJbXBsZW1lbnRhdGlvbgoKYGBge3IgcmVndWxhcml6ZWQtcmVncmVzc2lvbi1kYXRhLXByZXB9CiMgQ3JlYXRlIHRyYWluaW5nICBmZWF0dXJlIG1hdHJpY2VzCiMgd2UgdXNlIG1vZGVsLm1hdHJpeCguLi4pWywgLTFdIHRvIGRpc2NhcmQgdGhlIGludGVyY2VwdApYIDwtIG1vZGVsLm1hdHJpeChTYWxlX1ByaWNlIH4gLiwgYW1lc190cmFpbilbLCAtMV0KCiMgdHJhbnNmb3JtIHkgd2l0aCBsb2cgdHJhbnNmb3JtYXRpb24KWSA8LSBsb2coYW1lc190cmFpbiRTYWxlX1ByaWNlKQpgYGAKCmBgYHtyIHJpZGdlMSwgZmlnLmNhcD0iQ29lZmZpY2llbnRzIGZvciBvdXIgcmlkZ2UgcmVncmVzc2lvbiBtb2RlbCBhcyAkXFxsYW1iZGEkIGdyb3dzIGZyb20gICQwIFxccmlnaHRhcnJvdyBcXGluZnR5JC4iLCBmaWcuaGVpZ2h0PTQuNSwgZmlnLndpZHRoPTd9CiMgQXBwbHkgcmlkZ2UgcmVncmVzc2lvbiB0byBhbWVzIGRhdGEKcmlkZ2UgPC0gZ2xtbmV0KAogIHggPSBYLAogIHkgPSBZLAogIGFscGhhID0gMAopCgpwbG90KHJpZGdlLCB4dmFyID0gImxhbWJkYSIpCmBgYAoKYGBge3IgcmlkZ2UxLXJlc3VsdHN9CiMgbGFtYmRhcyBhcHBsaWVkIHRvIHBlbmFsdHkgcGFyYW1ldGVyCnJpZGdlJGxhbWJkYSAlPiUgaGVhZCgpCgojIHNtYWxsIGxhbWJkYSByZXN1bHRzIGluIGxhcmdlIGNvZWZmaWNpZW50cwpjb2VmKHJpZGdlKVtjKCJMYXRpdHVkZSIsICJPdmVyYWxsX1F1YWxWZXJ5X0V4Y2VsbGVudCIpLCAxMDBdCgojIGxhcmdlIGxhbWJkYSByZXN1bHRzIGluIHNtYWxsIGNvZWZmaWNpZW50cwpjb2VmKHJpZGdlKVtjKCJMYXRpdHVkZSIsICJPdmVyYWxsX1F1YWxWZXJ5X0V4Y2VsbGVudCIpLCAxXSAgCmBgYAoKCiMjIFR1bmluZwoKYGBge3IgcmlkZ2UtbGFzc28tY3YtbW9kZWxzLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD05LCBmaWcuY2FwPSIxMC1mb2xkIENWIE1TRSBmb3IgYSByaWRnZSBhbmQgbGFzc28gbW9kZWwuIEZpcnN0IGRvdHRlZCB2ZXJ0aWNhbCBsaW5lIGluIGVhY2ggcGxvdCByZXByZXNlbnRzIHRoZSAkXFxsYW1iZGEkIHdpdGggdGhlIHNtYWxsZXN0IE1TRSBhbmQgdGhlIHNlY29uZCByZXByZXNlbnRzIHRoZSAkXFxsYW1iZGEkIHdpdGggYW4gTVNFIHdpdGhpbiBvbmUgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIG1pbmltdW0gTVNFLiIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQXBwbHkgQ1YgcmlkZ2UgcmVncmVzc2lvbiB0byBBbWVzIGRhdGEKcmlkZ2UgPC0gY3YuZ2xtbmV0KAogIHggPSBYLAogIHkgPSBZLAogIGFscGhhID0gMAopCgojIEFwcGx5IENWIGxhc3NvIHJlZ3Jlc3Npb24gdG8gQW1lcyBkYXRhCmxhc3NvIDwtIGN2LmdsbW5ldCgKICB4ID0gWCwKICB5ID0gWSwKICBhbHBoYSA9IDEKKQoKIyBwbG90IHJlc3VsdHMKcGFyKG1mcm93ID0gYygxLCAyKSkKcGxvdChyaWRnZSwgbWFpbiA9ICJSaWRnZSBwZW5hbHR5XG5cbiIpCnBsb3QobGFzc28sIG1haW4gPSAiTGFzc28gcGVuYWx0eVxuXG4iKQpgYGAKCmBgYHtyIHJpZGdlLWxhc3NvLWN2LXJlc3VsdHN9CiMgUmlkZ2UgbW9kZWwKbWluKHJpZGdlJGN2bSkgICAgICAgIyBtaW5pbXVtIE1TRQpyaWRnZSRsYW1iZGEubWluICAgICAjIGxhbWJkYSBmb3IgdGhpcyBtaW4gTVNFCgpyaWRnZSRjdm1bcmlkZ2UkbGFtYmRhID09IHJpZGdlJGxhbWJkYS4xc2VdICAjIDEtU0UgcnVsZQpyaWRnZSRsYW1iZGEuMXNlICAjIGxhbWJkYSBmb3IgdGhpcyBNU0UKCiMgTGFzc28gbW9kZWwKbWluKGxhc3NvJGN2bSkgICAgICAgIyBtaW5pbXVtIE1TRQpsYXNzbyRsYW1iZGEubWluICAgICAjIGxhbWJkYSBmb3IgdGhpcyBtaW4gTVNFCgpsYXNzbyRjdm1bbGFzc28kbGFtYmRhID09IGxhc3NvJGxhbWJkYS4xc2VdICAjIDEtU0UgcnVsZQpsYXNzbyRsYW1iZGEuMXNlICAjIGxhbWJkYSBmb3IgdGhpcyBNU0UKYGBgCgpgYGB7ciByaWRnZS1sYXNzby1jdi12aXotcmVzdWx0cywgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9OSwgZmlnLmNhcD0iQ29lZmZpY2llbnRzIGZvciBvdXIgcmlkZ2UgYW5kIGxhc3NvIG1vZGVscy4gRmlyc3QgZG90dGVkIHZlcnRpY2FsIGxpbmUgaW4gZWFjaCBwbG90IHJlcHJlc2VudHMgdGhlICRcXGxhbWJkYSQgd2l0aCB0aGUgc21hbGxlc3QgTVNFIGFuZCB0aGUgc2Vjb25kIHJlcHJlc2VudHMgdGhlICRcXGxhbWJkYSQgd2l0aCBhbiBNU0Ugd2l0aGluIG9uZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgbWluaW11bSBNU0UuIn0KIyBSaWRnZSBtb2RlbApyaWRnZV9taW4gPC0gZ2xtbmV0KAogIHggPSBYLAogIHkgPSBZLAogIGFscGhhID0gMAopCgojIExhc3NvIG1vZGVsCmxhc3NvX21pbiA8LSBnbG1uZXQoCiAgeCA9IFgsCiAgeSA9IFksCiAgYWxwaGEgPSAxCikKCnBhcihtZnJvdyA9IGMoMSwgMikpCiMgcGxvdCByaWRnZSBtb2RlbApwbG90KHJpZGdlX21pbiwgeHZhciA9ICJsYW1iZGEiLCBtYWluID0gIlJpZGdlIHBlbmFsdHlcblxuIikKYWJsaW5lKHYgPSBsb2cocmlkZ2UkbGFtYmRhLm1pbiksIGNvbCA9ICJyZWQiLCBsdHkgPSAiZGFzaGVkIikKYWJsaW5lKHYgPSBsb2cocmlkZ2UkbGFtYmRhLjFzZSksIGNvbCA9ICJibHVlIiwgbHR5ID0gImRhc2hlZCIpCgojIHBsb3QgbGFzc28gbW9kZWwKcGxvdChsYXNzb19taW4sIHh2YXIgPSAibGFtYmRhIiwgbWFpbiA9ICJMYXNzbyBwZW5hbHR5XG5cbiIpCmFibGluZSh2ID0gbG9nKGxhc3NvJGxhbWJkYS5taW4pLCBjb2wgPSAicmVkIiwgbHR5ID0gImRhc2hlZCIpCmFibGluZSh2ID0gbG9nKGxhc3NvJGxhbWJkYS4xc2UpLCBjb2wgPSAiYmx1ZSIsIGx0eSA9ICJkYXNoZWQiKQpgYGAKCkZpZ3VyZSA2Ljg6CgpgYGB7ciBnbG1uZXQtZWxhc3RpYy1jb21wYXJpc29uLCBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTksIGZpZy5jYXA9IkNvZWZmaWNpZW50cyBmb3IgdmFyaW91cyBwZW5hbHR5IHBhcmFtZXRlcnMuIn0KbGFzc28gICAgPC0gZ2xtbmV0KFgsIFksIGFscGhhID0gMS4wKSAKZWxhc3RpYzEgPC0gZ2xtbmV0KFgsIFksIGFscGhhID0gMC4yNSkgCmVsYXN0aWMyIDwtIGdsbW5ldChYLCBZLCBhbHBoYSA9IDAuNzUpIApyaWRnZSAgICA8LSBnbG1uZXQoWCwgWSwgYWxwaGEgPSAwLjApCgpwYXIobWZyb3cgPSBjKDIsIDIpLCBtYXIgPSBjKDYsIDQsIDYsIDIpICsgMC4xKQpwbG90KGxhc3NvLCB4dmFyID0gImxhbWJkYSIsIG1haW4gPSAiTGFzc28gKEFscGhhID0gMSlcblxuXG4iKQpwbG90KGVsYXN0aWMxLCB4dmFyID0gImxhbWJkYSIsIG1haW4gPSAiRWxhc3RpYyBOZXQgKEFscGhhID0gLjI1KVxuXG5cbiIpCnBsb3QoZWxhc3RpYzIsIHh2YXIgPSAibGFtYmRhIiwgbWFpbiA9ICJFbGFzdGljIE5ldCAoQWxwaGEgPSAuNzUpXG5cblxuIikKcGxvdChyaWRnZSwgeHZhciA9ICJsYW1iZGEiLCBtYWluID0gIlJpZGdlIChBbHBoYSA9IDApXG5cblxuIikKYGBgCgpgYGB7ciBnbG1uZXQtdHVuaW5nLWdyaWQsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9OCwgZmlnLmNhcD0iVGhlIDEwLWZvbGQgY3Jvc3MgdmFsZGF0aW9uIFJNU0UgYWNyb3NzIDEwIGFscGhhIHZhbHVlcyAoeC1heGlzKSBhbmQgMTAgbGFtYmRhIHZhbHVlcyAobGluZSBjb2xvcikuIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDEyMykKCiMgZ3JpZCBzZWFyY2ggYWNyb3NzIApjdl9nbG1uZXQgPC0gdHJhaW4oCiAgeCA9IFgsCiAgeSA9IFksCiAgbWV0aG9kID0gImdsbW5ldCIsCiAgcHJlUHJvYyA9IGMoInp2IiwgImNlbnRlciIsICJzY2FsZSIpLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCksCiAgdHVuZUxlbmd0aCA9IDEwCikKCiMgbW9kZWwgd2l0aCBsb3dlc3QgUk1TRQpjdl9nbG1uZXQkYmVzdFR1bmUKCiMgcGxvdCBjcm9zcy12YWxpZGF0ZWQgUk1TRQpnZ3Bsb3QoY3ZfZ2xtbmV0KQpgYGAKCmBgYHtyIHJlLXRyYW5zZm9ybX0KIyBwcmVkaWN0IHNhbGVzIHByaWNlIG9uIHRyYWluaW5nIGRhdGEKcHJlZCA8LSBwcmVkaWN0KGN2X2dsbW5ldCwgWCkKCiMgY29tcHV0ZSBSTVNFIG9mIHRyYW5zZm9ybWVkIHByZWRpY3RlZApSTVNFKGV4cChwcmVkKSwgZXhwKFkpKQpgYGAKCiMjIEZlYXR1cmUgaW50ZXJwcmV0YXRpb24KCmBgYHtyIHJlZ3VsYXJpemUtdmlwLCBmaWcuY2FwPSJUb3AgMjAgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGZvciB0aGUgb3B0aW1hbCByZWd1bGFyaXplZCByZWdyZXNzaW9uIG1vZGVsLiIsIGZpZy5oZWlnaHQ9NH0KdmlwKGN2X2dsbW5ldCwgbnVtX2ZlYXR1cmVzID0gMjAsIGJhciA9IEZBTFNFKQpgYGAKCkZpZ3VyZSA2LjExOgoKYGBge3IgcmVndWxhcml6ZWQtdG9wNC1wZHAsIGVjaG89VFJVRSwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NywgZmlnLmNhcD0iUGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIGZvciB0aGUgZmlyc3QgZm91ciBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIn0KcDEgPC0gcGRwOjpwYXJ0aWFsKGN2X2dsbW5ldCwgcHJlZC52YXIgPSAiR3JfTGl2X0FyZWEiLCBncmlkLnJlc29sdXRpb24gPSAyMCkgJT4lCiAgbXV0YXRlKHloYXQgPSBleHAoeWhhdCkpICU+JQogIGdncGxvdChhZXMoR3JfTGl2X0FyZWEsIHloYXQpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDIgPC0gcGRwOjpwYXJ0aWFsKGN2X2dsbW5ldCwgcHJlZC52YXIgPSAiT3ZlcmFsbF9RdWFsRXhjZWxsZW50IikgJT4lCiAgbXV0YXRlKAogICAgeWhhdCA9IGV4cCh5aGF0KSwKICAgIE92ZXJhbGxfUXVhbEV4Y2VsbGVudCA9IGZhY3RvcihPdmVyYWxsX1F1YWxFeGNlbGxlbnQpCiAgICApICU+JQogIGdncGxvdChhZXMoT3ZlcmFsbF9RdWFsRXhjZWxsZW50LCB5aGF0KSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAzMDAwMDApLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikKCnAzIDwtIHBkcDo6cGFydGlhbChjdl9nbG1uZXQsIHByZWQudmFyID0gIkZpcnN0X0Zscl9TRiIsIGdyaWQucmVzb2x1dGlvbiA9IDIwKSAlPiUKICBtdXRhdGUoeWhhdCA9IGV4cCh5aGF0KSkgJT4lCiAgZ2dwbG90KGFlcyhGaXJzdF9GbHJfU0YsIHloYXQpKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDQgPC0gcGRwOjpwYXJ0aWFsKGN2X2dsbW5ldCwgcHJlZC52YXIgPSAiR2FyYWdlX0NhcnMiKSAlPiUKICBtdXRhdGUoeWhhdCA9IGV4cCh5aGF0KSkgJT4lCiAgZ2dwbG90KGFlcyhHYXJhZ2VfQ2FycywgeWhhdCkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMzAwMDAwKSwgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpCgpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5yb3cgPSAyKQpgYGAKCkZpZ3VyZSA2LjEyOgoKYGBge3IgcmVndWxhcml6ZWQtbnVtNS1wZHAsIGVjaG89VFJVRSwgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9MywgZmlnLmNhcD0iUGFydGlhbCBkZXBlbmRlbmNlIHBsb3QgZm9yIHdoZW4gb3ZlcmFsbCBxdWFsaXR5IG9mIGEgaG9tZSBpcyAoMSkgdmVyc3VzIGlzIG5vdCBwb29yICgwKS4ifQpwZHA6OnBhcnRpYWwoY3ZfZ2xtbmV0LCBwcmVkLnZhciA9ICJPdmVyYWxsX1F1YWxQb29yIikgJT4lCiAgbXV0YXRlKAogICAgeWhhdCA9IGV4cCh5aGF0KSwKICAgIE92ZXJhbGxfUXVhbFBvb3IgPSBmYWN0b3IoT3ZlcmFsbF9RdWFsUG9vcikKICAgICkgJT4lCiAgZ2dwbG90KGFlcyhPdmVyYWxsX1F1YWxQb29yLCB5aGF0KSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAzMDAwMDApLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikKYGBgCgojIyBBdHRyaXRpb24gZGF0YQoKYGBge3IgYXR0cml0aW9uLW1vZGVsaW5nLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkZiA8LSBhdHRyaXRpb24gJT4lIG11dGF0ZV9pZihpcy5vcmRlcmVkLCBmYWN0b3IsIG9yZGVyZWQgPSBGQUxTRSkKCiMgQ3JlYXRlIHRyYWluaW5nICg3MCUpIGFuZCB0ZXN0ICgzMCUpIHNldHMgZm9yIHRoZQojIHJzYW1wbGU6OmF0dHJpdGlvbiBkYXRhLiBVc2Ugc2V0LnNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCgxMjMpCmNodXJuX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGYsIHByb3AgPSAuNywgc3RyYXRhID0gIkF0dHJpdGlvbiIpCnRyYWluIDwtIHRyYWluaW5nKGNodXJuX3NwbGl0KQp0ZXN0ICA8LSB0ZXN0aW5nKGNodXJuX3NwbGl0KQoKIyB0cmFpbiBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsCnNldC5zZWVkKDEyMykKZ2xtX21vZCA8LSB0cmFpbigKICBBdHRyaXRpb24gfiAuLCAKICBkYXRhID0gdHJhaW4sIAogIG1ldGhvZCA9ICJnbG0iLAogIGZhbWlseSA9ICJiaW5vbWlhbCIsCiAgcHJlUHJvYyA9IGMoInp2IiwgImNlbnRlciIsICJzY2FsZSIpLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkKICApCgojIHRyYWluIHJlZ3VsYXJpemVkIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwKc2V0LnNlZWQoMTIzKQpwZW5hbGl6ZWRfbW9kIDwtIHRyYWluKAogIEF0dHJpdGlvbiB+IC4sIAogIGRhdGEgPSB0cmFpbiwgCiAgbWV0aG9kID0gImdsbW5ldCIsCiAgZmFtaWx5ID0gImJpbm9taWFsIiwKICBwcmVQcm9jID0gYygienYiLCAiY2VudGVyIiwgInNjYWxlIiksCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKSwKICB0dW5lTGVuZ3RoID0gMTAKICApCgojIGV4dHJhY3Qgb3V0IG9mIHNhbXBsZSBwZXJmb3JtYW5jZSBtZWFzdXJlcwpzdW1tYXJ5KHJlc2FtcGxlcyhsaXN0KAogIGxvZ2lzdGljX21vZGVsID0gZ2xtX21vZCwgCiAgcGVuYWxpemVkX21vZGVsID0gcGVuYWxpemVkX21vZAogICkpKSRzdGF0aXN0aWNzJEFjY3VyYWN5CmBgYA==