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