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:
options(scipen = 999)
ggplot2::theme_set(ggplot2::theme_light())
knitr::opts_chunk$set(
fig.align = "center",
fig.height = 3.5
)
ames <- AmesHousing::make_ames()
Prerequisites
This chapter leverages the following packages:
library(dplyr)
library(ggplot2)
library(caret)
library(vip)
We’ll also continue working with the ames_train
data set:
library(rsample)
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:
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")
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)")
grid.arrange(p1, p2, nrow = 1)

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
[1] 56704.78
[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:
fit1 <- lm(Sale_Price ~ Gr_Liv_Area + Year_Built, data = ames_train)
fit2 <- lm(Sale_Price ~ Gr_Liv_Area * Year_Built, data = ames_train)
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)
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)

model3 <- lm(Sale_Price ~ ., data = ames_train)
broom::tidy(model3)
| | | |
---|
(Intercept) | -5.611498e+06 | 1.126188e+07 | |
MS_SubClassOne_Story_1945_and_Older | 3.558042e+03 | 3.842733e+03 | |
MS_SubClassOne_Story_with_Finished_Attic_All_Ages | 1.279289e+04 | 1.283374e+04 | |
MS_SubClassOne_and_Half_Story_Unfinished_All_Ages | 8.730803e+03 | 1.287125e+04 | |
MS_SubClassOne_and_Half_Story_Finished_All_Ages | 4.109338e+03 | 6.225566e+03 | |
MS_SubClassTwo_Story_1946_and_Newer | -1.093139e+03 | 5.790034e+03 | |
MS_SubClassTwo_Story_1945_and_Older | 7.141541e+03 | 6.349431e+03 | |
MS_SubClassTwo_and_Half_Story_All_Ages | -1.392803e+04 | 1.100256e+04 | |
MS_SubClassSplit_or_Multilevel | -1.145013e+04 | 1.051154e+04 | |
MS_SubClassSplit_Foyer | -4.391393e+03 | 8.056849e+03 | |
Assessing model accuracy
set.seed(123)
(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
set.seed(123)
cv_model2 <- train(
Sale_Price ~ Gr_Liv_Area + Year_Built,
data = ames_train,
method = "lm",
trControl = trainControl(method = "cv", number = 10)
)
set.seed(123)
cv_model3 <- train(
Sale_Price ~ .,
data = ames_train,
method = "lm",
trControl = trainControl(method = "cv", number = 10)
)
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)

summary(cv_model3) %>%
broom::tidy() %>%
filter(term %in% c("Garage_Area", "Garage_Cars"))
| | | | |
---|
Garage_Cars | 3020.85403 | 1771.093788 | 1.705643 | 0.088249983 |
Garage_Area | 19.67727 | 6.029965 | 3.263248 | 0.001122415 |
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")
| | | | |
---|
Garage_Area | 27.04567 | 4.209165 | 6.425423 | 1.685745e-10 |
Principal component regression
Figure 4.6:
knitr::include_graphics("images/pcr-steps.png")

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
)
cv_model_pcr$bestTune

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")

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
)
cv_model_pls$bestTune

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)

LS0tCnRpdGxlOiAiQ2hhcHRlciA0OiBMaW5lYXIgUmVncmVzc2lvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgZ2xvYmFsIFIgb3B0aW9ucwpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIFNldCBnbG9iYWwga25pdHIgY2h1bmsgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgZmlnLmhlaWdodCA9IDMuNQopCgphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpgYGAKCgojIyBQcmVyZXF1aXNpdGVzCgpUaGlzIGNoYXB0ZXIgbGV2ZXJhZ2VzIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6CgpgYGB7ciAwNC1wa2dzLCBtZXNzYWdlPUZBTFNFfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAjIGZvciBkYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KGdncGxvdDIpICAjIGZvciBhd2Vzb21lIGdyYXBoaWNzCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkoY2FyZXQpICAgICMgZm9yIGNyb3NzLXZhbGlkYXRpb24sIGV0Yy4KCiMgTW9kZWwgaW50ZXJwcmV0YWJpbGl0eSBwYWNrYWdlcwpsaWJyYXJ5KHZpcCkgICAgICAjIHZhcmlhYmxlIGltcG9ydGFuY2UKYGBgCgpXZSdsbCBhbHNvIGNvbnRpbnVlIHdvcmtpbmcgd2l0aCB0aGUgYGFtZXNfdHJhaW5gIGRhdGEgc2V0OgoKYGBge3IgMDQtYW1lcy10cmFpbiwgZWNobz1UUlVFfQpsaWJyYXJ5KHJzYW1wbGUpCiMgc3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgICA8LSB0ZXN0aW5nKHNwbGl0KQpgYGAKCiMjIFNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbgoKIyMjIEVzdGltYXRpb24KCmBgYHtyIDA0LW1vZGVsMX0KbW9kZWwxIDwtIGxtKFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSwgZGF0YSA9IGFtZXNfdHJhaW4pCmBgYAoKRmlndXJlIDQuMToKCmBgYHtyIDA0LXZpc3VhbGl6ZS1tb2RlbDEsIGV2YWw9VFJVRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMuNSwgZWNobz1UUlVFLCBmaWcuY2FwPSJUaGUgbGVhc3Qgc3F1YXJlcyBmaXQgZnJvbSByZWdyZXNzaW5nIHNhbGUgcHJpY2Ugb24gbGl2aW5nIHNwYWNlIGZvciB0aGUgdGhlIEFtZXMgaG91c2luZyBkYXRhLiBMZWZ0OiBGaXR0ZWQgcmVncmVzc2lvbiBsaW5lLiBSaWdodDogRml0dGVkIHJlZ3Jlc3Npb24gbGluZSB3aXRoIHZlcnRpY2FsIGdyZXkgYmFycyByZXByZXNlbnRpbmcgdGhlIHJlc2lkdWFscy4ifQojIEZpdHRlZCByZWdyZXNzaW9uIGxpbmUgKGZ1bGwgdHJhaW5pbmcgZGF0YSkKcDEgPC0gbW9kZWwxICU+JQogIGJyb29tOjphdWdtZW50KCkgJT4lCiAgZ2dwbG90KGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAwLjMpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBtZXRob2QgPSAibG0iKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArCiAgZ2d0aXRsZSgiRml0dGVkIHJlZ3Jlc3Npb24gbGluZSIpCgojIEZpdHRlZCByZWdyZXNzaW9uIGxpbmUgKHJlc3RyaWN0ZWQgcmFuZ2UpCnAyIDwtIG1vZGVsMSAlPiUKICBicm9vbTo6YXVnbWVudCgpICU+JQogIGdncGxvdChhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpKSArIAogIGdlb21fc2VnbWVudChhZXMoeCA9IEdyX0xpdl9BcmVhLCB5ID0gU2FsZV9QcmljZSwKICAgICAgICAgICAgICAgICAgIHhlbmQgPSBHcl9MaXZfQXJlYSwgeWVuZCA9IC5maXR0ZWQpLCAKICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBhbHBoYSA9IDAuMykgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIG1ldGhvZCA9ICJsbSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsKICBnZ3RpdGxlKCJGaXR0ZWQgcmVncmVzc2lvbiBsaW5lICh3aXRoIHJlc2lkdWFscykiKQoKIyBTaWRlLWJ5LXNpZGUgcGxvdHMKZ3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKYGBge3IgMDQtbW9kZWwxLXN1bW1hcnl9CnN1bW1hcnkobW9kZWwxKSAKYGBgCgpgYGB7ciBtb2RlbDEtc2lnbWF9CnNpZ21hKG1vZGVsMSkgICAgIyBSTVNFCnNpZ21hKG1vZGVsMSleMiAgIyBNU0UKYGBgCgojIyMgSW5mZXJlbmNlCgpgYGB7cn0KY29uZmludChtb2RlbDEsIGxldmVsID0gMC45NSkKYGBgCgojIyBNdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiB7I211bHRpLWxtfQoKYGBge3IgbW9kZWwyLCBsaW5ld2lkdGg9NTZ9Cihtb2RlbDIgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pKQpgYGAKCmBgYHtyIG1vZGVsMi11c2luZy11cGRhdGUsIGxpbmV3aWR0aD01Nn0KKG1vZGVsMiA8LSB1cGRhdGUobW9kZWwxLCAuIH4gLiArIFllYXJfQnVpbHQpKQpgYGAKCmBgYHtyIG1vZGVsMi13LWludGVyYWN0aW9uLCBsaW5ld2lkdGg9NTZ9CmxtKFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSArIFllYXJfQnVpbHQgKyBHcl9MaXZfQXJlYTpZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbikKYGBgCgpGaWd1cmUgNC4yOgoKYGBge3IgMDQtbWxyLWZpdCwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NC41LCBmaWcuY2FwPSJJbiBhIHRocmVlLWRpbWVuc2lvbmFsIHNldHRpbmcsIHdpdGggdHdvIHByZWRpY3RvcnMgYW5kIG9uZSByZXNwb25zZSwgdGhlIGxlYXN0IHNxdWFyZXMgcmVncmVzc2lvbiBsaW5lIGJlY29tZXMgYSBwbGFuZS4gVGhlICdiZXN0LWZpdCcgcGxhbmUgbWluaW1pemVzIHRoZSBzdW0gb2Ygc3F1YXJlZCBlcnJvcnMgYmV0d2VlbiB0aGUgYWN0dWFsIHNhbGVzIHByaWNlIChpbmRpdmlkdWFsIGRvdHMpIGFuZCB0aGUgcHJlZGljdGVkIHNhbGVzIHByaWNlIChwbGFuZSkuIn0KIyBGaXR0ZWQgbW9kZWxzCmZpdDEgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pCmZpdDIgPC0gbG0oU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICogWWVhcl9CdWlsdCwgZGF0YSA9IGFtZXNfdHJhaW4pCgojIFJlZ3Jlc3Npb24gcGxhbmUgZGF0YQpwbG90X2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgR3JfTGl2X0FyZWEgPSBzZXEoZnJvbSA9IG1pbihhbWVzX3RyYWluJEdyX0xpdl9BcmVhKSwgdG8gPSBtYXgoYW1lc190cmFpbiRHcl9MaXZfQXJlYSksIAogICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IDEwMCksIAogIFllYXJfQnVpbHQgPSBzZXEoZnJvbSA9IG1pbihhbWVzX3RyYWluJFllYXJfQnVpbHQpLCB0byA9IG1heChhbWVzX3RyYWluJFllYXJfQnVpbHQpLCAKICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IDEwMCkKKQpwbG90X2dyaWQkeTEgPC0gcHJlZGljdChmaXQxLCBuZXdkYXRhID0gcGxvdF9ncmlkKQpwbG90X2dyaWQkeTIgPC0gcHJlZGljdChmaXQyLCBuZXdkYXRhID0gcGxvdF9ncmlkKQoKIyBMZXZlbCBwbG90cwpwMSA8LSBnZ3Bsb3QocGxvdF9ncmlkLCBhZXMoeCA9IEdyX0xpdl9BcmVhLCB5ID0gWWVhcl9CdWlsdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB6ID0geTEsIGZpbGwgPSB5MSkpICsKICBnZW9tX3RpbGUoKSArCiAgZ2VvbV9jb250b3VyKGNvbG9yID0gIndoaXRlIikgKwogIHZpcmlkaXM6OnNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlByZWRpY3RlZFxudmFsdWUiLCBvcHRpb24gPSAiaW5mZXJubyIpICsKICB0aGVtZV9idygpICsKICBnZ3RpdGxlKCJNYWluIGVmZmVjdHMgb25seSIpCnAyIDwtIGdncGxvdChwbG90X2dyaWQsIGFlcyh4ID0gR3JfTGl2X0FyZWEsIHkgPSBZZWFyX0J1aWx0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHogPSB5MiwgZmlsbCA9IHkxKSkgKwogIGdlb21fdGlsZSgpICsKICBnZW9tX2NvbnRvdXIoY29sb3IgPSAid2hpdGUiKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUHJlZGljdGVkXG52YWx1ZSIsIG9wdGlvbiA9ICJpbmZlcm5vIikgKwogIHRoZW1lX2J3KCkgKwogIGdndGl0bGUoIk1haW4gZWZmZWN0cyB3aXRoIHR3by13YXkgaW50ZXJhY3Rpb24iKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBucm93ID0gMSkKYGBgCgpgYGB7ciBtb2RlbDN9CiMgaW5jbHVkZSBhbGwgcG9zc2libGUgbWFpbiBlZmZlY3RzCm1vZGVsMyA8LSBsbShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pIAoKIyBwcmludCBlc3RpbWF0ZWQgY29lZmZpY2llbnRzIGluIGEgdGlkeSBkYXRhIGZyYW1lCmJyb29tOjp0aWR5KG1vZGVsMykgIApgYGAKCgojIyBBc3Nlc3NpbmcgbW9kZWwgYWNjdXJhY3kKCmBgYHtyIG1vZGVsMS1hY2N1cmFjeX0KIyBUcmFpbiBtb2RlbCB1c2luZyAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24Kc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cihjdl9tb2RlbDEgPC0gdHJhaW4oCiAgZm9ybSA9IFNhbGVfUHJpY2UgfiBHcl9MaXZfQXJlYSwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopKQpgYGAKCmBgYHtyIG11bHQtbW9kZWxzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIG1vZGVsIDIgQ1YKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbDIgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhICsgWWVhcl9CdWlsdCwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopCgojIG1vZGVsIDMgQ1YKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbDMgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAibG0iLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkKKQoKIyBFeHRyYWN0IG91dCBvZiBzYW1wbGUgcGVyZm9ybWFuY2UgbWVhc3VyZXMKc3VtbWFyeShyZXNhbXBsZXMobGlzdCgKICBtb2RlbDEgPSBjdl9tb2RlbDEsIAogIG1vZGVsMiA9IGN2X21vZGVsMiwgCiAgbW9kZWwzID0gY3ZfbW9kZWwzCikpKQpgYGAKCiMjIE1vZGVsIGNvbmNlcm5zIHsjbG0tcmVzaWR1YWxzfQoKRmlndXJlIDQuMzoKCmBgYHtyIDA0LWxpbmVhci1yZWxhdGlvbnNoaXAsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJMaW5lYXIgcmVncmVzc2lvbiBhc3N1bWVzIGEgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBwcmVkaWN0b3IocykgYW5kIHRoZSByZXNwb25zZSB2YXJpYWJsZTsgaG93ZXZlciwgbm9uLWxpbmVhciByZWxhdGlvbnNoaXBzIGNhbiBvZnRlbiBiZSBhbHRlcmVkIHRvIGJlIG5lYXItbGluZWFyIGJ5IGFwcGx5aW5nIGEgdHJhbnNmb3JtYXRpb24gdG8gdGhlIHZhcmlhYmxlKHMpLiJ9CnAxIDwtIGdncGxvdChhbWVzX3RyYWluLCBhZXMoWWVhcl9CdWlsdCwgU2FsZV9QcmljZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlNhbGUgcHJpY2UiLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikgKwogIHhsYWIoIlllYXIgYnVpbHQiKSArCiAgZ2d0aXRsZShwYXN0ZSgiTm9uLXRyYW5zZm9ybWVkIHZhcmlhYmxlcyB3aXRoIGFcbiIsCiAgICAgICAgICAgICAgICAibm9uLWxpbmVhciByZWxhdGlvbnNoaXAuIikpCgpwMiA8LSBnZ3Bsb3QoYW1lc190cmFpbiwgYWVzKFllYXJfQnVpbHQsIFNhbGVfUHJpY2UpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGFscGhhID0gLjQpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKwogIHNjYWxlX3lfbG9nMTAoIlNhbGUgcHJpY2UiLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhciwgCiAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgNDAwMDAwLCBieSA9IDEwMDAwMCkpICsKICB4bGFiKCJZZWFyIGJ1aWx0IikgKwogIGdndGl0bGUocGFzdGUoIlRyYW5zZm9ybWluZyB2YXJpYWJsZXMgY2FuIHByb3ZpZGUgYVxuIiwKICAgICAgICAgICAgICAgICJuZWFyLWxpbmVhciByZWxhdGlvbnNoaXAuIikpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCkZpZ3VyZSA0LjQ6CgpgYGB7ciAwNC1ob21vc2tlZGFzdGljaXR5LCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMuNSwgZmlnLmNhcD0iTGluZWFyIHJlZ3Jlc3Npb24gYXNzdW1lcyBjb25zdGFudCB2YXJpYW5jZSBhbW9uZyB0aGUgcmVzaWR1YWxzLiBgbW9kZWwxYCAobGVmdCkgc2hvd3MgZGVmaW5pdGl2ZSBzaWducyBvZiBoZXRlcm9za2VkYXN0aWNpdHkgd2hlcmVhcyBgbW9kZWwzYCAocmlnaHQpIGFwcGVhcnMgdG8gaGF2ZSBjb25zdGFudCB2YXJpYW5jZS4ifQoKZGYxIDwtIGJyb29tOjphdWdtZW50KGN2X21vZGVsMSRmaW5hbE1vZGVsLCBkYXRhID0gYW1lc190cmFpbikKCnAxIDwtIGdncGxvdChkZjEsIGFlcyguZml0dGVkLCAucmVzaWQpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGFscGhhID0gLjQpICsKICB4bGFiKCJQcmVkaWN0ZWQgdmFsdWVzIikgKwogIHlsYWIoIlJlc2lkdWFscyIpICsKICBnZ3RpdGxlKCJNb2RlbCAxIiwgc3VidGl0bGUgPSAiU2FsZV9QcmljZSB+IEdyX0xpdl9BcmVhIikKCmRmMiA8LSBicm9vbTo6YXVnbWVudChjdl9tb2RlbDMkZmluYWxNb2RlbCwgZGF0YSA9IGFtZXNfdHJhaW4pCgpwMiA8LSBnZ3Bsb3QoZGYyLCBhZXMoLmZpdHRlZCwgLnJlc2lkKSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAxLCBhbHBoYSA9IC40KSAgKwogIHhsYWIoIlByZWRpY3RlZCB2YWx1ZXMiKSArCiAgeWxhYigiUmVzaWR1YWxzIikgKwogIGdndGl0bGUoIk1vZGVsIDMiLCBzdWJ0aXRsZSA9ICJTYWxlX1ByaWNlIH4gLiIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCkZpZ3VyZSA0LjU6CgpgYGB7ciAwNC1hdXRvY29ycmVsYXRpb24sIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJMaW5lYXIgcmVncmVzc2lvbiBhc3N1bWVzIHVuY29ycmVsYXRlZCBlcnJvcnMuIFRoZSByZXNpZHVhbHMgaW4gYG1vZGVsMWAgKGxlZnQpIGhhdmUgYSBkaXN0aW5jdCBwYXR0ZXJuIHN1Z2dlc3RpbmcgdGhhdCBpbmZvcm1hdGlvbiBhYm91dCAkXFxlcHNpbG9uXzEkIHByb3ZpZGVzIGluZm9ybWF0aW9uIGFib3V0ICRcXGVwc2lsb25fMiQuIFdoZXJlYXMgYG1vZGVsM2AgaGFzIG5vIHNpZ25zIG9mIGF1dG9jb3JyZWxhdGlvbi4ifQoKZGYxIDwtIG11dGF0ZShkZjEsIGlkID0gcm93X251bWJlcigpKQpkZjIgPC0gbXV0YXRlKGRmMiwgaWQgPSByb3dfbnVtYmVyKCkpCgpwMSA8LSBnZ3Bsb3QoZGYxLCBhZXMoaWQsIC5yZXNpZCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIHhsYWIoIlJvdyBJRCIpICsKICB5bGFiKCJSZXNpZHVhbHMiKSArCiAgZ2d0aXRsZSgiTW9kZWwgMSIsIHN1YnRpdGxlID0gIkNvcnJlbGF0ZWQgcmVzaWR1YWxzLiIpCgpwMiA8LSBnZ3Bsb3QoZGYyLCBhZXMoaWQsIC5yZXNpZCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMSwgYWxwaGEgPSAuNCkgKwogIHhsYWIoIlJvdyBJRCIpICsKICB5bGFiKCJSZXNpZHVhbHMiKSArCiAgZ2d0aXRsZSgiTW9kZWwgMyIsIHN1YnRpdGxlID0gIlVuY29ycmVsYXRlZCByZXNpZHVhbHMuIikKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKYGBge3IgY29sbGluZWFyaXR5LTF9CiMgZml0IHdpdGggdHdvIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgdmFyaWFibGVzCnN1bW1hcnkoY3ZfbW9kZWwzKSAlPiUKICBicm9vbTo6dGlkeSgpICU+JQogIGZpbHRlcih0ZXJtICVpbiUgYygiR2FyYWdlX0FyZWEiLCAiR2FyYWdlX0NhcnMiKSkKYGBgCgpgYGB7ciBjb2xsaW5lYXJpdHktMn0KIyBtb2RlbCB3aXRob3V0IEdhcmFnZV9BcmVhCnNldC5zZWVkKDEyMykKbW9kX3dvX0dhcmFnZV9DYXJzIDwtIHRyYWluKAogIFNhbGVfUHJpY2UgfiAuLCAKICBkYXRhID0gc2VsZWN0KGFtZXNfdHJhaW4sIC1HYXJhZ2VfQ2FycyksIAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQopCgpzdW1tYXJ5KG1vZF93b19HYXJhZ2VfQ2FycykgJT4lCiAgYnJvb206OnRpZHkoKSAlPiUKICBmaWx0ZXIodGVybSA9PSAiR2FyYWdlX0FyZWEiKQpgYGAKCiMjIFByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbgoKRmlndXJlIDQuNjoKCmBgYHtyIHBjci1zdGVwcywgZWNobz1UUlVFLCBvdXQuaGVpZ2h0PSI3MCUiLCBvdXQud2lkdGg9IjcwJSIsIGZpZy5jYXA9IkEgZGVwaWN0aW9uIG9mIHRoZSBzdGVwcyBpbnZvbHZlZCBpbiBwZXJmb3JtaW5nIHByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbi4ifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3Bjci1zdGVwcy5wbmciKQpgYGAKCmBgYHtyIHBjci1yZWdyZXNzaW9uLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTYsIGZpZy5jYXA9IlRoZSAxMC1mb2xkIGNyb3NzIHZhbGRhdGlvbiBSTVNFIG9idGFpbmVkIHVzaW5nIFBDUiB3aXRoIDEtMjAgcHJpbmNpcGFsIGNvbXBvbmVudHMuIn0KIyBwZXJmb3JtIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBvbiBhIFBDUiBtb2RlbCB0dW5pbmcgdGhlIAojIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB0byB1c2UgYXMgcHJlZGljdG9ycyBmcm9tIDEtMjAKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbF9wY3IgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAicGNyIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHByZVByb2Nlc3MgPSBjKCJ6diIsICJjZW50ZXIiLCAic2NhbGUiKSwKICB0dW5lTGVuZ3RoID0gMjAKICApCgojIG1vZGVsIHdpdGggbG93ZXN0IFJNU0UKY3ZfbW9kZWxfcGNyJGJlc3RUdW5lCgojIHBsb3QgY3Jvc3MtdmFsaWRhdGVkIFJNU0UKZ2dwbG90KGN2X21vZGVsX3BjcikKYGBgCgojIyBQYXJ0aWFsIGxlYXN0IHNxdWFyZXMKCkZpZ3VyZSA0Ljc6CgpgYGB7ciBwY3ItdnMtcGxzLCBlY2hvPVRSVUUsIGZpZy5jYXA9IkEgZGlhZ3JhbSBkZXBpY3RpbmcgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gUENSIChsZWZ0KSBhbmQgUExTIChyaWdodCkuIFBDUiBmaW5kcyBwcmluY2lwYWwgY29tcG9uZW50cyAoUENzKSB0aGF0IG1heGltYWxseSBzdW1tYXJpemUgdGhlIGZlYXR1cmVzIGluZGVwZW5kZW50IG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZSBhbmQgdGhlbiB1c2VzIHRob3NlIFBDcyBhcyBwcmVkaWN0b3IgdmFyaWFibGVzLiBQTFMgZmluZHMgY29tcG9uZW50cyB0aGF0IHNpbXVsdGFuZW91c2x5IHN1bW1hcml6ZSB2YXJpYXRpb24gb2YgdGhlIHByZWRpY3RvcnMgd2hpbGUgYmVpbmcgb3B0aW1hbGx5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgb3V0Y29tZSBhbmQgdGhlbiB1c2VzIHRob3NlIFBDcyBhcyBwcmVkaWN0b3JzLiIsIG91dC53aWR0aD0iMTAwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvcGxzLXZzLXBjci5wbmciKQpgYGAKCkZpZ3VyZSA0Ljk6CgpgYGB7ciBwbHMtdnMtcGNyLXJlbGF0aW9uc2hpcCwgZWNobz1UUlVFLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gc2hvd2luZyB0aGF0IHRoZSBmaXJzdCB0d28gUENzIHdoZW4gdXNpbmcgUENSIGhhdmUgdmVyeSBsaXR0bGUgcmVsYXRpb25zaGlwIHRvIHRoZSByZXNwb25zZSB2YXJpYWJsZSAodG9wIHJvdyk7IGhvd2V2ZXIsIHRoZSBmaXJzdCB0d28gUENzIHdoZW4gdXNpbmcgUExTIGhhdmUgYSBtdWNoIHN0cm9uZ2VyIGFzc29jaWF0aW9uIHRvIHRoZSByZXNwb25zZSAoYm90dG9tIHJvdykuIiwgZmlnLmhlaWdodD00LjUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoQXBwbGllZFByZWRpY3RpdmVNb2RlbGluZykKbGlicmFyeShyZWNpcGVzKQpsaWJyYXJ5KHRpZHlyKQoKZGF0YShzb2x1YmlsaXR5KQpkZiA8LSBjYmluZChzb2xUcmFpblgsIHNvbFRyYWluWSkKCnBjYV9kZiA8LSByZWNpcGUoc29sVHJhaW5ZIH4gLiwgZGF0YSA9IGRmKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogIHN0ZXBfcGNhKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogIHByZXAodHJhaW5pbmcgPSBkZiwgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKSAlPiUKICBzZWxlY3QoUEMxLCBQQzIsIHNvbFRyYWluWSkgJT4lCiAgcmVuYW1lKGBQQ1IgQ29tcG9uZW50IDFgID0gIlBDMSIsIGBQQ1IgQ29tcG9uZW50IDJgID0gIlBDMiIpICU+JSAgCiAgZ2F0aGVyKGNvbXBvbmVudCwgdmFsdWUsIC1zb2xUcmFpblkpCgpwbHNfZGYgPC0gcmVjaXBlKHNvbFRyYWluWSB+IC4sIGRhdGEgPSBkZikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX3ByZWRpY3RvcnMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKSAlPiUKICBzdGVwX3BscyhhbGxfcHJlZGljdG9ycygpLCBvdXRjb21lID0gInNvbFRyYWluWSIpICU+JQogIHByZXAodHJhaW5pbmcgPSBkZiwgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKSAlPiUKICByZW5hbWUoYFBMUyBDb21wb25lbnQgMWAgPSAiUExTMSIsIGBQTFMgQ29tcG9uZW50IDJgID0gIlBMUzIiKSAlPiUKICBnYXRoZXIoY29tcG9uZW50LCB2YWx1ZSwgLXNvbFRyYWluWSkKCnBjYV9kZiAlPiUgCiAgYmluZF9yb3dzKHBsc19kZikgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSwgc29sVHJhaW5ZKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMTUpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBsdHkgPSAiZGFzaGVkIikgKwogIGZhY2V0X3dyYXAofiBjb21wb25lbnQsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJQQyBFaWdlbnZhbHVlcyIsIHkgPSAiUmVzcG9uc2UiKQogIApgYGAKCmBgYHtyIHBscy1yZWdyZXNzaW9uLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTYsIGZpZy5jYXA9IlRoZSAxMC1mb2xkIGNyb3NzIHZhbGRhdGlvbiBSTVNFIG9idGFpbmVkIHVzaW5nIFBMUyB3aXRoIDEtMjAgcHJpbmNpcGFsIGNvbXBvbmVudHMuIn0KIyBwZXJmb3JtIDEwLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBvbiBhIFBMUyBtb2RlbCB0dW5pbmcgdGhlIAojIG51bWJlciBvZiBwcmluY2lwYWwgY29tcG9uZW50cyB0byB1c2UgYXMgcHJlZGljdG9ycyBmcm9tIDEtMjAKc2V0LnNlZWQoMTIzKQpjdl9tb2RlbF9wbHMgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAicGxzIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHByZVByb2Nlc3MgPSBjKCJ6diIsICJjZW50ZXIiLCAic2NhbGUiKSwKICB0dW5lTGVuZ3RoID0gMjAKKQoKIyBtb2RlbCB3aXRoIGxvd2VzdCBSTVNFCmN2X21vZGVsX3BscyRiZXN0VHVuZQoKIyBwbG90IGNyb3NzLXZhbGlkYXRlZCBSTVNFCmdncGxvdChjdl9tb2RlbF9wbHMpCmBgYAoKIyMgRmVhdHVyZSBpbnRlcnByZXRhdGlvbgoKYGBge3IgcGxzLXZpcCwgZmlnLmNhcD0iVG9wIDIwIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyBmb3IgdGhlIFBMUyBtb2RlbC4iLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp2aXAoY3ZfbW9kZWxfcGxzLCBudW1fZmVhdHVyZXMgPSAyMCwgbWV0aG9kID0gIm1vZGVsIikKYGBgCgpGaWd1cmUgNC4xMjoKCmBgYHtyIHBscy1wZHAsIGVjaG89VFJVRSwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NywgZmlnLmNhcD0iUGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzIGZvciB0aGUgZmlyc3QgZm91ciBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIn0KcDEgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiR3JfTGl2X0FyZWEiLCBncmlkLnJlc29sdXRpb24gPSAyMCkgJT4lIAogIGF1dG9wbG90KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDIgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiRmlyc3RfRmxyX1NGIiwgZ3JpZC5yZXNvbHV0aW9uID0gMjApICU+JSAKICBhdXRvcGxvdCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAzMDAwMDApLCBsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikKCnAzIDwtIHBkcDo6cGFydGlhbChjdl9tb2RlbF9wbHMsIHByZWQudmFyID0gIlRvdGFsX0JzbXRfU0YiLCBncmlkLnJlc29sdXRpb24gPSAyMCkgJT4lIAogIGF1dG9wbG90KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDMwMDAwMCksIGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKQoKcDQgPC0gcGRwOjpwYXJ0aWFsKGN2X21vZGVsX3BscywgcHJlZC52YXIgPSAiR2FyYWdlX0NhcnMiLCBncmlkLnJlc29sdXRpb24gPSA0KSAlPiUgCiAgYXV0b3Bsb3QoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMzAwMDAwKSwgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpCgpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5yb3cgPSAyKQpgYGAKCmBgYHtyfQojIGNsZWFuIHVwCnJtKGxpc3QgPSBscygpKQpgYGAK