Homework 5
For this assignment, we will be working with the file
"pokemon.csv", found in /data. The file is
from Kaggle: https://www.kaggle.com/abcsds/pokemon.
The Pokémon franchise
encompasses video games, TV shows, movies, books, and a card game. This
data set was drawn from the video game series and contains statistics
about 721 Pokémon, or “pocket monsters.” In Pokémon games, the user
plays as a trainer who collects, trades, and battles Pokémon to (a)
collect all the Pokémon and (b) become the champion Pokémon trainer.
Each Pokémon has a primary type
(some even have secondary types). Based on their type, a Pokémon is
strong against some types, and vulnerable to others. (Think rock, paper,
scissors.) A Fire-type Pokémon, for example, is vulnerable to Water-type
Pokémon, but strong against Grass-type.
Fig 1. Vulpix, a Fire-type fox Pokémon from
Generation 1 (also my favorite Pokémon!)
The goal of this assignment is to build a statistical learning model
that can predict the primary type of a Pokémon based on
its generation, legendary status, and six battle statistics. This is
an example of a classification problem, but these
models can also be used for regression
problems.
Read in the file and familiarize yourself with the variables using
pokemon_codebook.txt.
Exercise 1
Install and load the janitor package. Use its
clean_names() function on the Pokémon data, and save the
results to work with for the rest of the assignment. What happened to
the data? Why do you think clean_names() is useful?
pokemon <- read.csv("/Users/zhaolei/Downloads/homework-5/data/Pokemon.csv")
library(vip)
library(janitor)
library(forcats)
library(tidyverse)
library(tidymodels)
library(ISLR)
library(ISLR2)
library(glmnet)
library(modeldata)
library(ggthemes)
library(naniar) # to assess missing data patterns
library(corrplot) # for a correlation plot
library(patchwork) # for putting plots together
library(rpart.plot)
library(verification)
library(verification)
tidymodels_prefer()
library(ggplot2)
pokemon <-clean_names(pokemon)
head(pokemon)
## x name type_1 type_2 total hp attack defense sp_atk sp_def
## 1 1 Bulbasaur Grass Poison 318 45 49 49 65 65
## 2 2 Ivysaur Grass Poison 405 60 62 63 80 80
## 3 3 Venusaur Grass Poison 525 80 82 83 100 100
## 4 3 VenusaurMega Venusaur Grass Poison 625 80 100 123 122 120
## 5 4 Charmander Fire 309 39 52 43 60 50
## 6 5 Charmeleon Fire 405 58 64 58 80 65
## speed generation legendary
## 1 45 1 False
## 2 60 1 False
## 3 80 1 False
## 4 80 1 False
## 5 65 1 False
## 6 80 1 False
The clean_names function standardizes the dataset’s column names by
removing leading and trailing spaces and replacing special characters
with underscores. This ensures consistency among variable names, making
them easier to access and work with.
Exercise 2
Using the entire data set, create a bar chart of the outcome
variable, type_1.
How many classes of the outcome are there? Are there any Pokémon
types with very few Pokémon? If so, which ones?
For this assignment, we’ll handle the rarer classes by grouping them,
or “lumping them,” together into an ‘other’ category. Using the forcats
package, determine how to do this, and lump all the other
levels together except for the top 6 most frequent (which are
Bug, Fire, Grass, Normal, Water, and Psychic).
Convert type_1 and legendary to
factors.
pokemon%>%
ggplot(aes(x=type_1))+
geom_bar()
There are 18 outcomes here, and classes such as Flying, fairy, Ice do
include very small number of observations, so we have to lumping
them.
pokemon$type_1 <- pokemon %>%
mutate(type_1 = fct_lump_n(type_1, n = 6, w = NULL, other_level = "Other"))%>%
pull(type_1)
pokemon %>%
ggplot(aes(x = type_1)) +
geom_bar()

# factorization
pokemon$type_1 <- as.factor(pokemon$type_1)
pokemon$legendary <- as.factor(pokemon $legendary)
Exercise 3
Perform an initial split of the data. Stratify by the outcome
variable. You can choose a proportion to use. Verify that your training
and test sets have the desired number of observations.
Next, use v-fold cross-validation on the training set. Use 5
folds. Stratify the folds by type_1 as well. Hint: Look
for a strata argument.
Why do you think doing stratified sampling for cross-validation is
useful?
set.seed(0926)
pokemon_split <- initial_split(pokemon, prop = 0.75,strata = "type_1",)
pokemon_train <- training(pokemon_split)
pokemon_test <- testing(pokemon_split)
pokemon_folds <-vfold_cv(pokemon_train, v = 5, strata = "type_1")
Stratified sampling for cross-validation ensures that the training
and testing sets maintain the same proportion of features as the
original data, allowing the results to better represent the entire
dataset.
Exercise 4
Create a correlation matrix of the training set, using the
corrplot package. Note: You can choose how to handle
the categorical variables for this plot; justify your
decision(s).
What relationships, if any, do you notice?
pokemon_train%>%select(is.numeric)%>%cor()%>%corrplot()
the variable total is strongly and positively related with hp, attack,
defense, sp_atk, sp_def, and speed.
Exercise 5
Set up a recipe to predict type_1 with
legendary, generation, sp_atk,
attack, speed, defense,
hp, and sp_def.
pokemon_recipe<-recipe(type_1~legendary + generation + sp_atk + attack + speed
+defense + hp + sp_def,data=pokemon_train)%>%
step_dummy(all_nominal(), -all_outcomes())%>%
step_scale(all_predictors())%>%
step_center(all_predictors())
Exercise 6
We’ll be fitting and tuning an elastic net, tuning
penalty and mixture (use
multinom_reg() with the glmnet engine).
Set up this model and workflow. Create a regular grid for
penalty and mixture with 10 levels each;
mixture should range from 0 to 1. For this assignment, let
penalty range from 0.01 to 3 (this is on the
identity_trans() scale; note that you’ll need to specify
these values in base 10 otherwise).
nom_model <- multinom_reg(penalty = tune(),
mixture = tune()) %>%
set_engine("glmnet") %>%
set_mode("classification")
# set up the workflow
nom_wkflow <-workflow() %>%
add_model(nom_model) %>%
add_recipe(pokemon_recipe)
# create grid
nom_grid <- grid_regular(penalty(range =c(0.01, 3), trans = identity_trans()),
mixture(range = c(0,1)),
levels = 10)
Exercise 7
Now set up a random forest model and workflow. Use the
ranger engine and set importance = "impurity";
we’ll be tuning mtry, trees, and
min_n. Using the documentation for
rand_forest(), explain in your own words what each of these
hyperparameters represent.
Create a regular grid with 8 levels each. You can choose plausible
ranges for each hyperparameter. Note that mtry should not
be smaller than 1 or larger than 8. Explain why neither of those
values would make sense.
What type of model does mtry = 8 represent?
# set up model
randomf_model <- rand_forest(mtry = tune(), trees = tune(), min_n = tune()) %>%
set_engine("ranger", importance = "impurity") %>%
set_mode("classification")
# set up workflow
randomf_wkflow <- workflow() %>%
add_model(randomf_model) %>%
add_recipe(pokemon_recipe)
randomf_grid <- grid_regular(mtry(range = c(2,7)),
trees(range = c(100,1000)),
min_n(range = c(2,10)),
levels = 8)
mtry represents the number of variables randomly sampled as
candidates at each split when building a tree. We need at least one
variable, so mtry can’t be less than 1. Since we have at most 8
predictors in the model, mtry can’t be larger than 8. When mtry is set
to 8, the model becomes a decision tree that uses all features.
Exercise 8
Fit all models to your folded data using
tune_grid().
Note: Tuning your random forest model will take a few minutes
to run, anywhere from 5 minutes to 15 minutes and up. Consider running
your models outside of the .Rmd, storing the results, and loading them
in your .Rmd to minimize time to knit. We’ll go over how to do this in
lecture.
Use autoplot() on the results. What do you notice? Do
larger or smaller values of penalty and
mixture produce better ROC AUC? What about values of
min_n, trees, and mtry?
What elastic net model and what random forest model perform the best
on your folded data? (What specific values of the hyperparameters
resulted in the optimal ROC AUC?)
nom_res <- tune_grid(
nom_wkflow,
resamples = pokemon_folds,
grid = nom_grid,
control = control_grid(save_pred = TRUE)
)
load("/Users/zhaolei/Downloads/homework-5/tune_random_forest.rda")
autoplot(nom_res)
For this particular dataset and elastic net model, using smaller values
for both the penalty and mixture parameters results in better model
performance, as indicated by higher ROC AUC values. This suggests that
the model benefits from less regularization and a balance that favors
Ridge regularization.
collect_metrics(nom_res) %>%
filter(.metric == "roc_auc") %>%
select(penalty, mixture, mean, std_err)
## # A tibble: 100 × 4
## penalty mixture mean std_err
## <dbl> <dbl> <dbl> <dbl>
## 1 0.01 0 0.682 0.0167
## 2 0.342 0 0.638 0.0157
## 3 0.674 0 0.628 0.0159
## 4 1.01 0 0.623 0.0160
## 5 1.34 0 0.620 0.0158
## 6 1.67 0 0.617 0.0164
## 7 2.00 0 0.615 0.0161
## 8 2.34 0 0.613 0.0161
## 9 2.67 0 0.611 0.0162
## 10 3 0 0.611 0.0162
## # ℹ 90 more rows
select_best(nom_res)
## # A tibble: 1 × 3
## penalty mixture .config
## <dbl> <dbl> <chr>
## 1 0.01 0.667 Preprocessor1_Model061
The best roc_auc value is when penalty = 0.01 and mixture is about
0.556
autoplot(rf_res)

select_best(rf_res)
## # A tibble: 1 × 4
## mtry trees min_n .config
## <int> <int> <int> <chr>
## 1 3 742 6 Preprocessor1_Model224
e random forest model, although the results varies a lot, the highest
roc_auc value is when selecting 3 predictors , node size64, and 742
trees. ### Exercise 9
Select your optimal random forest modelin
terms of roc_auc. Then fit that model to your training set
and evaluate its performance on the testing set.
Using the training set:
Using the testing set:
Create plots of the different ROC curves, one per level of the
outcome variable;
Make a heat map of the confusion matrix.
final_wkflow <- finalize_workflow(randomf_wkflow,
select_best(rf_res))
pokemon_fit <- fit(final_wkflow, pokemon_train) # fit the training set
vip(pokemon_fit, importance = "impurity")

predict(pokemon_fit, new_data=pokemon_test, type ="prob")
## # A tibble: 201 × 7
## .pred_Bug .pred_Fire .pred_Grass .pred_Normal .pred_Psychic .pred_Water
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.00620 0.516 0.0553 0.0294 0.0208 0.126
## 2 0.0739 0.262 0.0544 0.0337 0.0729 0.158
## 3 0.0201 0.0509 0.350 0.0233 0.0196 0.291
## 4 0.0134 0.147 0.0510 0.0988 0.0616 0.109
## 5 0.0271 0.00460 0.0211 0.471 0.00106 0.0710
## 6 0.0668 0.0148 0.173 0.114 0.1 0.284
## 7 0.124 0.0710 0.0832 0.0812 0.0580 0.354
## 8 0.0144 0.0652 0.267 0.0870 0.0302 0.229
## 9 0.110 0.131 0.188 0.191 0.0207 0.102
## 10 0.0494 0.00966 0.0192 0.201 0.0412 0.388
## # ℹ 191 more rows
## # ℹ 1 more variable: .pred_Other <dbl>
augment(pokemon_fit, new_data = pokemon_test) %>%
roc_curve(type_1, .pred_Bug,.pred_Fire, .pred_Grass, .pred_Normal, .pred_Psychic, .pred_Water, .pred_Other) %>%
autoplot()

augment(pokemon_fit, new_data= pokemon_test) %>%
conf_mat(truth = type_1, estimate = .pred_class) %>%
autoplot(type = "heatmap")
The model is best at predicting Other type, this might because after the
lumping, the Other type becomes most prevalent in the dataset.
Accuracy: 46.27%
Indicates that the model correctly predicted the Pokémon type for 46.27%
of the instances. Sensitivity (Recall): 25.15%
Suggests the model struggles to identify true positives, often missing
the correct Pokémon types. Specificity: 88.07%
Shows the model is good at avoiding false positives, correctly
identifying instances that do not belong to each type.
Exercise 10
How did your best random forest model do on the testing set?
Which Pokemon types is the model best at predicting, and which is it
worst at? (Do you have any ideas why this might be?)
multi_metric <- metric_set(accuracy, sensitivity, specificity)
augment(pokemon_fit, new_data = pokemon_test) %>%
multi_metric(truth = type_1, estimate = .pred_class)
## # A tibble: 3 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy multiclass 0.478
## 2 sensitivity macro 0.269
## 3 specificity macro 0.884
LS0tCnRpdGxlOiAiSG9tZXdvcmsgNSIKYXV0aG9yOiAiTGVpIFpoYW8iCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSkKYGBgCgojIyBIb21ld29yayA1CgpGb3IgdGhpcyBhc3NpZ25tZW50LCB3ZSB3aWxsIGJlIHdvcmtpbmcgd2l0aCB0aGUgZmlsZSBgInBva2Vtb24uY3N2ImAsIGZvdW5kIGluIGAvZGF0YWAuIFRoZSBmaWxlIGlzIGZyb20gS2FnZ2xlOiA8aHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9hYmNzZHMvcG9rZW1vbj4uCgpUaGUgW1Bva8OpbW9uXShodHRwczovL3d3dy5wb2tlbW9uLmNvbS91cy8pIGZyYW5jaGlzZSBlbmNvbXBhc3NlcyB2aWRlbyBnYW1lcywgVFYgc2hvd3MsIG1vdmllcywgYm9va3MsIGFuZCBhIGNhcmQgZ2FtZS4gVGhpcyBkYXRhIHNldCB3YXMgZHJhd24gZnJvbSB0aGUgdmlkZW8gZ2FtZSBzZXJpZXMgYW5kIGNvbnRhaW5zIHN0YXRpc3RpY3MgYWJvdXQgNzIxIFBva8OpbW9uLCBvciAicG9ja2V0IG1vbnN0ZXJzLiIgSW4gUG9rw6ltb24gZ2FtZXMsIHRoZSB1c2VyIHBsYXlzIGFzIGEgdHJhaW5lciB3aG8gY29sbGVjdHMsIHRyYWRlcywgYW5kIGJhdHRsZXMgUG9rw6ltb24gdG8gKGEpIGNvbGxlY3QgYWxsIHRoZSBQb2vDqW1vbiBhbmQgKGIpIGJlY29tZSB0aGUgY2hhbXBpb24gUG9rw6ltb24gdHJhaW5lci4KCkVhY2ggUG9rw6ltb24gaGFzIGEgW3ByaW1hcnkgdHlwZV0oaHR0cHM6Ly9idWxiYXBlZGlhLmJ1bGJhZ2FyZGVuLm5ldC93aWtpL1R5cGUpIChzb21lIGV2ZW4gaGF2ZSBzZWNvbmRhcnkgdHlwZXMpLiBCYXNlZCBvbiB0aGVpciB0eXBlLCBhIFBva8OpbW9uIGlzIHN0cm9uZyBhZ2FpbnN0IHNvbWUgdHlwZXMsIGFuZCB2dWxuZXJhYmxlIHRvIG90aGVycy4gKFRoaW5rIHJvY2ssIHBhcGVyLCBzY2lzc29ycy4pIEEgRmlyZS10eXBlIFBva8OpbW9uLCBmb3IgZXhhbXBsZSwgaXMgdnVsbmVyYWJsZSB0byBXYXRlci10eXBlIFBva8OpbW9uLCBidXQgc3Ryb25nIGFnYWluc3QgR3Jhc3MtdHlwZS4KCiFbRmlnIDEuIFZ1bHBpeCwgYSBGaXJlLXR5cGUgZm94IFBva8OpbW9uIGZyb20gR2VuZXJhdGlvbiAxIChhbHNvIG15IGZhdm9yaXRlIFBva8OpbW9uISkgXShpbWFnZXMvdnVscGl4LnBuZyl7d2lkdGg9IjE5NiJ9CgpUaGUgZ29hbCBvZiB0aGlzIGFzc2lnbm1lbnQgaXMgdG8gYnVpbGQgYSBzdGF0aXN0aWNhbCBsZWFybmluZyBtb2RlbCB0aGF0IGNhbiBwcmVkaWN0IHRoZSAqKnByaW1hcnkgdHlwZSoqIG9mIGEgUG9rw6ltb24gYmFzZWQgb24gaXRzIGdlbmVyYXRpb24sIGxlZ2VuZGFyeSBzdGF0dXMsIGFuZCBzaXggYmF0dGxlIHN0YXRpc3RpY3MuICpUaGlzIGlzIGFuIGV4YW1wbGUgb2YgYSAqKmNsYXNzaWZpY2F0aW9uIHByb2JsZW0qKiwgYnV0IHRoZXNlIG1vZGVscyBjYW4gYWxzbyBiZSB1c2VkIGZvciAqKnJlZ3Jlc3Npb24gcHJvYmxlbXMqKiouCgpSZWFkIGluIHRoZSBmaWxlIGFuZCBmYW1pbGlhcml6ZSB5b3Vyc2VsZiB3aXRoIHRoZSB2YXJpYWJsZXMgdXNpbmcgYHBva2Vtb25fY29kZWJvb2sudHh0YC4KCgojIyMgRXhlcmNpc2UgMQoKSW5zdGFsbCBhbmQgbG9hZCB0aGUgYGphbml0b3JgIHBhY2thZ2UuIFVzZSBpdHMgYGNsZWFuX25hbWVzKClgIGZ1bmN0aW9uIG9uIHRoZSBQb2vDqW1vbiBkYXRhLCBhbmQgc2F2ZSB0aGUgcmVzdWx0cyB0byB3b3JrIHdpdGggZm9yIHRoZSByZXN0IG9mIHRoZSBhc3NpZ25tZW50LiBXaGF0IGhhcHBlbmVkIHRvIHRoZSBkYXRhPyBXaHkgZG8geW91IHRoaW5rIGBjbGVhbl9uYW1lcygpYCBpcyB1c2VmdWw/CmBgYHtyfQpwb2tlbW9uIDwtIHJlYWQuY3N2KCIvVXNlcnMvemhhb2xlaS9Eb3dubG9hZHMvaG9tZXdvcmstNS9kYXRhL1Bva2Vtb24uY3N2IikKbGlicmFyeSh2aXApCmxpYnJhcnkoamFuaXRvcikKbGlicmFyeShmb3JjYXRzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoSVNMUjIpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KG1vZGVsZGF0YSkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShuYW5pYXIpICMgdG8gYXNzZXNzIG1pc3NpbmcgZGF0YSBwYXR0ZXJucwpsaWJyYXJ5KGNvcnJwbG90KSAjIGZvciBhIGNvcnJlbGF0aW9uIHBsb3QKbGlicmFyeShwYXRjaHdvcmspICMgZm9yIHB1dHRpbmcgcGxvdHMgdG9nZXRoZXIKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KHZlcmlmaWNhdGlvbikKbGlicmFyeSh2ZXJpZmljYXRpb24pCnRpZHltb2RlbHNfcHJlZmVyKCkKbGlicmFyeShnZ3Bsb3QyKQpwb2tlbW9uIDwtY2xlYW5fbmFtZXMocG9rZW1vbikKaGVhZChwb2tlbW9uKQoKYGBgClRoZSBjbGVhbl9uYW1lcyBmdW5jdGlvbiBzdGFuZGFyZGl6ZXMgdGhlIGRhdGFzZXQncyBjb2x1bW4gbmFtZXMgYnkgcmVtb3ZpbmcgbGVhZGluZyBhbmQgdHJhaWxpbmcgc3BhY2VzIGFuZCByZXBsYWNpbmcgc3BlY2lhbCBjaGFyYWN0ZXJzIHdpdGggdW5kZXJzY29yZXMuIFRoaXMgZW5zdXJlcyBjb25zaXN0ZW5jeSBhbW9uZyB2YXJpYWJsZSBuYW1lcywgbWFraW5nIHRoZW0gZWFzaWVyIHRvIGFjY2VzcyBhbmQgd29yayB3aXRoLgoKIyMjIEV4ZXJjaXNlIDIKClVzaW5nIHRoZSBlbnRpcmUgZGF0YSBzZXQsIGNyZWF0ZSBhIGJhciBjaGFydCBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZSwgYHR5cGVfMWAuCgpIb3cgbWFueSBjbGFzc2VzIG9mIHRoZSBvdXRjb21lIGFyZSB0aGVyZT8gQXJlIHRoZXJlIGFueSBQb2vDqW1vbiB0eXBlcyB3aXRoIHZlcnkgZmV3IFBva8OpbW9uPyBJZiBzbywgd2hpY2ggb25lcz8KCkZvciB0aGlzIGFzc2lnbm1lbnQsIHdlJ2xsIGhhbmRsZSB0aGUgcmFyZXIgY2xhc3NlcyBieSBncm91cGluZyB0aGVtLCBvciAibHVtcGluZyB0aGVtLCIgdG9nZXRoZXIgaW50byBhbiAnb3RoZXInIGNhdGVnb3J5LiBbVXNpbmcgdGhlIGBmb3JjYXRzYCBwYWNrYWdlXShodHRwczovL2ZvcmNhdHMudGlkeXZlcnNlLm9yZy8pLCBkZXRlcm1pbmUgaG93IHRvIGRvIHRoaXMsIGFuZCAqKmx1bXAgYWxsIHRoZSBvdGhlciBsZXZlbHMgdG9nZXRoZXIgZXhjZXB0IGZvciB0aGUgdG9wIDYgbW9zdCBmcmVxdWVudCoqICh3aGljaCBhcmUgQnVnLCBGaXJlLCBHcmFzcywgTm9ybWFsLCBXYXRlciwgYW5kIFBzeWNoaWMpLgoKQ29udmVydCBgdHlwZV8xYCBhbmQgYGxlZ2VuZGFyeWAgdG8gZmFjdG9ycy4KCmBgYHtyfQpwb2tlbW9uJT4lCiAgZ2dwbG90KGFlcyh4PXR5cGVfMSkpKwogIGdlb21fYmFyKCkKCmBgYApUaGVyZSBhcmUgMTggb3V0Y29tZXMgaGVyZSwgYW5kIGNsYXNzZXMgc3VjaCBhcyBGbHlpbmcsIGZhaXJ5LCBJY2UgZG8gaW5jbHVkZSB2ZXJ5IHNtYWxsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMsIHNvIHdlIGhhdmUgdG8gbHVtcGluZyB0aGVtLgoKYGBge3J9CnBva2Vtb24kdHlwZV8xIDwtIHBva2Vtb24gJT4lCiAgbXV0YXRlKHR5cGVfMSA9IGZjdF9sdW1wX24odHlwZV8xLCBuID0gNiwgdyA9IE5VTEwsIG90aGVyX2xldmVsID0gIk90aGVyIikpJT4lCiAgIHB1bGwodHlwZV8xKQoKcG9rZW1vbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0eXBlXzEpKSArCiAgZ2VvbV9iYXIoKQoKIyBmYWN0b3JpemF0aW9uCnBva2Vtb24kdHlwZV8xIDwtIGFzLmZhY3Rvcihwb2tlbW9uJHR5cGVfMSkKcG9rZW1vbiRsZWdlbmRhcnkgPC0gYXMuZmFjdG9yKHBva2Vtb24gJGxlZ2VuZGFyeSkKYGBgCiMjIyBFeGVyY2lzZSAzCgpQZXJmb3JtIGFuIGluaXRpYWwgc3BsaXQgb2YgdGhlIGRhdGEuIFN0cmF0aWZ5IGJ5IHRoZSBvdXRjb21lIHZhcmlhYmxlLiBZb3UgY2FuIGNob29zZSBhIHByb3BvcnRpb24gdG8gdXNlLiBWZXJpZnkgdGhhdCB5b3VyIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMgaGF2ZSB0aGUgZGVzaXJlZCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLgoKTmV4dCwgdXNlICp2Ki1mb2xkIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldC4gVXNlIDUgZm9sZHMuIFN0cmF0aWZ5IHRoZSBmb2xkcyBieSBgdHlwZV8xYCBhcyB3ZWxsLiAqSGludDogTG9vayBmb3IgYSBgc3RyYXRhYCBhcmd1bWVudC4qCgpXaHkgZG8geW91IHRoaW5rIGRvaW5nIHN0cmF0aWZpZWQgc2FtcGxpbmcgZm9yIGNyb3NzLXZhbGlkYXRpb24gaXMgdXNlZnVsPwoKYGBge3J9CnNldC5zZWVkKDA5MjYpCgoKcG9rZW1vbl9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KHBva2Vtb24sIHByb3AgPSAwLjc1LHN0cmF0YSA9ICAgInR5cGVfMSIsKQoKcG9rZW1vbl90cmFpbiA8LSB0cmFpbmluZyhwb2tlbW9uX3NwbGl0KQpwb2tlbW9uX3Rlc3QgPC0gdGVzdGluZyhwb2tlbW9uX3NwbGl0KQpwb2tlbW9uX2ZvbGRzIDwtdmZvbGRfY3YocG9rZW1vbl90cmFpbiwgdiA9IDUsIHN0cmF0YSA9ICJ0eXBlXzEiKQoKYGBgClN0cmF0aWZpZWQgc2FtcGxpbmcgZm9yIGNyb3NzLXZhbGlkYXRpb24gZW5zdXJlcyB0aGF0IHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzIG1haW50YWluIHRoZSBzYW1lIHByb3BvcnRpb24gb2YgZmVhdHVyZXMgYXMgdGhlIG9yaWdpbmFsIGRhdGEsIGFsbG93aW5nIHRoZSByZXN1bHRzIHRvIGJldHRlciByZXByZXNlbnQgdGhlIGVudGlyZSBkYXRhc2V0LgoKIyMjIEV4ZXJjaXNlIDQKCkNyZWF0ZSBhIGNvcnJlbGF0aW9uIG1hdHJpeCBvZiB0aGUgdHJhaW5pbmcgc2V0LCB1c2luZyB0aGUgYGNvcnJwbG90YCBwYWNrYWdlLiAqTm90ZTogWW91IGNhbiBjaG9vc2UgaG93IHRvIGhhbmRsZSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGZvciB0aGlzIHBsb3Q7IGp1c3RpZnkgeW91ciBkZWNpc2lvbihzKS4qCgpXaGF0IHJlbGF0aW9uc2hpcHMsIGlmIGFueSwgZG8geW91IG5vdGljZT8KCmBgYHtyfQpwb2tlbW9uX3RyYWluJT4lc2VsZWN0KGlzLm51bWVyaWMpJT4lY29yKCklPiVjb3JycGxvdCgpCmBgYAp0aGUgdmFyaWFibGUgdG90YWwgaXMgc3Ryb25nbHkgYW5kIHBvc2l0aXZlbHkgcmVsYXRlZCB3aXRoIGhwLCBhdHRhY2ssIGRlZmVuc2UsIHNwX2F0aywgc3BfZGVmLCBhbmQgc3BlZWQuCgoKIyMjIEV4ZXJjaXNlIDUKClNldCB1cCBhIHJlY2lwZSB0byBwcmVkaWN0IGB0eXBlXzFgIHdpdGggYGxlZ2VuZGFyeWAsIGBnZW5lcmF0aW9uYCwgYHNwX2F0a2AsIGBhdHRhY2tgLCBgc3BlZWRgLCBgZGVmZW5zZWAsIGBocGAsIGFuZCBgc3BfZGVmYC4KCi0gICBEdW1teS1jb2RlIGBsZWdlbmRhcnlgIGFuZCBgZ2VuZXJhdGlvbmA7CgotICAgQ2VudGVyIGFuZCBzY2FsZSBhbGwgcHJlZGljdG9ycy4KCmBgYHtyfQpwb2tlbW9uX3JlY2lwZTwtcmVjaXBlKHR5cGVfMX5sZWdlbmRhcnkgKyBnZW5lcmF0aW9uICsgc3BfYXRrICsgYXR0YWNrICsgc3BlZWQgCiAgICAgICAgICAgICAgICAgICAgICAgK2RlZmVuc2UgKyBocCArIHNwX2RlZixkYXRhPXBva2Vtb25fdHJhaW4pJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCkpJT4lCiAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKSU+JQogIHN0ZXBfY2VudGVyKGFsbF9wcmVkaWN0b3JzKCkpCgoKYGBgCgojIyMgRXhlcmNpc2UgNgoKV2UnbGwgYmUgZml0dGluZyBhbmQgdHVuaW5nIGFuIGVsYXN0aWMgbmV0LCB0dW5pbmcgYHBlbmFsdHlgIGFuZCBgbWl4dHVyZWAgKHVzZSBgbXVsdGlub21fcmVnKClgIHdpdGggdGhlIGBnbG1uZXRgIGVuZ2luZSkuCgpTZXQgdXAgdGhpcyBtb2RlbCBhbmQgd29ya2Zsb3cuIENyZWF0ZSBhIHJlZ3VsYXIgZ3JpZCBmb3IgYHBlbmFsdHlgIGFuZCBgbWl4dHVyZWAgd2l0aCAxMCBsZXZlbHMgZWFjaDsgYG1peHR1cmVgIHNob3VsZCByYW5nZSBmcm9tIDAgdG8gMS4gRm9yIHRoaXMgYXNzaWdubWVudCwgbGV0IGBwZW5hbHR5YCByYW5nZSBmcm9tIDAuMDEgdG8gMyAodGhpcyBpcyBvbiB0aGUgYGlkZW50aXR5X3RyYW5zKClgIHNjYWxlOyBub3RlIHRoYXQgeW91J2xsIG5lZWQgdG8gc3BlY2lmeSB0aGVzZSB2YWx1ZXMgaW4gYmFzZSAxMCBvdGhlcndpc2UpLgoKYGBge3J9Cm5vbV9tb2RlbCA8LSBtdWx0aW5vbV9yZWcocGVuYWx0eSA9IHR1bmUoKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1peHR1cmUgPSB0dW5lKCkpICU+JQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCiAgCiMgc2V0IHVwIHRoZSB3b3JrZmxvdwpub21fd2tmbG93IDwtd29ya2Zsb3coKSAlPiUKICBhZGRfbW9kZWwobm9tX21vZGVsKSAlPiUKICBhZGRfcmVjaXBlKHBva2Vtb25fcmVjaXBlKQoKIyBjcmVhdGUgZ3JpZApub21fZ3JpZCA8LSBncmlkX3JlZ3VsYXIocGVuYWx0eShyYW5nZSA9YygwLjAxLCAzKSwgdHJhbnMgPSBpZGVudGl0eV90cmFucygpKSwKICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgIG1peHR1cmUocmFuZ2UgPSBjKDAsMSkpLAogICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAxMCkKYGBgCgojIyMgRXhlcmNpc2UgNwoKTm93IHNldCB1cCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYW5kIHdvcmtmbG93LiBVc2UgdGhlIGByYW5nZXJgIGVuZ2luZSBhbmQgc2V0IGBpbXBvcnRhbmNlID0gImltcHVyaXR5ImA7IHdlJ2xsIGJlIHR1bmluZyBgbXRyeWAsIGB0cmVlc2AsIGFuZCBgbWluX25gLiBVc2luZyB0aGUgZG9jdW1lbnRhdGlvbiBmb3IgYHJhbmRfZm9yZXN0KClgLCBleHBsYWluIGluIHlvdXIgb3duIHdvcmRzIHdoYXQgZWFjaCBvZiB0aGVzZSBoeXBlcnBhcmFtZXRlcnMgcmVwcmVzZW50LgoKQ3JlYXRlIGEgcmVndWxhciBncmlkIHdpdGggOCBsZXZlbHMgZWFjaC4gWW91IGNhbiBjaG9vc2UgcGxhdXNpYmxlIHJhbmdlcyBmb3IgZWFjaCBoeXBlcnBhcmFtZXRlci4gTm90ZSB0aGF0IGBtdHJ5YCBzaG91bGQgbm90IGJlIHNtYWxsZXIgdGhhbiAxIG9yIGxhcmdlciB0aGFuIDguICoqRXhwbGFpbiB3aHkgbmVpdGhlciBvZiB0aG9zZSB2YWx1ZXMgd291bGQgbWFrZSBzZW5zZS4qKgoKV2hhdCB0eXBlIG9mIG1vZGVsIGRvZXMgYG10cnkgPSA4YCByZXByZXNlbnQ/CgpgYGB7cn0KIyBzZXQgdXAgbW9kZWwKcmFuZG9tZl9tb2RlbCA8LSByYW5kX2ZvcmVzdChtdHJ5ID0gdHVuZSgpLCB0cmVlcyA9IHR1bmUoKSwgbWluX24gPSB0dW5lKCkpICU+JQogIHNldF9lbmdpbmUoInJhbmdlciIsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSAlPiUKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQoKIyBzZXQgdXAgd29ya2Zsb3cKcmFuZG9tZl93a2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfbW9kZWwocmFuZG9tZl9tb2RlbCkgJT4lCiAgYWRkX3JlY2lwZShwb2tlbW9uX3JlY2lwZSkKCnJhbmRvbWZfZ3JpZCA8LSBncmlkX3JlZ3VsYXIobXRyeShyYW5nZSA9IGMoMiw3KSksCiAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVzKHJhbmdlID0gYygxMDAsMTAwMCkpLAogICAgICAgICAgICAgICAgICAgICAgICBtaW5fbihyYW5nZSA9IGMoMiwxMCkpLAogICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSA4KQoKCmBgYAptdHJ5IHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgcmFuZG9tbHkgc2FtcGxlZCBhcyBjYW5kaWRhdGVzIGF0IGVhY2ggc3BsaXQgd2hlbiBidWlsZGluZyBhIHRyZWUuIFdlIG5lZWQgYXQgbGVhc3Qgb25lIHZhcmlhYmxlLCBzbyBtdHJ5IGNhbid0IGJlIGxlc3MgdGhhbiAxLiBTaW5jZSB3ZSBoYXZlIGF0IG1vc3QgOCBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbCwgbXRyeSBjYW4ndCBiZSBsYXJnZXIgdGhhbiA4LiBXaGVuIG10cnkgaXMgc2V0IHRvIDgsIHRoZSBtb2RlbCBiZWNvbWVzIGEgZGVjaXNpb24gdHJlZSB0aGF0IHVzZXMgYWxsIGZlYXR1cmVzLgoKIyMjIEV4ZXJjaXNlIDgKCkZpdCBhbGwgbW9kZWxzIHRvIHlvdXIgZm9sZGVkIGRhdGEgdXNpbmcgYHR1bmVfZ3JpZCgpYC4KCioqTm90ZTogVHVuaW5nIHlvdXIgcmFuZG9tIGZvcmVzdCBtb2RlbCB3aWxsIHRha2UgYSBmZXcgbWludXRlcyB0byBydW4sIGFueXdoZXJlIGZyb20gNSBtaW51dGVzIHRvIDE1IG1pbnV0ZXMgYW5kIHVwLiBDb25zaWRlciBydW5uaW5nIHlvdXIgbW9kZWxzIG91dHNpZGUgb2YgdGhlIC5SbWQsIHN0b3JpbmcgdGhlIHJlc3VsdHMsIGFuZCBsb2FkaW5nIHRoZW0gaW4geW91ciAuUm1kIHRvIG1pbmltaXplIHRpbWUgdG8ga25pdC4gV2UnbGwgZ28gb3ZlciBob3cgdG8gZG8gdGhpcyBpbiBsZWN0dXJlLioqCgpVc2UgYGF1dG9wbG90KClgIG9uIHRoZSByZXN1bHRzLiBXaGF0IGRvIHlvdSBub3RpY2U/IERvIGxhcmdlciBvciBzbWFsbGVyIHZhbHVlcyBvZiBgcGVuYWx0eWAgYW5kIGBtaXh0dXJlYCBwcm9kdWNlIGJldHRlciBST0MgQVVDPyBXaGF0IGFib3V0IHZhbHVlcyBvZiBgbWluX25gLCBgdHJlZXNgLCBhbmQgYG10cnlgPwoKV2hhdCBlbGFzdGljIG5ldCBtb2RlbCBhbmQgd2hhdCByYW5kb20gZm9yZXN0IG1vZGVsIHBlcmZvcm0gdGhlIGJlc3Qgb24geW91ciBmb2xkZWQgZGF0YT8gKFdoYXQgc3BlY2lmaWMgdmFsdWVzIG9mIHRoZSBoeXBlcnBhcmFtZXRlcnMgcmVzdWx0ZWQgaW4gdGhlIG9wdGltYWwgUk9DIEFVQz8pCgpgYGB7cn0Kbm9tX3JlcyA8LSB0dW5lX2dyaWQoCiAgbm9tX3drZmxvdywKICByZXNhbXBsZXMgPSBwb2tlbW9uX2ZvbGRzLAogIGdyaWQgPSBub21fZ3JpZCwKICBjb250cm9sID0gY29udHJvbF9ncmlkKHNhdmVfcHJlZCA9IFRSVUUpCikKCmxvYWQoIi9Vc2Vycy96aGFvbGVpL0Rvd25sb2Fkcy9ob21ld29yay01L3R1bmVfcmFuZG9tX2ZvcmVzdC5yZGEiKQpgYGAKCgpgYGB7cn0KYXV0b3Bsb3Qobm9tX3JlcykKYGBgCkZvciB0aGlzIHBhcnRpY3VsYXIgZGF0YXNldCBhbmQgZWxhc3RpYyBuZXQgbW9kZWwsIHVzaW5nIHNtYWxsZXIgdmFsdWVzIGZvciBib3RoIHRoZSBwZW5hbHR5IGFuZCBtaXh0dXJlIHBhcmFtZXRlcnMgcmVzdWx0cyBpbiBiZXR0ZXIgbW9kZWwgcGVyZm9ybWFuY2UsIGFzIGluZGljYXRlZCBieSBoaWdoZXIgUk9DIEFVQyB2YWx1ZXMuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgbW9kZWwgYmVuZWZpdHMgZnJvbSBsZXNzIHJlZ3VsYXJpemF0aW9uIGFuZCBhIGJhbGFuY2UgdGhhdCBmYXZvcnMgUmlkZ2UgcmVndWxhcml6YXRpb24uCmBgYHtyfQpjb2xsZWN0X21ldHJpY3Mobm9tX3JlcykgJT4lCiAgZmlsdGVyKC5tZXRyaWMgPT0gInJvY19hdWMiKSAlPiUKICBzZWxlY3QocGVuYWx0eSwgbWl4dHVyZSwgbWVhbiwgc3RkX2VycikKYGBgCmBgYHtyfQpzZWxlY3RfYmVzdChub21fcmVzKQpgYGAKVGhlIGJlc3Qgcm9jX2F1YyB2YWx1ZSBpcyB3aGVuIHBlbmFsdHkgPSAwLjAxIGFuZCBtaXh0dXJlIGlzIGFib3V0IDAuNTU2CgoKYGBge3J9CmF1dG9wbG90KHJmX3JlcykKYGBgCgpgYGB7cn0Kc2VsZWN0X2Jlc3QocmZfcmVzKQpgYGAKZSByYW5kb20gZm9yZXN0IG1vZGVsLCBhbHRob3VnaCB0aGUgcmVzdWx0cyB2YXJpZXMgYSBsb3QsIHRoZSBoaWdoZXN0IHJvY19hdWMgdmFsdWUgaXMgd2hlbiBzZWxlY3RpbmcgMyBwcmVkaWN0b3JzICwgbm9kZSBzaXplNjQsIGFuZCA3NDIgdHJlZXMuCiMjIyBFeGVyY2lzZSA5CgpTZWxlY3QgeW91ciBvcHRpbWFsIFsqKnJhbmRvbSBmb3Jlc3QgbW9kZWwqKl17LnVuZGVybGluZX1pbiB0ZXJtcyBvZiBgcm9jX2F1Y2AuIFRoZW4gZml0IHRoYXQgbW9kZWwgdG8geW91ciB0cmFpbmluZyBzZXQgYW5kIGV2YWx1YXRlIGl0cyBwZXJmb3JtYW5jZSBvbiB0aGUgdGVzdGluZyBzZXQuCgpVc2luZyB0aGUgKip0cmFpbmluZyoqIHNldDoKCi0gICBDcmVhdGUgYSB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3QsIHVzaW5nIGB2aXAoKWAuICpOb3RlIHRoYXQgeW91J2xsIHN0aWxsIG5lZWQgdG8gaGF2ZSBzZXQgYGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiYCB3aGVuIGZpdHRpbmcgdGhlIG1vZGVsIHRvIHlvdXIgZW50aXJlIHRyYWluaW5nIHNldCBpbiBvcmRlciBmb3IgdGhpcyB0byB3b3JrLioKCiAgICAtICAgV2hhdCB2YXJpYWJsZXMgd2VyZSBtb3N0IHVzZWZ1bD8gV2hpY2ggd2VyZSBsZWFzdCB1c2VmdWw/IEFyZSB0aGVzZSByZXN1bHRzIHdoYXQgeW91IGV4cGVjdGVkLCBvciBub3Q/CgpVc2luZyB0aGUgdGVzdGluZyBzZXQ6CgotICAgQ3JlYXRlIHBsb3RzIG9mIHRoZSBkaWZmZXJlbnQgUk9DIGN1cnZlcywgb25lIHBlciBsZXZlbCBvZiB0aGUgb3V0Y29tZSB2YXJpYWJsZTsKCi0gICBNYWtlIGEgaGVhdCBtYXAgb2YgdGhlIGNvbmZ1c2lvbiBtYXRyaXguCgpgYGB7cn0KZmluYWxfd2tmbG93IDwtIGZpbmFsaXplX3dvcmtmbG93KHJhbmRvbWZfd2tmbG93LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0X2Jlc3QocmZfcmVzKSkKcG9rZW1vbl9maXQgPC0gZml0KGZpbmFsX3drZmxvdywgcG9rZW1vbl90cmFpbikgIyBmaXQgdGhlIHRyYWluaW5nIHNldAoKdmlwKHBva2Vtb25fZml0LCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikKCmBgYAoKYGBge3J9CnByZWRpY3QocG9rZW1vbl9maXQsIG5ld19kYXRhPXBva2Vtb25fdGVzdCwgdHlwZSA9InByb2IiKQpgYGAKCmBgYHtyfQphdWdtZW50KHBva2Vtb25fZml0LCBuZXdfZGF0YSA9IHBva2Vtb25fdGVzdCkgJT4lCiAgcm9jX2N1cnZlKHR5cGVfMSwgLnByZWRfQnVnLC5wcmVkX0ZpcmUsIC5wcmVkX0dyYXNzLCAucHJlZF9Ob3JtYWwsIC5wcmVkX1BzeWNoaWMsIC5wcmVkX1dhdGVyLCAucHJlZF9PdGhlcikgJT4lCiAgYXV0b3Bsb3QoKQpgYGAKCmBgYHtyfQphdWdtZW50KHBva2Vtb25fZml0LCBuZXdfZGF0YT0gcG9rZW1vbl90ZXN0KSAlPiUKICBjb25mX21hdCh0cnV0aCA9IHR5cGVfMSwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lCiAgYXV0b3Bsb3QodHlwZSA9ICJoZWF0bWFwIikKYGBgClRoZSBtb2RlbCBpcyBiZXN0IGF0IHByZWRpY3RpbmcgT3RoZXIgdHlwZSwgdGhpcyBtaWdodCBiZWNhdXNlIGFmdGVyIHRoZSBsdW1waW5nLCB0aGUgT3RoZXIgdHlwZSBiZWNvbWVzIG1vc3QgcHJldmFsZW50IGluIHRoZSBkYXRhc2V0LgoKQWNjdXJhY3k6IDQ2LjI3JVwKSW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGNvcnJlY3RseSBwcmVkaWN0ZWQgdGhlIFBva8OpbW9uIHR5cGUgZm9yIDQ2LjI3JSBvZiB0aGUgaW5zdGFuY2VzLgpTZW5zaXRpdml0eSAoUmVjYWxsKTogMjUuMTUlXApTdWdnZXN0cyB0aGUgbW9kZWwgc3RydWdnbGVzIHRvIGlkZW50aWZ5IHRydWUgcG9zaXRpdmVzLCBvZnRlbiBtaXNzaW5nIHRoZSBjb3JyZWN0IFBva8OpbW9uIHR5cGVzLgpTcGVjaWZpY2l0eTogODguMDclXApTaG93cyB0aGUgbW9kZWwgaXMgZ29vZCBhdCBhdm9pZGluZyBmYWxzZSBwb3NpdGl2ZXMsIGNvcnJlY3RseSBpZGVudGlmeWluZyBpbnN0YW5jZXMgdGhhdCBkbyBub3QgYmVsb25nIHRvIGVhY2ggdHlwZS4KCiMjIyBFeGVyY2lzZSAxMAoKSG93IGRpZCB5b3VyIGJlc3QgcmFuZG9tIGZvcmVzdCBtb2RlbCBkbyBvbiB0aGUgdGVzdGluZyBzZXQ/CgpXaGljaCBQb2tlbW9uIHR5cGVzIGlzIHRoZSBtb2RlbCBiZXN0IGF0IHByZWRpY3RpbmcsIGFuZCB3aGljaCBpcyBpdCB3b3JzdCBhdD8gKERvIHlvdSBoYXZlIGFueSBpZGVhcyB3aHkgdGhpcyBtaWdodCBiZT8pCgpgYGB7cn0KbXVsdGlfbWV0cmljIDwtIG1ldHJpY19zZXQoYWNjdXJhY3ksIHNlbnNpdGl2aXR5LCBzcGVjaWZpY2l0eSkKCmF1Z21lbnQocG9rZW1vbl9maXQsIG5ld19kYXRhID0gcG9rZW1vbl90ZXN0KSAlPiUKICBtdWx0aV9tZXRyaWModHJ1dGggPSB0eXBlXzEsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKCiMjIEZvciAyMzEgU3R1ZGVudHMKCiMjIyBFeGVyY2lzZSAxMQoKSW4gdGhlIDIwMjAtMjAyMSBzZWFzb24sIFN0ZXBoZW4gQ3VycnksIGFuIE5CQSBiYXNrZXRiYWxsIHBsYXllciwgbWFkZSAzMzcgb3V0IG9mIDgwMSB0aHJlZSBwb2ludCBzaG90IGF0dGVtcHRzICg0Mi4xJSkuIFVzZSBib290c3RyYXAgcmVzYW1wbGluZyBvbiBhIHNlcXVlbmNlIG9mIDMzNyAxJ3MgKG1ha2VzKSBhbmQgNDY0IDAncyAobWlzc2VzKS4gRm9yIGVhY2ggYm9vdHN0cmFwIHNhbXBsZSwgY29tcHV0ZSBhbmQgc2F2ZSB0aGUgc2FtcGxlIG1lYW4gKGUuZy4gYm9vdHN0cmFwIEZHJSBmb3IgdGhlIHBsYXllcikuIFVzZSAxMDAwIGJvb3RzdHJhcCBzYW1wbGVzIHRvIHBsb3QgYSBoaXN0b2dyYW0gb2YgdGhvc2UgdmFsdWVzLiBDb21wdXRlIHRoZSA5OSUgYm9vdHN0cmFwIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yIFN0ZXBoZW4gQ3VycnkncyAidHJ1ZSIgZW5kLW9mLXNlYXNvbiBGRyUgdXNpbmcgdGhlIHF1YW50aWxlIGZ1bmN0aW9uIGluIFIuIFByaW50IHRoZSBlbmRwb2ludHMgb2YgdGhpcyBpbnRlcnZhbC4KCiMjIyBFeGVyY2lzZSAxMgoKVXNpbmcgdGhlIGBhYmFsb25lLnR4dGAgZGF0YSBmcm9tIHByZXZpb3VzIGFzc2lnbm1lbnRzLCBmaXQgYW5kIHR1bmUgYSAqKnJhbmRvbSBmb3Jlc3QqKiBtb2RlbCB0byBwcmVkaWN0IGBhZ2VgLiBVc2Ugc3RyYXRpZmllZCBjcm9zcy12YWxpZGF0aW9uIGFuZCBzZWxlY3QgcmFuZ2VzIGZvciBgbXRyeWAsIGBtaW5fbmAsIGFuZCBgdHJlZXNgLiBQcmVzZW50IHlvdXIgcmVzdWx0cy4gV2hhdCB3YXMgeW91ciBmaW5hbCBjaG9zZW4gbW9kZWwncyAqKlJNU0UqKiBvbiB5b3VyIHRlc3Rpbmcgc2V0Pwo=