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.

# additional package required in hidden code chunks
library(purrr)
library(tidyr)
library(readr)
library(kableExtra)

# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())

Prerequisites

This chapter leverages the following packages:

# Helper packages
library(dplyr)    # for data manipulation
library(ggplot2)  # for awesome graphics
library(visdat)   # for additional visualizations
# Feature engineering packages
library(caret)    # for various ML tasks
library(recipes)  # for feature engineering tasks

We’ll also continue working with the ames_train data set:

ames <- AmesHousing::make_ames()

# Load and split the Ames housing data using stratified sampling
set.seed(123)  # for reproducibility
split  <- rsample::initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train  <- rsample::training(split)
ames_test   <- rsample::testing(split)

Target engineering

Figure 3.1:

models <- c("Non-log transformed model residuals", 
            "Log transformed model residuals")
list(
  m1 = lm(Sale_Price ~ Year_Built, data = ames_train),
  m2 = lm(log(Sale_Price) ~ Year_Built, data = ames_train)
) %>%
  map2_dfr(models, ~ broom::augment(.x) %>% mutate(model = .y)) %>%
  ggplot(aes(.resid)) +
    geom_histogram(bins = 75) +
    facet_wrap(~ model, scales = "free_x") +
    ylab(NULL) +
    xlab("Residuals")

transformed_response <- log(ames_train$Sale_Price)
# log transformation
ames_recipe <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_log(all_outcomes())
ames_recipe
Data Recipe

Inputs:

Operations:

Log transformation on all_outcomes
log(-0.5)
NaNs produced
[1] NaN
log1p(-0.5)
[1] -0.6931472

\[ \begin{equation} y(\lambda) = \begin{cases} \frac{Y^\lambda-1}{\lambda}, & \text{if}\ \lambda \neq 0 \\ \log\left(Y\right), & \text{if}\ \lambda = 0. \end{cases} \end{equation} \]

Figure 3.2

# Log transformation
train_log_y <- log(ames_train$Sale_Price)
test_log_y  <- log(ames_train$Sale_Price)

# Box Cox transformation
lambda  <- forecast::BoxCox.lambda(ames_train$Sale_Price)
train_bc_y <- forecast::BoxCox(ames_train$Sale_Price, lambda)
test_bc_y  <- forecast::BoxCox(ames_test$Sale_Price, lambda)

# Plot differences
levs <- c("Normal", "Log_Transform", "BoxCox_Transform")
data.frame(
  Normal = ames_train$Sale_Price,
  Log_Transform = train_log_y,
  BoxCox_Transform = train_bc_y
) %>%
  gather(Transform, Value) %>%
  mutate(Transform = factor(Transform, levels = levs)) %>% 
  ggplot(aes(Value, fill = Transform)) +
    geom_histogram(show.legend = FALSE, bins = 40) +
    facet_wrap(~ Transform, scales = "free_x")

# Log transform a value
y <- log(10)

# Undo log-transformation
exp(y)
[1] 10
# Box Cox transform a value
y <- forecast::BoxCox(10, lambda)

# Inverse Box Cox function
inv_box_cox <- function(x, lambda) {
  # for Box-Cox, lambda = 0 --> log transform
  if (lambda == 0) exp(x) else (lambda*x + 1)^(1/lambda) 
}

# Undo Box Cox-transformation
inv_box_cox(y, lambda)
[1] 10
attr(,"lambda")
[1] -0.05425403

Dealing with missingness

sum(is.na(AmesHousing::ames_raw))
[1] 13997

Figure 3.3:

AmesHousing::ames_raw %>%
  is.na() %>%
  reshape2::melt() %>%
  ggplot(aes(Var2, Var1, fill=value)) + 
    geom_raster() + 
    coord_flip() +
    scale_y_continuous(NULL, expand = c(0, 0)) +
    scale_fill_grey(name = "", 
                    labels = c("Present", 
                               "Missing")) +
    xlab("Observation") +
    theme(axis.text.y  = element_text(size = 4))

AmesHousing::ames_raw %>% 
  filter(is.na(`Garage Type`)) %>% 
  select(`Garage Type`, `Garage Cars`, `Garage Area`)

Figure 3.4:

vis_miss(AmesHousing::ames_raw, cluster = TRUE)

Imputation

ames_recipe %>%
  step_medianimpute(Gr_Liv_Area)
Data Recipe

Inputs:

Operations:

Log transformation on all_outcomes
Median Imputation for Gr_Liv_Area
ames_recipe %>%
  step_knnimpute(all_predictors(), neighbors = 6)
Data Recipe

Inputs:

Operations:

Log transformation on all_outcomes
K-nearest neighbor imputation for all_predictors
ames_recipe %>%
  step_bagimpute(all_predictors())
Data Recipe

Inputs:

Operations:

Log transformation on all_outcomes
Bagged tree imputation for all_predictors

Figure 3.5:

impute_ames <- ames_train
set.seed(123)
index <- sample(seq_along(impute_ames$Gr_Liv_Area), 50)
actuals <- ames_train[index, ]
impute_ames$Gr_Liv_Area[index] <- NA

p1 <- ggplot() +
  geom_point(data = impute_ames, aes(Gr_Liv_Area, Sale_Price), alpha = .2) +
  geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
  scale_x_log10(limits = c(300, 5000)) +
  scale_y_log10(limits = c(10000, 500000)) +
  ggtitle("Actual values")

# Mean imputation
mean_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
  step_meanimpute(Gr_Liv_Area) %>%
  prep(training = impute_ames, retain = TRUE) %>%
  juice()
mean_impute <- mean_juiced[index, ]
  
p2 <- ggplot() +
  geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
  geom_point(data = mean_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
  scale_x_log10(limits = c(300, 5000)) +
  scale_y_log10(limits = c(10000, 500000)) +
  ggtitle("Mean Imputation")

# KNN imputation
knn_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
  step_knnimpute(Gr_Liv_Area) %>%
  prep(training = impute_ames, retain = TRUE) %>%
  juice()
knn_impute <- knn_juiced[index, ]
  
p3 <- ggplot() +
  geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
  geom_point(data = knn_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
  scale_x_log10(limits = c(300, 5000)) +
  scale_y_log10(limits = c(10000, 500000)) +
  ggtitle("KNN Imputation")

# Bagged imputation
bagged_juiced <- recipe(Sale_Price ~ ., data = impute_ames) %>%
  step_bagimpute(Gr_Liv_Area) %>%
  prep(training = impute_ames, retain = TRUE) %>%
  juice()
bagged_impute <- bagged_juiced[index, ]
  
p4 <- ggplot() +
  geom_point(data = actuals, aes(Gr_Liv_Area, Sale_Price), color = "red") +
  geom_point(data = bagged_impute, aes(Gr_Liv_Area, Sale_Price), color = "blue") +
  scale_x_log10(limits = c(300, 5000)) +
  scale_y_log10(limits = c(10000, 500000)) +
  ggtitle("Bagged Trees Imputation")

gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 2)

Feature filtering

Figure 3.6 (generated from benchmark data performed in the past):

model_results <- read_csv("data/feature-selection-impacts-results.csv") %>%
  mutate(type = case_when(
    model %in% c("lm", "pcr", "pls", "glmnet", "lasso") ~ "Linear models",
    model %in% c("earth", "svmLinear", "nn") ~ "Non-linear Models",
    TRUE ~ "Tree-based Models"
  )) %>%
  mutate(model = case_when(
    model == "lm" ~ "Linear regression",
    model == "earth" ~ "Multivariate adaptive regression splines",
    model == "gbm" ~ "Gradient boosting machines",
    model == "glmnet" ~ "Elastic net",
    model == "lasso" ~ "Lasso",
    model == "nn" ~ "Neural net",
    model == "pcr" ~ "Principal component regression",
    model == "pls" ~ "Partial least squares",
    model == "ranger" ~ "Random forest",
    TRUE ~ "Support vector machine"
  ))
Parsed with column specification:
cols(
  model = col_character(),
  NIP = col_double(),
  RMSE = col_double(),
  time = col_double()
)
ggplot(model_results, aes(NIP, RMSE, color = model, lty = model)) +
  geom_line() +
  geom_point() +
  facet_wrap(~ type, nrow = 1) +
  xlab("Number of additional non-informative predictors")

Figure 3.7 (generated from benchmark data performed in the past):

model_results %>%
  group_by(model) %>%
  mutate(
    time_impact = time / first(time),
    time_impact = time_impact - 1
  ) %>%
  ggplot(aes(NIP, time_impact, color = model, lty = model)) +
    geom_line() +
    geom_point() +
    facet_wrap(~ type, nrow = 1) +
    scale_y_continuous("Percent increase in training time", 
                       labels = scales::percent) +
    xlab("Number of additional non-informative predictors")

caret::nearZeroVar(ames_train, saveMetrics = TRUE) %>% 
  tibble::rownames_to_column() %>% 
  filter(nzv)

Numeric feature engineering

# Normalize all numeric columns
recipe(Sale_Price ~ ., data = ames_train) %>%
  step_YeoJohnson(all_numeric())                 
Data Recipe

Inputs:

Operations:

Yeo-Johnson transformation on all_numeric

Figure 3.8:

set.seed(123)
x1 <- tibble(
  variable = "x1",
  `Real value` = runif(25, min = -30, max = 5),
  `Standardized value` = scale(`Real value`) %>% as.numeric()
)

set.seed(456)
x2 <- tibble(
  variable = "x2",
  `Real value` = rlnorm(25, log(25)),
  `Standardized value` = scale(`Real value`) %>% as.numeric()
)

set.seed(789)
x3 <- tibble(
  variable = "x3",
  `Real value` = rnorm(25, 150, 15),
  `Standardized value` = scale(`Real value`) %>% as.numeric()
)

x1 %>%
  bind_rows(x2) %>%
  bind_rows(x3) %>%
  gather(key, value, -variable) %>%
  mutate(variable = factor(variable, levels = c("x3", "x2", "x1"))) %>%
  ggplot(aes(value, variable)) +
    geom_point(alpha = .6) +
    facet_wrap(~ key, scales = "free_x") +
    ylab("Feature") +
    xlab("Value")

ames_recipe %>%
  step_center(all_numeric(), -all_outcomes()) %>%
  step_scale(all_numeric(), -all_outcomes())
Data Recipe

Inputs:

Operations:

Log transformation on all_outcomes
Centering for all_numeric, -, all_outcomes()
Scaling for all_numeric, -, all_outcomes()

Categorical feature engineering

count(ames_train, Neighborhood) %>% arrange(n)
count(ames_train, Screen_Porch) %>% arrange(n)
# Lump levels for two features
lumping <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_other(Neighborhood, threshold = 0.01, 
             other = "other") %>%
  step_other(Screen_Porch, threshold = 0.1, 
             other = ">0")

# Apply this blue print --> you will learn about this at 
# the end of the chapter
apply_2_training <- prep(lumping, training = ames_train) %>%
  bake(ames_train)

# New distribution of Neighborhood
count(apply_2_training, Neighborhood) %>% arrange(n)

# New distribution of Screen_Porch
count(apply_2_training, Screen_Porch) %>% arrange(n)
knitr::include_graphics("images/ohe-vs-dummy.png")

# Lump levels for two features
recipe(Sale_Price ~ ., data = ames_train) %>%
  step_dummy(all_nominal(), one_hot = TRUE)
Data Recipe

Inputs:

Operations:

Dummy variables from all_nominal
# Original categories
count(ames_train, MS_SubClass)

# Label encoded
recipe(Sale_Price ~ ., data = ames_train) %>%
  step_integer(MS_SubClass) %>%
  prep(ames_train) %>%
  bake(ames_train) %>%
  count(MS_SubClass)
ames_train %>% select(contains("Qual"))
# Original categories
count(ames_train, Overall_Qual)

# Label encoded
recipe(Sale_Price ~ ., data = ames_train) %>%
  step_integer(Overall_Qual) %>%
  prep(ames_train) %>%
  bake(ames_train) %>%
  count(Overall_Qual)
ames_train %>%
  group_by(Neighborhood) %>%
  summarize(`Avg Sale_Price` = mean(Sale_Price, na.rm = TRUE)) %>%
  head(10) %>%
  kable(caption = "Example of target encoding the Neighborhood feature of the Ames housing data set.") %>%
  kable_styling(bootstrap_options = "striped", full_width = TRUE)
Example of target encoding the Neighborhood feature of the Ames housing data set.
Neighborhood Avg Sale_Price
North_Ames 144562.7
College_Creek 199831.7
Old_Town 122736.7
Edwards 130652.2
Somerset 227379.6
Northridge_Heights 323289.5
Gilbert 192162.9
Sawyer 136460.6
Northwest_Ames 187328.2
Sawyer_West 188644.6
ames_train %>%
  count(Neighborhood) %>%
  mutate(Proportion = n / sum(n)) %>%
  select(-n) %>%
  head(10) %>%
  kable(caption = 'Example of categorical proportion encoding the Neighborhood feature of the Ames housing data set.') %>%
  kable_styling(bootstrap_options = "striped", full_width = TRUE)
Example of categorical proportion encoding the Neighborhood feature of the Ames housing data set.
Neighborhood Proportion
North_Ames 0.1451534
College_Creek 0.0910862
Old_Town 0.0832927
Edwards 0.0711154
Somerset 0.0623478
Northridge_Heights 0.0560156
Gilbert 0.0565027
Sawyer 0.0496834
Northwest_Ames 0.0467608
Sawyer_West 0.0414028

Dimension reduction

recipe(Sale_Price ~ ., data = ames_train) %>%
  step_center(all_numeric()) %>%
  step_scale(all_numeric()) %>%
  step_pca(all_numeric(), threshold = .95)
Data Recipe

Inputs:

Operations:

Centering for all_numeric
Scaling for all_numeric
No PCA components were extracted.

Proper implementation

Figure 3.10:

knitr::include_graphics("images/minimize-leakage.png")

blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_nzv(all_nominal())  %>%
  step_integer(matches("Qual|Cond|QC|Qu")) %>%
  step_center(all_numeric(), -all_outcomes()) %>%
  step_scale(all_numeric(), -all_outcomes()) %>%
  step_pca(all_numeric(), -all_outcomes())
  
blueprint
Data Recipe

Inputs:

Operations:

Sparse, unbalanced variable filter on all_nominal
Integer encoding for matches, Qual|Cond|QC|Qu
Centering for all_numeric, -, all_outcomes()
Scaling for all_numeric, -, all_outcomes()
No PCA components were extracted.
prepare <- prep(blueprint, training = ames_train)
prepare
Data Recipe

Inputs:

Training data contained 2053 data points and no missing data.

Operations:

Sparse, unbalanced variable filter removed Street, Alley, Land_Contour, Utilities, ... [trained]
Integer encoding for Condition_1, Overall_Qual, Overall_Cond, Exter_Qual, Exter_Cond, ... [trained]
Centering for Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
Scaling for Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
PCA extraction with Lot_Frontage, Lot_Area, Condition_1, Overall_Qual, ... [trained]
baked_train <- bake(prepare, new_data = ames_train)
baked_test <- bake(prepare, new_data = ames_test)
baked_train
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_nzv(all_nominal()) %>%
  step_integer(matches("Qual|Cond|QC|Qu")) %>%
  step_center(all_numeric(), -all_outcomes()) %>%
  step_scale(all_numeric(), -all_outcomes()) %>%
  step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)
# Specify resampling plan
cv <- trainControl(
  method = "repeatedcv", 
  number = 10, 
  repeats = 5
)

# Construct grid of hyperparameter values
hyper_grid <- expand.grid(k = seq(2, 25, by = 1))

# Tune a knn model using grid search
knn_fit2 <- train(
  blueprint, 
  data = ames_train, 
  method = "knn", 
  trControl = cv, 
  tuneGrid = hyper_grid,
  metric = "RMSE"
)
# print model results
knn_fit2
k-Nearest Neighbors 

2053 samples
  80 predictor

Recipe steps: nzv, integer, center, scale, dummy 
Resampling: Cross-Validated (10 fold, repeated 5 times) 
Summary of sample sizes: 1848, 1849, 1848, 1847, 1848, 1848, ... 
Resampling results across tuning parameters:

  k   RMSE      Rsquared   MAE     
   2  35939.89  0.8048642  22437.87
   3  35010.19  0.8158644  21727.75
   4  34397.34  0.8237016  21241.75
   5  33930.03  0.8316443  20941.83
   6  33546.26  0.8383965  20777.11
   7  33336.67  0.8433273  20688.80
   8  33132.85  0.8467156  20578.56
   9  33054.25  0.8486616  20536.96
  10  32948.08  0.8505378  20529.58
  11  32902.66  0.8522363  20537.99
  12  32812.52  0.8543514  20571.13
  13  32835.43  0.8554652  20625.39
  14  32859.76  0.8564118  20685.16
  15  32890.51  0.8571297  20728.81
  16  32939.40  0.8577373  20774.40
  17  32982.90  0.8578918  20817.29
  18  33040.08  0.8579879  20910.09
  19  33113.72  0.8579053  20977.65
  20  33210.68  0.8574767  21057.82
  21  33274.76  0.8570757  21110.48
  22  33296.28  0.8574598  21162.57
  23  33397.05  0.8571769  21252.19
  24  33421.53  0.8575578  21282.18
  25  33461.60  0.8574388  21320.31

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was k = 12.
# plot cross validation results
ggplot(knn_fit2)

LS0tCnRpdGxlOiAiQ2hhcHRlciAzOiBGZWF0dXJlICYgVGFyZ2V0IEVuZ2luZWVyaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIAoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rcy4KCmBgYHtyIHNldHVwfQojIGFkZGl0aW9uYWwgcGFja2FnZSByZXF1aXJlZCBpbiBoaWRkZW4gY29kZSBjaHVua3MKbGlicmFyeShwdXJycikKbGlicmFyeSh0aWR5cikKbGlicmFyeShyZWFkcikKbGlicmFyeShrYWJsZUV4dHJhKQoKIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKYGBgCgojIyBQcmVyZXF1aXNpdGVzCgpUaGlzIGNoYXB0ZXIgbGV2ZXJhZ2VzIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6CgpgYGB7ciBlbmdpbmVlcmluZy1wcmVyZXFzfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAjIGZvciBkYXRhIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KGdncGxvdDIpICAjIGZvciBhd2Vzb21lIGdyYXBoaWNzCmxpYnJhcnkodmlzZGF0KSAgICMgZm9yIGFkZGl0aW9uYWwgdmlzdWFsaXphdGlvbnMKIyBGZWF0dXJlIGVuZ2luZWVyaW5nIHBhY2thZ2VzCmxpYnJhcnkoY2FyZXQpICAgICMgZm9yIHZhcmlvdXMgTUwgdGFza3MKbGlicmFyeShyZWNpcGVzKSAgIyBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZyB0YXNrcwpgYGAKCldlJ2xsIGFsc28gY29udGludWUgd29ya2luZyB3aXRoIHRoZSBgYW1lc190cmFpbmAgZGF0YSBzZXQ6CgpgYGB7ciBlbmdpbmVlcmluZy1hbWVzLXRyYWluLCBlY2hvPVRSVUV9CmFtZXMgPC0gQW1lc0hvdXNpbmc6Om1ha2VfYW1lcygpCgojIExvYWQgYW5kIHNwbGl0IHRoZSBBbWVzIGhvdXNpbmcgZGF0YSB1c2luZyBzdHJhdGlmaWVkIHNhbXBsaW5nCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpzcGxpdCAgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHJzYW1wbGU6OnRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgICA8LSByc2FtcGxlOjp0ZXN0aW5nKHNwbGl0KQpgYGAKCiMjIFRhcmdldCBlbmdpbmVlcmluZwoKRmlndXJlIDMuMToKCmBgYHtyIGVuZ2luZWVyaW5nLXNrZXdlZC1yZXNpZHVhbHMsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTMsIGVjaG89VFJVRSwgZmlnLmNhcD0iVHJhbnNmb3JtaW5nIHRoZSByZXNwb25zZSB2YXJpYWJsZSB0byBtaW5pbWl6ZSBza2V3bmVzcyBjYW4gcmVzb2x2ZSBjb25jZXJucyB3aXRoIG5vbi1ub3JtYWxseSBkaXN0cmlidXRlZCBlcnJvcnMuIn0KbW9kZWxzIDwtIGMoIk5vbi1sb2cgdHJhbnNmb3JtZWQgbW9kZWwgcmVzaWR1YWxzIiwgCiAgICAgICAgICAgICJMb2cgdHJhbnNmb3JtZWQgbW9kZWwgcmVzaWR1YWxzIikKbGlzdCgKICBtMSA9IGxtKFNhbGVfUHJpY2UgfiBZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbiksCiAgbTIgPSBsbShsb2coU2FsZV9QcmljZSkgfiBZZWFyX0J1aWx0LCBkYXRhID0gYW1lc190cmFpbikKKSAlPiUKICBtYXAyX2Rmcihtb2RlbHMsIH4gYnJvb206OmF1Z21lbnQoLngpICU+JSBtdXRhdGUobW9kZWwgPSAueSkpICU+JQogIGdncGxvdChhZXMoLnJlc2lkKSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDc1KSArCiAgICBmYWNldF93cmFwKH4gbW9kZWwsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICB5bGFiKE5VTEwpICsKICAgIHhsYWIoIlJlc2lkdWFscyIpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctZXhhbXBsZS1sb2csIGV2YWw9RkFMU0V9CnRyYW5zZm9ybWVkX3Jlc3BvbnNlIDwtIGxvZyhhbWVzX3RyYWluJFNhbGVfUHJpY2UpCmBgYAoKYGBge3IgZW5naW5lZXJpbmcteV9sb2d9CiMgbG9nIHRyYW5zZm9ybWF0aW9uCmFtZXNfcmVjaXBlIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfbG9nKGFsbF9vdXRjb21lcygpKQphbWVzX3JlY2lwZQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLW5lZy1sb2csIGVycm9yPVRSVUV9CmxvZygtMC41KQpsb2cxcCgtMC41KQpgYGAKCiQkCiBcYmVnaW57ZXF1YXRpb259IAogeShcbGFtYmRhKSA9ClxiZWdpbntjYXNlc30KICAgXGZyYWN7WV5cbGFtYmRhLTF9e1xsYW1iZGF9LCAmIFx0ZXh0e2lmfVwgXGxhbWJkYSBcbmVxIDAgXFwKICAgXGxvZ1xsZWZ0KFlccmlnaHQpLCAmIFx0ZXh0e2lmfVwgXGxhbWJkYSA9IDAuClxlbmR7Y2FzZXN9ClxlbmR7ZXF1YXRpb259CiQkCgpGaWd1cmUgMy4yCgpgYGB7ciBlbmdpbmVlcmluZy1kaXN0cmlidXRpb24tY29tcGFyaXNvbiwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuY2FwPSJSZXNwb25zZSB2YXJpYWJsZSB0cmFuc2Zvcm1hdGlvbnMuIiwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9OX0KIyBMb2cgdHJhbnNmb3JtYXRpb24KdHJhaW5fbG9nX3kgPC0gbG9nKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKdGVzdF9sb2dfeSAgPC0gbG9nKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKCiMgQm94IENveCB0cmFuc2Zvcm1hdGlvbgpsYW1iZGEgIDwtIGZvcmVjYXN0OjpCb3hDb3gubGFtYmRhKGFtZXNfdHJhaW4kU2FsZV9QcmljZSkKdHJhaW5fYmNfeSA8LSBmb3JlY2FzdDo6Qm94Q294KGFtZXNfdHJhaW4kU2FsZV9QcmljZSwgbGFtYmRhKQp0ZXN0X2JjX3kgIDwtIGZvcmVjYXN0OjpCb3hDb3goYW1lc190ZXN0JFNhbGVfUHJpY2UsIGxhbWJkYSkKCiMgUGxvdCBkaWZmZXJlbmNlcwpsZXZzIDwtIGMoIk5vcm1hbCIsICJMb2dfVHJhbnNmb3JtIiwgIkJveENveF9UcmFuc2Zvcm0iKQpkYXRhLmZyYW1lKAogIE5vcm1hbCA9IGFtZXNfdHJhaW4kU2FsZV9QcmljZSwKICBMb2dfVHJhbnNmb3JtID0gdHJhaW5fbG9nX3ksCiAgQm94Q294X1RyYW5zZm9ybSA9IHRyYWluX2JjX3kKKSAlPiUKICBnYXRoZXIoVHJhbnNmb3JtLCBWYWx1ZSkgJT4lCiAgbXV0YXRlKFRyYW5zZm9ybSA9IGZhY3RvcihUcmFuc2Zvcm0sIGxldmVscyA9IGxldnMpKSAlPiUgCiAgZ2dwbG90KGFlcyhWYWx1ZSwgZmlsbCA9IFRyYW5zZm9ybSkpICsKICAgIGdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UsIGJpbnMgPSA0MCkgKwogICAgZmFjZXRfd3JhcCh+IFRyYW5zZm9ybSwgc2NhbGVzID0gImZyZWVfeCIpCmBgYAoKCmBgYHtyIGVuZ2luZWVyaW5nLWVuaW52ZXJzZS1iY30KIyBMb2cgdHJhbnNmb3JtIGEgdmFsdWUKeSA8LSBsb2coMTApCgojIFVuZG8gbG9nLXRyYW5zZm9ybWF0aW9uCmV4cCh5KQoKIyBCb3ggQ294IHRyYW5zZm9ybSBhIHZhbHVlCnkgPC0gZm9yZWNhc3Q6OkJveENveCgxMCwgbGFtYmRhKQoKIyBJbnZlcnNlIEJveCBDb3ggZnVuY3Rpb24KaW52X2JveF9jb3ggPC0gZnVuY3Rpb24oeCwgbGFtYmRhKSB7CiAgIyBmb3IgQm94LUNveCwgbGFtYmRhID0gMCAtLT4gbG9nIHRyYW5zZm9ybQogIGlmIChsYW1iZGEgPT0gMCkgZXhwKHgpIGVsc2UgKGxhbWJkYSp4ICsgMSleKDEvbGFtYmRhKSAKfQoKIyBVbmRvIEJveCBDb3gtdHJhbnNmb3JtYXRpb24KaW52X2JveF9jb3goeSwgbGFtYmRhKQpgYGAKCiMjIERlYWxpbmcgd2l0aCBtaXNzaW5nbmVzcwoKYGBge3IgZW5naW5lZXJpbmctYW1lcy1yYXctbWlzc2luZ30Kc3VtKGlzLm5hKEFtZXNIb3VzaW5nOjphbWVzX3JhdykpCmBgYAoKRmlndXJlIDMuMzoKCmBgYHtyIGVuZ2luZWVyaW5nLWhlYXQtbWFwLW1pc3NpbmduZXNzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01LCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwID0gIkhlYXQgbWFwIG9mIG1pc3NpbmcgdmFsdWVzIGluIHRoZSByYXcgQW1lcyBob3VzaW5nIGRhdGEuIn0KQW1lc0hvdXNpbmc6OmFtZXNfcmF3ICU+JQogIGlzLm5hKCkgJT4lCiAgcmVzaGFwZTI6Om1lbHQoKSAlPiUKICBnZ3Bsb3QoYWVzKFZhcjIsIFZhcjEsIGZpbGw9dmFsdWUpKSArIAogICAgZ2VvbV9yYXN0ZXIoKSArIAogICAgY29vcmRfZmxpcCgpICsKICAgIHNjYWxlX3lfY29udGludW91cyhOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgICBzY2FsZV9maWxsX2dyZXkobmFtZSA9ICIiLCAKICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJQcmVzZW50IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWlzc2luZyIpKSArCiAgICB4bGFiKCJPYnNlcnZhdGlvbiIpICsKICAgIHRoZW1lKGF4aXMudGV4dC55ICA9IGVsZW1lbnRfdGV4dChzaXplID0gNCkpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctbWlzc2luZ25lc3MtZ2FyYWdlc30KQW1lc0hvdXNpbmc6OmFtZXNfcmF3ICU+JSAKICBmaWx0ZXIoaXMubmEoYEdhcmFnZSBUeXBlYCkpICU+JSAKICBzZWxlY3QoYEdhcmFnZSBUeXBlYCwgYEdhcmFnZSBDYXJzYCwgYEdhcmFnZSBBcmVhYCkKYGBgCgpGaWd1cmUgMy40OgoKYGBge3IgZW5naW5lZXJpbmctbWlzc2luZ25lc3MtdmlzbmEsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEyLCBmaWcuY2FwPSJWaXN1YWxpemluZyBtaXNzaW5nIGRhdGEgcGF0dGVybnMgaW4gdGhlIHJhdyBBbWVzIGhvdXNpbmcgZGF0YS4ifQp2aXNfbWlzcyhBbWVzSG91c2luZzo6YW1lc19yYXcsIGNsdXN0ZXIgPSBUUlVFKQpgYGAKCiMjIyBJbXB1dGF0aW9uCgpgYGB7ciBlbmdpbmVlcmluZy1tZWFuLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9tZWRpYW5pbXB1dGUoR3JfTGl2X0FyZWEpCmBgYAoKYGBge3IgZW5naW5lZXJpbmcta25uLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9rbm5pbXB1dGUoYWxsX3ByZWRpY3RvcnMoKSwgbmVpZ2hib3JzID0gNikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1iYWdnaW5nLWltcHV0ZX0KYW1lc19yZWNpcGUgJT4lCiAgc3RlcF9iYWdpbXB1dGUoYWxsX3ByZWRpY3RvcnMoKSkKYGBgCgpGaWd1cmUgMy41OgoKYGBge3IgZW5naW5lZXJpbmctaW1wdXRhdGlvbi1leGFtcGxlcywgZWNobz1UUlVFLCBmaWcuY2FwPSJDb21wYXJpc29uIG9mIHRocmVlIGRpZmZlcmVudCBpbXB1dGF0aW9uIG1ldGhvZHMuIFRoZSByZWQgcG9pbnRzIHJlcHJlc2VudCBhY3R1YWwgdmFsdWVzIHdoaWNoIHdlcmUgcmVtb3ZlZCBhbmQgbWFkZSBtaXNzaW5nIGFuZCB0aGUgYmx1ZSBwb2ludHMgcmVwcmVzZW50IHRoZSBpbXB1dGVkIHZhbHVlcy4gRXN0aW1hdGVkIHN0YXRpc3RpYyBpbXB1dGF0aW9uIG1ldGhvZHMgKGkuZS4gbWVhbiwgbWVkaWFuKSBtZXJlbHkgcHJlZGljdCB0aGUgc2FtZSB2YWx1ZSBmb3IgZWFjaCBvYnNlcnZhdGlvbiBhbmQgY2FuIHJlZHVjZSB0aGUgc2lnbmFsIGJldHdlZW4gYSBmZWF0dXJlIGFuZCB0aGUgcmVzcG9uc2U7IHdoZXJlYXMgS05OIGFuZCB0cmVlLWJhc2VkIHByb2NlZHVyZXMgdGVuZCB0byBtYWludGFpbiB0aGUgZmVhdHVyZSBkaXN0cmlidXRpb24gYW5kIHJlbGF0aW9uc2hpcC4ifQppbXB1dGVfYW1lcyA8LSBhbWVzX3RyYWluCnNldC5zZWVkKDEyMykKaW5kZXggPC0gc2FtcGxlKHNlcV9hbG9uZyhpbXB1dGVfYW1lcyRHcl9MaXZfQXJlYSksIDUwKQphY3R1YWxzIDwtIGFtZXNfdHJhaW5baW5kZXgsIF0KaW1wdXRlX2FtZXMkR3JfTGl2X0FyZWFbaW5kZXhdIDwtIE5BCgpwMSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gaW1wdXRlX2FtZXMsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGFscGhhID0gLjIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBhY3R1YWxzLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeF9sb2cxMChsaW1pdHMgPSBjKDMwMCwgNTAwMCkpICsKICBzY2FsZV95X2xvZzEwKGxpbWl0cyA9IGMoMTAwMDAsIDUwMDAwMCkpICsKICBnZ3RpdGxlKCJBY3R1YWwgdmFsdWVzIikKCiMgTWVhbiBpbXB1dGF0aW9uCm1lYW5fanVpY2VkIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGltcHV0ZV9hbWVzKSAlPiUKICBzdGVwX21lYW5pbXB1dGUoR3JfTGl2X0FyZWEpICU+JQogIHByZXAodHJhaW5pbmcgPSBpbXB1dGVfYW1lcywgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKQptZWFuX2ltcHV0ZSA8LSBtZWFuX2p1aWNlZFtpbmRleCwgXQogIApwMiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gYWN0dWFscywgYWVzKEdyX0xpdl9BcmVhLCBTYWxlX1ByaWNlKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IG1lYW5faW1wdXRlLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJibHVlIikgKwogIHNjYWxlX3hfbG9nMTAobGltaXRzID0gYygzMDAsIDUwMDApKSArCiAgc2NhbGVfeV9sb2cxMChsaW1pdHMgPSBjKDEwMDAwLCA1MDAwMDApKSArCiAgZ2d0aXRsZSgiTWVhbiBJbXB1dGF0aW9uIikKCiMgS05OIGltcHV0YXRpb24Ka25uX2p1aWNlZCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBpbXB1dGVfYW1lcykgJT4lCiAgc3RlcF9rbm5pbXB1dGUoR3JfTGl2X0FyZWEpICU+JQogIHByZXAodHJhaW5pbmcgPSBpbXB1dGVfYW1lcywgcmV0YWluID0gVFJVRSkgJT4lCiAganVpY2UoKQprbm5faW1wdXRlIDwtIGtubl9qdWljZWRbaW5kZXgsIF0KICAKcDMgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGFjdHVhbHMsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBrbm5faW1wdXRlLCBhZXMoR3JfTGl2X0FyZWEsIFNhbGVfUHJpY2UpLCBjb2xvciA9ICJibHVlIikgKwogIHNjYWxlX3hfbG9nMTAobGltaXRzID0gYygzMDAsIDUwMDApKSArCiAgc2NhbGVfeV9sb2cxMChsaW1pdHMgPSBjKDEwMDAwLCA1MDAwMDApKSArCiAgZ2d0aXRsZSgiS05OIEltcHV0YXRpb24iKQoKIyBCYWdnZWQgaW1wdXRhdGlvbgpiYWdnZWRfanVpY2VkIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGltcHV0ZV9hbWVzKSAlPiUKICBzdGVwX2JhZ2ltcHV0ZShHcl9MaXZfQXJlYSkgJT4lCiAgcHJlcCh0cmFpbmluZyA9IGltcHV0ZV9hbWVzLCByZXRhaW4gPSBUUlVFKSAlPiUKICBqdWljZSgpCmJhZ2dlZF9pbXB1dGUgPC0gYmFnZ2VkX2p1aWNlZFtpbmRleCwgXQogIApwNCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gYWN0dWFscywgYWVzKEdyX0xpdl9BcmVhLCBTYWxlX1ByaWNlKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGJhZ2dlZF9pbXB1dGUsIGFlcyhHcl9MaXZfQXJlYSwgU2FsZV9QcmljZSksIGNvbG9yID0gImJsdWUiKSArCiAgc2NhbGVfeF9sb2cxMChsaW1pdHMgPSBjKDMwMCwgNTAwMCkpICsKICBzY2FsZV95X2xvZzEwKGxpbWl0cyA9IGMoMTAwMDAsIDUwMDAwMCkpICsKICBnZ3RpdGxlKCJCYWdnZWQgVHJlZXMgSW1wdXRhdGlvbiIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbnJvdyA9IDIpCmBgYAoKIyMgRmVhdHVyZSBmaWx0ZXJpbmcgCgpGaWd1cmUgMy42IChnZW5lcmF0ZWQgZnJvbSBiZW5jaG1hcmsgZGF0YSBwZXJmb3JtZWQgaW4gdGhlIHBhc3QpOgoKYGBge3IgZW5naW5lZXJpbmctYWNjdXJhY3ktY29tcGFyaXNvbiwgZWNobz1UUlVFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9My41LCBmaWcuY2FwPSJUZXN0IHNldCBSTVNFIHByb2ZpbGVzIHdoZW4gbm9uLWluZm9ybWF0aXZlIHByZWRpY3RvcnMgYXJlIGFkZGVkLiJ9Cm1vZGVsX3Jlc3VsdHMgPC0gcmVhZF9jc3YoImRhdGEvZmVhdHVyZS1zZWxlY3Rpb24taW1wYWN0cy1yZXN1bHRzLmNzdiIpICU+JQogIG11dGF0ZSh0eXBlID0gY2FzZV93aGVuKAogICAgbW9kZWwgJWluJSBjKCJsbSIsICJwY3IiLCAicGxzIiwgImdsbW5ldCIsICJsYXNzbyIpIH4gIkxpbmVhciBtb2RlbHMiLAogICAgbW9kZWwgJWluJSBjKCJlYXJ0aCIsICJzdm1MaW5lYXIiLCAibm4iKSB+ICJOb24tbGluZWFyIE1vZGVscyIsCiAgICBUUlVFIH4gIlRyZWUtYmFzZWQgTW9kZWxzIgogICkpICU+JQogIG11dGF0ZShtb2RlbCA9IGNhc2Vfd2hlbigKICAgIG1vZGVsID09ICJsbSIgfiAiTGluZWFyIHJlZ3Jlc3Npb24iLAogICAgbW9kZWwgPT0gImVhcnRoIiB+ICJNdWx0aXZhcmlhdGUgYWRhcHRpdmUgcmVncmVzc2lvbiBzcGxpbmVzIiwKICAgIG1vZGVsID09ICJnYm0iIH4gIkdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmVzIiwKICAgIG1vZGVsID09ICJnbG1uZXQiIH4gIkVsYXN0aWMgbmV0IiwKICAgIG1vZGVsID09ICJsYXNzbyIgfiAiTGFzc28iLAogICAgbW9kZWwgPT0gIm5uIiB+ICJOZXVyYWwgbmV0IiwKICAgIG1vZGVsID09ICJwY3IiIH4gIlByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbiIsCiAgICBtb2RlbCA9PSAicGxzIiB+ICJQYXJ0aWFsIGxlYXN0IHNxdWFyZXMiLAogICAgbW9kZWwgPT0gInJhbmdlciIgfiAiUmFuZG9tIGZvcmVzdCIsCiAgICBUUlVFIH4gIlN1cHBvcnQgdmVjdG9yIG1hY2hpbmUiCiAgKSkKCmdncGxvdChtb2RlbF9yZXN1bHRzLCBhZXMoTklQLCBSTVNFLCBjb2xvciA9IG1vZGVsLCBsdHkgPSBtb2RlbCkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH4gdHlwZSwgbnJvdyA9IDEpICsKICB4bGFiKCJOdW1iZXIgb2YgYWRkaXRpb25hbCBub24taW5mb3JtYXRpdmUgcHJlZGljdG9ycyIpCmBgYAoKRmlndXJlIDMuNyAoZ2VuZXJhdGVkIGZyb20gYmVuY2htYXJrIGRhdGEgcGVyZm9ybWVkIGluIHRoZSBwYXN0KToKCmBgYHtyIGVuZ2luZWVyaW5nLWltcGFjdC1vbi10aW1lLCBlY2hvPVRSVUUsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0zLjUsIGZpZy5jYXA9IkltcGFjdCBpbiBtb2RlbCB0cmFpbmluZyB0aW1lIGFzIG5vbi1pbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIGFyZSBhZGRlZC4ifQptb2RlbF9yZXN1bHRzICU+JQogIGdyb3VwX2J5KG1vZGVsKSAlPiUKICBtdXRhdGUoCiAgICB0aW1lX2ltcGFjdCA9IHRpbWUgLyBmaXJzdCh0aW1lKSwKICAgIHRpbWVfaW1wYWN0ID0gdGltZV9pbXBhY3QgLSAxCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKE5JUCwgdGltZV9pbXBhY3QsIGNvbG9yID0gbW9kZWwsIGx0eSA9IG1vZGVsKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGZhY2V0X3dyYXAofiB0eXBlLCBucm93ID0gMSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCJQZXJjZW50IGluY3JlYXNlIGluIHRyYWluaW5nIHRpbWUiLCAKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAgIHhsYWIoIk51bWJlciBvZiBhZGRpdGlvbmFsIG5vbi1pbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1uenZ9CmNhcmV0OjpuZWFyWmVyb1ZhcihhbWVzX3RyYWluLCBzYXZlTWV0cmljcyA9IFRSVUUpICU+JSAKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAKICBmaWx0ZXIobnp2KQpgYGAKCiMjIE51bWVyaWMgZmVhdHVyZSBlbmdpbmVlcmluZwoKYGBge3IgZW5naW5lZXJpbmctbm9ybWFsaXppbmd9CiMgTm9ybWFsaXplIGFsbCBudW1lcmljIGNvbHVtbnMKcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljKCkpICAgICAgICAgICAgICAgICAKYGBgCgpGaWd1cmUgMy44OgoKYGBge3IgZW5naW5lZXJpbmctc3RhbmRhcmRpemluZywgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTMsIGZpZy5jYXA9IlN0YW5kYXJkaXppbmcgZmVhdHVyZXMgYWxsb3dzIGFsbCBmZWF0dXJlcyB0byBiZSBjb21wYXJlZCBvbiBhIGNvbW1vbiB2YWx1ZSBzY2FsZSByZWdhcmRsZXNzIG9mIHRoZWlyIHJlYWwgdmFsdWUgZGlmZmVyZW5jZXMuIn0Kc2V0LnNlZWQoMTIzKQp4MSA8LSB0aWJibGUoCiAgdmFyaWFibGUgPSAieDEiLAogIGBSZWFsIHZhbHVlYCA9IHJ1bmlmKDI1LCBtaW4gPSAtMzAsIG1heCA9IDUpLAogIGBTdGFuZGFyZGl6ZWQgdmFsdWVgID0gc2NhbGUoYFJlYWwgdmFsdWVgKSAlPiUgYXMubnVtZXJpYygpCikKCnNldC5zZWVkKDQ1NikKeDIgPC0gdGliYmxlKAogIHZhcmlhYmxlID0gIngyIiwKICBgUmVhbCB2YWx1ZWAgPSBybG5vcm0oMjUsIGxvZygyNSkpLAogIGBTdGFuZGFyZGl6ZWQgdmFsdWVgID0gc2NhbGUoYFJlYWwgdmFsdWVgKSAlPiUgYXMubnVtZXJpYygpCikKCnNldC5zZWVkKDc4OSkKeDMgPC0gdGliYmxlKAogIHZhcmlhYmxlID0gIngzIiwKICBgUmVhbCB2YWx1ZWAgPSBybm9ybSgyNSwgMTUwLCAxNSksCiAgYFN0YW5kYXJkaXplZCB2YWx1ZWAgPSBzY2FsZShgUmVhbCB2YWx1ZWApICU+JSBhcy5udW1lcmljKCkKKQoKeDEgJT4lCiAgYmluZF9yb3dzKHgyKSAlPiUKICBiaW5kX3Jvd3MoeDMpICU+JQogIGdhdGhlcihrZXksIHZhbHVlLCAtdmFyaWFibGUpICU+JQogIG11dGF0ZSh2YXJpYWJsZSA9IGZhY3Rvcih2YXJpYWJsZSwgbGV2ZWxzID0gYygieDMiLCAieDIiLCAieDEiKSkpICU+JQogIGdncGxvdChhZXModmFsdWUsIHZhcmlhYmxlKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IC42KSArCiAgICBmYWNldF93cmFwKH4ga2V5LCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgeWxhYigiRmVhdHVyZSIpICsKICAgIHhsYWIoIlZhbHVlIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGFuZGFyZGl6aW5nLXJlY2lwZXN9CmFtZXNfcmVjaXBlICU+JQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpCmBgYAoKCiMjIENhdGVnb3JpY2FsIGZlYXR1cmUgZW5naW5lZXJpbmcKCmBgYHtyIGVuZ2luZWVyaW5nLW92ZXJhbGwtcXVhbC1sZXZlbHN9CmNvdW50KGFtZXNfdHJhaW4sIE5laWdoYm9yaG9vZCkgJT4lIGFycmFuZ2UobikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zY3JlZW4tcG9yY2h9CmNvdW50KGFtZXNfdHJhaW4sIFNjcmVlbl9Qb3JjaCkgJT4lIGFycmFuZ2UobikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy10YmR9CiMgTHVtcCBsZXZlbHMgZm9yIHR3byBmZWF0dXJlcwpsdW1waW5nIDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfb3RoZXIoTmVpZ2hib3Job29kLCB0aHJlc2hvbGQgPSAwLjAxLCAKICAgICAgICAgICAgIG90aGVyID0gIm90aGVyIikgJT4lCiAgc3RlcF9vdGhlcihTY3JlZW5fUG9yY2gsIHRocmVzaG9sZCA9IDAuMSwgCiAgICAgICAgICAgICBvdGhlciA9ICI+MCIpCgojIEFwcGx5IHRoaXMgYmx1ZSBwcmludCAtLT4geW91IHdpbGwgbGVhcm4gYWJvdXQgdGhpcyBhdCAKIyB0aGUgZW5kIG9mIHRoZSBjaGFwdGVyCmFwcGx5XzJfdHJhaW5pbmcgPC0gcHJlcChsdW1waW5nLCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pICU+JQogIGJha2UoYW1lc190cmFpbikKCiMgTmV3IGRpc3RyaWJ1dGlvbiBvZiBOZWlnaGJvcmhvb2QKY291bnQoYXBwbHlfMl90cmFpbmluZywgTmVpZ2hib3Job29kKSAlPiUgYXJyYW5nZShuKQoKIyBOZXcgZGlzdHJpYnV0aW9uIG9mIFNjcmVlbl9Qb3JjaApjb3VudChhcHBseV8yX3RyYWluaW5nLCBTY3JlZW5fUG9yY2gpICU+JSBhcnJhbmdlKG4pCmBgYAoKYGBge3IgZW5naW5lZXJpbmctb25lLWhvdCwgZWNobz1UUlVFLCBmaWcuY2FwPSdFaWdodCBvYnNlcnZhdGlvbnMgY29udGFpbmluZyBhIGNhdGVnb3JpY2FsIGZlYXR1cmUgWCBhbmQgdGhlIGRpZmZlcmVuY2UgaW4gaG93IG9uZS1ob3QgYW5kIGR1bW15IGVuY29kaW5nIHRyYW5zZm9ybXMgdGhpcyBmZWF0dXJlLicsIG91dC5oZWlnaHQ9Ijk5JSIsIG91dC53aWR0aD0iOTklIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9vaGUtdnMtZHVtbXkucG5nIikKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy10YmQyfQojIEx1bXAgbGV2ZWxzIGZvciB0d28gZmVhdHVyZXMKcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCBvbmVfaG90ID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1sYWJlbC1lbmNvZGluZ30KIyBPcmlnaW5hbCBjYXRlZ29yaWVzCmNvdW50KGFtZXNfdHJhaW4sIE1TX1N1YkNsYXNzKQoKIyBMYWJlbCBlbmNvZGVkCnJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfaW50ZWdlcihNU19TdWJDbGFzcykgJT4lCiAgcHJlcChhbWVzX3RyYWluKSAlPiUKICBiYWtlKGFtZXNfdHJhaW4pICU+JQogIGNvdW50KE1TX1N1YkNsYXNzKQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLXF1YWwtdmFyaWFibGVzfQphbWVzX3RyYWluICU+JSBzZWxlY3QoY29udGFpbnMoIlF1YWwiKSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1vcmRpbmFsLWVuY29kaW5nfQojIE9yaWdpbmFsIGNhdGVnb3JpZXMKY291bnQoYW1lc190cmFpbiwgT3ZlcmFsbF9RdWFsKQoKIyBMYWJlbCBlbmNvZGVkCnJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfaW50ZWdlcihPdmVyYWxsX1F1YWwpICU+JQogIHByZXAoYW1lc190cmFpbikgJT4lCiAgYmFrZShhbWVzX3RyYWluKSAlPiUKICBjb3VudChPdmVyYWxsX1F1YWwpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctdGFyZ2V0LWVuY29kaW5nLCBlY2hvPVRSVUUsIGZpZy5jYXA9J0V4YW1wbGUgb2YgdGFyZ2V0IGVuY29kaW5nIHRoZSBOZWlnaGJvcmhvb2QgZmVhdHVyZSBvZiB0aGUgQW1lcyBob3VzaW5nIGRhdGEgc2V0Lid9CmFtZXNfdHJhaW4gJT4lCiAgZ3JvdXBfYnkoTmVpZ2hib3Job29kKSAlPiUKICBzdW1tYXJpemUoYEF2ZyBTYWxlX1ByaWNlYCA9IG1lYW4oU2FsZV9QcmljZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgaGVhZCgxMCkgJT4lCiAga2FibGUoY2FwdGlvbiA9ICJFeGFtcGxlIG9mIHRhcmdldCBlbmNvZGluZyB0aGUgTmVpZ2hib3Job29kIGZlYXR1cmUgb2YgdGhlIEFtZXMgaG91c2luZyBkYXRhIHNldC4iKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1wcm9wb3J0aW9uLWVuY29kaW5nLCBlY2hvPVRSVUUsIGZpZy5jYXA9J0V4YW1wbGUgb2YgY2F0ZWdvcmljYWwgcHJvcG9ydGlvbiBlbmNvZGluZyB0aGUgTmVpZ2hib3Job29kIGZlYXR1cmUgb2YgdGhlIEFtZXMgaG91c2luZyBkYXRhIHNldC4nfQphbWVzX3RyYWluICU+JQogIGNvdW50KE5laWdoYm9yaG9vZCkgJT4lCiAgbXV0YXRlKFByb3BvcnRpb24gPSBuIC8gc3VtKG4pKSAlPiUKICBzZWxlY3QoLW4pICU+JQogIGhlYWQoMTApICU+JQogIGthYmxlKGNhcHRpb24gPSAnRXhhbXBsZSBvZiBjYXRlZ29yaWNhbCBwcm9wb3J0aW9uIGVuY29kaW5nIHRoZSBOZWlnaGJvcmhvb2QgZmVhdHVyZSBvZiB0aGUgQW1lcyBob3VzaW5nIGRhdGEgc2V0LicpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUUlVFKQpgYGAKCiMjIERpbWVuc2lvbiByZWR1Y3Rpb24KCmBgYHtyIGVuZ2luZWVyaW5nLXBjYX0KcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpKSAlPiUKICBzdGVwX3BjYShhbGxfbnVtZXJpYygpLCB0aHJlc2hvbGQgPSAuOTUpCmBgYAoKIyMgUHJvcGVyIGltcGxlbWVudGF0aW9uCgpGaWd1cmUgMy4xMDoKCmBgYHtyIGVuZ2luZWVyaW5nLW1pbmltaXplLWxlYWthZ2UsIGVjaG89VFJVRSwgZmlnLmNhcD0iUGVyZm9ybWluZyBmZWF0dXJlIGVuZ2luZWVyaW5nIHByZXByb2Nlc3Npbmcgd2l0aGluIGVhY2ggcmVzYW1wbGUgaGVscHMgdG8gbWluaW1pemUgZGF0YSBsZWFrYWdlLiIsIG91dC53aWR0aD0nOTAlJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9taW5pbWl6ZS1sZWFrYWdlLnBuZyIpCmBgYAoKYGBge3IgZW5naW5lZXJpbmctc3RlcDEtcmVjaXBlfQpibHVlcHJpbnQgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgICU+JQogIHN0ZXBfaW50ZWdlcihtYXRjaGVzKCJRdWFsfENvbmR8UUN8UXUiKSkgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9wY2EoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKQogIApibHVlcHJpbnQKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGVwMi1wcmVwYXJlfQpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pCnByZXBhcmUKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1zdGVwMy1iYWtlfQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQpiYWtlZF90cmFpbgpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLWtubi1ibHVlcHJpbnR9CmJsdWVwcmludCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzX3RyYWluKSAlPiUKICBzdGVwX256dihhbGxfbm9taW5hbCgpKSAlPiUKICBzdGVwX2ludGVnZXIobWF0Y2hlcygiUXVhbHxDb25kfFFDfFF1IikpICU+JQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9zY2FsZShhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpLCBvbmVfaG90ID0gVFJVRSkKYGBgCgpgYGB7ciBlbmdpbmVlcmluZy1rbm4td2l0aC1ibHVlcHJpbnR9CiMgU3BlY2lmeSByZXNhbXBsaW5nIHBsYW4KY3YgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgCiAgbnVtYmVyID0gMTAsIAogIHJlcGVhdHMgPSA1CikKCiMgQ29uc3RydWN0IGdyaWQgb2YgaHlwZXJwYXJhbWV0ZXIgdmFsdWVzCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQoayA9IHNlcSgyLCAyNSwgYnkgPSAxKSkKCiMgVHVuZSBhIGtubiBtb2RlbCB1c2luZyBncmlkIHNlYXJjaAprbm5fZml0MiA8LSB0cmFpbigKICBibHVlcHJpbnQsIAogIGRhdGEgPSBhbWVzX3RyYWluLCAKICBtZXRob2QgPSAia25uIiwgCiAgdHJDb250cm9sID0gY3YsIAogIHR1bmVHcmlkID0gaHlwZXJfZ3JpZCwKICBtZXRyaWMgPSAiUk1TRSIKKQpgYGAKCmBgYHtyIGVuZ2luZWVyaW5nLWtubi13aXRoLWJsdWVwcmludC1hc3Nlc3MsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iUmVzdWx0cyBmcm9tIHRoZSBzYW1lIGdyaWQgc2VhcmNoIHBlcmZvcm1lZCBpbiBTZWN0aW9uIDIuNyBidXQgd2l0aCBmZWF0dXJlIGVuZ2luZWVyaW5nIHBlcmZvcm1lZCB3aXRoaW4gZWFjaCByZXNhbXBsZS4ifQojIHByaW50IG1vZGVsIHJlc3VsdHMKa25uX2ZpdDIKIyBwbG90IGNyb3NzIHZhbGlkYXRpb24gcmVzdWx0cwpnZ3Bsb3Qoa25uX2ZpdDIpCmBgYA==