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 chapter’s 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:

knitr::opts_chunk$set(
  message = FALSE, 
  warning = FALSE, 
  cache = FALSE
)

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

Prerequisites

# Helper packages
library(dplyr)      # for data wrangling
library(ggplot2)    # for awesome graphics

# Modeling packages
library(h2o)       # for interfacing with H2O
library(recipes)   # for ML recipes
library(rsample)   # for data splitting
library(xgboost)   # for fitting GBMs

# Model interpretability packages
library(pdp)       # for partial dependence plots (and ICE curves)
library(vip)       # for variable importance plots
library(iml)       # for general IML-related functions
library(DALEX)     # for general IML-related functions
library(lime)      # for local interpretable model-agnostic explanations

To illustrate various concepts we’ll continue working with the h2o version of the Ames housing data. We’ll also use the stacked ensemble model created here.

# Connect to H2O
h2o.no_progress()
h2o.init(max_mem_size = "5g")

# Load and split Ames housing data
ames <- AmesHousing::make_ames()
set.seed(123)  # for reproducibility
split <- initial_split(ames, strata = "Sale_Price")
ames_train <- training(split)
ames_test <- testing(split)

# Make sure we have consistent categorical levels
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
  step_other(all_nominal(), threshold = .005)

# Create training & test sets
train_h2o <- prep(blueprint, training = ames_train, retain = TRUE) %>%
  juice() %>%
  as.h2o()
test_h2o <- prep(blueprint, training = ames_train) %>%
  bake(new_data = ames_test) %>%
  as.h2o()

# Get names of response and features
Y <- "Sale_Price"
X <- setdiff(names(ames_train), Y)
# Train & cross-validate a GLM model
best_glm <- h2o.glm(
  x = X, y = Y, training_frame = train_h2o, alpha = 0.1,
  remove_collinear_columns = TRUE, nfolds = 10, fold_assignment = "Modulo",
  keep_cross_validation_predictions = TRUE, seed = 123
)

# Train & cross-validate a RF model
best_rf <- h2o.randomForest(
  x = X, y = Y, training_frame = train_h2o, ntrees = 1000, mtries = 20,
  max_depth = 30, min_rows = 1, sample_rate = 0.8, nfolds = 10,
  fold_assignment = "Modulo", keep_cross_validation_predictions = TRUE,
  seed = 123, stopping_rounds = 50, stopping_metric = "RMSE",
  stopping_tolerance = 0
)

# Train & cross-validate a GBM model
best_gbm <- h2o.gbm(
  x = X, y = Y, training_frame = train_h2o, ntrees = 5000, learn_rate = 0.01,
  max_depth = 7, min_rows = 5, sample_rate = 0.8, nfolds = 10,
  fold_assignment = "Modulo", keep_cross_validation_predictions = TRUE,
  seed = 123, stopping_rounds = 50, stopping_metric = "RMSE",
  stopping_tolerance = 0
)

# Train & cross-validate an XGBoost model
best_xgb <- h2o.xgboost(
  x = X, y = Y, training_frame = train_h2o, ntrees = 5000, learn_rate = 0.05,
  max_depth = 3, min_rows = 3, sample_rate = 0.8, categorical_encoding = "Enum",
  nfolds = 10, fold_assignment = "Modulo", 
  keep_cross_validation_predictions = TRUE, seed = 123, stopping_rounds = 50,
  stopping_metric = "RMSE", stopping_tolerance = 0
)

# Train a stacked tree ensemble
ensemble_tree <- h2o.stackedEnsemble(
  x = X, y = Y, training_frame = train_h2o, model_id = "my_tree_ensemble",
  base_models = list(best_glm, best_rf, best_gbm, best_xgb),
  metalearner_algorithm = "drf"
)

The idea

Local interpretation

predictions <- predict(ensemble_tree, train_h2o) %>% as.vector()
# Compute predictions
predictions <- predict(ensemble_tree, train_h2o) %>% as.vector()

# Print the highest and lowest predicted sales price
paste("Observation", which.max(predictions), 
      "has a predicted sale price of", scales::dollar(max(predictions))) 
[1] "Observation 1315 has a predicted sale price of $697,273"
paste("Observation", which.min(predictions), 
      "has a predicted sale price of", scales::dollar(min(predictions)))  
[1] "Observation 548 has a predicted sale price of $44,227.53"
# Grab feature values for observations with min/max predicted sales price
high_ob <- as.data.frame(train_h2o)[which.max(predictions), ] %>% select(-Sale_Price)
low_ob  <- as.data.frame(train_h2o)[which.min(predictions), ] %>% select(-Sale_Price)

Model-specific vs. model-agnostic

# 1) create a data frame with just the features
features <- as.data.frame(train_h2o) %>% select(-Sale_Price)

# 2) Create a vector with the actual responses
response <- as.data.frame(train_h2o) %>% pull(Sale_Price)

# 3) Create custom predict function that returns the predicted values as a vector
pred <- function(object, newdata)  {
  results <- as.vector(h2o.predict(object, as.h2o(newdata)))
  return(results)
}

# Example of prediction output
pred(ensemble_tree, features) %>% head()
[1] 217750.2 109165.7 178811.5 200595.1 194434.3 209172.5
# iml model agnostic object
components_iml <- Predictor$new(
  model = ensemble_tree, 
  data = features, 
  y = response, 
  predict.fun = pred
)

# DALEX model agnostic object
components_dalex <- DALEX::explain(
  model = ensemble_tree,
  data = features,
  y = response,
  predict_function = pred
)

Permutation-based feature importance

Implementation

vip(
  ensemble_tree,
  train = as.data.frame(train_h2o),
  method = "permute",
  target = "Sale_Price",
  metric = "RMSE",
  nsim = 5,
  sample_frac = 0.5,
  pred_wrapper = pred
)

Partial dependence

Concept

Figure 16.1:

knitr::include_graphics("images/pdp-illustration.png")

Implementation

# Custom prediction function wrapper
pdp_pred <- function(object, newdata)  {
  results <- mean(as.vector(h2o.predict(object, as.h2o(newdata))))
  return(results)
}

# Compute partial dependence values
pd_values <- partial(
  ensemble_tree,
  train = as.data.frame(train_h2o), 
  pred.var = "Gr_Liv_Area",
  pred.fun = pdp_pred,
  grid.resolution = 20
)
head(pd_values)  # take a peak

# Partial dependence plot
autoplot(pd_values, rug = TRUE, train = as.data.frame(train_h2o))

Individual conditional expectation

Concept

Figure 16.2:


# Construct ICE curves
ice_non_centered <- partial(
  ensemble_tree,
  train = as.data.frame(train_h2o), 
  pred.var = "Gr_Liv_Area",
  pred.fun = pred,
  grid.resolution = 20
) %>%
  autoplot(alpha = 0.05, center = FALSE) +
  ggtitle("A) Non-centered ICE curves")

# Construct c-ICE curves
ice_centered <- partial(
  ensemble_tree,
  train = as.data.frame(train_h2o), 
  pred.var = "Gr_Liv_Area",
  pred.fun = pred,
  grid.resolution = 20
) %>%
  autoplot(alpha = 0.05, center = TRUE) +
  ggtitle("B) Centered ICE curves")

# Display plots side by side
gridExtra::grid.arrange(ice_non_centered, ice_centered, ncol = 2)

Implementation

# Construct c-ICE curves
partial(
  ensemble_tree,
  train = as.data.frame(train_h2o), 
  pred.var = "Gr_Liv_Area",
  pred.fun = pred,
  grid.resolution = 20,
  plot = TRUE,
  center = TRUE,
  plot.engine = "ggplot2"
)

Feature interactions

Implementation

interact_2way <- Interaction$new(components_iml, feature = "First_Flr_SF")
interact_2way$results %>% 
  arrange(desc(.interaction)) %>% 
  top_n(10)
# Two-way PDP using iml
interaction_pdp <- Partial$new(
  components_iml, 
  c("First_Flr_SF", "Overall_Qual"), 
  ice = FALSE, 
  grid.size = 20
)
labels <- interaction_pdp$results %>% filter(First_Flr_SF == max(First_Flr_SF))
plot(interaction_pdp) + 
  ggrepel::geom_label_repel(
    data = labels, 
    aes(label = Overall_Qual),
    label.size = .05, 
    label.padding = .15
  )

Local interpretable model-agnostic explanations

Implementation

# Create explainer object
components_lime <- lime(
  x = features,
  model = ensemble_tree, 
  n_bins = 10
)

class(components_lime)
[1] "data_frame_explainer" "explainer"            "list"                
summary(components_lime)
                     Length Class              Mode     
model                 1     H2ORegressionModel S4       
preprocess            1     -none-             function 
bin_continuous        1     -none-             logical  
n_bins                1     -none-             numeric  
quantile_bins         1     -none-             logical  
use_density           1     -none-             logical  
feature_type         80     -none-             character
bin_cuts             80     -none-             list     
feature_distribution 80     -none-             list     
# Use LIME to explain previously defined instances: high_ob and low_ob
lime_explanation <- lime::explain(
  x = rbind(high_ob, low_ob), 
  explainer = components_lime, 
  n_permutations = 5000,
  dist_fun = "gower",
  kernel_width = 0.25,
  n_features = 10, 
  feature_select = "highest_weights"
)
glimpse(lime_explanation)
Observations: 20
Variables: 11
$ model_type       <chr> "regression", "regression", "regression", "regression", "regression", "regr…
$ case             <chr> "1315", "1315", "1315", "1315", "1315", "1315", "1315", "1315", "1315", "13…
$ model_r2         <dbl> 0.50020562, 0.50020562, 0.50020562, 0.50020562, 0.50020562, 0.50020562, 0.5…
$ model_intercept  <dbl> 142204.0, 142204.0, 142204.0, 142204.0, 142204.0, 142204.0, 142204.0, 14220…
$ model_prediction <dbl> 700416.0, 700416.0, 700416.0, 700416.0, 700416.0, 700416.0, 700416.0, 70041…
$ feature          <chr> "Pool_Area", "Gr_Liv_Area", "Total_Bsmt_SF", "Overall_Qual", "First_Flr_SF"…
$ feature_value    <int> 555, 4476, 2396, 8, 2411, 15623, 2065, 1, 1, 3, 0, 612, 1, 0, 1940, 0, 4, 6…
$ feature_weight   <dbl> 338848.604, 48179.163, 41363.918, 26280.478, 23465.130, 21227.388, 19015.07…
$ feature_desc     <chr> "516.6 < Pool_Area <= 590.4", "2145 < Gr_Liv_Area", "1603 < Total_Bsmt_SF",…
$ data             <list> [[Two_Story_1946_and_Newer, Residential_Low_Density, 160, 15623, Pave, No_…
$ prediction       <dbl> 700415.52, 700415.52, 700415.52, 700415.52, 700415.52, 700415.52, 700415.52…
plot_features(lime_explanation, ncol = 1)

Tuning

# Tune the LIME algorithm a bit
lime_explanation2 <- explain(
  x = rbind(high_ob, low_ob), 
  explainer = components_lime, 
  n_permutations = 5000,
  dist_fun = "euclidean",
  kernel_width = 0.75,
  n_features = 10, 
  feature_select = "lasso_path"
)

# Plot the results
plot_features(lime_explanation2, ncol = 1)

Shapley values

Concept

Figure 16.8:

knitr::include_graphics("images/approx-shapley-idea.png")

Implementation

# Compute (approximate) Shapley values
(shapley <- Shapley$new(components_iml, x.interest = high_ob, sample.size = 1000))
Interpretation method:  Shapley 
Predicted value: 700415.520000, Average prediction: 181296.077636 (diff = 519119.442364)

Analysed predictor: 
Prediction task: unknown 


Analysed data:
Sampling from data.frame with 2199 rows and 80 columns.

Head of results:
# Plot results
plot(shapley)

# Reuse existing object
shapley$explain(x.interest = low_ob)

# Plot results
shapley$results %>%
  top_n(25, wt = abs(phi)) %>%
  ggplot(aes(phi, reorder(feature.value, phi), color = phi > 0)) +
  geom_point(show.legend = FALSE)

XGBoost and built-in Shapley values

# Compute tree SHAP for a previously obtained XGBoost model
X <- readr::read_rds("data/xgb-features.rds")
xgb.fit.final <- readr::read_rds("data/xgb-fit-final.rds")
# Try to re-scale features (low to high)
feature_values <- X %>%
  as.data.frame() %>%
  mutate_all(scale) %>%
  gather(feature, feature_value) %>% 
  pull(feature_value)

# Compute SHAP values, wrangle a bit, compute SHAP-based importance, etc.
shap_df <- xgb.fit.final %>%
  predict(newdata = X, predcontrib = TRUE) %>%
  as.data.frame() %>%
  select(-BIAS) %>%
  gather(feature, shap_value) %>%
  mutate(feature_value = feature_values) %>%
  group_by(feature) %>%
  mutate(shap_importance = mean(abs(shap_value)))

# SHAP contribution plot
p1 <- ggplot(shap_df, aes(x = shap_value, y = reorder(feature, shap_importance))) +
  ggbeeswarm::geom_quasirandom(groupOnX = FALSE, varwidth = TRUE, size = 0.4, alpha = 0.25) +
  xlab("SHAP value") +
  ylab(NULL)

# SHAP importance plot
p2 <- shap_df %>% 
  select(feature, shap_importance) %>%
  filter(row_number() == 1) %>%
  ggplot(aes(x = reorder(feature, shap_importance), y = shap_importance)) +
    geom_col() +
    coord_flip() +
    xlab(NULL) +
    ylab("mean(|SHAP value|)")

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

shap_df %>% 
  filter(feature %in% c("Overall_Qual", "Gr_Liv_Area")) %>%
  ggplot(aes(x = feature_value, y = shap_value)) +
    geom_point(aes(color = shap_value)) +
    scale_colour_viridis_c(name = "Feature value\n(standardized)", option = "C") +
    facet_wrap(~ feature, scales = "free") +
    scale_y_continuous('Shapley value', labels = scales::comma) +
    xlab('Normalized feature value')

Localized step-wise procedure

Implementation

high_breakdown <- prediction_breakdown(components_dalex, observation = high_ob)

# class of prediction_breakdown output
class(high_breakdown)

# check out the top 10 influential variables for this observation
high_breakdown[1:10, 1:5]
h2o.shutdown(prompt = FALSE)
[1] TRUE
LS0tCnRpdGxlOiAiQ2hhcHRlciAxNjogSW50ZXJwcmV0YWJsZSBNYWNoaW5lIExlYXJuaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZiBzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZSBkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuIERvIHRvIG91dHB1dCBzaXplLCBtb3N0IG9mIHRoaXMKY2hhcHRlcidzIGNvZGUgY2h1bmtzIHNob3VsZCBub3QgYmUgcmFuIG9uIFJTdHVkaW8gQ2xvdWQuCgpIaWRkZW4gY2hhcHRlciByZXF1aXJlbWVudHMgdXNlZCBpbiB0aGUgYm9vayB0byBzZXQgdGhlIHBsb3R0aW5nIHRoZW1lIGFuZCBsb2FkIHBhY2thZ2VzIHVzZWQgaW4gaGlkZGVuIGNvZGUgY2h1bmtzOgoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBtZXNzYWdlID0gRkFMU0UsIAogIHdhcm5pbmcgPSBGQUxTRSwgCiAgY2FjaGUgPSBGQUxTRQopCgojIFNldCB0aGUgZ3JhcGhpY2FsIHRoZW1lCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9saWdodCgpKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKCmBgYHtyIGltbC1wa2ctcHJlcmVxc30KIyBIZWxwZXIgcGFja2FnZXMKbGlicmFyeShkcGx5cikgICAgICAjIGZvciBkYXRhIHdyYW5nbGluZwpsaWJyYXJ5KGdncGxvdDIpICAgICMgZm9yIGF3ZXNvbWUgZ3JhcGhpY3MKCiMgTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShoMm8pICAgICAgICMgZm9yIGludGVyZmFjaW5nIHdpdGggSDJPCmxpYnJhcnkocmVjaXBlcykgICAjIGZvciBNTCByZWNpcGVzCmxpYnJhcnkocnNhbXBsZSkgICAjIGZvciBkYXRhIHNwbGl0dGluZwpsaWJyYXJ5KHhnYm9vc3QpICAgIyBmb3IgZml0dGluZyBHQk1zCgojIE1vZGVsIGludGVycHJldGFiaWxpdHkgcGFja2FnZXMKbGlicmFyeShwZHApICAgICAgICMgZm9yIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cyAoYW5kIElDRSBjdXJ2ZXMpCmxpYnJhcnkodmlwKSAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzCmxpYnJhcnkoaW1sKSAgICAgICAjIGZvciBnZW5lcmFsIElNTC1yZWxhdGVkIGZ1bmN0aW9ucwpsaWJyYXJ5KERBTEVYKSAgICAgIyBmb3IgZ2VuZXJhbCBJTUwtcmVsYXRlZCBmdW5jdGlvbnMKbGlicmFyeShsaW1lKSAgICAgICMgZm9yIGxvY2FsIGludGVycHJldGFibGUgbW9kZWwtYWdub3N0aWMgZXhwbGFuYXRpb25zCmBgYAoKVG8gaWxsdXN0cmF0ZSB2YXJpb3VzIGNvbmNlcHRzIHdlJ2xsIGNvbnRpbnVlIHdvcmtpbmcgd2l0aCB0aGUgX19oMm9fXyB2ZXJzaW9uIG9mIHRoZSBBbWVzIGhvdXNpbmcgZGF0YS4gV2UnbGwgYWxzbyB1c2UgdGhlIHN0YWNrZWQgZW5zZW1ibGUgbW9kZWwgY3JlYXRlZCBbaGVyZV0oaHR0cHM6Ly9rb2FsYXZlcnNlLmdpdGh1Yi5pby9ob21sci9ub3RlYm9va3MvMTUtc3RhY2tpbmctbW9kZWxzLm5iLmh0bWwjc3RhY2tpbmctZXhpc3RpbmctbW9kZWxzKS4KCmBgYHtyIGltbC1kYXRhLXByZXJlcXN9CiMgQ29ubmVjdCB0byBIMk8KaDJvLm5vX3Byb2dyZXNzKCkKaDJvLmluaXQobWF4X21lbV9zaXplID0gIjVnIikKCiMgTG9hZCBhbmQgc3BsaXQgQW1lcyBob3VzaW5nIGRhdGEKYW1lcyA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnNwbGl0IDwtIGluaXRpYWxfc3BsaXQoYW1lcywgc3RyYXRhID0gIlNhbGVfUHJpY2UiKQphbWVzX3RyYWluIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgPC0gdGVzdGluZyhzcGxpdCkKCiMgTWFrZSBzdXJlIHdlIGhhdmUgY29uc2lzdGVudCBjYXRlZ29yaWNhbCBsZXZlbHMKYmx1ZXByaW50IDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfb3RoZXIoYWxsX25vbWluYWwoKSwgdGhyZXNob2xkID0gLjAwNSkKCiMgQ3JlYXRlIHRyYWluaW5nICYgdGVzdCBzZXRzCnRyYWluX2gybyA8LSBwcmVwKGJsdWVwcmludCwgdHJhaW5pbmcgPSBhbWVzX3RyYWluLCByZXRhaW4gPSBUUlVFKSAlPiUKICBqdWljZSgpICU+JQogIGFzLmgybygpCnRlc3RfaDJvIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pICU+JQogIGJha2UobmV3X2RhdGEgPSBhbWVzX3Rlc3QpICU+JQogIGFzLmgybygpCgojIEdldCBuYW1lcyBvZiByZXNwb25zZSBhbmQgZmVhdHVyZXMKWSA8LSAiU2FsZV9QcmljZSIKWCA8LSBzZXRkaWZmKG5hbWVzKGFtZXNfdHJhaW4pLCBZKQpgYGAKCmBgYHtyIHRyYWluLWgyby1tb2RlbHN9CiMgVHJhaW4gJiBjcm9zcy12YWxpZGF0ZSBhIEdMTSBtb2RlbApiZXN0X2dsbSA8LSBoMm8uZ2xtKAogIHggPSBYLCB5ID0gWSwgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIGFscGhhID0gMC4xLAogIHJlbW92ZV9jb2xsaW5lYXJfY29sdW1ucyA9IFRSVUUsIG5mb2xkcyA9IDEwLCBmb2xkX2Fzc2lnbm1lbnQgPSAiTW9kdWxvIiwKICBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLCBzZWVkID0gMTIzCikKCiMgVHJhaW4gJiBjcm9zcy12YWxpZGF0ZSBhIFJGIG1vZGVsCmJlc3RfcmYgPC0gaDJvLnJhbmRvbUZvcmVzdCgKICB4ID0gWCwgeSA9IFksIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5faDJvLCBudHJlZXMgPSAxMDAwLCBtdHJpZXMgPSAyMCwKICBtYXhfZGVwdGggPSAzMCwgbWluX3Jvd3MgPSAxLCBzYW1wbGVfcmF0ZSA9IDAuOCwgbmZvbGRzID0gMTAsCiAgZm9sZF9hc3NpZ25tZW50ID0gIk1vZHVsbyIsIGtlZXBfY3Jvc3NfdmFsaWRhdGlvbl9wcmVkaWN0aW9ucyA9IFRSVUUsCiAgc2VlZCA9IDEyMywgc3RvcHBpbmdfcm91bmRzID0gNTAsIHN0b3BwaW5nX21ldHJpYyA9ICJSTVNFIiwKICBzdG9wcGluZ190b2xlcmFuY2UgPSAwCikKCiMgVHJhaW4gJiBjcm9zcy12YWxpZGF0ZSBhIEdCTSBtb2RlbApiZXN0X2dibSA8LSBoMm8uZ2JtKAogIHggPSBYLCB5ID0gWSwgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbl9oMm8sIG50cmVlcyA9IDUwMDAsIGxlYXJuX3JhdGUgPSAwLjAxLAogIG1heF9kZXB0aCA9IDcsIG1pbl9yb3dzID0gNSwgc2FtcGxlX3JhdGUgPSAwLjgsIG5mb2xkcyA9IDEwLAogIGZvbGRfYXNzaWdubWVudCA9ICJNb2R1bG8iLCBrZWVwX2Nyb3NzX3ZhbGlkYXRpb25fcHJlZGljdGlvbnMgPSBUUlVFLAogIHNlZWQgPSAxMjMsIHN0b3BwaW5nX3JvdW5kcyA9IDUwLCBzdG9wcGluZ19tZXRyaWMgPSAiUk1TRSIsCiAgc3RvcHBpbmdfdG9sZXJhbmNlID0gMAopCgojIFRyYWluICYgY3Jvc3MtdmFsaWRhdGUgYW4gWEdCb29zdCBtb2RlbApiZXN0X3hnYiA8LSBoMm8ueGdib29zdCgKICB4ID0gWCwgeSA9IFksIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5faDJvLCBudHJlZXMgPSA1MDAwLCBsZWFybl9yYXRlID0gMC4wNSwKICBtYXhfZGVwdGggPSAzLCBtaW5fcm93cyA9IDMsIHNhbXBsZV9yYXRlID0gMC44LCBjYXRlZ29yaWNhbF9lbmNvZGluZyA9ICJFbnVtIiwKICBuZm9sZHMgPSAxMCwgZm9sZF9hc3NpZ25tZW50ID0gIk1vZHVsbyIsIAogIGtlZXBfY3Jvc3NfdmFsaWRhdGlvbl9wcmVkaWN0aW9ucyA9IFRSVUUsIHNlZWQgPSAxMjMsIHN0b3BwaW5nX3JvdW5kcyA9IDUwLAogIHN0b3BwaW5nX21ldHJpYyA9ICJSTVNFIiwgc3RvcHBpbmdfdG9sZXJhbmNlID0gMAopCgojIFRyYWluIGEgc3RhY2tlZCB0cmVlIGVuc2VtYmxlCmVuc2VtYmxlX3RyZWUgPC0gaDJvLnN0YWNrZWRFbnNlbWJsZSgKICB4ID0gWCwgeSA9IFksIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5faDJvLCBtb2RlbF9pZCA9ICJteV90cmVlX2Vuc2VtYmxlIiwKICBiYXNlX21vZGVscyA9IGxpc3QoYmVzdF9nbG0sIGJlc3RfcmYsIGJlc3RfZ2JtLCBiZXN0X3hnYiksCiAgbWV0YWxlYXJuZXJfYWxnb3JpdGhtID0gImRyZiIKKQpgYGAKCiMjIFRoZSBpZGVhCgojIyMgTG9jYWwgaW50ZXJwcmV0YXRpb24KCmBgYHtyIHByZWRpY3Rpb25zfQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGVuc2VtYmxlX3RyZWUsIHRyYWluX2gybykgJT4lIGFzLnZlY3RvcigpCmBgYAoKYGBge3J9CiMgQ29tcHV0ZSBwcmVkaWN0aW9ucwpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGVuc2VtYmxlX3RyZWUsIHRyYWluX2gybykgJT4lIGFzLnZlY3RvcigpCgojIFByaW50IHRoZSBoaWdoZXN0IGFuZCBsb3dlc3QgcHJlZGljdGVkIHNhbGVzIHByaWNlCnBhc3RlKCJPYnNlcnZhdGlvbiIsIHdoaWNoLm1heChwcmVkaWN0aW9ucyksIAogICAgICAiaGFzIGEgcHJlZGljdGVkIHNhbGUgcHJpY2Ugb2YiLCBzY2FsZXM6OmRvbGxhcihtYXgocHJlZGljdGlvbnMpKSkgCnBhc3RlKCJPYnNlcnZhdGlvbiIsIHdoaWNoLm1pbihwcmVkaWN0aW9ucyksIAogICAgICAiaGFzIGEgcHJlZGljdGVkIHNhbGUgcHJpY2Ugb2YiLCBzY2FsZXM6OmRvbGxhcihtaW4ocHJlZGljdGlvbnMpKSkgIAoKIyBHcmFiIGZlYXR1cmUgdmFsdWVzIGZvciBvYnNlcnZhdGlvbnMgd2l0aCBtaW4vbWF4IHByZWRpY3RlZCBzYWxlcyBwcmljZQpoaWdoX29iIDwtIGFzLmRhdGEuZnJhbWUodHJhaW5faDJvKVt3aGljaC5tYXgocHJlZGljdGlvbnMpLCBdICU+JSBzZWxlY3QoLVNhbGVfUHJpY2UpCmxvd19vYiAgPC0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pW3doaWNoLm1pbihwcmVkaWN0aW9ucyksIF0gJT4lIHNlbGVjdCgtU2FsZV9QcmljZSkKYGBgCgojIyMgTW9kZWwtc3BlY2lmaWMgdnMuIG1vZGVsLWFnbm9zdGljIAoKYGBge3IgY3JlYXRpbmctY29tcG9uZW50c30KIyAxKSBjcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGgganVzdCB0aGUgZmVhdHVyZXMKZmVhdHVyZXMgPC0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pICU+JSBzZWxlY3QoLVNhbGVfUHJpY2UpCgojIDIpIENyZWF0ZSBhIHZlY3RvciB3aXRoIHRoZSBhY3R1YWwgcmVzcG9uc2VzCnJlc3BvbnNlIDwtIGFzLmRhdGEuZnJhbWUodHJhaW5faDJvKSAlPiUgcHVsbChTYWxlX1ByaWNlKQoKIyAzKSBDcmVhdGUgY3VzdG9tIHByZWRpY3QgZnVuY3Rpb24gdGhhdCByZXR1cm5zIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGFzIGEgdmVjdG9yCnByZWQgPC0gZnVuY3Rpb24ob2JqZWN0LCBuZXdkYXRhKSAgewogIHJlc3VsdHMgPC0gYXMudmVjdG9yKGgyby5wcmVkaWN0KG9iamVjdCwgYXMuaDJvKG5ld2RhdGEpKSkKICByZXR1cm4ocmVzdWx0cykKfQoKIyBFeGFtcGxlIG9mIHByZWRpY3Rpb24gb3V0cHV0CnByZWQoZW5zZW1ibGVfdHJlZSwgZmVhdHVyZXMpICU+JSBoZWFkKCkKYGBgCgpgYGB7ciBtb2RlbC1hZ25vc3RpYy1vYmplY3RzfQojIGltbCBtb2RlbCBhZ25vc3RpYyBvYmplY3QKY29tcG9uZW50c19pbWwgPC0gUHJlZGljdG9yJG5ldygKICBtb2RlbCA9IGVuc2VtYmxlX3RyZWUsIAogIGRhdGEgPSBmZWF0dXJlcywgCiAgeSA9IHJlc3BvbnNlLCAKICBwcmVkaWN0LmZ1biA9IHByZWQKKQoKIyBEQUxFWCBtb2RlbCBhZ25vc3RpYyBvYmplY3QKY29tcG9uZW50c19kYWxleCA8LSBEQUxFWDo6ZXhwbGFpbigKICBtb2RlbCA9IGVuc2VtYmxlX3RyZWUsCiAgZGF0YSA9IGZlYXR1cmVzLAogIHkgPSByZXNwb25zZSwKICBwcmVkaWN0X2Z1bmN0aW9uID0gcHJlZAopCmBgYAoKCiMjIFBlcm11dGF0aW9uLWJhc2VkIGZlYXR1cmUgaW1wb3J0YW5jZQoKIyMjIEltcGxlbWVudGF0aW9uIAoKYGBge3IgdmlwLCBmaWcuY2FwPSJUb3AgMTAgbW9zdCBpbmZsdWVudGlhbCB2YXJpYWJsZXMgZm9yIHRoZSBzdGFja2VkIEgyTyBtb2RlbCB1c2luZyBwZXJtdXRhdGlvbi1iYXNlZCBmZWF0dXJlIGltcG9ydGFuY2UuIn0KdmlwKAogIGVuc2VtYmxlX3RyZWUsCiAgdHJhaW4gPSBhcy5kYXRhLmZyYW1lKHRyYWluX2gybyksCiAgbWV0aG9kID0gInBlcm11dGUiLAogIHRhcmdldCA9ICJTYWxlX1ByaWNlIiwKICBtZXRyaWMgPSAiUk1TRSIsCiAgbnNpbSA9IDUsCiAgc2FtcGxlX2ZyYWMgPSAwLjUsCiAgcHJlZF93cmFwcGVyID0gcHJlZAopCmBgYAoKCiMjIFBhcnRpYWwgZGVwZW5kZW5jZQoKIyMjIENvbmNlcHQKCkZpZ3VyZSAxNi4xOgoKYGBge3IgcGRwLWlsbHVzdHJhdGlvbiwgZmlnLmNhcD0iSWxsdXN0cmF0aW9uIG9mIHRoZSBwYXJ0aWFsIGRlcGVuZGVuY2UgcHJvY2Vzcy4ifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3BkcC1pbGx1c3RyYXRpb24ucG5nIikKYGBgCgojIyMgSW1wbGVtZW50YXRpb24gCgpgYGB7ciBwZHAsIGZpZy5jYXA9IlBhcnRpYWwgZGVwZW5kZW5jZSBwbG90IGZvciBgR3JfTGl2X0FyZWFgIGlsbHVzdHJhdGluZyB0aGUgYXZlcmFnZSBpbmNyZWFzZSBpbiBwcmVkaWN0ZWQgYFNhbGVfUHJpY2VgIGFzIGBHcl9MaXZfQXJlYWAgaW5jcmVhc2VzLiJ9CiMgQ3VzdG9tIHByZWRpY3Rpb24gZnVuY3Rpb24gd3JhcHBlcgpwZHBfcHJlZCA8LSBmdW5jdGlvbihvYmplY3QsIG5ld2RhdGEpICB7CiAgcmVzdWx0cyA8LSBtZWFuKGFzLnZlY3RvcihoMm8ucHJlZGljdChvYmplY3QsIGFzLmgybyhuZXdkYXRhKSkpKQogIHJldHVybihyZXN1bHRzKQp9CgojIENvbXB1dGUgcGFydGlhbCBkZXBlbmRlbmNlIHZhbHVlcwpwZF92YWx1ZXMgPC0gcGFydGlhbCgKICBlbnNlbWJsZV90cmVlLAogIHRyYWluID0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pLCAKICBwcmVkLnZhciA9ICJHcl9MaXZfQXJlYSIsCiAgcHJlZC5mdW4gPSBwZHBfcHJlZCwKICBncmlkLnJlc29sdXRpb24gPSAyMAopCmhlYWQocGRfdmFsdWVzKSAgIyB0YWtlIGEgcGVhawoKIyBQYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdAphdXRvcGxvdChwZF92YWx1ZXMsIHJ1ZyA9IFRSVUUsIHRyYWluID0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pKQpgYGAKCiMjIEluZGl2aWR1YWwgY29uZGl0aW9uYWwgZXhwZWN0YXRpb24KCiMjIyBDb25jZXB0CgpGaWd1cmUgMTYuMjoKCmBgYHtyIGljZS1pbGx1c3RyYXRpb24sIGZpZy5jYXA9Ik5vbi1jZW50ZXJlZCAoQSkgYW5kIGNlbnRlcmVkIChCKSBJQ0UgY3VydmVzIGZvciBgR3JfTGl2X0FyZWFgIGlsbHVzdHJhdGluZyB0aGUgb2JzZXJ2YXRpb24tbGV2ZWwgZWZmZWN0cyAoYmxhY2sgbGluZXMpIGluIHByZWRpY3RlZCBgU2FsZV9QcmljZWAgYXMgYEdyX0xpdl9BcmVhYCBpbmNyZWFzZXMuIFRoZSBwbG90IGFsc28gaWxsdXN0cmF0ZXMgdGhlIFBEUCBsaW5lIChyZWQpLCByZXByZXNlbnRpbmcgdGhlIGF2ZXJhZ2UgdmFsdWVzIGFjcm9zcyBhbGwgb2JzZXJ2YXRpb25zLiJ9CgojIENvbnN0cnVjdCBJQ0UgY3VydmVzCmljZV9ub25fY2VudGVyZWQgPC0gcGFydGlhbCgKICBlbnNlbWJsZV90cmVlLAogIHRyYWluID0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pLCAKICBwcmVkLnZhciA9ICJHcl9MaXZfQXJlYSIsCiAgcHJlZC5mdW4gPSBwcmVkLAogIGdyaWQucmVzb2x1dGlvbiA9IDIwCikgJT4lCiAgYXV0b3Bsb3QoYWxwaGEgPSAwLjA1LCBjZW50ZXIgPSBGQUxTRSkgKwogIGdndGl0bGUoIkEpIE5vbi1jZW50ZXJlZCBJQ0UgY3VydmVzIikKCiMgQ29uc3RydWN0IGMtSUNFIGN1cnZlcwppY2VfY2VudGVyZWQgPC0gcGFydGlhbCgKICBlbnNlbWJsZV90cmVlLAogIHRyYWluID0gYXMuZGF0YS5mcmFtZSh0cmFpbl9oMm8pLCAKICBwcmVkLnZhciA9ICJHcl9MaXZfQXJlYSIsCiAgcHJlZC5mdW4gPSBwcmVkLAogIGdyaWQucmVzb2x1dGlvbiA9IDIwCikgJT4lCiAgYXV0b3Bsb3QoYWxwaGEgPSAwLjA1LCBjZW50ZXIgPSBUUlVFKSArCiAgZ2d0aXRsZSgiQikgQ2VudGVyZWQgSUNFIGN1cnZlcyIpCgojIERpc3BsYXkgcGxvdHMgc2lkZSBieSBzaWRlCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGljZV9ub25fY2VudGVyZWQsIGljZV9jZW50ZXJlZCwgbmNvbCA9IDIpCmBgYAoKIyMjIEltcGxlbWVudGF0aW9uIAoKYGBge3IgaWNlLXBkcCwgZmlnLmNhcD0iQ2VudGVyZWQgSUNFIGN1cnZlIGZvciBgR3JfTGl2X0FyZWFgIGlsbHVzdHJhdGluZyB0aGUgb2JzZXJ2YXRpb24tbGV2ZWwgZWZmZWN0cyBpbiBwcmVkaWN0ZWQgYFNhbGVfUHJpY2VgIGFzIGBHcl9MaXZfQXJlYWAgaW5jcmVhc2VzLiJ9CiMgQ29uc3RydWN0IGMtSUNFIGN1cnZlcwpwYXJ0aWFsKAogIGVuc2VtYmxlX3RyZWUsCiAgdHJhaW4gPSBhcy5kYXRhLmZyYW1lKHRyYWluX2gybyksIAogIHByZWQudmFyID0gIkdyX0xpdl9BcmVhIiwKICBwcmVkLmZ1biA9IHByZWQsCiAgZ3JpZC5yZXNvbHV0aW9uID0gMjAsCiAgcGxvdCA9IFRSVUUsCiAgY2VudGVyID0gVFJVRSwKICBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIgopCmBgYAoKCiMjIEZlYXR1cmUgaW50ZXJhY3Rpb25zCgojIyMgSW1wbGVtZW50YXRpb24KCmBgYHtyIGgtc3RhdCwgZmlnLmhlaWdodD0xMH0KaW50ZXJhY3QgPC0gSW50ZXJhY3Rpb24kbmV3KGNvbXBvbmVudHNfaW1sKQoKaW50ZXJhY3QkcmVzdWx0cyAlPiUgCiAgYXJyYW5nZShkZXNjKC5pbnRlcmFjdGlvbikpICU+JSAKICBoZWFkKCkKCnBsb3QoaW50ZXJhY3QpCmBgYAoKYGBge3IgaC1zdGF0LTJ3YXksIGZpZy5oZWlnaHQ9MTB9CmludGVyYWN0XzJ3YXkgPC0gSW50ZXJhY3Rpb24kbmV3KGNvbXBvbmVudHNfaW1sLCBmZWF0dXJlID0gIkZpcnN0X0Zscl9TRiIpCmludGVyYWN0XzJ3YXkkcmVzdWx0cyAlPiUgCiAgYXJyYW5nZShkZXNjKC5pbnRlcmFjdGlvbikpICU+JSAKICB0b3BfbigxMCkKYGBgCgpgYGB7ciBpbnRlcmFjdGlvbi1wZHB9CiMgVHdvLXdheSBQRFAgdXNpbmcgaW1sCmludGVyYWN0aW9uX3BkcCA8LSBQYXJ0aWFsJG5ldygKICBjb21wb25lbnRzX2ltbCwgCiAgYygiRmlyc3RfRmxyX1NGIiwgIk92ZXJhbGxfUXVhbCIpLCAKICBpY2UgPSBGQUxTRSwgCiAgZ3JpZC5zaXplID0gMjAKKQpgYGAKCmBgYHtyIGludGVyYWN0aW9uLXBkcDIsIGZpZy5jYXA9IkludGVyYWN0aW9uIFBEUCBpbGx1c3RyYXRpbmcgdGhlIGpvaW50IGVmZmVjdCBvZiBgRmlyc3RfRmxyX1NGYCBhbmQgYE92ZXJhbGxfUXVhbGAgb24gYFNhbGVfUHJpY2VgLiJ9CmxhYmVscyA8LSBpbnRlcmFjdGlvbl9wZHAkcmVzdWx0cyAlPiUgZmlsdGVyKEZpcnN0X0Zscl9TRiA9PSBtYXgoRmlyc3RfRmxyX1NGKSkKcGxvdChpbnRlcmFjdGlvbl9wZHApICsgCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbCgKICAgIGRhdGEgPSBsYWJlbHMsIAogICAgYWVzKGxhYmVsID0gT3ZlcmFsbF9RdWFsKSwKICAgIGxhYmVsLnNpemUgPSAuMDUsIAogICAgbGFiZWwucGFkZGluZyA9IC4xNQogICkKYGBgCgojIyBMb2NhbCBpbnRlcnByZXRhYmxlIG1vZGVsLWFnbm9zdGljIGV4cGxhbmF0aW9ucwoKIyMjIEltcGxlbWVudGF0aW9uIAoKYGBge3IgbGltZV9leHBsYWluZXJ9CiMgQ3JlYXRlIGV4cGxhaW5lciBvYmplY3QKY29tcG9uZW50c19saW1lIDwtIGxpbWUoCiAgeCA9IGZlYXR1cmVzLAogIG1vZGVsID0gZW5zZW1ibGVfdHJlZSwgCiAgbl9iaW5zID0gMTAKKQoKY2xhc3MoY29tcG9uZW50c19saW1lKQpzdW1tYXJ5KGNvbXBvbmVudHNfbGltZSkKYGBgCgpgYGB7ciBsaW1lLWV4cGxhaW4xfQojIFVzZSBMSU1FIHRvIGV4cGxhaW4gcHJldmlvdXNseSBkZWZpbmVkIGluc3RhbmNlczogaGlnaF9vYiBhbmQgbG93X29iCmxpbWVfZXhwbGFuYXRpb24gPC0gbGltZTo6ZXhwbGFpbigKICB4ID0gcmJpbmQoaGlnaF9vYiwgbG93X29iKSwgCiAgZXhwbGFpbmVyID0gY29tcG9uZW50c19saW1lLCAKICBuX3Blcm11dGF0aW9ucyA9IDUwMDAsCiAgZGlzdF9mdW4gPSAiZ293ZXIiLAogIGtlcm5lbF93aWR0aCA9IDAuMjUsCiAgbl9mZWF0dXJlcyA9IDEwLCAKICBmZWF0dXJlX3NlbGVjdCA9ICJoaWdoZXN0X3dlaWdodHMiCikKYGBgCgpgYGB7cn0KZ2xpbXBzZShsaW1lX2V4cGxhbmF0aW9uKQpgYGAKCmBgYHtyIGZpcnN0LWxpbWUtZml0LCBmaWcuY2FwPSJMb2NhbCBleHBsYW5hdGlvbiBmb3Igb2JzZXJ2YXRpb25zIDE4MjUgKGBoaWdoX29iYCkgYW5kIDEzOSAoYGxvd19vYmApIHVzaW5nIExJTUUuIn0KcGxvdF9mZWF0dXJlcyhsaW1lX2V4cGxhbmF0aW9uLCBuY29sID0gMSkKYGBgCgojIyMgVHVuaW5nCgpgYGB7ciBsaW1lLWV4cGxhaW4yLCBmaWcuY2FwPSJMb2NhbCBleHBsYW5hdGlvbiBmb3Igb2JzZXJ2YXRpb25zIDE4MjUgKGNhc2UgMSkgYW5kIDEzOSAoY2FzZSAyKSBhZnRlciB0dW5pbmcgdGhlIExJTUUgYWxnb3JpdGhtLiJ9CiMgVHVuZSB0aGUgTElNRSBhbGdvcml0aG0gYSBiaXQKbGltZV9leHBsYW5hdGlvbjIgPC0gZXhwbGFpbigKICB4ID0gcmJpbmQoaGlnaF9vYiwgbG93X29iKSwgCiAgZXhwbGFpbmVyID0gY29tcG9uZW50c19saW1lLCAKICBuX3Blcm11dGF0aW9ucyA9IDUwMDAsCiAgZGlzdF9mdW4gPSAiZXVjbGlkZWFuIiwKICBrZXJuZWxfd2lkdGggPSAwLjc1LAogIG5fZmVhdHVyZXMgPSAxMCwgCiAgZmVhdHVyZV9zZWxlY3QgPSAibGFzc29fcGF0aCIKKQoKIyBQbG90IHRoZSByZXN1bHRzCnBsb3RfZmVhdHVyZXMobGltZV9leHBsYW5hdGlvbjIsIG5jb2wgPSAxKQpgYGAKCiMjIFNoYXBsZXkgdmFsdWVzCgojIyMgQ29uY2VwdAoKRmlndXJlIDE2Ljg6CgpgYGB7ciBzaGFwbGV5LWlkZWEsIGZpZy5jYXA9IkdlbmVyYWxpemVkIGNvbmNlcHQgYmVoaW5kIGFwcHJveGltYXRlIFNoYXBsZXkgdmFsdWUgY29tcHV0YXRpb24uIiwgb3V0LmhlaWdodD0nOTUlJywgb3V0LndpZHRoPSc5NSUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2FwcHJveC1zaGFwbGV5LWlkZWEucG5nIikKYGBgCgojIyMgSW1wbGVtZW50YXRpb24gCgpgYGB7ciBzaGFwbGV5LCBmaWcuY2FwPSJMb2NhbCBleHBsYW5hdGlvbiBmb3Igb2JzZXJ2YXRpb24gMTgyNSB1c2luZyB0aGUgU2hhcGxleSB2YWx1ZSBhbGdvcml0aG0uIiwgZmlnLmhlaWdodD0xMH0KIyBDb21wdXRlIChhcHByb3hpbWF0ZSkgU2hhcGxleSB2YWx1ZXMKKHNoYXBsZXkgPC0gU2hhcGxleSRuZXcoY29tcG9uZW50c19pbWwsIHguaW50ZXJlc3QgPSBoaWdoX29iLCBzYW1wbGUuc2l6ZSA9IDEwMDApKQoKIyBQbG90IHJlc3VsdHMKcGxvdChzaGFwbGV5KQpgYGAKCgpgYGB7ciBzaGFwbGV5MiwgZmlnLmNhcD0iTG9jYWwgZXhwbGFuYXRpb24gZm9yIG9ic2VydmF0aW9uIDEzOSB1c2luZyB0aGUgU2hhcGxleSB2YWx1ZSBhbGdvcml0aG0uIn0KIyBSZXVzZSBleGlzdGluZyBvYmplY3QKc2hhcGxleSRleHBsYWluKHguaW50ZXJlc3QgPSBsb3dfb2IpCgojIFBsb3QgcmVzdWx0cwpzaGFwbGV5JHJlc3VsdHMgJT4lCiAgdG9wX24oMjUsIHd0ID0gYWJzKHBoaSkpICU+JQogIGdncGxvdChhZXMocGhpLCByZW9yZGVyKGZlYXR1cmUudmFsdWUsIHBoaSksIGNvbG9yID0gcGhpID4gMCkpICsKICBnZW9tX3BvaW50KHNob3cubGVnZW5kID0gRkFMU0UpCmBgYAoKIyMjIFhHQm9vc3QgYW5kIGJ1aWx0LWluIFNoYXBsZXkgdmFsdWVzCgpgYGB7ciBpbXBvcnQteGdib29zdH0KIyBDb21wdXRlIHRyZWUgU0hBUCBmb3IgYSBwcmV2aW91c2x5IG9idGFpbmVkIFhHQm9vc3QgbW9kZWwKWCA8LSByZWFkcjo6cmVhZF9yZHMoImRhdGEveGdiLWZlYXR1cmVzLnJkcyIpCnhnYi5maXQuZmluYWwgPC0gcmVhZHI6OnJlYWRfcmRzKCJkYXRhL3hnYi1maXQtZmluYWwucmRzIikKYGBgCgpgYGB7ciBzaGFwLXZpcCwgZmlnLmNhcD0iU2hhcGxleSBjb250cmlidXRpb24gKGxlZnQpIGFuZCBnbG9iYWwgaW1wb3J0YW5jZSAocmlnaHQpIHBsb3RzLiIsIGZpZy5oZWlnaHQ9MTB9CiMgVHJ5IHRvIHJlLXNjYWxlIGZlYXR1cmVzIChsb3cgdG8gaGlnaCkKZmVhdHVyZV92YWx1ZXMgPC0gWCAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlX2FsbChzY2FsZSkgJT4lCiAgZ2F0aGVyKGZlYXR1cmUsIGZlYXR1cmVfdmFsdWUpICU+JSAKICBwdWxsKGZlYXR1cmVfdmFsdWUpCgojIENvbXB1dGUgU0hBUCB2YWx1ZXMsIHdyYW5nbGUgYSBiaXQsIGNvbXB1dGUgU0hBUC1iYXNlZCBpbXBvcnRhbmNlLCBldGMuCnNoYXBfZGYgPC0geGdiLmZpdC5maW5hbCAlPiUKICBwcmVkaWN0KG5ld2RhdGEgPSBYLCBwcmVkY29udHJpYiA9IFRSVUUpICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBzZWxlY3QoLUJJQVMpICU+JQogIGdhdGhlcihmZWF0dXJlLCBzaGFwX3ZhbHVlKSAlPiUKICBtdXRhdGUoZmVhdHVyZV92YWx1ZSA9IGZlYXR1cmVfdmFsdWVzKSAlPiUKICBncm91cF9ieShmZWF0dXJlKSAlPiUKICBtdXRhdGUoc2hhcF9pbXBvcnRhbmNlID0gbWVhbihhYnMoc2hhcF92YWx1ZSkpKQoKIyBTSEFQIGNvbnRyaWJ1dGlvbiBwbG90CnAxIDwtIGdncGxvdChzaGFwX2RmLCBhZXMoeCA9IHNoYXBfdmFsdWUsIHkgPSByZW9yZGVyKGZlYXR1cmUsIHNoYXBfaW1wb3J0YW5jZSkpKSArCiAgZ2diZWVzd2FybTo6Z2VvbV9xdWFzaXJhbmRvbShncm91cE9uWCA9IEZBTFNFLCB2YXJ3aWR0aCA9IFRSVUUsIHNpemUgPSAwLjQsIGFscGhhID0gMC4yNSkgKwogIHhsYWIoIlNIQVAgdmFsdWUiKSArCiAgeWxhYihOVUxMKQoKIyBTSEFQIGltcG9ydGFuY2UgcGxvdApwMiA8LSBzaGFwX2RmICU+JSAKICBzZWxlY3QoZmVhdHVyZSwgc2hhcF9pbXBvcnRhbmNlKSAlPiUKICBmaWx0ZXIocm93X251bWJlcigpID09IDEpICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoZmVhdHVyZSwgc2hhcF9pbXBvcnRhbmNlKSwgeSA9IHNoYXBfaW1wb3J0YW5jZSkpICsKICAgIGdlb21fY29sKCkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHhsYWIoTlVMTCkgKwogICAgeWxhYigibWVhbih8U0hBUCB2YWx1ZXwpIikKCiMgQ29tYmluZSBwbG90cwpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCmBgYHtyIHNoYXAtcGRwLCBmaWcuY2FwPSJTaGFwbGV5LWJhc2VkIGRlcGVuZGVuY2UgcGxvdCBpbGx1c3RyYXRpbmcgdGhlIHZhcmlhYmlsaXR5IGluIGNvbnRyaWJ1dGlvbiBhY3Jvc3MgdGhlIHJhbmdlIG9mIGBHcl9MaXZfQXJlYWAgYW5kIGBPdmVyYWxsX1F1YWxgIHZhbHVlcy4ifQpzaGFwX2RmICU+JSAKICBmaWx0ZXIoZmVhdHVyZSAlaW4lIGMoIk92ZXJhbGxfUXVhbCIsICJHcl9MaXZfQXJlYSIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBmZWF0dXJlX3ZhbHVlLCB5ID0gc2hhcF92YWx1ZSkpICsKICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gc2hhcF92YWx1ZSkpICsKICAgIHNjYWxlX2NvbG91cl92aXJpZGlzX2MobmFtZSA9ICJGZWF0dXJlIHZhbHVlXG4oc3RhbmRhcmRpemVkKSIsIG9wdGlvbiA9ICJDIikgKwogICAgZmFjZXRfd3JhcCh+IGZlYXR1cmUsIHNjYWxlcyA9ICJmcmVlIikgKwogICAgc2NhbGVfeV9jb250aW51b3VzKCdTaGFwbGV5IHZhbHVlJywgbGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogICAgeGxhYignTm9ybWFsaXplZCBmZWF0dXJlIHZhbHVlJykKYGBgCgojIyBMb2NhbGl6ZWQgc3RlcC13aXNlIHByb2NlZHVyZQoKIyMjIEltcGxlbWVudGF0aW9uIAoKYGBge3IgYnJlYWtkb3dufQpoaWdoX2JyZWFrZG93biA8LSBwcmVkaWN0aW9uX2JyZWFrZG93bihjb21wb25lbnRzX2RhbGV4LCBvYnNlcnZhdGlvbiA9IGhpZ2hfb2IpCgojIGNsYXNzIG9mIHByZWRpY3Rpb25fYnJlYWtkb3duIG91dHB1dApjbGFzcyhoaWdoX2JyZWFrZG93bikKCiMgY2hlY2sgb3V0IHRoZSB0b3AgMTAgaW5mbHVlbnRpYWwgdmFyaWFibGVzIGZvciB0aGlzIG9ic2VydmF0aW9uCmhpZ2hfYnJlYWtkb3duWzE6MTAsIDE6NV0KYGBgCgpgYGB7cn0KaDJvLnNodXRkb3duKHByb21wdCA9IEZBTFNFKQpgYGAKCg==