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. Do to output size, most of this chapters code chunks should not be ran on RStudio Cloud.

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(
  cache = FALSE,
  warning = FALSE, 
  message = FALSE
)

# Load required packages
library(tidyr)
library(rpart)
library(ggplot2)

Prerequisites

# Helper packages
library(dplyr)    # for general data wrangling needs

# Modeling packages
library(gbm)      # for original implementation of regular and stochastic GBMs
library(h2o)      # for a java-based implementation of GBM variants
library(xgboost)  # for fitting extreme gradient boosting
library(rsample)
# create Ames training data
set.seed(123)
ames <- AmesHousing::make_ames()
split  <- initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train  <- training(split)
h2o.init(max_mem_size = "10g")

train_h2o <- as.h2o(ames_train)
response <- "Sale_Price"
predictors <- setdiff(colnames(ames_train), response)

How boosting works

A sequential ensemble approach

Figure 12.1:


knitr::include_graphics("images/boosted-trees-process.png")

Figure 12.2:


# Simulate sine wave data
set.seed(1112)  # for reproducibility
df <- tibble::tibble(
  x = seq(from = 0, to = 2 * pi, length = 1000),
  y = sin(x) + rnorm(length(x), sd = 0.5),
  truth = sin(x)
)

# Function to boost `rpart::rpart()` trees
rpartBoost <- function(x, y, data, num_trees = 100, learn_rate = 0.1, tree_depth = 6) {
  x <- data[[deparse(substitute(x))]]
  y <- data[[deparse(substitute(y))]]
  G_b_hat <- matrix(0, nrow = length(y), ncol = num_trees + 1)
  r <- y
  for(tree in seq_len(num_trees)) {
    g_b_tilde <- rpart(r ~ x, control = list(cp = 0, maxdepth = tree_depth))
    g_b_hat <- learn_rate * predict(g_b_tilde)
    G_b_hat[, tree + 1] <- G_b_hat[, tree] + matrix(g_b_hat)
    r <- r - g_b_hat
    colnames(G_b_hat) <- paste0("tree_", c(0, seq_len(num_trees)))
  }
  cbind(df, as.data.frame(G_b_hat)) %>%
    gather(tree, prediction, starts_with("tree")) %>%
    mutate(tree = stringr::str_extract(tree, "\\d+") %>% as.numeric())
}

# Plot boosted tree sequence
rpartBoost(x, y, data = df, num_trees = 2^10, learn_rate = 0.05, tree_depth = 1) %>%
  filter(tree %in% c(0, 2^c(0:10))) %>%
  ggplot(aes(x, prediction)) +
    ylab("y") +
    geom_point(data = df, aes(x, y), alpha = .1) +
    geom_line(data = df, aes(x, truth), color = "blue") +
    geom_line(colour = "red", size = 1) +
    facet_wrap(~ tree, nrow = 3)

Gradient descent

Figure 12.3:


# create data to plot
x <- seq(-5, 5, by = .05)
y <- x^2 + 3
df <- data.frame(x, y)
step <- 5
step_size <- .2
for(i in seq_len(18)) {
  next_step <- max(step) + round(diff(range(max(step), which.min(df$y))) * step_size, 0)
  step <- c(step, next_step)
  next
}
steps <- df[step, ] %>%
  mutate(x2 = lag(x), y2 = lag(y)) %>%
  dplyr::slice(1:18)
# plot
ggplot(df, aes(x, y)) +
  geom_line(size = 1.5, alpha = .5) +
  theme_classic() +
  scale_y_continuous("Loss function", limits = c(0, 30)) +
  xlab(expression(theta)) +
  geom_segment(data = df[c(5, which.min(df$y)), ], aes(x = x, y = y, xend = x, yend = -Inf), lty = "dashed") +
  geom_point(data = filter(df, y == min(y)), aes(x, y), size = 4, shape = 21, fill = "yellow") +
  geom_point(data = steps, aes(x, y), size = 3, shape = 21, fill = "blue", alpha = .5) +
  geom_curve(data = steps, aes(x = x, y = y, xend = x2, yend = y2), curvature = 1, lty = "dotted") +
  theme(
    axis.ticks = element_blank(),
    axis.text = element_blank()
  ) +
  annotate("text", x = df[5, "x"], y = 1, label = "Initial value", hjust = -0.1, vjust = .8) +
  annotate("text", x = df[which.min(df$y), "x"], y = 1, label = "Minimium", hjust = -0.1, vjust = .8) +
  annotate("text", x = df[5, "x"], y = df[5, "y"], label = "Learning step", hjust = -.8, vjust = 0)

Figure 12.4:


# create too small of a learning rate
step <- 5
step_size <- .05
for(i in seq_len(10)) {
  next_step <- max(step) + round(diff(range(max(step), which.min(df$y))) * step_size, 0)
  step <- c(step, next_step)
  next
}
too_small <- df[step, ] %>%
  mutate(x2 = lag(x), y2 = lag(y))
# plot
p1 <- ggplot(df, aes(x, y)) +
  geom_line(size = 1.5, alpha = .5) +
  theme_classic() +
  scale_y_continuous("Loss function", limits = c(0, 30)) +
  xlab(expression(theta)) +
  geom_segment(data = too_small[1, ], aes(x = x, y = y, xend = x, yend = -Inf), lty = "dashed") +
  geom_point(data = too_small, aes(x, y), size = 3, shape = 21, fill = "blue", alpha = .5) +
  geom_curve(data = too_small, aes(x = x, y = y, xend = x2, yend = y2), curvature = 1, lty = "dotted") +
  theme(
    axis.ticks = element_blank(),
    axis.text = element_blank()
  ) +
  annotate("text", x = df[5, "x"], y = 1, label = "Start", hjust = -0.1, vjust = .8) +
  ggtitle("b) too small")
# create too large of a learning rate
too_large <- df[round(which.min(df$y) * (1 + c(-.9, -.6, -.2, .3)), 0), ] %>%
  mutate(x2 = lag(x), y2 = lag(y))
# plot
p2 <- ggplot(df, aes(x, y)) +
  geom_line(size = 1.5, alpha = .5) +
  theme_classic() +
  scale_y_continuous("Loss function", limits = c(0, 30)) +
  xlab(expression(theta)) +
  geom_segment(data = too_large[1, ], aes(x = x, y = y, xend = x, yend = -Inf), lty = "dashed") +
  geom_point(data = too_large, aes(x, y), size = 3, shape = 21, fill = "blue", alpha = .5) +
  geom_curve(data = too_large, aes(x = x, y = y, xend = x2, yend = y2), curvature = 1, lty = "dotted") +
  theme(
    axis.ticks = element_blank(),
    axis.text = element_blank()
  ) +
  annotate("text", x = too_large[1, "x"], y = 1, label = "Start", hjust = -0.1, vjust = .8) +
  ggtitle("a) too big")
gridExtra::grid.arrange(p2, p1, nrow = 1)

Figure 12.5:


# create random walk data
set.seed(123)
x <- sample(seq(3, 5, by = .05), 10, replace = TRUE)
set.seed(123)
y <- seq(2, 28, length.out = 10)

random_walk <- data.frame(
  x = x,
  y = y[order(y, decreasing = TRUE)]
)

optimal <- data.frame(x = 0, y = 0)

# plot
ggplot(df, aes(x, y)) + 
  coord_polar() +
  theme_minimal() +
  theme(
    axis.ticks = element_blank(),
    axis.text = element_blank()
  ) +
  xlab(expression(theta[1])) +
  ylab(expression(theta[2])) +
  geom_point(data = random_walk, aes(x, y), size = 3, shape = 21, fill = "blue", alpha = .5) + 
  geom_point(data = optimal, aes(x, y), size = 2, shape = 21, fill = "yellow") + 
  geom_path(data = random_walk, aes(x, y), lty = "dotted") +
  annotate("text", x = random_walk[1, "x"], y = random_walk[1, "y"], label = "Start", hjust = 1, vjust = -1) +
  annotate("text", x = optimal[1, "x"], y = optimal[1, "y"], label = "Minimum", hjust = -.2, vjust = 1) +
  ylim(c(0, 28)) + 
  xlim(-5, 5)

As we’ll see in the sections that follow, there are several hyperparameter tuning options available in stochastic gradient boosting (some control the gradient descent and others control the tree growing process). If properly tuned (e.g., with k-fold CV) GBMs can lead to some of the most flexible and accurate predictive models you can build!

Basic GBM

Implementation

Note: this code chunks takes approximately 2 minutes to run

# run a basic GBM model
set.seed(123)  # for reproducibility
ames_gbm1 <- gbm(
  formula = Sale_Price ~ .,
  data = ames_train,
  distribution = "gaussian",  # SSE loss function
  n.trees = 5000,
  shrinkage = 0.1,
  interaction.depth = 3,
  n.minobsinnode = 10,
  cv.folds = 10
)

# find index for number trees with minimum CV error
best <- which.min(ames_gbm1$cv.error)

# get MSE and compute RMSE
sqrt(ames_gbm1$cv.error[best])
[1] 24586.21
# plot error curve
gbm.perf(ames_gbm1, method = "cv")
[1] 477

General tuning strategy

Note: this code chunks takes approximately 10 minutes to run

# create grid search
hyper_grid <- expand.grid(
  learning_rate = c(0.3, 0.1, 0.05, 0.01, 0.005),
  RMSE = NA,
  trees = NA,
  time = NA
)

# execute grid search
for(i in seq_len(nrow(hyper_grid))) {

  # fit gbm
  set.seed(123)  # for reproducibility
  train_time <- system.time({
    m <- gbm(
      formula = Sale_Price ~ .,
      data = ames_train,
      distribution = "gaussian",
      n.trees = 5000, 
      shrinkage = hyper_grid$learning_rate[i], 
      interaction.depth = 3, 
      n.minobsinnode = 10,
      cv.folds = 10 
   )
  })
  
  # add SSE, trees, and training time to results
  hyper_grid$RMSE[i]  <- sqrt(min(m$cv.error))
  hyper_grid$trees[i] <- which.min(m$cv.error)
  hyper_grid$Time[i]  <- train_time[["elapsed"]]

}

# results
arrange(hyper_grid, RMSE)

Note: this code chunks takes approximately 30 minutes to run

# search grid
hyper_grid <- expand.grid(
  n.trees = 6000,
  shrinkage = 0.01,
  interaction.depth = c(3, 5, 7),
  n.minobsinnode = c(5, 10, 15)
)

# create model fit function
model_fit <- function(n.trees, shrinkage, interaction.depth, n.minobsinnode) {
  set.seed(123)
  m <- gbm(
    formula = Sale_Price ~ .,
    data = ames_train,
    distribution = "gaussian",
    n.trees = n.trees,
    shrinkage = shrinkage,
    interaction.depth = interaction.depth,
    n.minobsinnode = n.minobsinnode,
    cv.folds = 10
  )
  # compute RMSE
  sqrt(min(m$cv.error))
}

# perform search grid with functional programming
hyper_grid$rmse <- purrr::pmap_dbl(
  hyper_grid,
  ~ model_fit(
    n.trees = ..1,
    shrinkage = ..2,
    interaction.depth = ..3,
    n.minobsinnode = ..4
    )
)

# results
arrange(hyper_grid, rmse)

Stochastic GBMs

Implementation

Note: this code chunks takes over 60 minutes to run

# refined hyperparameter grid
hyper_grid <- list(
  sample_rate = c(0.5, 0.75, 1),              # row subsampling
  col_sample_rate = c(0.5, 0.75, 1),          # col subsampling for each split
  col_sample_rate_per_tree = c(0.5, 0.75, 1)  # col subsampling for each tree
)

# random grid search strategy
search_criteria <- list(
  strategy = "RandomDiscrete",
  stopping_metric = "mse",
  stopping_tolerance = 0.001,   
  stopping_rounds = 10,         
  max_runtime_secs = 60*60      
)

# perform grid search 
grid <- h2o.grid(
  algorithm = "gbm",
  grid_id = "gbm_grid",
  x = predictors, 
  y = response,
  training_frame = train_h2o,
  hyper_params = hyper_grid,
  ntrees = 6000,
  learn_rate = 0.01,
  max_depth = 7,
  min_rows = 5,
  nfolds = 10,
  stopping_rounds = 10,
  stopping_tolerance = 0,
  search_criteria = search_criteria,
  seed = 123
)

# collect the results and sort by our model performance metric of choice
grid_perf <- h2o.getGrid(
  grid_id = "gbm_grid", 
  sort_by = "mse", 
  decreasing = FALSE
)

grid_perf
H2O Grid Details
================

Grid ID: gbm_grid 
Used hyper parameters: 
  -  col_sample_rate 
  -  col_sample_rate_per_tree 
  -  sample_rate 
Number of models: 18 
Number of failed models: 0 

Hyper-Parameter Search Summary: ordered by increasing mse
# Grab the model_id for the top model, chosen by cross validation error
best_model_id <- grid_perf@model_ids[[1]]
best_model <- h2o.getModel(best_model_id)

# Now let’s get performance metrics on the best model
h2o.performance(model = best_model, xval = TRUE)
H2ORegressionMetrics: gbm
** Reported on cross-validation data. **
** 10-fold cross-validation on training data (Metrics computed for combined holdout predictions) **

MSE:  509970975
RMSE:  22582.54
MAE:  13642.5
RMSLE:  0.1211079
Mean Residual Deviance :  509970975

XGBoost

Tuning strategy

library(recipes)
xgb_prep <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_integer(all_nominal()) %>%
  prep(training = ames_train, retain = TRUE) %>%
  juice()

X <- as.matrix(xgb_prep[setdiff(names(xgb_prep), "Sale_Price")])
Y <- xgb_prep$Sale_Price
set.seed(123)
ames_xgb <- xgb.cv(
  data = X,
  label = Y,
  nrounds = 6000,
  objective = "reg:linear",
  early_stopping_rounds = 50, 
  nfold = 10,
  params = list(
    eta = 0.1,
    max_depth = 3,
    min_child_weight = 3,
    subsample = 0.8,
    colsample_bytree = 1.0),
  verbose = 0
)  

# minimum test CV RMSE
min(ames_xgb$evaluation_log$test_rmse_mean)
[1] 23090.54

Note: this code chunks takes approximately 2 hours to run

# hyperparameter grid
hyper_grid <- expand.grid(
  eta = 0.01,
  max_depth = 3, 
  min_child_weight = 3,
  subsample = 0.5, 
  colsample_bytree = 0.5,
  gamma = c(0, 1, 10, 100, 1000),
  lambda = c(0, 1e-2, 0.1, 1, 100, 1000, 10000),
  alpha = c(0, 1e-2, 0.1, 1, 100, 1000, 10000),
  rmse = 0,          # a place to dump RMSE results
  trees = 0          # a place to dump required number of trees
)

# grid search
for(i in seq_len(nrow(hyper_grid))) {
  set.seed(123)
  m <- xgb.cv(
    data = X,
    label = Y,
    nrounds = 4000,
    objective = "reg:linear",
    early_stopping_rounds = 50, 
    nfold = 10,
    verbose = 0,
    params = list( 
      eta = hyper_grid$eta[i], 
      max_depth = hyper_grid$max_depth[i],
      min_child_weight = hyper_grid$min_child_weight[i],
      subsample = hyper_grid$subsample[i],
      colsample_bytree = hyper_grid$colsample_bytree[i],
      gamma = hyper_grid$gamma[i], 
      lambda = hyper_grid$lambda[i], 
      alpha = hyper_grid$alpha[i]
    ) 
  )
  hyper_grid$rmse[i] <- min(m$evaluation_log$test_rmse_mean)
  hyper_grid$trees[i] <- m$best_iteration
}

# results
hyper_grid %>%
  filter(rmse > 0) %>%
  arrange(rmse) %>%
  glimpse()
Observations: 245
Variables: 10
$ eta              <dbl> 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, …
$ max_depth        <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, …
$ min_child_weight <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, …
$ subsample        <dbl> 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,…
$ colsample_bytree <dbl> 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,…
$ gamma            <dbl> 0, 1, 10, 100, 1000, 0, 1, 10, 100, 1000, 0, 1, 10, 100, 1000, 0, 1, 10, 100, 1000, …
$ lambda           <dbl> 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,…
$ alpha            <dbl> 10000.00, 10000.00, 10000.00, 10000.00, 10000.00, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00…
$ rmse             <dbl> 22700.17, 22700.17, 22700.17, 22700.17, 22700.17, 22896.96, 22896.96, 22896.96, 2289…
$ trees            <dbl> 3998, 3998, 3998, 3998, 3998, 2962, 2962, 2962, 2962, 2962, 2962, 2962, 2962, 2962, …
# optimal parameter list
params <- list(
  eta = 0.01,
  max_depth = 3,
  min_child_weight = 3,
  subsample = 0.5,
  colsample_bytree = 0.5
)

# train final model
xgb.fit.final <- xgboost(
  params = params,
  data = X,
  label = Y,
  nrounds = 3944,
  objective = "reg:linear",
  verbose = 0
)

Feature interpretation

# variable importance plot
vip::vip(xgb.fit.final) 

h2o.shutdown(prompt = FALSE)
[1] TRUE
LS0tCnRpdGxlOiAiQ2hhcHRlciAxMjogR3JhZGllbnQgQm9vc3RpbmciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCl9fTm90ZV9fOiBTb21lIHJlc3VsdHMgbWF5IGRpZmZlciBmcm9tIHRoZSBoYXJkIGNvcHkgYm9vayBkdWUgdG8gdGhlIGNoYW5naW5nIG9mIHNhbXBsaW5nIHByb2NlZHVyZXMgaW50cm9kdWNlZCBpbiBSIDMuNi4wLiBTZWUgaHR0cDovL2JpdC5seS8zNUQxU1c3IGZvciBtb3JlIGRldGFpbHMuIEFjY2VzcyBhbmQgcnVuIHRoZSBzb3VyY2UgY29kZSBmb3IgdGhpcyBub3RlYm9vayBbaGVyZV0oaHR0cHM6Ly9yc3R1ZGlvLmNsb3VkL3Byb2plY3QvODAxMTg1KS4gRG8gdG8gb3V0cHV0IHNpemUsIG1vc3Qgb2YgdGhpcwpjaGFwdGVycyBjb2RlIGNodW5rcyBzaG91bGQgbm90IGJlIHJhbiBvbiBSU3R1ZGlvIENsb3VkLgoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZCBwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rczoKCmBgYHtyIHNldHVwfQojIFNldCBnbG9iYWwgUiBvcHRpb25zCm9wdGlvbnMoc2NpcGVuID0gOTk5KQoKIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKCiMgU2V0IGdsb2JhbCBrbml0ciBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjYWNoZSA9IEZBTFNFLAogIHdhcm5pbmcgPSBGQUxTRSwgCiAgbWVzc2FnZSA9IEZBTFNFCikKCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcwpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHJwYXJ0KQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKIyMgUHJlcmVxdWlzaXRlcwoKYGBge3IgZ2JtLXBrZy1yZXF9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICMgZm9yIGdlbmVyYWwgZGF0YSB3cmFuZ2xpbmcgbmVlZHMKCiMgTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShnYm0pICAgICAgIyBmb3Igb3JpZ2luYWwgaW1wbGVtZW50YXRpb24gb2YgcmVndWxhciBhbmQgc3RvY2hhc3RpYyBHQk1zCmxpYnJhcnkoaDJvKSAgICAgICMgZm9yIGEgamF2YS1iYXNlZCBpbXBsZW1lbnRhdGlvbiBvZiBHQk0gdmFyaWFudHMKbGlicmFyeSh4Z2Jvb3N0KSAgIyBmb3IgZml0dGluZyBleHRyZW1lIGdyYWRpZW50IGJvb3N0aW5nCmBgYAoKYGBge3IgZ2JtLWFtZXMtdHJhaW4sIGVjaG89VFJVRX0KbGlicmFyeShyc2FtcGxlKQojIGNyZWF0ZSBBbWVzIHRyYWluaW5nIGRhdGEKc2V0LnNlZWQoMTIzKQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQpgYGAKCmBgYHtyIGdibS1oMm8tb2JqZWN0fQpoMm8uaW5pdChtYXhfbWVtX3NpemUgPSAiMTBnIikKCnRyYWluX2gybyA8LSBhcy5oMm8oYW1lc190cmFpbikKcmVzcG9uc2UgPC0gIlNhbGVfUHJpY2UiCnByZWRpY3RvcnMgPC0gc2V0ZGlmZihjb2xuYW1lcyhhbWVzX3RyYWluKSwgcmVzcG9uc2UpCmBgYAoKCiMjIEhvdyBib29zdGluZyB3b3JrcwoKIyMjIEEgc2VxdWVudGlhbCBlbnNlbWJsZSBhcHByb2FjaAoKRmlndXJlIDEyLjE6CgpgYGB7ciBzZXF1ZW50aWFsLWZpZywgZWNobz1UUlVFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5jYXA9IlNlcXVlbnRpYWwgZW5zZW1ibGUgYXBwcm9hY2guIiwgb3V0LmhlaWdodD0iNzUlIiwgb3V0LndpZHRoPSI3NSUifQoKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9ib29zdGVkLXRyZWVzLXByb2Nlc3MucG5nIikKYGBgCgpGaWd1cmUgMTIuMjoKCmBgYHtyIGJvb3N0aW5nLWluLWFjdGlvbiwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTAsIGVjaG89VFJVRSwgZmlnLmNhcD0iQm9vc3RlZCByZWdyZXNzaW9uIGRlY2lzaW9uIHN0dW1wcyBhcyAwLTEwMjQgc3VjY2Vzc2l2ZSB0cmVlcyBhcmUgYWRkZWQuIn0KCiMgU2ltdWxhdGUgc2luZSB3YXZlIGRhdGEKc2V0LnNlZWQoMTExMikgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkZiA8LSB0aWJibGU6OnRpYmJsZSgKICB4ID0gc2VxKGZyb20gPSAwLCB0byA9IDIgKiBwaSwgbGVuZ3RoID0gMTAwMCksCiAgeSA9IHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSwgc2QgPSAwLjUpLAogIHRydXRoID0gc2luKHgpCikKCiMgRnVuY3Rpb24gdG8gYm9vc3QgYHJwYXJ0OjpycGFydCgpYCB0cmVlcwpycGFydEJvb3N0IDwtIGZ1bmN0aW9uKHgsIHksIGRhdGEsIG51bV90cmVlcyA9IDEwMCwgbGVhcm5fcmF0ZSA9IDAuMSwgdHJlZV9kZXB0aCA9IDYpIHsKICB4IDwtIGRhdGFbW2RlcGFyc2Uoc3Vic3RpdHV0ZSh4KSldXQogIHkgPC0gZGF0YVtbZGVwYXJzZShzdWJzdGl0dXRlKHkpKV1dCiAgR19iX2hhdCA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aCh5KSwgbmNvbCA9IG51bV90cmVlcyArIDEpCiAgciA8LSB5CiAgZm9yKHRyZWUgaW4gc2VxX2xlbihudW1fdHJlZXMpKSB7CiAgICBnX2JfdGlsZGUgPC0gcnBhcnQociB+IHgsIGNvbnRyb2wgPSBsaXN0KGNwID0gMCwgbWF4ZGVwdGggPSB0cmVlX2RlcHRoKSkKICAgIGdfYl9oYXQgPC0gbGVhcm5fcmF0ZSAqIHByZWRpY3QoZ19iX3RpbGRlKQogICAgR19iX2hhdFssIHRyZWUgKyAxXSA8LSBHX2JfaGF0WywgdHJlZV0gKyBtYXRyaXgoZ19iX2hhdCkKICAgIHIgPC0gciAtIGdfYl9oYXQKICAgIGNvbG5hbWVzKEdfYl9oYXQpIDwtIHBhc3RlMCgidHJlZV8iLCBjKDAsIHNlcV9sZW4obnVtX3RyZWVzKSkpCiAgfQogIGNiaW5kKGRmLCBhcy5kYXRhLmZyYW1lKEdfYl9oYXQpKSAlPiUKICAgIGdhdGhlcih0cmVlLCBwcmVkaWN0aW9uLCBzdGFydHNfd2l0aCgidHJlZSIpKSAlPiUKICAgIG11dGF0ZSh0cmVlID0gc3RyaW5ncjo6c3RyX2V4dHJhY3QodHJlZSwgIlxcZCsiKSAlPiUgYXMubnVtZXJpYygpKQp9CgojIFBsb3QgYm9vc3RlZCB0cmVlIHNlcXVlbmNlCnJwYXJ0Qm9vc3QoeCwgeSwgZGF0YSA9IGRmLCBudW1fdHJlZXMgPSAyXjEwLCBsZWFybl9yYXRlID0gMC4wNSwgdHJlZV9kZXB0aCA9IDEpICU+JQogIGZpbHRlcih0cmVlICVpbiUgYygwLCAyXmMoMDoxMCkpKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHByZWRpY3Rpb24pKSArCiAgICB5bGFiKCJ5IikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZGYsIGFlcyh4LCB5KSwgYWxwaGEgPSAuMSkgKwogICAgZ2VvbV9saW5lKGRhdGEgPSBkZiwgYWVzKHgsIHRydXRoKSwgY29sb3IgPSAiYmx1ZSIpICsKICAgIGdlb21fbGluZShjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEpICsKICAgIGZhY2V0X3dyYXAofiB0cmVlLCBucm93ID0gMykKYGBgCgoKIyMjIEdyYWRpZW50IGRlc2NlbnQKCkZpZ3VyZSAxMi4zOgoKYGBge3IgZ3JhZGllbnQtZGVzY2VudC1maWcsIGVjaG89VFJVRSwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgZmlnLmNhcD0iR3JhZGllbnQgZGVzY2VudCBpcyB0aGUgcHJvY2VzcyBvZiBncmFkdWFsbHkgZGVjcmVhc2luZyB0aGUgY29zdCBmdW5jdGlvbiAoaS5lLiBNU0UpIGJ5IHR3ZWFraW5nIHBhcmFtZXRlcihzKSBpdGVyYXRpdmVseSB1bnRpbCB5b3UgaGF2ZSByZWFjaGVkIGEgbWluaW11bS4ifQoKIyBjcmVhdGUgZGF0YSB0byBwbG90CnggPC0gc2VxKC01LCA1LCBieSA9IC4wNSkKeSA8LSB4XjIgKyAzCmRmIDwtIGRhdGEuZnJhbWUoeCwgeSkKc3RlcCA8LSA1CnN0ZXBfc2l6ZSA8LSAuMgpmb3IoaSBpbiBzZXFfbGVuKDE4KSkgewogIG5leHRfc3RlcCA8LSBtYXgoc3RlcCkgKyByb3VuZChkaWZmKHJhbmdlKG1heChzdGVwKSwgd2hpY2gubWluKGRmJHkpKSkgKiBzdGVwX3NpemUsIDApCiAgc3RlcCA8LSBjKHN0ZXAsIG5leHRfc3RlcCkKICBuZXh0Cn0Kc3RlcHMgPC0gZGZbc3RlcCwgXSAlPiUKICBtdXRhdGUoeDIgPSBsYWcoeCksIHkyID0gbGFnKHkpKSAlPiUKICBkcGx5cjo6c2xpY2UoMToxOCkKIyBwbG90CmdncGxvdChkZiwgYWVzKHgsIHkpKSArCiAgZ2VvbV9saW5lKHNpemUgPSAxLjUsIGFscGhhID0gLjUpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHNjYWxlX3lfY29udGludW91cygiTG9zcyBmdW5jdGlvbiIsIGxpbWl0cyA9IGMoMCwgMzApKSArCiAgeGxhYihleHByZXNzaW9uKHRoZXRhKSkgKwogIGdlb21fc2VnbWVudChkYXRhID0gZGZbYyg1LCB3aGljaC5taW4oZGYkeSkpLCBdLCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geCwgeWVuZCA9IC1JbmYpLCBsdHkgPSAiZGFzaGVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihkZiwgeSA9PSBtaW4oeSkpLCBhZXMoeCwgeSksIHNpemUgPSA0LCBzaGFwZSA9IDIxLCBmaWxsID0gInllbGxvdyIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBzdGVwcywgYWVzKHgsIHkpLCBzaXplID0gMywgc2hhcGUgPSAyMSwgZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAuNSkgKwogIGdlb21fY3VydmUoZGF0YSA9IHN0ZXBzLCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geDIsIHllbmQgPSB5MiksIGN1cnZhdHVyZSA9IDEsIGx0eSA9ICJkb3R0ZWQiKSArCiAgdGhlbWUoCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gZGZbNSwgIngiXSwgeSA9IDEsIGxhYmVsID0gIkluaXRpYWwgdmFsdWUiLCBoanVzdCA9IC0wLjEsIHZqdXN0ID0gLjgpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSBkZlt3aGljaC5taW4oZGYkeSksICJ4Il0sIHkgPSAxLCBsYWJlbCA9ICJNaW5pbWl1bSIsIGhqdXN0ID0gLTAuMSwgdmp1c3QgPSAuOCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGRmWzUsICJ4Il0sIHkgPSBkZls1LCAieSJdLCBsYWJlbCA9ICJMZWFybmluZyBzdGVwIiwgaGp1c3QgPSAtLjgsIHZqdXN0ID0gMCkKYGBgCgpGaWd1cmUgMTIuNDoKCmBgYHtyIGxlYXJuaW5nLXJhdGUtZmlnLCBlY2hvPVRSVUUsIGZpZy5jYXA9IkEgbGVhcm5pbmcgcmF0ZSB0aGF0IGlzIHRvbyBzbWFsbCB3aWxsIHJlcXVpcmUgbWFueSBpdGVyYXRpb25zIHRvIGZpbmQgdGhlIG1pbmltdW0uIEEgbGVhcm5pbmcgcmF0ZSB0b28gYmlnIG1heSBqdW1wIG92ZXIgdGhlIG1pbmltdW0uIn0KCiMgY3JlYXRlIHRvbyBzbWFsbCBvZiBhIGxlYXJuaW5nIHJhdGUKc3RlcCA8LSA1CnN0ZXBfc2l6ZSA8LSAuMDUKZm9yKGkgaW4gc2VxX2xlbigxMCkpIHsKICBuZXh0X3N0ZXAgPC0gbWF4KHN0ZXApICsgcm91bmQoZGlmZihyYW5nZShtYXgoc3RlcCksIHdoaWNoLm1pbihkZiR5KSkpICogc3RlcF9zaXplLCAwKQogIHN0ZXAgPC0gYyhzdGVwLCBuZXh0X3N0ZXApCiAgbmV4dAp9CnRvb19zbWFsbCA8LSBkZltzdGVwLCBdICU+JQogIG11dGF0ZSh4MiA9IGxhZyh4KSwgeTIgPSBsYWcoeSkpCiMgcGxvdApwMSA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKwogIGdlb21fbGluZShzaXplID0gMS41LCBhbHBoYSA9IC41KSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIkxvc3MgZnVuY3Rpb24iLCBsaW1pdHMgPSBjKDAsIDMwKSkgKwogIHhsYWIoZXhwcmVzc2lvbih0aGV0YSkpICsKICBnZW9tX3NlZ21lbnQoZGF0YSA9IHRvb19zbWFsbFsxLCBdLCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geCwgeWVuZCA9IC1JbmYpLCBsdHkgPSAiZGFzaGVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IHRvb19zbWFsbCwgYWVzKHgsIHkpLCBzaXplID0gMywgc2hhcGUgPSAyMSwgZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAuNSkgKwogIGdlb21fY3VydmUoZGF0YSA9IHRvb19zbWFsbCwgYWVzKHggPSB4LCB5ID0geSwgeGVuZCA9IHgyLCB5ZW5kID0geTIpLCBjdXJ2YXR1cmUgPSAxLCBsdHkgPSAiZG90dGVkIikgKwogIHRoZW1lKAogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGRmWzUsICJ4Il0sIHkgPSAxLCBsYWJlbCA9ICJTdGFydCIsIGhqdXN0ID0gLTAuMSwgdmp1c3QgPSAuOCkgKwogIGdndGl0bGUoImIpIHRvbyBzbWFsbCIpCiMgY3JlYXRlIHRvbyBsYXJnZSBvZiBhIGxlYXJuaW5nIHJhdGUKdG9vX2xhcmdlIDwtIGRmW3JvdW5kKHdoaWNoLm1pbihkZiR5KSAqICgxICsgYygtLjksIC0uNiwgLS4yLCAuMykpLCAwKSwgXSAlPiUKICBtdXRhdGUoeDIgPSBsYWcoeCksIHkyID0gbGFnKHkpKQojIHBsb3QKcDIgPC0gZ2dwbG90KGRmLCBhZXMoeCwgeSkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEuNSwgYWxwaGEgPSAuNSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJMb3NzIGZ1bmN0aW9uIiwgbGltaXRzID0gYygwLCAzMCkpICsKICB4bGFiKGV4cHJlc3Npb24odGhldGEpKSArCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSB0b29fbGFyZ2VbMSwgXSwgYWVzKHggPSB4LCB5ID0geSwgeGVuZCA9IHgsIHllbmQgPSAtSW5mKSwgbHR5ID0gImRhc2hlZCIpICsKICBnZW9tX3BvaW50KGRhdGEgPSB0b29fbGFyZ2UsIGFlcyh4LCB5KSwgc2l6ZSA9IDMsIHNoYXBlID0gMjEsIGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gLjUpICsKICBnZW9tX2N1cnZlKGRhdGEgPSB0b29fbGFyZ2UsIGFlcyh4ID0geCwgeSA9IHksIHhlbmQgPSB4MiwgeWVuZCA9IHkyKSwgY3VydmF0dXJlID0gMSwgbHR5ID0gImRvdHRlZCIpICsKICB0aGVtZSgKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkKICApICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSB0b29fbGFyZ2VbMSwgIngiXSwgeSA9IDEsIGxhYmVsID0gIlN0YXJ0IiwgaGp1c3QgPSAtMC4xLCB2anVzdCA9IC44KSArCiAgZ2d0aXRsZSgiYSkgdG9vIGJpZyIpCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAyLCBwMSwgbnJvdyA9IDEpCmBgYAoKRmlndXJlIDEyLjU6CgpgYGB7ciBzdG9jaGFzdGljLWdyYWRpZW50LWRlc2NlbnQtZmlnLCBlY2hvPVRSVUUsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iU3RvY2hhc3RpYyBncmFkaWVudCBkZXNjZW50IHdpbGwgb2Z0ZW4gZmluZCBhIG5lYXItb3B0aW1hbCBzb2x1dGlvbiBieSBqdW1waW5nIG91dCBvZiBsb2NhbCBtaW5pbWFzIGFuZCBvZmYgcGxhdGVhdXMuIn0KCiMgY3JlYXRlIHJhbmRvbSB3YWxrIGRhdGEKc2V0LnNlZWQoMTIzKQp4IDwtIHNhbXBsZShzZXEoMywgNSwgYnkgPSAuMDUpLCAxMCwgcmVwbGFjZSA9IFRSVUUpCnNldC5zZWVkKDEyMykKeSA8LSBzZXEoMiwgMjgsIGxlbmd0aC5vdXQgPSAxMCkKCnJhbmRvbV93YWxrIDwtIGRhdGEuZnJhbWUoCiAgeCA9IHgsCiAgeSA9IHlbb3JkZXIoeSwgZGVjcmVhc2luZyA9IFRSVUUpXQopCgpvcHRpbWFsIDwtIGRhdGEuZnJhbWUoeCA9IDAsIHkgPSAwKQoKIyBwbG90CmdncGxvdChkZiwgYWVzKHgsIHkpKSArIAogIGNvb3JkX3BvbGFyKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgeGxhYihleHByZXNzaW9uKHRoZXRhWzFdKSkgKwogIHlsYWIoZXhwcmVzc2lvbih0aGV0YVsyXSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSByYW5kb21fd2FsaywgYWVzKHgsIHkpLCBzaXplID0gMywgc2hhcGUgPSAyMSwgZmlsbCA9ICJibHVlIiwgYWxwaGEgPSAuNSkgKyAKICBnZW9tX3BvaW50KGRhdGEgPSBvcHRpbWFsLCBhZXMoeCwgeSksIHNpemUgPSAyLCBzaGFwZSA9IDIxLCBmaWxsID0gInllbGxvdyIpICsgCiAgZ2VvbV9wYXRoKGRhdGEgPSByYW5kb21fd2FsaywgYWVzKHgsIHkpLCBsdHkgPSAiZG90dGVkIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IHJhbmRvbV93YWxrWzEsICJ4Il0sIHkgPSByYW5kb21fd2Fsa1sxLCAieSJdLCBsYWJlbCA9ICJTdGFydCIsIGhqdXN0ID0gMSwgdmp1c3QgPSAtMSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IG9wdGltYWxbMSwgIngiXSwgeSA9IG9wdGltYWxbMSwgInkiXSwgbGFiZWwgPSAiTWluaW11bSIsIGhqdXN0ID0gLS4yLCB2anVzdCA9IDEpICsKICB5bGltKGMoMCwgMjgpKSArIAogIHhsaW0oLTUsIDUpCmBgYAoKQXMgd2UnbGwgc2VlIGluIHRoZSBzZWN0aW9ucyB0aGF0IGZvbGxvdywgdGhlcmUgYXJlIHNldmVyYWwgaHlwZXJwYXJhbWV0ZXIgdHVuaW5nIG9wdGlvbnMgYXZhaWxhYmxlIGluIHN0b2NoYXN0aWMgZ3JhZGllbnQgYm9vc3RpbmcgKHNvbWUgY29udHJvbCB0aGUgZ3JhZGllbnQgZGVzY2VudCBhbmQgb3RoZXJzIGNvbnRyb2wgdGhlIHRyZWUgZ3Jvd2luZyBwcm9jZXNzKS4gSWYgcHJvcGVybHkgdHVuZWQgKGUuZy4sIHdpdGggX2tfLWZvbGQgQ1YpIEdCTXMgY2FuIGxlYWQgdG8gc29tZSBvZiB0aGUgbW9zdCBmbGV4aWJsZSBhbmQgYWNjdXJhdGUgcHJlZGljdGl2ZSBtb2RlbHMgeW91IGNhbiBidWlsZCEKCiMjIEJhc2ljIEdCTQoKIyMjIEltcGxlbWVudGF0aW9uCgpfX19Ob3RlOiB0aGlzIGNvZGUgY2h1bmtzIHRha2VzIGFwcHJveGltYXRlbHkgMiBtaW51dGVzIHRvIHJ1bl9fXwoKYGBge3IgYmFzaWMtZ2JtfQojIHJ1biBhIGJhc2ljIEdCTSBtb2RlbApzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKYW1lc19nYm0xIDwtIGdibSgKICBmb3JtdWxhID0gU2FsZV9QcmljZSB+IC4sCiAgZGF0YSA9IGFtZXNfdHJhaW4sCiAgZGlzdHJpYnV0aW9uID0gImdhdXNzaWFuIiwgICMgU1NFIGxvc3MgZnVuY3Rpb24KICBuLnRyZWVzID0gNTAwMCwKICBzaHJpbmthZ2UgPSAwLjEsCiAgaW50ZXJhY3Rpb24uZGVwdGggPSAzLAogIG4ubWlub2JzaW5ub2RlID0gMTAsCiAgY3YuZm9sZHMgPSAxMAopCgojIGZpbmQgaW5kZXggZm9yIG51bWJlciB0cmVlcyB3aXRoIG1pbmltdW0gQ1YgZXJyb3IKYmVzdCA8LSB3aGljaC5taW4oYW1lc19nYm0xJGN2LmVycm9yKQoKIyBnZXQgTVNFIGFuZCBjb21wdXRlIFJNU0UKc3FydChhbWVzX2dibTEkY3YuZXJyb3JbYmVzdF0pCmBgYAoKCmBgYHtyIGJhc2ljLWdibS1lcnJvci1jdXJ2ZSwgZmlnLmNhcD0iVHJhaW5pbmcgYW5kIGNyb3NzLXZhbGlkYXRlZCBNU0UgYXMgbiB0cmVlcyBhcmUgYWRkZWQgdG8gdGhlIEdCTSBhbGdvcml0aG0uIiwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NX0KIyBwbG90IGVycm9yIGN1cnZlCmdibS5wZXJmKGFtZXNfZ2JtMSwgbWV0aG9kID0gImN2IikKYGBgCgoKIyMjIEdlbmVyYWwgdHVuaW5nIHN0cmF0ZWd5CgpfX19Ob3RlOiB0aGlzIGNvZGUgY2h1bmtzIHRha2VzIGFwcHJveGltYXRlbHkgMTAgbWludXRlcyB0byBydW5fX18KCmBgYHtyIGxlYXJuaW5nLXJhdGUtc2VhcmNofQojIGNyZWF0ZSBncmlkIHNlYXJjaApoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGxlYXJuaW5nX3JhdGUgPSBjKDAuMywgMC4xLCAwLjA1LCAwLjAxLCAwLjAwNSksCiAgUk1TRSA9IE5BLAogIHRyZWVzID0gTkEsCiAgdGltZSA9IE5BCikKCiMgZXhlY3V0ZSBncmlkIHNlYXJjaApmb3IoaSBpbiBzZXFfbGVuKG5yb3coaHlwZXJfZ3JpZCkpKSB7CgogICMgZml0IGdibQogIHNldC5zZWVkKDEyMykgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQogIHRyYWluX3RpbWUgPC0gc3lzdGVtLnRpbWUoewogICAgbSA8LSBnYm0oCiAgICAgIGZvcm11bGEgPSBTYWxlX1ByaWNlIH4gLiwKICAgICAgZGF0YSA9IGFtZXNfdHJhaW4sCiAgICAgIGRpc3RyaWJ1dGlvbiA9ICJnYXVzc2lhbiIsCiAgICAgIG4udHJlZXMgPSA1MDAwLCAKICAgICAgc2hyaW5rYWdlID0gaHlwZXJfZ3JpZCRsZWFybmluZ19yYXRlW2ldLCAKICAgICAgaW50ZXJhY3Rpb24uZGVwdGggPSAzLCAKICAgICAgbi5taW5vYnNpbm5vZGUgPSAxMCwKICAgICAgY3YuZm9sZHMgPSAxMCAKICAgKQogIH0pCiAgCiAgIyBhZGQgU1NFLCB0cmVlcywgYW5kIHRyYWluaW5nIHRpbWUgdG8gcmVzdWx0cwogIGh5cGVyX2dyaWQkUk1TRVtpXSAgPC0gc3FydChtaW4obSRjdi5lcnJvcikpCiAgaHlwZXJfZ3JpZCR0cmVlc1tpXSA8LSB3aGljaC5taW4obSRjdi5lcnJvcikKICBoeXBlcl9ncmlkJFRpbWVbaV0gIDwtIHRyYWluX3RpbWVbWyJlbGFwc2VkIl1dCgp9CgojIHJlc3VsdHMKYXJyYW5nZShoeXBlcl9ncmlkLCBSTVNFKQpgYGAKCl9fX05vdGU6IHRoaXMgY29kZSBjaHVua3MgdGFrZXMgYXBwcm94aW1hdGVseSAzMCBtaW51dGVzIHRvIHJ1bl9fXwoKYGBge3IgdHJlZS1oeXBlcnBhcmFtZXRlci1zZWFyY2h9CiMgc2VhcmNoIGdyaWQKaHlwZXJfZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBuLnRyZWVzID0gNjAwMCwKICBzaHJpbmthZ2UgPSAwLjAxLAogIGludGVyYWN0aW9uLmRlcHRoID0gYygzLCA1LCA3KSwKICBuLm1pbm9ic2lubm9kZSA9IGMoNSwgMTAsIDE1KQopCgojIGNyZWF0ZSBtb2RlbCBmaXQgZnVuY3Rpb24KbW9kZWxfZml0IDwtIGZ1bmN0aW9uKG4udHJlZXMsIHNocmlua2FnZSwgaW50ZXJhY3Rpb24uZGVwdGgsIG4ubWlub2JzaW5ub2RlKSB7CiAgc2V0LnNlZWQoMTIzKQogIG0gPC0gZ2JtKAogICAgZm9ybXVsYSA9IFNhbGVfUHJpY2UgfiAuLAogICAgZGF0YSA9IGFtZXNfdHJhaW4sCiAgICBkaXN0cmlidXRpb24gPSAiZ2F1c3NpYW4iLAogICAgbi50cmVlcyA9IG4udHJlZXMsCiAgICBzaHJpbmthZ2UgPSBzaHJpbmthZ2UsCiAgICBpbnRlcmFjdGlvbi5kZXB0aCA9IGludGVyYWN0aW9uLmRlcHRoLAogICAgbi5taW5vYnNpbm5vZGUgPSBuLm1pbm9ic2lubm9kZSwKICAgIGN2LmZvbGRzID0gMTAKICApCiAgIyBjb21wdXRlIFJNU0UKICBzcXJ0KG1pbihtJGN2LmVycm9yKSkKfQoKIyBwZXJmb3JtIHNlYXJjaCBncmlkIHdpdGggZnVuY3Rpb25hbCBwcm9ncmFtbWluZwpoeXBlcl9ncmlkJHJtc2UgPC0gcHVycnI6OnBtYXBfZGJsKAogIGh5cGVyX2dyaWQsCiAgfiBtb2RlbF9maXQoCiAgICBuLnRyZWVzID0gLi4xLAogICAgc2hyaW5rYWdlID0gLi4yLAogICAgaW50ZXJhY3Rpb24uZGVwdGggPSAuLjMsCiAgICBuLm1pbm9ic2lubm9kZSA9IC4uNAogICAgKQopCgojIHJlc3VsdHMKYXJyYW5nZShoeXBlcl9ncmlkLCBybXNlKQpgYGAKCgojIyBTdG9jaGFzdGljIEdCTXMKCiMjIyBJbXBsZW1lbnRhdGlvbgoKX19fTm90ZTogdGhpcyBjb2RlIGNodW5rcyB0YWtlcyBvdmVyIDYwIG1pbnV0ZXMgdG8gcnVuX19fCgpgYGB7ciBzdG9jaGFzdGljLWdibX0KIyByZWZpbmVkIGh5cGVycGFyYW1ldGVyIGdyaWQKaHlwZXJfZ3JpZCA8LSBsaXN0KAogIHNhbXBsZV9yYXRlID0gYygwLjUsIDAuNzUsIDEpLCAgICAgICAgICAgICAgIyByb3cgc3Vic2FtcGxpbmcKICBjb2xfc2FtcGxlX3JhdGUgPSBjKDAuNSwgMC43NSwgMSksICAgICAgICAgICMgY29sIHN1YnNhbXBsaW5nIGZvciBlYWNoIHNwbGl0CiAgY29sX3NhbXBsZV9yYXRlX3Blcl90cmVlID0gYygwLjUsIDAuNzUsIDEpICAjIGNvbCBzdWJzYW1wbGluZyBmb3IgZWFjaCB0cmVlCikKCiMgcmFuZG9tIGdyaWQgc2VhcmNoIHN0cmF0ZWd5CnNlYXJjaF9jcml0ZXJpYSA8LSBsaXN0KAogIHN0cmF0ZWd5ID0gIlJhbmRvbURpc2NyZXRlIiwKICBzdG9wcGluZ19tZXRyaWMgPSAibXNlIiwKICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwMSwgICAKICBzdG9wcGluZ19yb3VuZHMgPSAxMCwgICAgICAgICAKICBtYXhfcnVudGltZV9zZWNzID0gNjAqNjAgICAgICAKKQoKIyBwZXJmb3JtIGdyaWQgc2VhcmNoIApncmlkIDwtIGgyby5ncmlkKAogIGFsZ29yaXRobSA9ICJnYm0iLAogIGdyaWRfaWQgPSAiZ2JtX2dyaWQiLAogIHggPSBwcmVkaWN0b3JzLCAKICB5ID0gcmVzcG9uc2UsCiAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sCiAgaHlwZXJfcGFyYW1zID0gaHlwZXJfZ3JpZCwKICBudHJlZXMgPSA2MDAwLAogIGxlYXJuX3JhdGUgPSAwLjAxLAogIG1heF9kZXB0aCA9IDcsCiAgbWluX3Jvd3MgPSA1LAogIG5mb2xkcyA9IDEwLAogIHN0b3BwaW5nX3JvdW5kcyA9IDEwLAogIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAsCiAgc2VhcmNoX2NyaXRlcmlhID0gc2VhcmNoX2NyaXRlcmlhLAogIHNlZWQgPSAxMjMKKQoKIyBjb2xsZWN0IHRoZSByZXN1bHRzIGFuZCBzb3J0IGJ5IG91ciBtb2RlbCBwZXJmb3JtYW5jZSBtZXRyaWMgb2YgY2hvaWNlCmdyaWRfcGVyZiA8LSBoMm8uZ2V0R3JpZCgKICBncmlkX2lkID0gImdibV9ncmlkIiwgCiAgc29ydF9ieSA9ICJtc2UiLCAKICBkZWNyZWFzaW5nID0gRkFMU0UKKQoKZ3JpZF9wZXJmCmBgYAoKCmBgYHtyIGgyby1nYm0tYmVzdC1tb2R9CiMgR3JhYiB0aGUgbW9kZWxfaWQgZm9yIHRoZSB0b3AgbW9kZWwsIGNob3NlbiBieSBjcm9zcyB2YWxpZGF0aW9uIGVycm9yCmJlc3RfbW9kZWxfaWQgPC0gZ3JpZF9wZXJmQG1vZGVsX2lkc1tbMV1dCmJlc3RfbW9kZWwgPC0gaDJvLmdldE1vZGVsKGJlc3RfbW9kZWxfaWQpCgojIE5vdyBsZXTigJlzIGdldCBwZXJmb3JtYW5jZSBtZXRyaWNzIG9uIHRoZSBiZXN0IG1vZGVsCmgyby5wZXJmb3JtYW5jZShtb2RlbCA9IGJlc3RfbW9kZWwsIHh2YWwgPSBUUlVFKQpgYGAKCgojIyBYR0Jvb3N0CgojIyMgVHVuaW5nIHN0cmF0ZWd5CgpgYGB7ciB4Z2ItcHJlcH0KbGlicmFyeShyZWNpcGVzKQp4Z2JfcHJlcCA8LSByZWNpcGUoU2FsZV9QcmljZSB+IC4sIGRhdGEgPSBhbWVzX3RyYWluKSAlPiUKICBzdGVwX2ludGVnZXIoYWxsX25vbWluYWwoKSkgJT4lCiAgcHJlcCh0cmFpbmluZyA9IGFtZXNfdHJhaW4sIHJldGFpbiA9IFRSVUUpICU+JQogIGp1aWNlKCkKClggPC0gYXMubWF0cml4KHhnYl9wcmVwW3NldGRpZmYobmFtZXMoeGdiX3ByZXApLCAiU2FsZV9QcmljZSIpXSkKWSA8LSB4Z2JfcHJlcCRTYWxlX1ByaWNlCmBgYAoKYGBge3IgeGdiLW1vZDF9CnNldC5zZWVkKDEyMykKYW1lc194Z2IgPC0geGdiLmN2KAogIGRhdGEgPSBYLAogIGxhYmVsID0gWSwKICBucm91bmRzID0gNjAwMCwKICBvYmplY3RpdmUgPSAicmVnOmxpbmVhciIsCiAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gNTAsIAogIG5mb2xkID0gMTAsCiAgcGFyYW1zID0gbGlzdCgKICAgIGV0YSA9IDAuMSwKICAgIG1heF9kZXB0aCA9IDMsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gMywKICAgIHN1YnNhbXBsZSA9IDAuOCwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAxLjApLAogIHZlcmJvc2UgPSAwCikgIAoKIyBtaW5pbXVtIHRlc3QgQ1YgUk1TRQptaW4oYW1lc194Z2IkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCmBgYAoKX19fTm90ZTogdGhpcyBjb2RlIGNodW5rcyB0YWtlcyBhcHByb3hpbWF0ZWx5IDIgaG91cnMgdG8gcnVuX19fCgpgYGB7ciBwZXJmb3JtLXhnYi1ncmlkLXNlYXJjaH0KIyBoeXBlcnBhcmFtZXRlciBncmlkCmh5cGVyX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gMC4wMSwKICBtYXhfZGVwdGggPSAzLCAKICBtaW5fY2hpbGRfd2VpZ2h0ID0gMywKICBzdWJzYW1wbGUgPSAwLjUsIAogIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjUsCiAgZ2FtbWEgPSBjKDAsIDEsIDEwLCAxMDAsIDEwMDApLAogIGxhbWJkYSA9IGMoMCwgMWUtMiwgMC4xLCAxLCAxMDAsIDEwMDAsIDEwMDAwKSwKICBhbHBoYSA9IGMoMCwgMWUtMiwgMC4xLCAxLCAxMDAsIDEwMDAsIDEwMDAwKSwKICBybXNlID0gMCwgICAgICAgICAgIyBhIHBsYWNlIHRvIGR1bXAgUk1TRSByZXN1bHRzCiAgdHJlZXMgPSAwICAgICAgICAgICMgYSBwbGFjZSB0byBkdW1wIHJlcXVpcmVkIG51bWJlciBvZiB0cmVlcwopCgojIGdyaWQgc2VhcmNoCmZvcihpIGluIHNlcV9sZW4obnJvdyhoeXBlcl9ncmlkKSkpIHsKICBzZXQuc2VlZCgxMjMpCiAgbSA8LSB4Z2IuY3YoCiAgICBkYXRhID0gWCwKICAgIGxhYmVsID0gWSwKICAgIG5yb3VuZHMgPSA0MDAwLAogICAgb2JqZWN0aXZlID0gInJlZzpsaW5lYXIiLAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gNTAsIAogICAgbmZvbGQgPSAxMCwKICAgIHZlcmJvc2UgPSAwLAogICAgcGFyYW1zID0gbGlzdCggCiAgICAgIGV0YSA9IGh5cGVyX2dyaWQkZXRhW2ldLCAKICAgICAgbWF4X2RlcHRoID0gaHlwZXJfZ3JpZCRtYXhfZGVwdGhbaV0sCiAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBoeXBlcl9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICAgIHN1YnNhbXBsZSA9IGh5cGVyX2dyaWQkc3Vic2FtcGxlW2ldLAogICAgICBjb2xzYW1wbGVfYnl0cmVlID0gaHlwZXJfZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLAogICAgICBnYW1tYSA9IGh5cGVyX2dyaWQkZ2FtbWFbaV0sIAogICAgICBsYW1iZGEgPSBoeXBlcl9ncmlkJGxhbWJkYVtpXSwgCiAgICAgIGFscGhhID0gaHlwZXJfZ3JpZCRhbHBoYVtpXQogICAgKSAKICApCiAgaHlwZXJfZ3JpZCRybXNlW2ldIDwtIG1pbihtJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIGh5cGVyX2dyaWQkdHJlZXNbaV0gPC0gbSRiZXN0X2l0ZXJhdGlvbgp9CgojIHJlc3VsdHMKaHlwZXJfZ3JpZCAlPiUKICBmaWx0ZXIocm1zZSA+IDApICU+JQogIGFycmFuZ2Uocm1zZSkgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKCmBgYHtyIGZpbmFsLXhnYn0KIyBvcHRpbWFsIHBhcmFtZXRlciBsaXN0CnBhcmFtcyA8LSBsaXN0KAogIGV0YSA9IDAuMDEsCiAgbWF4X2RlcHRoID0gMywKICBtaW5fY2hpbGRfd2VpZ2h0ID0gMywKICBzdWJzYW1wbGUgPSAwLjUsCiAgY29sc2FtcGxlX2J5dHJlZSA9IDAuNQopCgojIHRyYWluIGZpbmFsIG1vZGVsCnhnYi5maXQuZmluYWwgPC0geGdib29zdCgKICBwYXJhbXMgPSBwYXJhbXMsCiAgZGF0YSA9IFgsCiAgbGFiZWwgPSBZLAogIG5yb3VuZHMgPSAzOTQ0LAogIG9iamVjdGl2ZSA9ICJyZWc6bGluZWFyIiwKICB2ZXJib3NlID0gMAopCmBgYAoKCiMjIEZlYXR1cmUgaW50ZXJwcmV0YXRpb24KCmBgYHtyIHhnYi1mZWF0dXJlLWltcG9ydGFuY2UsIGZpZy5jYXA9IlRvcCAxMCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgYmFzZWQgb24gdGhlIGltcHVyaXR5IChnYWluKSBtZXRyaWMuIn0KIyB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3QKdmlwOjp2aXAoeGdiLmZpdC5maW5hbCkgCmBgYAoKYGBge3J9Cmgyby5zaHV0ZG93bihwcm9tcHQgPSBGQUxTRSkKYGBgCgo=