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 global R options
options(scipen = 999)
# 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
)
ames <- AmesHousing::make_ames()
Prerequisites
This chapter leverages the following packages:
# Helper packages
library(dplyr) # for data manipulation
library(ggplot2) # for awesome graphics
# Modeling packages
library(caret) # for cross-validation, etc.
# Model interpretability packages
library(vip) # variable importance
We’ll also continue working with the ames_train
data set:
library(rsample)
# 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)
Simple linear regression
Estimation
model1 <- lm(Sale_Price ~ Gr_Liv_Area, data = ames_train)
Figure 4.1:
# Fitted regression line (full training data)
p1 <- model1 %>%
broom::augment() %>%
ggplot(aes(Gr_Liv_Area, Sale_Price)) +
geom_point(size = 1, alpha = 0.3) +
geom_smooth(se = FALSE, method = "lm") +
scale_y_continuous(labels = scales::dollar) +
ggtitle("Fitted regression line")
# Fitted regression line (restricted range)
p2 <- 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, alpha = 0.3) +
geom_smooth(se = FALSE, method = "lm") +
scale_y_continuous(labels = scales::dollar) +
ggtitle("Fitted regression line (with residuals)")
# Side-by-side plots
grid.arrange(p1, p2, nrow = 1)
summary(model1)
Call:
lm(formula = Sale_Price ~ Gr_Liv_Area, data = ames_train)
Residuals:
Min 1Q Median 3Q Max
-361143 -30668 -2449 22838 331357
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 8732.938 3996.613 2.185 0.029 *
Gr_Liv_Area 114.876 2.531 45.385 <0.0000000000000002 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 56700 on 2051 degrees of freedom
Multiple R-squared: 0.5011, Adjusted R-squared: 0.5008
F-statistic: 2060 on 1 and 2051 DF, p-value: < 0.00000000000000022
sigma(model1) # RMSE
[1] 56704.78
sigma(model1)^2 # MSE
[1] 3215432370
Inference
confint(model1, level = 0.95)
2.5 % 97.5 %
(Intercept) 895.0961 16570.7805
Gr_Liv_Area 109.9121 119.8399
Multiple linear regression
(model2 <- lm(Sale_Price ~ Gr_Liv_Area + Year_Built, data = ames_train))
Call:
lm(formula = Sale_Price ~ Gr_Liv_Area + Year_Built, data = ames_train)
Coefficients:
(Intercept) Gr_Liv_Area Year_Built
-2123054.21 99.18 1093.48
(model2 <- update(model1, . ~ . + Year_Built))
Call:
lm(formula = Sale_Price ~ Gr_Liv_Area + Year_Built, data = ames_train)
Coefficients:
(Intercept) Gr_Liv_Area Year_Built
-2123054.21 99.18 1093.48
lm(Sale_Price ~ Gr_Liv_Area + Year_Built + Gr_Liv_Area:Year_Built, data = ames_train)
Call:
lm(formula = Sale_Price ~ Gr_Liv_Area + Year_Built + Gr_Liv_Area:Year_Built,
data = ames_train)
Coefficients:
(Intercept) Gr_Liv_Area Year_Built
382194.3015 -1483.8810 -179.7979
Gr_Liv_Area:Year_Built
0.8037
Figure 4.2:
# Fitted models
fit1 <- lm(Sale_Price ~ Gr_Liv_Area + Year_Built, data = ames_train)
fit2 <- lm(Sale_Price ~ Gr_Liv_Area * Year_Built, data = ames_train)
# Regression plane data
plot_grid <- expand.grid(
Gr_Liv_Area = seq(from = min(ames_train$Gr_Liv_Area), to = max(ames_train$Gr_Liv_Area),
length = 100),
Year_Built = seq(from = min(ames_train$Year_Built), to = max(ames_train$Year_Built),
length = 100)
)
plot_grid$y1 <- predict(fit1, newdata = plot_grid)
plot_grid$y2 <- predict(fit2, newdata = plot_grid)
# Level plots
p1 <- ggplot(plot_grid, aes(x = Gr_Liv_Area, y = Year_Built,
z = y1, fill = y1)) +
geom_tile() +
geom_contour(color = "white") +
viridis::scale_fill_viridis(name = "Predicted\nvalue", option = "inferno") +
theme_bw() +
ggtitle("Main effects only")
p2 <- ggplot(plot_grid, aes(x = Gr_Liv_Area, y = Year_Built,
z = y2, fill = y1)) +
geom_tile() +
geom_contour(color = "white") +
viridis::scale_fill_viridis(name = "Predicted\nvalue", option = "inferno") +
theme_bw() +
ggtitle("Main effects with two-way interaction")
gridExtra::grid.arrange(p1, p2, nrow = 1)
# include all possible main effects
model3 <- lm(Sale_Price ~ ., data = ames_train)
# print estimated coefficients in a tidy data frame
broom::tidy(model3)
Assessing model accuracy
# Train model using 10-fold cross-validation
set.seed(123) # for reproducibility
(cv_model1 <- train(
form = Sale_Price ~ Gr_Liv_Area,
data = ames_train,
method = "lm",
trControl = trainControl(method = "cv", number = 10)
))
Linear Regression
2053 samples
1 predictor
No pre-processing
Resampling: Cross-Validated (10 fold)
Summary of sample sizes: 1846, 1848, 1848, 1848, 1848, 1848, ...
Resampling results:
RMSE Rsquared MAE
56410.89 0.5069425 39169.09
Tuning parameter 'intercept' was held constant at a value of TRUE
# model 2 CV
set.seed(123)
cv_model2 <- train(
Sale_Price ~ Gr_Liv_Area + Year_Built,
data = ames_train,
method = "lm",
trControl = trainControl(method = "cv", number = 10)
)
# model 3 CV
set.seed(123)
cv_model3 <- train(
Sale_Price ~ .,
data = ames_train,
method = "lm",
trControl = trainControl(method = "cv", number = 10)
)
# Extract out of sample performance measures
summary(resamples(list(
model1 = cv_model1,
model2 = cv_model2,
model3 = cv_model3
)))
Call:
summary.resamples(object = resamples(list(model1 = cv_model1, model2 =
cv_model2, model3 = cv_model3)))
Models: model1, model2, model3
Number of resamples: 10
MAE
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
model1 34457.58 36323.74 38943.81 39169.09 41660.81 45005.17 0
model2 28094.79 30594.47 31959.30 32246.86 34210.70 37441.82 0
model3 12458.27 15420.10 16484.77 16258.84 17262.39 19029.29 0
RMSE
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
model1 47211.34 52363.41 54948.96 56410.89 60672.31 67679.05 0
model2 37698.17 42607.11 45407.14 46292.38 49668.59 54692.06 0
model3 20844.33 22581.04 24947.45 26098.00 27695.65 39521.49 0
Rsquared
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
model1 0.3598237 0.4550791 0.5289068 0.5069425 0.5619841 0.5965793 0
model2 0.5714665 0.6392504 0.6800818 0.6703298 0.7067458 0.7348562 0
model3 0.7869022 0.9018567 0.9104351 0.8949642 0.9166564 0.9303504 0
Model concerns
Figure 4.3:
p1 <- ggplot(ames_train, aes(Year_Built, Sale_Price)) +
geom_point(size = 1, alpha = .4) +
geom_smooth(se = FALSE) +
scale_y_continuous("Sale price", labels = scales::dollar) +
xlab("Year built") +
ggtitle(paste("Non-transformed variables with a\n",
"non-linear relationship."))
p2 <- ggplot(ames_train, aes(Year_Built, Sale_Price)) +
geom_point(size = 1, alpha = .4) +
geom_smooth(method = "lm", se = FALSE) +
scale_y_log10("Sale price", labels = scales::dollar,
breaks = seq(0, 400000, by = 100000)) +
xlab("Year built") +
ggtitle(paste("Transforming variables can provide a\n",
"near-linear relationship."))
gridExtra::grid.arrange(p1, p2, nrow = 1)
Figure 4.4:
df1 <- broom::augment(cv_model1$finalModel, data = ames_train)
p1 <- ggplot(df1, aes(.fitted, .resid)) +
geom_point(size = 1, alpha = .4) +
xlab("Predicted values") +
ylab("Residuals") +
ggtitle("Model 1", subtitle = "Sale_Price ~ Gr_Liv_Area")
df2 <- broom::augment(cv_model3$finalModel, data = ames_train)
p2 <- ggplot(df2, aes(.fitted, .resid)) +
geom_point(size = 1, alpha = .4) +
xlab("Predicted values") +
ylab("Residuals") +
ggtitle("Model 3", subtitle = "Sale_Price ~ .")
gridExtra::grid.arrange(p1, p2, nrow = 1)
Figure 4.5:
df1 <- mutate(df1, id = row_number())
df2 <- mutate(df2, id = row_number())
p1 <- ggplot(df1, aes(id, .resid)) +
geom_point(size = 1, alpha = .4) +
xlab("Row ID") +
ylab("Residuals") +
ggtitle("Model 1", subtitle = "Correlated residuals.")
p2 <- ggplot(df2, aes(id, .resid)) +
geom_point(size = 1, alpha = .4) +
xlab("Row ID") +
ylab("Residuals") +
ggtitle("Model 3", subtitle = "Uncorrelated residuals.")
gridExtra::grid.arrange(p1, p2, nrow = 1)
# fit with two strongly correlated variables
summary(cv_model3) %>%
broom::tidy() %>%
filter(term %in% c("Garage_Area", "Garage_Cars"))
# model without Garage_Area
set.seed(123)
mod_wo_Garage_Cars <- train(
Sale_Price ~ .,
data = select(ames_train, -Garage_Cars),
method = "lm",
trControl = trainControl(method = "cv", number = 10)
)
prediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleading
summary(mod_wo_Garage_Cars) %>%
broom::tidy() %>%
filter(term == "Garage_Area")
Principal component regression
Figure 4.6:
knitr::include_graphics("images/pcr-steps.png")
# perform 10-fold cross validation on a PCR model tuning the
# number of principal components to use as predictors from 1-20
set.seed(123)
cv_model_pcr <- train(
Sale_Price ~ .,
data = ames_train,
method = "pcr",
trControl = trainControl(method = "cv", number = 10),
preProcess = c("zv", "center", "scale"),
tuneLength = 20
)
# model with lowest RMSE
cv_model_pcr$bestTune
# plot cross-validated RMSE
ggplot(cv_model_pcr)
Partial least squares
Figure 4.7:
knitr::include_graphics("images/pls-vs-pcr.png")
Figure 4.9:
library(AppliedPredictiveModeling)
library(recipes)
library(tidyr)
data(solubility)
df <- cbind(solTrainX, solTrainY)
pca_df <- recipe(solTrainY ~ ., data = df) %>%
step_center(all_predictors()) %>%
step_scale(all_predictors()) %>%
step_pca(all_predictors()) %>%
prep(training = df, retain = TRUE) %>%
juice() %>%
select(PC1, PC2, solTrainY) %>%
rename(`PCR Component 1` = "PC1", `PCR Component 2` = "PC2") %>%
gather(component, value, -solTrainY)
pls_df <- recipe(solTrainY ~ ., data = df) %>%
step_center(all_predictors()) %>%
step_scale(all_predictors()) %>%
step_pls(all_predictors(), outcome = "solTrainY") %>%
prep(training = df, retain = TRUE) %>%
juice() %>%
rename(`PLS Component 1` = "PLS1", `PLS Component 2` = "PLS2") %>%
gather(component, value, -solTrainY)
pca_df %>%
bind_rows(pls_df) %>%
ggplot(aes(value, solTrainY)) +
geom_point(alpha = .15) +
geom_smooth(method = "lm", se = FALSE, lty = "dashed") +
facet_wrap(~ component, scales = "free") +
labs(x = "PC Eigenvalues", y = "Response")
NA
# perform 10-fold cross validation on a PLS model tuning the
# number of principal components to use as predictors from 1-20
set.seed(123)
cv_model_pls <- train(
Sale_Price ~ .,
data = ames_train,
method = "pls",
trControl = trainControl(method = "cv", number = 10),
preProcess = c("zv", "center", "scale"),
tuneLength = 20
)
# model with lowest RMSE
cv_model_pls$bestTune
# plot cross-validated RMSE
ggplot(cv_model_pls)
Feature interpretation
vip(cv_model_pls, num_features = 20, method = "model")
Figure 4.12:
p1 <- pdp::partial(cv_model_pls, pred.var = "Gr_Liv_Area", grid.resolution = 20) %>%
autoplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p2 <- pdp::partial(cv_model_pls, pred.var = "First_Flr_SF", grid.resolution = 20) %>%
autoplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p3 <- pdp::partial(cv_model_pls, pred.var = "Total_Bsmt_SF", grid.resolution = 20) %>%
autoplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
p4 <- pdp::partial(cv_model_pls, pred.var = "Garage_Cars", grid.resolution = 4) %>%
autoplot() +
scale_y_continuous(limits = c(0, 300000), labels = scales::dollar)
grid.arrange(p1, p2, p3, p4, nrow = 2)
# clean up
rm(list = ls())
LS0tCnRpdGxlOiAiQ2hhcHRlciA0OiBMaW5lYXIgUmVncmVzc2lvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgZ2xvYmFsIFIgb3B0aW9ucwpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIFNldCBnbG9iYWwga25pdHIgY2h1bmsgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgZmlnLmhlaWdodCA9IDMuNQopCgphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpgYGAKCgojIyBQcmVyZXF1aXNpdGVzCgpUaGlzIGNoYXB0ZXIgbGV2ZXJhZ2VzIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6CgpgYGB7ciAwNC1wa2dzLCBtZXNzYWdlPUZBTFNFfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAjIGZvciBkYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KGdncGxvdDIpICAjIGZvciBhd2Vzb21lIGdyYXBoaWNzCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkoY2FyZXQpICAgICMgZm9yIGNyb3NzLXZhbGlkYXRpb24sIGV0Yy4KCiMgTW9kZWwgaW50ZXJwcmV0YWJpbGl0eSBwYWNrYWdlcwpsaWJyYXJ5KHZpcCkgICAgICAjIHZhcmlhYmxlIGltcG9ydGFuY2UKYGBgCgpXZSdsbCBhbHNvIGNvbnRpbnVlIHdvcmtpbmcgd2l0aCB0aGUgYGFtZXNfdHJhaW5gIGRhdGEgc2V0OgoKYGBge3IgMDQtYW1lcy10cmFpbiwgZWNobz1UUlVFfQpsaWJyYXJ5KHJzYW1wbGUpCiMgc3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgICA8LSB0ZXN0aW5nKHNwbGl0KQpgYGAKCiMjIFNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbgoKIyMjIEVzdGltYXRpb24KCmBgYHtyIDA0LW1vZGVsMX0KbW9kZWwxIDwtIGxtKFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSwgZGF0YSA9IGFtZXNfdHJhaW4pCmBgYAoKRmlndXJlIDQuMToKCmBgYHtyIDA0LXZpc3VhbGl6ZS1tb2RlbDEsIGV2YWw9VFJVRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMuNSwgZWNobz1UUlVFLCBmaWcuY2FwPSJUaGUgbGVhc3Qgc3F1YXJlcyBmaXQgZnJvbSByZWdyZXNzaW5nIHNhbGUgcHJpY2Ugb24gbGl2aW5nIHNwYWNlIGZvciB0aGUgdGhlIEFtZXMgaG91c2luZyBkYXRhLiBMZWZ0OiBGaXR0ZWQgcmVncmVzc2lvbiBsaW5lLiBSaWdodDogRml0dGVkIHJlZ3Jlc3Npb24gbGluZSB3aXRoIHZlcnRpY2FsIGdyZXkgYmFycyByZXByZXNlbnRpbmcgdGhlIHJlc2lkdWFscy4ifQojIEZpdHRlZCByZWdyZXNzaW9uIGxpbmUgKGZ1bGwgdHJhaW5pbmcgZGF0YSkKcDEgPC0gbW9kZWwxICU+JQogIGJyb29tOjphdWdtZW50KCkgJT4lCiAgZ2dwbG90KGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAwLjMpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBtZXRob2QgPSAibG0iKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArCiAgZ2d0aXRsZSgiRml0dGVkIHJlZ3Jlc3Npb24gbGluZSIpCgojIEZpdHRlZCByZWdyZXNzaW9uIGxpbmUgKHJlc3RyaWN0ZWQgcmFuZ2UpCnAyIDwtIG1vZGVsMSAlPiUKICBicm9vbTo6YXVnbWVudCgpICU+JQogIGdncGxvdChhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpKSArIAogIGdlb21fc2VnbWVudChhZXMoeCA9IEdyX0xpdl9BcmVhLCB5ID0gU2FsZV9QcmljZSwKICAgICAgICAgICAgICAgICAgIHhlbmQgPSBHcl9MaXZfQXJlYSwgeWVuZCA9IC5maXR0ZWQpLCAKICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBhbHBoYSA9IDAuMykgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIG1ldGhvZCA9ICJsbSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsKICBnZ3RpdGxlKCJGaXR0ZWQgcmVncmVzc2lvbiBsaW5lICh3aXRoIHJlc2lkdWFscykiKQoKIyBTaWRlLWJ5LXNpZGUgcGxvdHMKZ3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKYGBge3IgMDQtbW9kZWwxLXN1bW1hcnl9CnN1bW1hcnkobW9kZWwxKSAKYGBgCgpgYGB7ciBtb2RlbDEtc2lnbWF9CnNpZ21hKG1vZGVsMSkgICAgIyBSTVNFCnNpZ21hKG1vZGVsMSleMiAgIyBNU0UKYGBgCgojIyMgSW5mZXJlbmNlCgpgYGB7cn0KY29uZmludChtb2RlbDEsIGxldmVsID0gMC45NSkKYGBgCgojIyBNdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiB7I211bHRpLWxtfQoKYGBge3IgbW9kZWwyLCBsaW5ld2lkdGg9NTZ9Cihtb2RlbDIgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pKQpgYGAKCmBgYHtyIG1vZGVsMi11c2luZy11cGRhdGUsIGxpbmV3aWR0aD01Nn0KKG1vZGVsMiA8LSB1cGRhdGUobW9kZWwxLCAuIH4gLiArIFllYXJfQnVpbHQpKQpgYGAKCmBgYHtyIG1vZGVsMi13LWludGVyYWN0aW9uLCBsaW5ld2lkdGg9NTZ9CmxtKFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSArIFllYXJfQnVpbHQgKyBHcl9MaXZfQXJlYTpZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbikKYGBgCgpGaWd1cmUgNC4yOgoKYGBge3IgMDQtbWxyLWZpdCwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NC41LCBmaWcuY2FwPSJJbiBhIHRocmVlLWRpbWVuc2lvbmFsIHNldHRpbmcsIHdpdGggdHdvIHByZWRpY3RvcnMgYW5kIG9uZSByZXNwb25zZSwgdGhlIGxlYXN0IHNxdWFyZXMgcmVncmVzc2lvbiBsaW5lIGJlY29tZXMgYSBwbGFuZS4gVGhlICdiZXN0LWZpdCcgcGxhbmUgbWluaW1pemVzIHRoZSBzdW0gb2Ygc3F1YXJlZCBlcnJvcnMgYmV0d2VlbiB0aGUgYWN0dWFsIHNhbGVzIHByaWNlIChpbmRpdmlkdWFsIGRvdHMpIGFuZCB0aGUgcHJlZGljdGVkIHNhbGVzIHByaWNlIChwbGFuZSkuIn0KIyBGaXR0ZWQgbW9kZWxzCmZpdDEgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pCmZpdDIgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICogWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pCgojIFJlZ3Jlc3Npb24gcGxhbmUgZGF0YQpwbG90X2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgR3JfTGl2X0FyZWEgPSBzZXEoZnJvbSA9IG1pbihhbWVzX3RyYWluJEdyX0xpdl9BcmVhKSwgdG8gPSBtYXgoYW1lc190cmFpbiRHcl9MaXZfQXJlYSksIAogICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IDEwMCksIAogIFllYXJfQnVpbHQgPSBzZXEoZnJvbSA9IG1pbihhbWVzX3RyYWluJFllYXJfQnVpbHQpLCB0byA9IG1heChhbWVzX3RyYWluJFllYXJfQnVpbHQpLCAKICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IDEwMCkKKQpwbG90X2dyaWQkeTEgPC0gcHJlZGljdChmaXQxLCBuZXdkYXRhID0gcGxvdF9ncmlkKQpwbG90X2dyaWQkeTIgPC0gcHJlZGljdChmaXQyLCBuZXdkYXRhID0gcGxvdF9ncmlkKQoKIyBMZXZlbCBwbG90cwpwMSA8LSBnZ3Bsb3QocGxvdF9ncmlkLCBhZXMoeCA9IEdyX0xpdl9BcmVhLCB5ID0gWWVhcl9CdWlsdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB6ID0geTEsIGZpbGwgPSB5MSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV9jb250b3VyKGNvbG9yID0gIndoaXRlIikgKwogIHZpcmlkaXM6OnNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlByZWRpY3RlZFxudmFsdWUiLCBvcHRpb24gPSAiaW5mZXJubyIpICsKICB0aGVtZV9idygpICsKICBnZ3RpdGxlKCJNYWluIGVmZmVjdHMgb25seSIpCnAyIDwtIGdncGxvdChwbG90X2dyaWQsIGFlcyh4ID0gR3JfTGl2X0FyZWEsIHkgPSBZZWFyX0J1aWx0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSB5MiwgZmlsbCA9IHkxKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX2NvbnRvdXIoY29sb3IgPSAid2hpdGUiKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUHJlZGljdGVkXG52YWx1ZSIsIG9wdGlvbiA9ICJpbmZlcm5vIikgKwogIHRoZW1lX2J3KCkgKwogIGdndGl0bGUoIk1haW4gZWZmZWN0cyB3aXRoIHR3by13YXkgaW50ZXJhY3Rpb24iKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBucm93ID0gMSkKYGBgCgpgYGB7ciBtb2RlbDN9CiMgaW5jbHVkZSBhbGwgcG9zc2libGUgbWFpbiBlZmZlY3RzCm1vZGVsMyA8LSBsbShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pIAoKIyBwcmludCBlc3RpbWF0ZWQgY29lZmZpY2llbnRzIGluIGEgdGlkeSBkYXRhIGZyYW1lCmJyb29tOjp0aWR5KG1vZGVsMykgIApgYGAKCgojIyBBc3Nlc3NpbmcgbW9kZWwgYWNjdXJhY3kKCmBgYHtyIG1vZGVsMS1hY2N1cmFjeX0KIyBUcmFpbiBtb2RlbCB1c2luZyAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24Kc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cihjdl9tb2RlbDEgPC0gdHJhaW4oCiAgZm9ybSA9IFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopKQpgYGAKCmBgYHtyIG11bHQtbW9kZWxzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIG1vZGVsIDIgQ1YKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbDIgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopCgojIG1vZGVsIDMgQ1YKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbDMgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAibG0iLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkKKQoKIyBFeHRyYWN0IG91dCBvZiBzYW1wbGUgcGVyZm9ybWFuY2UgbWVhc3VyZXMKc3VtbWFyeShyZXNhbXBsZXMobGlzdCgKICBtb2RlbDEgPSBjdl9tb2RlbDEsIAogIG1vZGVsMiA9IGN2X21vZGVsMiwgCiAgbW9kZWwzID0gY3ZfbW9kZWwzCikpKQpgYGAKCiMjIE1vZGVsIGNvbmNlcm5zIHsjbG0tcmVzaWR1YWxzfQoKRmlndXJlIDQuMzoKCmBgYHtyIDA0LWxpbmVhci1yZWxhdGlvbnNoaXAsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJMaW5lYXIgcmVncmVzc2lvbiBhc3N1bWVzIGEgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBwcmVkaWN0b3IocykgYW5kIHRoZSByZXNwb25zZSB2YXJpYWJsZTsgaG93ZXZlciwgbm9uLWxpbmVhciByZWxhdGlvbnNoaXBzIGNhbiBvZnRlbiBiZSBhbHRlcmVkIHRvIGJlIG5lYXItbGluZWFyIGJ5IGFwcGx5aW5nIGEgdHJhbnNmb3JtYXRpb24gdG8gdGhlIHZhcmlhYmxlKHMpLiJ9CnAxIDwtIGdncGxvdChhbWVzX3RyYWluLCBhZXMoWWVhcl9CdWlsdCwgU2FsZV9QcmljZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlNhbGUgcHJpY2UiLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikgKwogIHhsYWIoIlllYXIgYnVpbHQiKSArCiAgZ2d0aXRsZShwYXN0ZSgiTm9uLXRyYW5zZm9ybWVkIHZhcmlhYmxlcyB3aXRoIGFcbiIsCiAgICAgICAgICAgICAgICAibm9uLWxpbmVhciByZWxhdGlvbnNoaXAuIikpCgpwMiA8LSBnZ3Bsb3QoYW1lc190cmFpbiwgYWVzKFllYXJfQnVpbHQsIFNhbGVfUHJpY2UpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGFscGhhID0gLjQpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKwogIHNjYWxlX3lfbG9nMTAoIlNhbGUgcHJpY2UiLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhciwgCiAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgNDAwMDAwLCBieSA9IDEwMDAwMCkpICsKICB4bGFiKCJZZWFyIGJ1aWx0IikgKwogIGdndGl0bGUocGFzdGUoIlRyYW5zZm9ybWluZyB2YXJpYWJsZXMgY2FuIHByb3ZpZGUgYVxuIiwKICAgICAgICAgICAgICAgICJuZWFyLWxpbmVhciByZWxhdGlvbnNoaXAuIikpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCkZpZ3VyZSA0LjQ6CgpgYGB7ciAwNC1ob21vc2tlZGFzdGljaXR5LCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMuNSwgZmlnLmNhcD0iTGluZWFyIHJlZ3Jlc3Npb24gYXNzdW1lcyBjb25zdGFudCB2YXJpYW5jZSBhbW9uZyB0aGUgcmVzaWR1YWxzLiBgbW9kZWwxYCAobGVmdCkgc2hvd3MgZGVmaW5pdGl2ZSBzaWducyBvZiBoZXRlcm9za2VkYXN0aWNpdHkgd2hlcmVhcyBgbW9kZWwzYCAocmlnaHQpIGFwcGVhcnMgdG8gaGF2ZSBjb25zdGFudCB2YXJpYW5jZS4ifQoKZGYxIDwtIGJyb29tOjphdWdtZW50KGN2X21vZGVsMSRmaW5hbE1vZGVsLCBkYXRhID0gYW1lc190cmFpbikKCnAxIDwtIGdncGxvdChkZjEsIGFlcyguZml0dGVkLCAucmVzaWQpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGFscGhhID0gLjQpICsKICB4bGFiKCJQcmVkaWN0ZWQgdmFsdWVzIikgKwogIHlsYWIoIlJlc2lkdWFscyIpICsKICBnZ3RpdGxlKCJNb2RlbCAxIiwgc3VidGl0bGUgPSAiU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhIikKCmRmMiA8LSBicm9vbTo6YXVnbWVudChjdl9tb2RlbDMkZmluYWxNb2RlbCwgZGF0YSA9IGFtZXNfdHJhaW4pCgpwMiA8LSBnZ3Bsb3QoZGYyLCBhZXMoLmZpdHRlZCwgLnJlc2lkKSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLCBhbHBoYSA9IC40KSAgKwogIHhsYWIoIlByZWRpY3RlZCB2YWx1ZXMiKSArCiAgeWxhYigiUmVzaWR1YWxzIikgKwogIGdndGl0bGUoIk1vZGVsIDMiLCBzdWJ0aXRsZSA9ICJTYWxlX1ByaWNlIH4gLiIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCkZpZ3VyZSA0LjU6CgpgYGB7ciAwNC1hdXRvY29ycmVsYXRpb24sIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJMaW5lYXIgcmVncmVzc2lvbiBhc3N1bWVzIHVuY29ycmVsYXRlZCBlcnJvcnMuIFRoZSByZXNpZHVhbHMgaW4gYG1vZGVsMWAgKGxlZnQpIGhhdmUgYSBkaXN0aW5jdCBwYXR0ZXJuIHN1Z2dlc3RpbmcgdGhhdCBpbmZvcm1hdGlvbiBhYm91dCAkXFxlcHNpbG9uXzEkIHByb3ZpZGVzIGluZm9ybWF0aW9uIGFib3V0ICRcXGVwc2lsb25fMiQuIFdoZXJlYXMgYG1vZGVsM2AgaGFzIG5vIHNpZ25zIG9mIGF1dG9jb3JyZWxhdGlvbi4ifQoKZGYxIDwtIG11dGF0ZShkZjEsIGlkID0gcm93X251bWJlcigpKQpkZjIgPC0gbXV0YXRlKGRmMiwgaWQgPSByb3dfbnVtYmVyKCkpCgpwMSA8LSBnZ3Bsb3QoZGYxLCBhZXMoaWQsIC5yZXNpZCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIHhsYWIoIlJvdyBJRCIpICsKICB5bGFiKCJSZXNpZHVhbHMiKSArCiAgZ2d0aXRsZSgiTW9kZWwgMSIsIHN1YnRpdGxlID0gIkNvcnJlbGF0ZWQgcmVzaWR1YWxzLiIpCgpwMiA8LSBnZ3Bsb3QoZGYyLCBhZXMoaWQsIC5yZXNpZCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIHhsYWIoIlJvdyBJRCIpICsKICB5bGFiKCJSZXNpZHVhbHMiKSArCiAgZ2d0aXRsZSgiTW9kZWwgMyIsIHN1YnRpdGxlID0gIlVuY29ycmVsYXRlZCByZXNpZHVhbHMuIikKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKYGBge3IgY29sbGluZWFyaXR5LTF9CiMgZml0IHdpdGggdHdvIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgdmFyaWFibGVzCnN1bW1hcnkoY3ZfbW9kZWwzKSAlPiUKICBicm9vbTo6dGlkeSgpICU+JQogIGZpbHRlcih0ZXJtICVpbiUgYygiR2FyYWdlX0FyZWEiLCAiR2FyYWdlX0NhcnMiKSkKYGBgCgpgYGB7ciBjb2xsaW5lYXJpdHktMn0KIyBtb2RlbCB3aXRob3V0IEdhcmFnZV9BcmVhCnNldC5zZWVkKDEyMykKbW9kX3dvX0dhcmFnZV9DYXJzIDwtIHRyYWluKAogIFNhbGVfUHJpY2UgfiAuLCAKICBkYXRhID0gc2VsZWN0KGFtZXNfdHJhaW4sIC1HYXJhZ2VfQ2FycyksIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopCgpzdW1tYXJ5KG1vZF93b19HYXJhZ2VfQ2FycykgJT4lCiAgYnJvb206OnRpZHkoKSAlPiUKICBmaWx0ZXIodGVybSA9PSAiR2FyYWdlX0FyZWEiKQpgYGAKCiMjIFByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbgoKRmlndXJlIDQuNjoKCmBgYHtyIHBjci1zdGVwcywgZWNobz1UUlVFLCBvdXQuaGVpZ2h0PSI3MCUiLCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkEgZGVwaWN0aW9uIG9mIHRoZSBzdGVwcyBpbnZvbHZlZCBpbiBwZXJmb3JtaW5nIHByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbi4ifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3Bjci1zdGVwcy5wbmciKQpgYGAKCmBgYHtyIHBjci1yZWdyZXNzaW9uLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTYsIGZpZy5jYXA9IlRoZSAxMC1mb2xkIGNyb3NzIHZhbGRhdGlvbiBSTVNFIG9idGFpbmVkIHVzaW5nIFBDUiB3aXRoIDEtMjAgcHJpbmNpcGFsIGNvbXBvbmVudHMuIn0KIyBwZXJmb3JtIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBvbiBhIFBDUiBtb2RlbCB0dW5pbmcgdGhlIAojIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB0byB1c2UgYXMgcHJlZGljdG9ycyBmcm9tIDEtMjAKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbF9wY3IgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAicGNyIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHByZVByb2Nlc3MgPSBjKCJ6diIsICJjZW50ZXIiLCAic2NhbGUiKSwKICB0dW5lTGVuZ3RoID0gMjAKICApCgojIG1vZGVsIHdpdGggbG93ZXN0IFJNU0UKY3ZfbW9kZWxfcGNyJGJlc3RUdW5lCgojIHBsb3QgY3Jvc3MtdmFsaWRhdGVkIFJNU0UKZ2dwbG90KGN2X21vZGVsX3BjcikKYGBgCgojIyBQYXJ0aWFsIGxlYXN0IHNxdWFyZXMKCkZpZ3VyZSA0Ljc6CgpgYGB7ciBwY3ItdnMtcGxzLCBlY2hvPVRSVUUsIGZpZy5jYXA9IkEgZGlhZ3JhbSBkZXBpY3RpbmcgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gUENSIChsZWZ0KSBhbmQgUExTIChyaWdodCkuIFBDUiBmaW5kcyBwcmluY2lwYWwgY29tcG9uZW50cyAoUENzKSB0aGF0IG1heGltYWxseSBzdW1tYXJpemUgdGhlIGZlYXR1cmVzIGluZGVwZW5kZW50IG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZSBhbmQgdGhlbiB1c2VzIHRob3NlIFBDcyBhcyBwcmVkaWN0b3IgdmFyaWFibGVzLiBQTFMgZmluZHMgY29tcG9uZW50cyB0aGF0IHNpbXVsdGFuZW91c2x5IHN1bW1hcml6ZSB2YXJpYXRpb24gb2YgdGhlIHByZWRpY3RvcnMgd2hpbGUgYmVpbmcgb3B0aW1hbGx5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgb3V0Y29tZSBhbmQgdGhlbiB1c2VzIHRob3NlIFBDcyBhcyBwcmVkaWN0b3JzLiIsIG91dC53aWR0aD0iMTAwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvcGxzLXZzLXBjci5wbmciKQpgYGAKCkZpZ3VyZSA0Ljk6CgpgYGB7ciBwbHMtdnMtcGNyLXJlbGF0aW9uc2hpcCwgZWNobz1UUlVFLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gc2hvd2luZyB0aGF0IHRoZSBmaXJzdCB0d28gUENzIHdoZW4gdXNpbmcgUENSIGhhdmUgdmVyeSBsaXR0bGUgcmVsYXRpb25zaGlwIHRvIHRoZSByZXNwb25zZSB2YXJpYWJsZSAodG9wIHJvdyk7IGhvd2V2ZXIsIHRoZSBmaXJzdCB0d28gUENzIHdoZW4gdXNpbmcgUExTIGhhdmUgYSBtdWNoIHN0cm9uZ2VyIGFzc29jaWF0aW9uIHRvIHRoZSByZXNwb25zZSAoYm90dG9tIHJvdykuIiwgZmlnLmhlaWdodD00LjUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoQXBwbGllZFByZWRpY3RpdmVNb2RlbGluZykKbGlicmFyeShyZWNpcGVzKQpsaWJyYXJ5KHRpZHlyKQoKZGF0YShzb2x1YmlsaXR5KQpkZiA8LSBjYmluZChzb2xUcmFpblgsIHNvbFRyYWluWSkKCnBjYV9kZiA8LSByZWNpcGUoc29sVHJhaW5ZIH4gLiwgZGF0YSA9IGRmKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogIHN0ZXBfcGNhKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogIHByZXAodHJhaW5pbmcgPSBkZiwgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKSAlPiUKICBzZWxlY3QoUEMxLCBQQzIsIHNvbFRyYWluWSkgJT4lCiAgcmVuYW1lKGBQQ1IgQ29tcG9uZW50IDFgID0gIlBDMSIsIGBQQ1IgQ29tcG9uZW50IDJgID0gIlBDMiIpICU+JSAgCiAgZ2F0aGVyKGNvbXBvbmVudCwgdmFsdWUsIC1zb2xUcmFpblkpCgpwbHNfZGYgPC0gcmVjaXBlKHNvbFRyYWluWSB+IC4sIGRhdGEgPSBkZikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX3ByZWRpY3RvcnMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX3BscyhhbGxfcHJlZGljdG9ycygpLCBvdXRjb21lID0gInNvbFRyYWluWSIpICU+JQogIHByZXAodHJhaW5pbmcgPSBkZiwgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKSAlPiUKICByZW5hbWUoYFBMUyBDb21wb25lbnQgMWAgPSAiUExTMSIsIGBQTFMgQ29tcG9uZW50IDJgID0gIlBMUzIiKSAlPiUKICBnYXRoZXIoY29tcG9uZW50LCB2YWx1ZSwgLXNvbFRyYWluWSkKCnBjYV9kZiAlPiUgCiAgYmluZF9yb3dzKHBsc19kZikgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSwgc29sVHJhaW5ZKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMTUpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsdHkgPSAiZGFzaGVkIikgKwogIGZhY2V0X3dyYXAofiBjb21wb25lbnQsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJQQyBFaWdlbnZhbHVlcyIsIHkgPSAiUmVzcG9uc2UiKQogIApgYGAKCmBgYHtyIHBscy1yZWdyZXNzaW9uLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTYsIGZpZy5jYXA9IlRoZSAxMC1mb2xkIGNyb3NzIHZhbGRhdGlvbiBSTVNFIG9idGFpbmVkIHVzaW5nIFBMUyB3aXRoIDEtMjAgcHJpbmNpcGFsIGNvbXBvbmVudHMuIn0KIyBwZXJmb3JtIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBvbiBhIFBMUyBtb2RlbCB0dW5pbmcgdGhlIAojIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB0byB1c2UgYXMgcHJlZGljdG9ycyBmcm9tIDEtMjAKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbF9wbHMgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAicGxzIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHByZVByb2Nlc3MgPSBjKCJ6diIsICJjZW50ZXIiLCAic2NhbGUiKSwKICB0dW5lTGVuZ3RoID0gMjAKKQoKIyBtb2RlbCB3aXRoIGxvd2VzdCBSTVNFCmN2X21vZGVsX3BscyRiZXN0VHVuZQoKIyBwbG90IGNyb3NzLXZhbGlkYXRlZCBSTVNFCmdncGxvdChjdl9tb2RlbF9wbHMpCmBgYAoKIyMgRmVhdHVyZSBpbnRlcnByZXRhdGlvbgoKYGBge3IgcGxzLXZpcCwgZmlnLmNhcD0iVG9wIDIwIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBmb3IgdGhlIFBMUyBtb2RlbC4iLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp2aXAoY3ZfbW9kZWxfcGxzLCBudW1fZmVhdHVyZXMgPSAyMCwgbWV0aG9kID0gIm1vZGVsIikKYGBgCgpGaWd1cmUgNC4xMjoKCmBgYHtyIHBscy1wZHAsIGVjaG89VFJVRSwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NywgZmlnLmNhcD0iUGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIGZvciB0aGUgZmlyc3QgZm91ciBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIn0KcDEgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiR3JfTGl2X0FyZWEiLCBncmlkLnJlc29sdXRpb24gPSAyMCkgJT4lIAogIGF1dG9wbG90KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDIgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiRmlyc3RfRmxyX1NGIiwgZ3JpZC5yZXNvbHV0aW9uID0gMjApICU+JSAKICBhdXRvcGxvdCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAzMDAwMDApLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikKCnAzIDwtIHBkcDo6cGFydGlhbChjdl9tb2RlbF9wbHMsIHByZWQudmFyID0gIlRvdGFsX0JzbXRfU0YiLCBncmlkLnJlc29sdXRpb24gPSAyMCkgJT4lIAogIGF1dG9wbG90KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDQgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiR2FyYWdlX0NhcnMiLCBncmlkLnJlc29sdXRpb24gPSA0KSAlPiUgCiAgYXV0b3Bsb3QoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMzAwMDAwKSwgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpCgpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5yb3cgPSAyKQpgYGAKCmBgYHtyfQojIGNsZWFuIHVwCnJtKGxpc3QgPSBscygpKQpgYGAK