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.

knitr::opts_chunk$set(
  echo = TRUE,
  fig.align = "center",
  message = FALSE,
  warning = FALSE,
  collapse = TRUE,
  cache = FALSE
)

# underlying code dependencies
library(tidyverse)

# Set the graphical theme
theme_set(theme_light())

Figure 2.1:

knitr::include_graphics("images/modeling_process.png")

Prerequisites

This chapter leverages the following packages.

Data used:

# Ames housing data
ames <- AmesHousing::make_ames()
ames.h2o <- as.h2o(ames)

# Job attrition data
churn <- rsample::attrition %>% 
  mutate_if(is.ordered, .funs = factor, ordered = FALSE)
churn.h2o <- as.h2o(churn)

Data splitting

Figure 2.2:

knitr::include_graphics("images/data_split.png")

Simple random sampling

# Using base R
set.seed(123)  # for reproducibility
index_1 <- sample(1:nrow(ames), round(nrow(ames) * 0.7))
train_1 <- ames[index_1, ]
test_1  <- ames[-index_1, ]

# Using caret package
set.seed(123)  # for reproducibility
index_2 <- createDataPartition(ames$Sale_Price, p = 0.7, 
                               list = FALSE)
train_2 <- ames[index_2, ]
test_2  <- ames[-index_2, ]

# Using rsample package
set.seed(123)  # for reproducibility
split_1  <- initial_split(ames, prop = 0.7)
train_3  <- training(split_1)
test_3   <- testing(split_1)

# Using h2o package
split_2 <- h2o.splitFrame(ames.h2o, ratios = 0.7, 
                          seed = 123)
train_4 <- split_2[[1]]
test_4  <- split_2[[2]]

Figure 2.3:

p1 <- ggplot(train_1, aes(x = Sale_Price)) + 
    geom_density(trim = TRUE) + 
    geom_density(data = test_1, trim = TRUE, col = "red") +
  ggtitle("Base R")

p2 <- ggplot(train_2, aes(x = Sale_Price)) + 
    geom_density(trim = TRUE) + 
    geom_density(data = test_2, trim = TRUE, col = "red") +
    theme(axis.title.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.text.y = element_blank()) +
    ggtitle("caret") 

p3 <- ggplot(train_3, aes(x = Sale_Price)) + 
    geom_density(trim = TRUE) + 
    geom_density(data = test_3, trim = TRUE, col = "red") +
    theme(axis.title.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.text.y = element_blank()) +
    ggtitle("rsample")

p4 <- ggplot(as.data.frame(train_4), aes(x = Sale_Price)) + 
    geom_density(trim = TRUE) + 
    geom_density(data = as.data.frame(test_4), trim = TRUE, col = "red") +
    theme(axis.title.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.text.y = element_blank()) +
    ggtitle("h2o")

# Side-by-side plots
gridExtra::grid.arrange(p1, p2, p3, p4, nrow = 1)


# clean up
rm(p1, p2, p3, p4)

Stratified sampling

# orginal response distribution
table(churn$Attrition) %>% prop.table()

       No       Yes 
0.8387755 0.1612245 
# stratified sampling with the rsample package
set.seed(123)
split_strat  <- initial_split(churn, prop = 0.7, 
                              strata = "Attrition")
train_strat  <- training(split_strat)
test_strat   <- testing(split_strat)

# consistent response ratio between train & test
table(train_strat$Attrition) %>% prop.table()

      No      Yes 
0.838835 0.161165 
table(test_strat$Attrition) %>% prop.table()

       No       Yes 
0.8386364 0.1613636 

Many formula interfaces

This code chunk is for illustrative purposes only; it is not designed to execute.

# Sale price as function of neighborhood and year sold
model_fn(Sale_Price ~ Neighborhood + Year_Sold, 
         data = ames)

# Variables + interactions
model_fn(Sale_Price ~ Neighborhood + Year_Sold + 
           Neighborhood:Year_Sold, data = ames)

# Shorthand for all predictors
model_fn(Sale_Price ~ ., data = ames)

# Inline functions / transformations
model_fn(log10(Sale_Price) ~ ns(Longitude, df = 3) + 
           ns(Latitude, df = 3), data = ames)

This code chunk is for illustrative purposes only; it is not designed to execute.

# Use separate inputs for X and Y
features <- c("Year_Sold", "Longitude", "Latitude")
model_fn(x = ames[, features], y = ames$Sale_Price)

This code chunk is for illustrative purposes only; it is not designed to execute.

model_fn(
  x = c("Year_Sold", "Longitude", "Latitude"),
  y = "Sale_Price",
  data = ames.h2o
)

Many engines

lm_lm <- lm(Sale_Price ~ ., data = ames)
lm_glm <- glm(Sale_Price ~ ., data = ames, family = gaussian)
lm_caret <- train(Sale_Price ~ ., data = ames, method = "lm")

Table 1:

Table 1: Syntax for computing predicted class probabilities with direct engines.
Algorithm Package Code
Linear discriminant analysis MASS predict(obj)
Generalized linear model stats predict(obj, type = "response")
Mixture discriminant analysis mda predict(obj, type = "posterior")
Decision tree rpart predict(obj, type = "prob")
Random Forest ranger predict(obj)$predictions
Gradient boosting machine gbm predict(obj, type = "response", n.trees)

Resampling methods

k-fold cross validation

Figure 2.4:

knitr::include_graphics("images/cv.png")

Figure 2.5:

cv <- vfold_cv(mtcars, 10)
cv_plot <- cv$splits %>%
  purrr::map2_dfr(seq_along(cv$splits), ~ mtcars %>% mutate(
    Resample = paste0("Fold_", stringr::str_pad(.y, 2, pad = 0)),
    ID = row_number(),
    Data = ifelse(ID %in% .x$in_id, "Training", "Validation"))
    ) %>%
  ggplot(aes(Resample, ID, fill = Data)) +
  geom_tile() +
  scale_fill_manual(values = c("#f2f2f2", "#AAAAAA")) +
  scale_y_reverse("Observation ID", breaks = 1:nrow(mtcars), expand = c(0, 0)) +
  scale_x_discrete(NULL, expand = c(0, 0)) +
  theme_classic() +
  theme(legend.title=element_blank())

cv_plot

This code chunk is for illustrative purposes only; it is not designed to execute.

# Example using h2o
h2o.cv <- h2o.glm(
  x = x, 
  y = y, 
  training_frame = ames.h2o,
  nfolds = 10  # perform 10-fold CV
)

Bootstrapping

Figure 2.6:

knitr::include_graphics("images/bootstrap-scheme.png")

Figure 2.7:

boots <- rsample::bootstraps(mtcars, 10)
boots_plot <- boots$splits %>%
  purrr::map2_dfr(seq_along(boots$splits), ~ mtcars %>% 
             mutate(
               Resample = paste0("Bootstrap_", stringr::str_pad(.y, 2, pad = 0)),
               ID = row_number()
             ) %>%
             group_by(ID) %>%
             mutate(Replicates = factor(sum(ID == .x$in_id)))) %>%
  ggplot(aes(Resample, ID, fill = Replicates)) +
  geom_tile() +
  scale_fill_manual(values = c("#FFFFFF", "#F5F5F5", "#C8C8C8", "#A0A0A0", "#707070", "#505050", "#000000")) +
  scale_y_reverse("Observation ID", breaks = 1:nrow(mtcars), expand = c(0, 0)) +
  scale_x_discrete(NULL, expand = c(0, 0)) +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  ggtitle("Bootstrap sampling") 

cv_plot <- cv_plot + 
  ggtitle("10-fold cross validation") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

cowplot::plot_grid(boots_plot, cv_plot, align = "h", nrow = 1)


# clean up
rm(boots, boots_plot, cv_plot)

Bias variance trade-off

Bias

Figure 2.8:

# Simulate some nonlinear monotonic data
set.seed(123)  # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
  filter(x < 4.5)

# Single model fit
bias_model <- lm(y ~ I(x^3), data = df)
df$predictions <- predict(bias_model, df)
p1 <- ggplot(df, aes(x, y)) +
  geom_point(alpha = .3) +
  geom_line(aes(x, predictions), size = 1.5, color = "dodgerblue") +
  scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
  ggtitle("Single biased model fit")

# Bootstrapped model fit
bootstrap_n <- 25
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
  set.seed(i)  # for reproducibility
  index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
  df_sim <- df[index, ]
  fit <- lm(y ~ I(x^3), data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("model", i)
  df_sim$ob <- index
  bootstrap_results <- rbind(bootstrap_results, df_sim)
}

p2 <- ggplot(bootstrap_results, aes(x, predictions, color = model)) +
  geom_line(show.legend = FALSE, size = .5) +
  scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
  ggtitle("25 biased models fit to bootstrap samples")

gridExtra::grid.arrange(p1, p2, nrow = 1)

Variance

Figure 2.9:

# Simulate some nonlinear monotonic data
set.seed(123)  # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
  filter(x < 4.5)

# Single model fit
variance_model <- knnreg(y ~ x, k = 3, data = df)
df$predictions <- predict(variance_model, df)
p1 <- ggplot(df, aes(x, y)) +
  geom_point(alpha = .3) +
  geom_line(aes(x, predictions), size = 1.5, color = "dodgerblue") +
  scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
  ggtitle("Single high variance model fit")

# Bootstrapped model fit
bootstrap_n <- 25
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
  set.seed(i)  # for reproducibility
  index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
  df_sim <- df[index, ]
  fit <- knnreg(y ~ x, k = 3, data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("model", i)
  df_sim$ob <- index
  bootstrap_results <- rbind(bootstrap_results, df_sim)
}

p2 <- ggplot(bootstrap_results, aes(x, predictions, color = model)) +
  geom_line(show.legend = FALSE) +
  scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
  ggtitle("25 high variance models fit to bootstrap samples")

gridExtra::grid.arrange(p1, p2, nrow = 1)

Hyperparameter tuning

Figure 2.10:

k_results <- NULL
k <- c(2, 5, 10, 20, 50, 150)

# Fit many different models
for(i in seq_along(k)) {
  df_sim <- df
  fit <- knnreg(y ~ x, k = k[i], data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("k = ", stringr::str_pad(k[i], 3, pad = " "))
  k_results <- rbind(k_results, df_sim)
}

ggplot() +
  geom_point(data = df, aes(x, y), alpha = .3) +
  geom_line(data = k_results, aes(x, predictions), color = "dodgerblue", size = 1.5) +
  scale_y_continuous("Response", limits = c(-1.75, 1.75), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 4.5), expand = c(0, 0)) +
  facet_wrap(~ model)

Figure 2.11:

cv <- trainControl(method = "repeatedcv", number = 10, repeats = 10, returnResamp = "all")
hyper_grid <- expand.grid(k = seq(2, 150, by = 2))
knn_fit <- train(x ~ y, data = df, method = "knn", trControl = cv, tuneGrid = hyper_grid)

ggplot() +
  geom_line(data = knn_fit$results, aes(k, RMSE)) +
  geom_point(data = knn_fit$results, aes(k, RMSE)) +
  geom_point(data = filter(knn_fit$results, k == as.numeric(knn_fit$bestTune)),
             aes(k, RMSE),
             shape = 21,
             fill = "yellow",
             color = "black",
             stroke = 1,
             size = 2) +
  scale_y_continuous("Error (RMSE)")


# clean up
rm(cv, hyper_grid, knn_fit)

Classification models

Figure 2.12

knitr::include_graphics("images/confusion-matrix.png")

Figure 2.13:

knitr::include_graphics("images/confusion-matrix2.png")

Figure 2.14:

library(plotROC)

# Generate data
set.seed(123)
response <- rbinom(200, size = 1, prob = .5)

set.seed(123)
curve1   <- rnorm(200, mean = response, sd = .40)

set.seed(123)
curve2   <- rnorm(200, mean = response, sd = .75)

set.seed(123)
curve3   <- rnorm(200, mean = response, sd = 2.0)

df <- tibble(response, curve1, curve2, curve3)

ggplot(df) + 
  geom_roc(aes(d = response, m = curve1), n.cuts = 0, size = .5, color = "#1E56F9") + 
  geom_roc(aes(d = response, m = curve2), n.cuts = 0, size = .5, color = "#7194F9") + 
  geom_roc(aes(d = response, m = curve3), n.cuts = 0, size = .5, color = "#B6C7F9") +
  geom_abline(lty = 'dashed') +
  annotate("text", x = .48, y = .46, label = c("No better than guessing"), 
           vjust = 1, angle = 34) +
  annotate("text", x = .3, y = .6, label = c("Ok"), 
           vjust = 1, angle = 33, color = "#B6C7F9") +
  annotate("text", x = .20, y = .75, label = c("Better"), 
           vjust = 1, angle = 33, color = "#7194F9") +
  annotate("text", x = .10, y = .96, label = c("Best"), 
           vjust = 1, angle = 33, color = "#1E56F9") +
  xlab("False positive rate") +
  ylab("True positive rate")

Putting the processes together

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

This grid search takes approximately 3.5 minutes:

# Specify resampling strategy
cv <- trainControl(
  method = "repeatedcv", 
  number = 10, 
  repeats = 5
)

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

# Tune a knn model using grid search
knn_fit <- train(
  Sale_Price ~ ., 
  data = ames_train, 
  method = "knn", 
  trControl = cv, 
  tuneGrid = hyper_grid,
  metric = "RMSE"
)

Figure 2.15:

# Print and plot the CV results
knn_fit
k-Nearest Neighbors 

2053 samples
  80 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 5 times) 
Summary of sample sizes: 1848, 1848, 1848, 1847, 1849, 1847, ... 
Resampling results across tuning parameters:

  k   RMSE      Rsquared   MAE     
   2  47844.53  0.6538046  31002.72
   3  45875.79  0.6769848  29784.69
   4  44529.50  0.6949240  28992.48
   5  43944.65  0.7026947  28738.66
   6  43645.76  0.7079683  28553.50
   7  43439.07  0.7129916  28617.80
   8  43658.35  0.7123254  28769.16
   9  43799.74  0.7128924  28905.50
  10  44058.76  0.7108900  29061.68
  11  44304.91  0.7091949  29197.78
  12  44565.82  0.7073437  29320.81
  13  44798.10  0.7056491  29475.33
  14  44966.27  0.7051474  29561.70
  15  45188.86  0.7036000  29731.56
  16  45376.09  0.7027152  29860.67
  17  45557.94  0.7016254  29974.44
  18  45666.30  0.7021351  30018.59
  19  45836.33  0.7013026  30105.50
  20  46044.44  0.6997198  30235.80
  21  46242.59  0.6983978  30367.95
  22  46441.87  0.6969620  30481.48
  23  46651.66  0.6953968  30611.48
  24  46788.22  0.6948738  30681.97
  25  46980.13  0.6928159  30777.25

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was k = 7.
ggplot(knn_fit)

Shutdown H2O and clean up

h2o.shutdown(prompt = FALSE)
[1] TRUE
rm(list = ls())
LS0tCnRpdGxlOiAiQ2hhcHRlciAyOiBNb2RlbGluZyBQcm9jZXNzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIAoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rcy4KCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgbWVzc2FnZSA9IEZBTFNFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBjb2xsYXBzZSA9IFRSVUUsCiAgY2FjaGUgPSBGQUxTRQopCgojIHVuZGVybHlpbmcgY29kZSBkZXBlbmRlbmNpZXMKbGlicmFyeSh0aWR5dmVyc2UpCgojIFNldCB0aGUgZ3JhcGhpY2FsIHRoZW1lCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQpgYGAKCkZpZ3VyZSAyLjE6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW1vZGVsaW5nLXByb2Nlc3MsIGVjaG89VFJVRSwgb3V0LmhlaWdodD0iOTAlIiwgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwPSJHZW5lcmFsIHByZWRpY3RpdmUgbWFjaGluZSBsZWFybmluZyBwcm9jZXNzLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvbW9kZWxpbmdfcHJvY2Vzcy5wbmciKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKClRoaXMgY2hhcHRlciBsZXZlcmFnZXMgdGhlIGZvbGxvd2luZyBwYWNrYWdlcy4KCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtcGtnLXByZXJlcXMsIHJlc3VsdHM9ImhpZGUifQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAgIyBmb3IgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAgICMgZm9yIGF3ZXNvbWUgZ3JhcGhpY3MKCiMgTW9kZWxpbmcgcHJvY2VzcyBwYWNrYWdlcwpsaWJyYXJ5KHJzYW1wbGUpICAgIyBmb3IgcmVzYW1wbGluZyBwcm9jZWR1cmVzCmxpYnJhcnkoY2FyZXQpICAgICAjIGZvciByZXNhbXBsaW5nIGFuZCBtb2RlbCB0cmFpbmluZwpsaWJyYXJ5KGgybykgICAgICAgIyBmb3IgcmVzYW1wbGluZyBhbmQgbW9kZWwgdHJhaW5pbmcKCiMgaDJvIHNldC11cCAKaDJvLm5vX3Byb2dyZXNzKCkgICMgdHVybiBvZmYgaDJvIHByb2dyZXNzIGJhcnMKaDJvLmluaXQoKSAgICAgICAgICMgbGF1bmNoIGgybwpgYGAKCkRhdGEgdXNlZDoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtbG9hZC1kYXRhfQojIEFtZXMgaG91c2luZyBkYXRhCmFtZXMgPC0gQW1lc0hvdXNpbmc6Om1ha2VfYW1lcygpCmFtZXMuaDJvIDwtIGFzLmgybyhhbWVzKQoKIyBKb2IgYXR0cml0aW9uIGRhdGEKY2h1cm4gPC0gcnNhbXBsZTo6YXR0cml0aW9uICU+JSAKICBtdXRhdGVfaWYoaXMub3JkZXJlZCwgLmZ1bnMgPSBmYWN0b3IsIG9yZGVyZWQgPSBGQUxTRSkKY2h1cm4uaDJvIDwtIGFzLmgybyhjaHVybikKYGBgCgoKIyMgRGF0YSBzcGxpdHRpbmcKCkZpZ3VyZSAyLjI6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXNwbGl0LCBlY2hvPVRSVUUsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iU3BsaXR0aW5nIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzLiIsIG91dC5oZWlnaHQ9IjMwJSIsIG91dC53aWR0aD0iMzAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9kYXRhX3NwbGl0LnBuZyIpCmBgYAoKIyMjIFNpbXBsZSByYW5kb20gc2FtcGxpbmcKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mtc3BsaXR0aW5nLWFwcGxpZWR9CiMgVXNpbmcgYmFzZSBSCnNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQppbmRleF8xIDwtIHNhbXBsZSgxOm5yb3coYW1lcyksIHJvdW5kKG5yb3coYW1lcykgKiAwLjcpKQp0cmFpbl8xIDwtIGFtZXNbaW5kZXhfMSwgXQp0ZXN0XzEgIDwtIGFtZXNbLWluZGV4XzEsIF0KCiMgVXNpbmcgY2FyZXQgcGFja2FnZQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKaW5kZXhfMiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGFtZXMkU2FsZV9QcmljZSwgcCA9IDAuNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UpCnRyYWluXzIgPC0gYW1lc1tpbmRleF8yLCBdCnRlc3RfMiAgPC0gYW1lc1staW5kZXhfMiwgXQoKIyBVc2luZyByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNwbGl0XzEgIDwtIGluaXRpYWxfc3BsaXQoYW1lcywgcHJvcCA9IDAuNykKdHJhaW5fMyAgPC0gdHJhaW5pbmcoc3BsaXRfMSkKdGVzdF8zICAgPC0gdGVzdGluZyhzcGxpdF8xKQoKIyBVc2luZyBoMm8gcGFja2FnZQpzcGxpdF8yIDwtIGgyby5zcGxpdEZyYW1lKGFtZXMuaDJvLCByYXRpb3MgPSAwLjcsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAxMjMpCnRyYWluXzQgPC0gc3BsaXRfMltbMV1dCnRlc3RfNCAgPC0gc3BsaXRfMltbMl1dCmBgYAoKRmlndXJlIDIuMzoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtZGlzdHJpYnV0aW9ucywgZWNobz1UUlVFLCBmaWcuY2FwPSJUcmFpbmluZyAoYmxhY2spIHZzLiB0ZXN0IChyZWQpIHJlc3BvbnNlIGRpc3RyaWJ1dGlvbi4iLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD05fQpwMSA8LSBnZ3Bsb3QodHJhaW5fMSwgYWVzKHggPSBTYWxlX1ByaWNlKSkgKyAKICAgIGdlb21fZGVuc2l0eSh0cmltID0gVFJVRSkgKyAKICAgIGdlb21fZGVuc2l0eShkYXRhID0gdGVzdF8xLCB0cmltID0gVFJVRSwgY29sID0gInJlZCIpICsKICBnZ3RpdGxlKCJCYXNlIFIiKQoKcDIgPC0gZ2dwbG90KHRyYWluXzIsIGFlcyh4ID0gU2FsZV9QcmljZSkpICsgCiAgICBnZW9tX2RlbnNpdHkodHJpbSA9IFRSVUUpICsgCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IHRlc3RfMiwgdHJpbSA9IFRSVUUsIGNvbCA9ICJyZWQiKSArCiAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZ2d0aXRsZSgiY2FyZXQiKSAKCnAzIDwtIGdncGxvdCh0cmFpbl8zLCBhZXMoeCA9IFNhbGVfUHJpY2UpKSArIAogICAgZ2VvbV9kZW5zaXR5KHRyaW0gPSBUUlVFKSArIAogICAgZ2VvbV9kZW5zaXR5KGRhdGEgPSB0ZXN0XzMsIHRyaW0gPSBUUlVFLCBjb2wgPSAicmVkIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIGdndGl0bGUoInJzYW1wbGUiKQoKcDQgPC0gZ2dwbG90KGFzLmRhdGEuZnJhbWUodHJhaW5fNCksIGFlcyh4ID0gU2FsZV9QcmljZSkpICsgCiAgICBnZW9tX2RlbnNpdHkodHJpbSA9IFRSVUUpICsgCiAgICBnZW9tX2RlbnNpdHkoZGF0YSA9IGFzLmRhdGEuZnJhbWUodGVzdF80KSwgdHJpbSA9IFRSVUUsIGNvbCA9ICJyZWQiKSArCiAgICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgZ2d0aXRsZSgiaDJvIikKCiMgU2lkZS1ieS1zaWRlIHBsb3RzCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBucm93ID0gMSkKCiMgY2xlYW4gdXAKcm0ocDEsIHAyLCBwMywgcDQpCmBgYAoKIyMjIFN0cmF0aWZpZWQgc2FtcGxpbmcKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mtc3RyYXRpZmllZC1zYW1wbGluZ30KIyBvcmdpbmFsIHJlc3BvbnNlIGRpc3RyaWJ1dGlvbgp0YWJsZShjaHVybiRBdHRyaXRpb24pICU+JSBwcm9wLnRhYmxlKCkKCiMgc3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdF9zdHJhdCAgPC0gaW5pdGlhbF9zcGxpdChjaHVybiwgcHJvcCA9IDAuNywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmF0YSA9ICJBdHRyaXRpb24iKQp0cmFpbl9zdHJhdCAgPC0gdHJhaW5pbmcoc3BsaXRfc3RyYXQpCnRlc3Rfc3RyYXQgICA8LSB0ZXN0aW5nKHNwbGl0X3N0cmF0KQoKIyBjb25zaXN0ZW50IHJlc3BvbnNlIHJhdGlvIGJldHdlZW4gdHJhaW4gJiB0ZXN0CnRhYmxlKHRyYWluX3N0cmF0JEF0dHJpdGlvbikgJT4lIHByb3AudGFibGUoKQp0YWJsZSh0ZXN0X3N0cmF0JEF0dHJpdGlvbikgJT4lIHByb3AudGFibGUoKQpgYGAKCgojIyMgTWFueSBmb3JtdWxhIGludGVyZmFjZXMKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLiAKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtZm9ybXVsYS1pbnRlcmZhY2UsIGV2YWw9RkFMU0V9CiMgU2FsZSBwcmljZSBhcyBmdW5jdGlvbiBvZiBuZWlnaGJvcmhvb2QgYW5kIHllYXIgc29sZAptb2RlbF9mbihTYWxlX1ByaWNlIH4gTmVpZ2hib3Job29kICsgWWVhcl9Tb2xkLCAKICAgICAgICAgZGF0YSA9IGFtZXMpCgojIFZhcmlhYmxlcyArIGludGVyYWN0aW9ucwptb2RlbF9mbihTYWxlX1ByaWNlIH4gTmVpZ2hib3Job29kICsgWWVhcl9Tb2xkICsgCiAgICAgICAgICAgTmVpZ2hib3Job29kOlllYXJfU29sZCwgZGF0YSA9IGFtZXMpCgojIFNob3J0aGFuZCBmb3IgYWxsIHByZWRpY3RvcnMKbW9kZWxfZm4oU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzKQoKIyBJbmxpbmUgZnVuY3Rpb25zIC8gdHJhbnNmb3JtYXRpb25zCm1vZGVsX2ZuKGxvZzEwKFNhbGVfUHJpY2UpIH4gbnMoTG9uZ2l0dWRlLCBkZiA9IDMpICsgCiAgICAgICAgICAgbnMoTGF0aXR1ZGUsIGRmID0gMyksIGRhdGEgPSBhbWVzKQpgYGAKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1ub24tZm9ydW1hbC1pbnRlcmZhY2UsIGV2YWw9RkFMU0V9CiMgVXNlIHNlcGFyYXRlIGlucHV0cyBmb3IgWCBhbmQgWQpmZWF0dXJlcyA8LSBjKCJZZWFyX1NvbGQiLCAiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIikKbW9kZWxfZm4oeCA9IGFtZXNbLCBmZWF0dXJlc10sIHkgPSBhbWVzJFNhbGVfUHJpY2UpCmBgYAoKVGhpcyBjb2RlIGNodW5rIGlzIGZvciBpbGx1c3RyYXRpdmUgcHVycG9zZXMgb25seTsgaXQgaXMgbm90IGRlc2lnbmVkIHRvIGV4ZWN1dGUuCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW5hbWUtaW50ZXJmYWNlLCBldmFsPUZBTFNFfQptb2RlbF9mbigKICB4ID0gYygiWWVhcl9Tb2xkIiwgIkxvbmdpdHVkZSIsICJMYXRpdHVkZSIpLAogIHkgPSAiU2FsZV9QcmljZSIsCiAgZGF0YSA9IGFtZXMuaDJvCikKYGBgCgojIyMgTWFueSBlbmdpbmVzCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLW1hbnktZW5naW5lcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbG1fbG0gPC0gbG0oU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzKQpsbV9nbG0gPC0gZ2xtKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lcywgZmFtaWx5ID0gZ2F1c3NpYW4pCmxtX2NhcmV0IDwtIHRyYWluKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lcywgbWV0aG9kID0gImxtIikKYGBgCgpUYWJsZSAxOgoKfCBBbGdvcml0aG0gfCBQYWNrYWdlIHwgQ29kZSB8CnwgLS0tLS0tLS0tIHwgLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0gfAp8IExpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgfCBfX01BU1NfXyB8IGBwcmVkaWN0KG9iailgIHwKfCBHZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwgfAlfX3N0YXRzX18JfCBgcHJlZGljdChvYmosIHR5cGUgPSAicmVzcG9uc2UiKWAgfAp8IE1peHR1cmUgZGlzY3JpbWluYW50IGFuYWx5c2lzIHwJX19tZGFfXwl8CWBwcmVkaWN0KG9iaiwgdHlwZSA9ICJwb3N0ZXJpb3IiKWAgfAp8IERlY2lzaW9uIHRyZWUgfAlfX3JwYXJ0X18JfAlgcHJlZGljdChvYmosIHR5cGUgPSAicHJvYiIpYCB8CnwgUmFuZG9tIEZvcmVzdCB8CV9fcmFuZ2VyX18gfAlgcHJlZGljdChvYmopJHByZWRpY3Rpb25zYCB8CnwgR3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZSB8CV9fZ2JtX18gfAlgcHJlZGljdChvYmosIHR5cGUgPSAicmVzcG9uc2UiLCBuLnRyZWVzKWAgfAoKVGFibGU6IFRhYmxlIDE6IFN5bnRheCBmb3IgY29tcHV0aW5nIHByZWRpY3RlZCBjbGFzcyBwcm9iYWJpbGl0aWVzIHdpdGggZGlyZWN0IGVuZ2luZXMuCgoKIyMgUmVzYW1wbGluZyBtZXRob2RzCgojIyMgX2tfLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbgoKRmlndXJlIDIuNDoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY3YtZGlhZ3JhbSwgZWNobz1UUlVFLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gb2YgdGhlIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHByb2Nlc3MuIiwgb3V0LndpZHRoPSc5MCUnLCBvdXQuaGVpZ2h0PSc5MCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2N2LnBuZyIpCmBgYAoKRmlndXJlIDIuNToKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY3YsIGVjaG89VFJVRSwgZmlnLmNhcD0iMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG9uIDMyIG9ic2VydmF0aW9ucy4gRWFjaCBvYnNlcnZhdGlvbiBpcyB1c2VkIG9uY2UgZm9yIHZhbGlkYXRpb24gYW5kIG5pbmUgdGltZXMgZm9yIHRyYWluaW5nLiJ9CmN2IDwtIHZmb2xkX2N2KG10Y2FycywgMTApCmN2X3Bsb3QgPC0gY3Ykc3BsaXRzICU+JQogIHB1cnJyOjptYXAyX2RmcihzZXFfYWxvbmcoY3Ykc3BsaXRzKSwgfiBtdGNhcnMgJT4lIG11dGF0ZSgKICAgIFJlc2FtcGxlID0gcGFzdGUwKCJGb2xkXyIsIHN0cmluZ3I6OnN0cl9wYWQoLnksIDIsIHBhZCA9IDApKSwKICAgIElEID0gcm93X251bWJlcigpLAogICAgRGF0YSA9IGlmZWxzZShJRCAlaW4lIC54JGluX2lkLCAiVHJhaW5pbmciLCAiVmFsaWRhdGlvbiIpKQogICAgKSAlPiUKICBnZ3Bsb3QoYWVzKFJlc2FtcGxlLCBJRCwgZmlsbCA9IERhdGEpKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNmMmYyZjIiLCAiI0FBQUFBQSIpKSArCiAgc2NhbGVfeV9yZXZlcnNlKCJPYnNlcnZhdGlvbiBJRCIsIGJyZWFrcyA9IDE6bnJvdyhtdGNhcnMpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKQoKY3ZfcGxvdApgYGAKClRoaXMgY29kZSBjaHVuayBpcyBmb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzIG9ubHk7IGl0IGlzIG5vdCBkZXNpZ25lZCB0byBleGVjdXRlLgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1kaXJlY3QtY3YsIGV2YWw9RkFMU0V9CiMgRXhhbXBsZSB1c2luZyBoMm8KaDJvLmN2IDwtIGgyby5nbG0oCiAgeCA9IHgsIAogIHkgPSB5LCAKICB0cmFpbmluZ19mcmFtZSA9IGFtZXMuaDJvLAogIG5mb2xkcyA9IDEwICAjIHBlcmZvcm0gMTAtZm9sZCBDVgopCmBgYAoKIyMjIEJvb3RzdHJhcHBpbmcKCkZpZ3VyZSAyLjY6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWJvb3RzdHJhcHNjaGVtZSwgZWNobz1UUlVFLCBvdXQud2lkdGg9JzcwJScsIG91dC5oZWlnaHQ9JzcwJScsIGZpZy5jYXA9IklsbHVzdHJhdGlvbiBvZiB0aGUgYm9vdHN0cmFwcGluZyBwcm9jZXNzLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvYm9vdHN0cmFwLXNjaGVtZS5wbmciKQpgYGAKCkZpZ3VyZSAyLjc6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXNhbXBsaW5nLWNvbXBhcmlzb24sIGVjaG89VFJVRSwgZmlnLmNhcD0iQm9vdHN0cmFwIHNhbXBsaW5nIChsZWZ0KSB2ZXJzdXMgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIChyaWdodCkgb24gMzIgb2JzZXJ2YXRpb25zLiBGb3IgYm9vdHN0cmFwIHNhbXBsaW5nLCB3YXJuaW5nPUZBTFNFLCB0aGUgb2JzZXJ2YXRpb25zIHRoYXQgaGF2ZSB6ZXJvIHJlcGxpY2F0aW9ucyAod2hpdGUpIGFyZSB0aGUgb3V0LW9mLWJhZyBvYnNlcnZhdGlvbnMgdXNlZCBmb3IgdmFsaWRhdGlvbi4iLCBtZXNzYWdlPUZBTFNFfQpib290cyA8LSByc2FtcGxlOjpib290c3RyYXBzKG10Y2FycywgMTApCmJvb3RzX3Bsb3QgPC0gYm9vdHMkc3BsaXRzICU+JQogIHB1cnJyOjptYXAyX2RmcihzZXFfYWxvbmcoYm9vdHMkc3BsaXRzKSwgfiBtdGNhcnMgJT4lIAogICAgICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgICBSZXNhbXBsZSA9IHBhc3RlMCgiQm9vdHN0cmFwXyIsIHN0cmluZ3I6OnN0cl9wYWQoLnksIDIsIHBhZCA9IDApKSwKICAgICAgICAgICAgICAgSUQgPSByb3dfbnVtYmVyKCkKICAgICAgICAgICAgICkgJT4lCiAgICAgICAgICAgICBncm91cF9ieShJRCkgJT4lCiAgICAgICAgICAgICBtdXRhdGUoUmVwbGljYXRlcyA9IGZhY3RvcihzdW0oSUQgPT0gLngkaW5faWQpKSkpICU+JQogIGdncGxvdChhZXMoUmVzYW1wbGUsIElELCBmaWxsID0gUmVwbGljYXRlcykpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0ZGRkZGRiIsICIjRjVGNUY1IiwgIiNDOEM4QzgiLCAiI0EwQTBBMCIsICIjNzA3MDcwIiwgIiM1MDUwNTAiLCAiIzAwMDAwMCIpKSArCiAgc2NhbGVfeV9yZXZlcnNlKCJPYnNlcnZhdGlvbiBJRCIsIGJyZWFrcyA9IDE6bnJvdyhtdGNhcnMpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShOVUxMLCBleHBhbmQgPSBjKDAsIDApKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArCiAgZ2d0aXRsZSgiQm9vdHN0cmFwIHNhbXBsaW5nIikgCgpjdl9wbG90IDwtIGN2X3Bsb3QgKyAKICBnZ3RpdGxlKCIxMC1mb2xkIGNyb3NzIHZhbGlkYXRpb24iKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKCmNvd3Bsb3Q6OnBsb3RfZ3JpZChib290c19wbG90LCBjdl9wbG90LCBhbGlnbiA9ICJoIiwgbnJvdyA9IDEpCgojIGNsZWFuIHVwCnJtKGJvb3RzLCBib290c19wbG90LCBjdl9wbG90KQpgYGAKCiMjIEJpYXMgdmFyaWFuY2UgdHJhZGUtb2ZmCgojIyMgQmlhcwoKRmlndXJlIDIuODoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtYmlhcy1tb2RlbCwgZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMCwgZmlnLmNhcD0iQSBiaWFzZWQgcG9seW5vbWlhbCBtb2RlbCBmaXQgdG8gYSBzaW5nbGUgZGF0YSBzZXQgZG9lcyBub3QgY2FwdHVyZSB0aGUgdW5kZXJseWluZyBub24tbGluZWFyLCBub24tbW9ub3RvbmljIGRhdGEgc3RydWN0dXJlIChsZWZ0KS4gIE1vZGVscyBmaXQgdG8gMjUgYm9vdHN0cmFwcGVkIHJlcGxpY2F0ZXMgb2YgdGhlIGRhdGEgYXJlIHVuZGVydGVycmVkIGJ5IHRoZSBub2lzZSBhbmQgZ2VuZXJhdGVzIHNpbWlsYXIsIHlldCBzdGlsbCBiaWFzZWQsIHByZWRpY3Rpb25zIChyaWdodCkuIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTaW11bGF0ZSBzb21lIG5vbmxpbmVhciBtb25vdG9uaWMgZGF0YQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKeCA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMiAqIHBpLCBsZW5ndGggPSA1MDApCnkgPC0gc2luKHgpICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IDAuMykKZGYgPC0gZGF0YS5mcmFtZSh4LCB5KSAlPiUKICBmaWx0ZXIoeCA8IDQuNSkKCiMgU2luZ2xlIG1vZGVsIGZpdApiaWFzX21vZGVsIDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZikKZGYkcHJlZGljdGlvbnMgPC0gcHJlZGljdChiaWFzX21vZGVsLCBkZikKcDEgPC0gZ2dwbG90KGRmLCBhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjMpICsKICBnZW9tX2xpbmUoYWVzKHgsIHByZWRpY3Rpb25zKSwgc2l6ZSA9IDEuNSwgY29sb3IgPSAiZG9kZ2VyYmx1ZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMS43NSwgMS43NSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA0LjUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiU2luZ2xlIGJpYXNlZCBtb2RlbCBmaXQiKQoKIyBCb290c3RyYXBwZWQgbW9kZWwgZml0CmJvb3RzdHJhcF9uIDwtIDI1CmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICBzZXQuc2VlZChpKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CiAgaW5kZXggPC0gc2FtcGxlKHNlcV9sZW4obnJvdyhkZikpLCBucm93KGRmKSwgcmVwbGFjZSA9IFRSVUUpCiAgZGZfc2ltIDwtIGRmW2luZGV4LCBdCiAgZml0IDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAyIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zLCBjb2xvciA9IG1vZGVsKSkgKwogIGdlb21fbGluZShzaG93LmxlZ2VuZCA9IEZBTFNFLCBzaXplID0gLjUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMS43NSwgMS43NSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA0LjUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiMjUgYmlhc2VkIG1vZGVscyBmaXQgdG8gYm9vdHN0cmFwIHNhbXBsZXMiKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBucm93ID0gMSkKYGBgCgojIyMgVmFyaWFuY2UKCkZpZ3VyZSAyLjk6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLXZhcmlhbmNlLW1vZGVsLCBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCBmaWcuY2FwPSJBIGhpZ2ggdmFyaWFuY2UgX2tfLW5lYXJlc3QgbmVpZ2hib3IgbW9kZWwgZml0IHRvIGEgc2luZ2xlIGRhdGEgc2V0IGNhcHR1cmVzIHRoZSB1bmRlcmx5aW5nIG5vbi1saW5lYXIsIG5vbi1tb25vdG9uaWMgZGF0YSBzdHJ1Y3R1cmUgd2VsbCBidXQgYWxzbyBvdmVyZml0cyB0byBpbmRpdmlkdWFsIGRhdGEgcG9pbnRzIChsZWZ0KS4gIE1vZGVscyBmaXQgdG8gMjUgYm9vdHN0cmFwcGVkIHJlcGxpY2F0ZXMgb2YgdGhlIGRhdGEgYXJlIGRldGVycmVkIGJ5IHRoZSBub2lzZSBhbmQgZ2VuZXJhdGUgaGlnaGx5IHZhcmlhYmxlIHByZWRpY3Rpb25zIChyaWdodCkuIiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTaW11bGF0ZSBzb21lIG5vbmxpbmVhciBtb25vdG9uaWMgZGF0YQpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKeCA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMiAqIHBpLCBsZW5ndGggPSA1MDApCnkgPC0gc2luKHgpICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IDAuMykKZGYgPC0gZGF0YS5mcmFtZSh4LCB5KSAlPiUKICBmaWx0ZXIoeCA8IDQuNSkKCiMgU2luZ2xlIG1vZGVsIGZpdAp2YXJpYW5jZV9tb2RlbCA8LSBrbm5yZWcoeSB+IHgsIGsgPSAzLCBkYXRhID0gZGYpCmRmJHByZWRpY3Rpb25zIDwtIHByZWRpY3QodmFyaWFuY2VfbW9kZWwsIGRmKQpwMSA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMykgKwogIGdlb21fbGluZShhZXMoeCwgcHJlZGljdGlvbnMpLCBzaXplID0gMS41LCBjb2xvciA9ICJkb2RnZXJibHVlIikgKwogIHNjYWxlX3lfY29udGludW91cygiUmVzcG9uc2UiLCBsaW1pdHMgPSBjKC0xLjc1LCAxLjc1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDQuNSksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBnZ3RpdGxlKCJTaW5nbGUgaGlnaCB2YXJpYW5jZSBtb2RlbCBmaXQiKQoKIyBCb290c3RyYXBwZWQgbW9kZWwgZml0CmJvb3RzdHJhcF9uIDwtIDI1CmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICBzZXQuc2VlZChpKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CiAgaW5kZXggPC0gc2FtcGxlKHNlcV9sZW4obnJvdyhkZikpLCBucm93KGRmKSwgcmVwbGFjZSA9IFRSVUUpCiAgZGZfc2ltIDwtIGRmW2luZGV4LCBdCiAgZml0IDwtIGtubnJlZyh5IH4geCwgayA9IDMsIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAyIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zLCBjb2xvciA9IG1vZGVsKSkgKwogIGdlb21fbGluZShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJSZXNwb25zZSIsIGxpbWl0cyA9IGMoLTEuNzUsIDEuNzUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNC41KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIjI1IGhpZ2ggdmFyaWFuY2UgbW9kZWxzIGZpdCB0byBib290c3RyYXAgc2FtcGxlcyIpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCiMjIyBIeXBlcnBhcmFtZXRlciB0dW5pbmcKCkZpZ3VyZSAyLjEwOgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1rbm4tb3B0aW9ucywgZmlnLndpZHRoPTEwLCBlY2hvPVRSVUUsIGZpZy5jYXA9Il9rXy1uZWFyZXN0IG5laWdoYm9yIG1vZGVsIHdpdGggZGlmZmVyaW5nIHZhbHVlcyBmb3IgX2tfLiIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmtfcmVzdWx0cyA8LSBOVUxMCmsgPC0gYygyLCA1LCAxMCwgMjAsIDUwLCAxNTApCgojIEZpdCBtYW55IGRpZmZlcmVudCBtb2RlbHMKZm9yKGkgaW4gc2VxX2Fsb25nKGspKSB7CiAgZGZfc2ltIDwtIGRmCiAgZml0IDwtIGtubnJlZyh5IH4geCwgayA9IGtbaV0sIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgiayA9ICIsIHN0cmluZ3I6OnN0cl9wYWQoa1tpXSwgMywgcGFkID0gIiAiKSkKICBrX3Jlc3VsdHMgPC0gcmJpbmQoa19yZXN1bHRzLCBkZl9zaW0pCn0KCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkZiwgYWVzKHgsIHkpLCBhbHBoYSA9IC4zKSArCiAgZ2VvbV9saW5lKGRhdGEgPSBrX3Jlc3VsdHMsIGFlcyh4LCBwcmVkaWN0aW9ucyksIGNvbG9yID0gImRvZGdlcmJsdWUiLCBzaXplID0gMS41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJSZXNwb25zZSIsIGxpbWl0cyA9IGMoLTEuNzUsIDEuNzUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNC41KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGZhY2V0X3dyYXAofiBtb2RlbCkKYGBgCgpGaWd1cmUgMi4xMToKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3Mta25uLXR1bmUsIGZpZy5oZWlnaHQ9MywgZWNobz1UUlVFLCBmaWcuY2FwPSJSZXN1bHRzIGZyb20gYSBncmlkIHNlYXJjaCBmb3IgYSBfa18tbmVhcmVzdCBuZWlnaGJvciBtb2RlbCBhc3Nlc3NpbmcgdmFsdWVzIGZvciBfa18gcmFuZ2luZyBmcm9tIDItMTUwLiAgV2Ugc2VlIGhpZ2ggZXJyb3IgdmFsdWVzIGR1ZSB0byBoaWdoIG1vZGVsIHZhcmlhbmNlIHdoZW4gX2tfIGlzIHNtYWxsIGFuZCB3ZSBhbHNvIHNlZSBoaWdoIGVycm9ycyB2YWx1ZXMgZHVlIHRvIGhpZ2ggbW9kZWwgYmlhcyB3aGVuIF9rXyBpcyBsYXJnZS4gIFRoZSBvcHRpbWFsIG1vZGVsIGlzIGZvdW5kIGF0IF9rXyA9IDQ2LiJ9CmN2IDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDEwLCByZXBlYXRzID0gMTAsIHJldHVyblJlc2FtcCA9ICJhbGwiKQpoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMiwgMTUwLCBieSA9IDIpKQprbm5fZml0IDwtIHRyYWluKHggfiB5LCBkYXRhID0gZGYsIG1ldGhvZCA9ICJrbm4iLCB0ckNvbnRyb2wgPSBjdiwgdHVuZUdyaWQgPSBoeXBlcl9ncmlkKQoKZ2dwbG90KCkgKwogIGdlb21fbGluZShkYXRhID0ga25uX2ZpdCRyZXN1bHRzLCBhZXMoaywgUk1TRSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBrbm5fZml0JHJlc3VsdHMsIGFlcyhrLCBSTVNFKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihrbm5fZml0JHJlc3VsdHMsIGsgPT0gYXMubnVtZXJpYyhrbm5fZml0JGJlc3RUdW5lKSksCiAgICAgICAgICAgICBhZXMoaywgUk1TRSksCiAgICAgICAgICAgICBzaGFwZSA9IDIxLAogICAgICAgICAgICAgZmlsbCA9ICJ5ZWxsb3ciLAogICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICAgc3Ryb2tlID0gMSwKICAgICAgICAgICAgIHNpemUgPSAyKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJFcnJvciAoUk1TRSkiKQoKIyBjbGVhbiB1cApybShjdiwgaHlwZXJfZ3JpZCwga25uX2ZpdCkKYGBgCgojIyMgQ2xhc3NpZmljYXRpb24gbW9kZWxzCgpGaWd1cmUgMi4xMgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1jb25mdXNpb24tbWF0cml4MSwgZWNobz1UUlVFLCBvdXQuaGVpZ2h0PSIxMDAlIiwgb3V0LndpZHRoPSIxMDAlIiwgZmlnLmNhcD0iQ29uZnVzaW9uIG1hdHJpeCBhbmQgcmVsYXRpb25zaGlwcyB0byB0ZXJtcyBzdWNoIGFzIHRydWUtcG9zaXRpdmUgYW5kIGZhbHNlLW5lZ2F0aXZlLiJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvY29uZnVzaW9uLW1hdHJpeC5wbmciKQpgYGAKCgpGaWd1cmUgMi4xMzoKCmBgYHtyIG1vZGVsaW5nLXByb2Nlc3MtY29uZnVzaW9uLW1hdHJpeDIsIGVjaG89VFJVRSwgb3V0LmhlaWdodD0iNTAlIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJFeGFtcGxlIGNvbmZ1c2lvbiBtYXRyaXguIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9jb25mdXNpb24tbWF0cml4Mi5wbmciKQpgYGAKCkZpZ3VyZSAyLjE0OgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1yb2MsIGVjaG89VFJVRSwgZmlnLmNhcD0iUk9DIGN1cnZlLiIsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9NX0KbGlicmFyeShwbG90Uk9DKQoKIyBHZW5lcmF0ZSBkYXRhCnNldC5zZWVkKDEyMykKcmVzcG9uc2UgPC0gcmJpbm9tKDIwMCwgc2l6ZSA9IDEsIHByb2IgPSAuNSkKCnNldC5zZWVkKDEyMykKY3VydmUxICAgPC0gcm5vcm0oMjAwLCBtZWFuID0gcmVzcG9uc2UsIHNkID0gLjQwKQoKc2V0LnNlZWQoMTIzKQpjdXJ2ZTIgICA8LSBybm9ybSgyMDAsIG1lYW4gPSByZXNwb25zZSwgc2QgPSAuNzUpCgpzZXQuc2VlZCgxMjMpCmN1cnZlMyAgIDwtIHJub3JtKDIwMCwgbWVhbiA9IHJlc3BvbnNlLCBzZCA9IDIuMCkKCmRmIDwtIHRpYmJsZShyZXNwb25zZSwgY3VydmUxLCBjdXJ2ZTIsIGN1cnZlMykKCmdncGxvdChkZikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUxKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjMUU1NkY5IikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUyKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjNzE5NEY5IikgKyAKICBnZW9tX3JvYyhhZXMoZCA9IHJlc3BvbnNlLCBtID0gY3VydmUzKSwgbi5jdXRzID0gMCwgc2l6ZSA9IC41LCBjb2xvciA9ICIjQjZDN0Y5IikgKwogIGdlb21fYWJsaW5lKGx0eSA9ICdkYXNoZWQnKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gLjQ4LCB5ID0gLjQ2LCBsYWJlbCA9IGMoIk5vIGJldHRlciB0aGFuIGd1ZXNzaW5nIiksIAogICAgICAgICAgIHZqdXN0ID0gMSwgYW5nbGUgPSAzNCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4zLCB5ID0gLjYsIGxhYmVsID0gYygiT2siKSwgCiAgICAgICAgICAgdmp1c3QgPSAxLCBhbmdsZSA9IDMzLCBjb2xvciA9ICIjQjZDN0Y5IikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4yMCwgeSA9IC43NSwgbGFiZWwgPSBjKCJCZXR0ZXIiKSwgCiAgICAgICAgICAgdmp1c3QgPSAxLCBhbmdsZSA9IDMzLCBjb2xvciA9ICIjNzE5NEY5IikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IC4xMCwgeSA9IC45NiwgbGFiZWwgPSBjKCJCZXN0IiksIAogICAgICAgICAgIHZqdXN0ID0gMSwgYW5nbGUgPSAzMywgY29sb3IgPSAiIzFFNTZGOSIpICsKICB4bGFiKCJGYWxzZSBwb3NpdGl2ZSByYXRlIikgKwogIHlsYWIoIlRydWUgcG9zaXRpdmUgcmF0ZSIpCmBgYAoKIyMgUHV0dGluZyB0aGUgcHJvY2Vzc2VzIHRvZ2V0aGVyCgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWV4YW1wbGUtcHJvY2Vzcy1zcGxpdHRpbmd9CiMgU3RyYXRpZmllZCBzYW1wbGluZyB3aXRoIHRoZSByc2FtcGxlIHBhY2thZ2UKc2V0LnNlZWQoMTIzKQpzcGxpdCA8LSBpbml0aWFsX3NwbGl0KGFtZXMsIHByb3AgPSAwLjcsIHN0cmF0YSA9ICJTYWxlX1ByaWNlIikKYW1lc190cmFpbiAgPC0gdHJhaW5pbmcoc3BsaXQpCmFtZXNfdGVzdCAgIDwtIHRlc3Rpbmcoc3BsaXQpCmBgYAoKVGhpcyBncmlkIHNlYXJjaCB0YWtlcyBhcHByb3hpbWF0ZWx5IDMuNSBtaW51dGVzOgoKYGBge3IgbW9kZWxpbmctcHJvY2Vzcy1leGFtcGxlLXByb2Nlc3MtdHJhaW5pbmd9CiMgU3BlY2lmeSByZXNhbXBsaW5nIHN0cmF0ZWd5CmN2IDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsIAogIG51bWJlciA9IDEwLCAKICByZXBlYXRzID0gNQopCgojIENyZWF0ZSBncmlkIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcwpoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMiwgMjUsIGJ5ID0gMSkpCgojIFR1bmUgYSBrbm4gbW9kZWwgdXNpbmcgZ3JpZCBzZWFyY2gKa25uX2ZpdCA8LSB0cmFpbigKICBTYWxlX1ByaWNlIH4gLiwgCiAgZGF0YSA9IGFtZXNfdHJhaW4sIAogIG1ldGhvZCA9ICJrbm4iLCAKICB0ckNvbnRyb2wgPSBjdiwgCiAgdHVuZUdyaWQgPSBoeXBlcl9ncmlkLAogIG1ldHJpYyA9ICJSTVNFIgopCmBgYAoKRmlndXJlIDIuMTU6CgpgYGB7ciBtb2RlbGluZy1wcm9jZXNzLWV4YW1wbGUtcHJvY2Vzcy1hc3Nlc3MsIGZpZy5oZWlnaHQ9MywgZmlnLmNhcD0iUmVzdWx0cyBmcm9tIGEgZ3JpZCBzZWFyY2ggZm9yIGEgX2tfLW5lYXJlc3QgbmVpZ2hib3IgbW9kZWwgb24gdGhlIEFtZXMgaG91c2luZyBkYXRhIGFzc2Vzc2luZyB2YWx1ZXMgZm9yIF9rXyByYW5naW5nIGZyb20gMi0yNS4ifQojIFByaW50IGFuZCBwbG90IHRoZSBDViByZXN1bHRzCmtubl9maXQKZ2dwbG90KGtubl9maXQpCmBgYAoKU2h1dGRvd24gSDJPIGFuZCBjbGVhbiB1cAoKYGBge3J9Cmgyby5zaHV0ZG93bihwcm9tcHQgPSBGQUxTRSkKcm0obGlzdCA9IGxzKCkpCmBgYAoK