Overview

In this notebook, we’ll apply the machine learning concepts covered in the classification supervised learning lecture. Note that there are many libraries that can perform the methods that we reviewed in class, but we’ll focus here on using the caret package to perform these operations.

require(tidyverse)
require(caret) # for machine learning
require(recipes) # For preprocessing your data
require(rsample) # For train/test split
require(yardstick) # For performance metrics
require(rattle) # For nice tree plots

# For parallelization (to run the models across many cores) -- speeds up computation!
# install.packages("doMC")
doMC::registerDoMC()

Data

The following data contains information regarding whether or not someone has health coverage. The outcome of interest is whether of not an individual has healthcare coverage or not. The available predictive features capture socio-economic and descriptive factors.

set.seed(1988)

dat = suppressMessages(read_csv("health-coverage.csv")) %>% 
  
  # Convert education into an ordered category.
  mutate(
    
    educ = case_when(
      educ == 'Less than HS' ~ 0,
      educ == 'HS Degree' ~ 1,
      educ == 'Undergraduate Degree' ~ 2,
      educ == 'Graduate Degree' ~ 3),
    
    race = case_when(
      race == 'White' ~ 'white',
      race == 'Black' ~ 'black',
      race == 'Asian' ~ 'asian',
      T ~ 'other'),
    
    race = factor(race,level=c("white",'black','asian','other')),
    
    mar = factor(mar,levels= c('Never Married',
                               'Divorced','Married',
                               'Separated','Widowed'))
    
    ) %>% 
  
  # Convert all remaining factor variables into character variables
  mutate_if(is.character,as.factor) %>% 
  
  # Make sure that the category you want to predict is the first category in
  # your factor variable
  mutate(coverage = factor(coverage,
                           levels = c("Coverage","No_Coverage"))) %>% 
  
  # Only taking a random sample of the data so the models run quicker
  sample_n(5000) 

head(dat) # Peek at the data just to make sure everything was read in correctly. 

Split the Sample: Training and test data

Before event looking at the data, let’s split the sample up into a training and test dataset. We’ll completely hold off on viewing the test data, so as not to bias our development of the learning model. Note that strata= argument ensures that we have a similar proportion of covered and not covered individuals in the training and test data.

set.seed(123)
splits = initial_split(dat,prop = .8,strata = coverage)
train_data = training(splits) # Use 80% of the data as training data 
test_data = testing(splits) # holdout 20% as test data 

dim(train_data)
[1] 4002    7
dim(test_data) 
[1] 998   7

Examine the data

NOTE: skimr provides a very nice summary of the data, but the mini-histograms will cause a lot of grief if you’re trying to knit to PDF. Feel free to use skimr interactively but not if you’re knitting to PDF.

skimr::skim(train_data)
── Data Summary ────────────────────────
                           Values    
Name                       train_data
Number of rows             4002      
Number of columns          7         
_______________________              
Column type frequency:               
  factor                   4         
  numeric                  3         
________________________             
Group variables                      

── Variable type: factor ────────────────────────────────────────────────
  skim_variable n_missing complete_rate ordered n_unique
1 coverage              0             1 FALSE          2
2 cit                   0             1 FALSE          2
3 mar                   0             1 FALSE          5
4 race                  0             1 FALSE          4
  top_counts                              
1 Cov: 2017, No_: 1985                    
2 Cit: 3603, Non: 399                     
3 Mar: 1736, Nev: 1450, Div: 508, Wid: 210
4 whi: 2451, bla: 1227, oth: 203, asi: 121

── Variable type: numeric ───────────────────────────────────────────────
  skim_variable n_missing complete_rate     mean        sd    p0   p25
1 age                   0             1    43.9     17.6      16    29
2 wage                  0             1 20119.   41877.        0     0
3 educ                  0             1     1.02     0.787     0     1
    p50   p75   p100 hist 
1    43    57     92 ▇▇▇▃▁
2  3000 25000 419000 ▇▁▁▁▁
3     1     1      3 ▃▇▁▂▁

Visualize the distribution for each variable.

First, let’s look at the categorical variables.

train_data %>% 
  select_if(is.factor) %>% 
  gather(var,val) %>% 
  ggplot(aes(val)) +
  geom_bar() +
  scale_y_log10() +
  facet_wrap(~var,scales="free_y",ncol=1) +
  coord_flip() +
  theme(text=element_text(size=16))
attributes are not identical across measure variables;
they will be dropped

Few things to note:

  • Balanced classes on the outcome (more or less)
  • Over representativeness of some racial groups in the data.
  • Good variation on marital outcomes.

Second, let’s look at the distribution of the continuous variables

train_data %>% 
  select_if(is.numeric) %>% 
  gather(var,val) %>% 
  ggplot(aes(val)) +
  geom_histogram(bins = 75) +
  facet_wrap(~var,scales="free",ncol=1) 

Things to note:

  • Wage is right skewed. There appears to an outlier making a lot of the money. This outlier could cause problems.
  • Good distribution on the age variable.
  • Majority of the observations only have a high school education.
  • The scales for these variables will have to be adjusted.

Let’s peek more closely at the distribution of wealth in the sample.

train_data %>% 
  mutate(wealth_bins = case_when(
    wage == 0 ~ "Unemployed",
    wage > 0 & wage<= 30000 ~ "Low",
    wage > 30000 & wage<= 75000 ~ "Mid",
    wage > 75000 & wage<= 200000 ~ "High",
    wage > 200000 ~ "Jeez...!",
  )) %>% 
  mutate(wealth_bins = fct_infreq(wealth_bins)) %>%
  ggplot(aes(wealth_bins)) +
  geom_bar()

There are a lot of individuals that report 0 income (Unemployed?). What is the age distribution for those individuals?

train_data %>% 
  filter(wage==0) %>% 
  ggplot(aes(age)) +
  geom_density(fill="steelblue",alpha=.5,color="white")

Pre-process the Data

What do we need to do?

  • Categorical variables to dummies
  • We’ll likely need to log wage to deal with skew.
  • Scale age and wage (so that they fall into a 0 to 1 range)
  • No need to impute missing values. All the data is there.
rcp <- 
  recipe(coverage ~  .,train_data) %>% 
  step_dummy(all_nominal(),-coverage) %>% # Why exclude outcome variable?
  step_log(wage,offset = 1) %>% # Log the skewed wage variable
  step_range(all_numeric()) %>%  # Normalize scale
  prep()


# Apply the recipe to the training and test data
train_data2 <- bake(rcp,train_data)
test_data2 <- bake(rcp,test_data) 

Check that our pre-processing worked.

head(train_data2)
train_data2 %>% 
  select(wage,age) %>% 
  gather(var,val) %>% 
  ggplot(aes(val)) +
  geom_histogram(bins = 75) +
  facet_wrap(~var,scales="free",ncol=1)

Cross-validation

When comparing different machine learning models, we want to make sure we’re making a fair and equal comparison. One way that random chance can sneak into our assessments of model fit is through cross-validation. We want to make sure that we’re cross-validating the data on the exact same data partitions.

caret makes this ease to do. Let’s use the k-fold cross-validation method with 10 folds in the data.

set.seed(1988) # set a seed for replication purposes 

# Partition the data into 5 equal folds
folds <- createFolds(train_data2$coverage, k = 5) 

sapply(folds,length)
Fold1 Fold2 Fold3 Fold4 Fold5 
  800   801   800   801   800 

Now, let’s use the trainControl() function from caret to set up our validation conditions. An important changes from last week: we have to tell caret that we are dealing with a classification problem. We can do this by adding summaryFunction = twoClassSummary and classProbs = TRUE to the cross-validation function.

control_conditions <- 
  trainControl(method='cv', # K-fold cross validation
               summaryFunction = twoClassSummary, # Need this b/c it's a classification problem
               classProbs = TRUE, # Need this b/c it's a classification problem
               index = folds # The indices for our folds (so they are always the same)
  )

We’ll now use this same cross-validation object for everything that we do.

Models

Let’s explore the different models that we covered in the lecture using the same package framework. As we saw last time in class, caret facilitates this task nicely.

Logistic Regression

mod_logit <-
  train(coverage ~ ., 
        data=train_data2, # Training data 
        method = "glm", # logit function
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )
mod_logit
Generalized Linear Model 

4002 samples
  11 predictor
   2 classes: 'Coverage', 'No_Coverage' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 800, 801, 800, 801, 800 
Resampling results:

  ROC        Sens       Spec     
  0.7800115  0.7149195  0.7083123

K-Nearest Neighbors

Remember: these algorithms are computationally demanding, so they can take a little time to run depending on the power of your machine

mod_knn <-
  train(coverage ~ ., # Equation (outcome and everything else)
        data=train_data2, # Training data 
        method = "knn", # K-Nearest Neighbors Algorithm
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )

Let’s look at the performance plots. What do we see?

mod_knn
k-Nearest Neighbors 

4002 samples
  11 predictor
   2 classes: 'Coverage', 'No_Coverage' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 800, 801, 800, 801, 800 
Resampling results across tuning parameters:

  k  ROC        Sens       Spec     
  5  0.7658549  0.6870269  0.7056675
  7  0.7708521  0.6790933  0.7148615
  9  0.7738201  0.6697991  0.7243073

ROC was used to select the optimal model using the largest value.
The final value used for the model was k = 9.
  • caret has default settings that auto explore different tunning parameters for the model.
  • We can easily plot these tuning features using the standard plot functions (these function have been over-ridden in R)
plot(mod_knn)

Now, let’s say we wanted to adjust the tunning parameters. We can explore different model tunings by using the tuneGrid argument.

knn_tune = expand.grid(k = c(1,3,10,50))
knn_tune
mod_knn2 <-
  train(coverage ~ ., # Equation (outcome and everything else)
        data=train_data2, # Training data 
        method = "knn", # K-Nearest Neighbors Algorithm
        metric = "ROC", # area under the curve
        tuneGrid = knn_tune, # add the tuning parameters here 
        trControl = control_conditions
  )
plot(mod_knn2)

Classification and Regression Trees (CART)

mod_cart <-
  train(coverage ~ ., # Equation (outcome and everything else)
        data=train_data2, # Training data 
        method = "rpart", # Classification Tree
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )
plot(mod_cart)

Shallower trees converge to a coin flip. The deepest tree appears to be the bets

Let’s visualize the decision tree…

# This tree goes really deep
fancyRpartPlot(mod_cart$finalModel)

We can actually print out the larger decision tree to look at it as a print out…. as we can see this is a LOT.

print(mod_cart$finalModel)
n= 4002 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 4002 1985 Coverage (0.50399800 0.49600200)  
  2) age>=0.6381579 522   20 Coverage (0.96168582 0.03831418) *
  3) age< 0.6381579 3480 1515 No_Coverage (0.43534483 0.56465517)  
    6) wage>=0.8002206 755  167 Coverage (0.77880795 0.22119205) *
    7) wage< 0.8002206 2725  927 No_Coverage (0.34018349 0.65981651) *

Random Forest

mod_rf <-
  train(coverage ~ ., # Equation (outcome and everything else)
        data=train_data2, # Training data 
        method = "ranger", # random forest (ranger is much faster than rf)
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )

There are three tunning parameters in this model:

  • mtry: the number of predictors that we’ll randomly select.
  • splitrule: the way we determine how the nodes should be split
    • gini = node purity (see class lecture)
    • extratrees = doesn’t bag, randomly select vars, but just randomly split, accept the best split. (Short for “Extremely Randomize Trees”)
  • ‘min.node.size’: the minimum number of observations that can be in each terminal node (default fixes this at 1)
plot(mod_rf)

Support Vector Machine

Let’s explore three versions of the support vector algorithm: linear boundary, a polynomial (kernel) boundary, and a radial boundary. Like the Logistic regression, SVM will complain when there isn’t sufficient variation within a variable.

Linear Boundary

mod_svm_linear <-
  train(coverage ~ . ,
        data=train_data2, # Training data 
        method = "svmLinear", # SVM with a polynomial Kernel
        metric = "ROC", # area under the curve
        tuneGrid = expand.grid(C = c(.5,1)), # Add two tuning parameters
        trControl = control_conditions
  )
mod_svm_linear
Support Vector Machines with Linear Kernel 

4002 samples
  11 predictor
   2 classes: 'Coverage', 'No_Coverage' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 800, 801, 800, 801, 800 
Resampling results across tuning parameters:

  C    ROC        Sens       Spec     
  0.5  0.7774877  0.6969392  0.7113350
  1.0  0.7767458  0.6887603  0.7193955

ROC was used to select the optimal model using the largest value.
The final value used for the model was C = 0.5.

Polynomial Boundary

mod_svm_poly <-
  train(coverage ~ .,
        data=train_data2, # Training data 
        method = "svmPoly", # SVM with a polynomial Kernel
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )
plot(mod_svm_poly)

Radial Boundary

mod_svm_radial <-
  train(coverage ~ .,
        data=train_data2, # Training data 
         method = "svmRadial", # SVM with a Radial Kernel
        metric = "ROC", # area under the curve
        trControl = control_conditions
  )
plot(mod_svm_radial)

Model Comparison

How did the different methods perform? Which one did the best?

# Organize all model imputs as a list.
mod_list <-
  list(
    glm=mod_logit,
    knn1 = mod_knn,
    knn2 = mod_knn2,
    cart = mod_cart,
    rf = mod_rf,
    svm_linear = mod_svm_linear,
    svm_poly = mod_svm_poly,  
    svm_radial = mod_svm_radial 
  )

# Generate Plot to compare output. 
dotplot(resamples(mod_list),metric = "ROC")

Predictive Performance

Examine the predictive performance of the best performing model.

pred <- predict(mod_rf, newdata = test_data2)
confusionMatrix(table(pred,test_data2$coverage))
Confusion Matrix and Statistics

             
pred          Coverage No_Coverage
  Coverage         341          90
  No_Coverage      162         405
                                          
               Accuracy : 0.7475          
                 95% CI : (0.7193, 0.7742)
    No Information Rate : 0.504           
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.4955          
                                          
 Mcnemar's Test P-Value : 7.728e-06       
                                          
            Sensitivity : 0.6779          
            Specificity : 0.8182          
         Pos Pred Value : 0.7912          
         Neg Pred Value : 0.7143          
             Prevalence : 0.5040          
         Detection Rate : 0.3417          
   Detection Prevalence : 0.4319          
      Balanced Accuracy : 0.7481          
                                          
       'Positive' Class : Coverage        
                                          

We can also use the yardstick package to generate specific performance metrics.

# Generates predicted probabilities for both 1 ("Coverage") and 0 ("No Coverage")
pred_probability <- predict(mod_rf,newdata = test_data2,type="prob")
pred <- predict(mod_rf,newdata = test_data2,type="raw")

# Organize as a data frame
preformance <- tibble(truth = test_data2$coverage,
                      prob = pred_probability$Coverage,
                      pred = pred)

# Calculate performance metrics 
bind_rows(
  preformance %>% roc_auc(truth,prob),
  preformance %>% accuracy(truth,pred)  
)
LS0tCnRpdGxlOiAiUFBPTDY3MCB8IEludHJvZHVjdGlvbiB0byBEYXRhIFNjaWVuY2UgfCBXZWVrIDExIgpzdWJ0aXRsZTogIldhbGt0aHJvdWdoIHVzaW5nIGNsYXNzaWZpY2F0aW9uIG1ldGhvZHMgd2l0aCBgY2FyZXRgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDQKLS0tCgpgYGB7ciBTZXR1cCwgaW5jbHVkZT1GfQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEYsZXJyb3IgPSBGLG1lc3NhZ2UgPSBGLGNvbW1lbnQgPSBGLGNhY2hlPVQpCmBgYAoKIyBPdmVydmlldyAKCkluIHRoaXMgbm90ZWJvb2ssIHdlJ2xsIGFwcGx5IHRoZSBtYWNoaW5lIGxlYXJuaW5nIGNvbmNlcHRzIGNvdmVyZWQgaW4gdGhlIGNsYXNzaWZpY2F0aW9uIHN1cGVydmlzZWQgbGVhcm5pbmcgbGVjdHVyZS4gTm90ZSB0aGF0IHRoZXJlIGFyZSBtYW55IGxpYnJhcmllcyB0aGF0IGNhbiBwZXJmb3JtIHRoZSBtZXRob2RzIHRoYXQgd2UgcmV2aWV3ZWQgaW4gY2xhc3MsIGJ1dCB3ZSdsbCBmb2N1cyBoZXJlIG9uIHVzaW5nIHRoZSBgY2FyZXRgIHBhY2thZ2UgdG8gcGVyZm9ybSB0aGVzZSBvcGVyYXRpb25zLiAKCmBgYHtyfQpyZXF1aXJlKHRpZHl2ZXJzZSkKcmVxdWlyZShjYXJldCkgIyBmb3IgbWFjaGluZSBsZWFybmluZwpyZXF1aXJlKHJlY2lwZXMpICMgRm9yIHByZXByb2Nlc3NpbmcgeW91ciBkYXRhCnJlcXVpcmUocnNhbXBsZSkgIyBGb3IgdHJhaW4vdGVzdCBzcGxpdApyZXF1aXJlKHlhcmRzdGljaykgIyBGb3IgcGVyZm9ybWFuY2UgbWV0cmljcwpyZXF1aXJlKHJhdHRsZSkgIyBGb3IgbmljZSB0cmVlIHBsb3RzCgojIEZvciBwYXJhbGxlbGl6YXRpb24gKHRvIHJ1biB0aGUgbW9kZWxzIGFjcm9zcyBtYW55IGNvcmVzKSAtLSBzcGVlZHMgdXAgY29tcHV0YXRpb24hCiMgaW5zdGFsbC5wYWNrYWdlcygiZG9NQyIpCmRvTUM6OnJlZ2lzdGVyRG9NQygpCmBgYAoKCiMgRGF0YSAKClRoZSBmb2xsb3dpbmcgZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiByZWdhcmRpbmcgd2hldGhlciBvciBub3Qgc29tZW9uZSBoYXMgaGVhbHRoIGNvdmVyYWdlLiBUaGUgb3V0Y29tZSBvZiBpbnRlcmVzdCBpcyB3aGV0aGVyIG9mIG5vdCBhbiBpbmRpdmlkdWFsIGhhcyBoZWFsdGhjYXJlIGNvdmVyYWdlIG9yIG5vdC4gVGhlIGF2YWlsYWJsZSBwcmVkaWN0aXZlIGZlYXR1cmVzIGNhcHR1cmUgc29jaW8tZWNvbm9taWMgYW5kIGRlc2NyaXB0aXZlIGZhY3RvcnMuIAoKCmBgYHtyfQpzZXQuc2VlZCgxOTg4KQoKZGF0ID0gc3VwcHJlc3NNZXNzYWdlcyhyZWFkX2NzdigiaGVhbHRoLWNvdmVyYWdlLmNzdiIpKSAlPiUgCiAgCiAgIyBDb252ZXJ0IGVkdWNhdGlvbiBpbnRvIGFuIG9yZGVyZWQgY2F0ZWdvcnkuCiAgbXV0YXRlKAogICAgCiAgICBlZHVjID0gY2FzZV93aGVuKAogICAgICBlZHVjID09ICdMZXNzIHRoYW4gSFMnIH4gMCwKICAgICAgZWR1YyA9PSAnSFMgRGVncmVlJyB+IDEsCiAgICAgIGVkdWMgPT0gJ1VuZGVyZ3JhZHVhdGUgRGVncmVlJyB+IDIsCiAgICAgIGVkdWMgPT0gJ0dyYWR1YXRlIERlZ3JlZScgfiAzKSwKICAgIAogICAgcmFjZSA9IGNhc2Vfd2hlbigKICAgICAgcmFjZSA9PSAnV2hpdGUnIH4gJ3doaXRlJywKICAgICAgcmFjZSA9PSAnQmxhY2snIH4gJ2JsYWNrJywKICAgICAgcmFjZSA9PSAnQXNpYW4nIH4gJ2FzaWFuJywKICAgICAgVCB+ICdvdGhlcicpLAogICAgCiAgICByYWNlID0gZmFjdG9yKHJhY2UsbGV2ZWw9Yygid2hpdGUiLCdibGFjaycsJ2FzaWFuJywnb3RoZXInKSksCiAgICAKICAgIG1hciA9IGZhY3RvcihtYXIsbGV2ZWxzPSBjKCdOZXZlciBNYXJyaWVkJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdEaXZvcmNlZCcsJ01hcnJpZWQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NlcGFyYXRlZCcsJ1dpZG93ZWQnKSkKICAgIAogICAgKSAlPiUgCiAgCiAgIyBDb252ZXJ0IGFsbCByZW1haW5pbmcgZmFjdG9yIHZhcmlhYmxlcyBpbnRvIGNoYXJhY3RlciB2YXJpYWJsZXMKICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGFzLmZhY3RvcikgJT4lIAogIAogICMgTWFrZSBzdXJlIHRoYXQgdGhlIGNhdGVnb3J5IHlvdSB3YW50IHRvIHByZWRpY3QgaXMgdGhlIGZpcnN0IGNhdGVnb3J5IGluCiAgIyB5b3VyIGZhY3RvciB2YXJpYWJsZQogIG11dGF0ZShjb3ZlcmFnZSA9IGZhY3Rvcihjb3ZlcmFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQ292ZXJhZ2UiLCJOb19Db3ZlcmFnZSIpKSkgJT4lIAogIAogICMgT25seSB0YWtpbmcgYSByYW5kb20gc2FtcGxlIG9mIHRoZSBkYXRhIHNvIHRoZSBtb2RlbHMgcnVuIHF1aWNrZXIKICBzYW1wbGVfbig1MDAwKSAKCmhlYWQoZGF0KSAjIFBlZWsgYXQgdGhlIGRhdGEganVzdCB0byBtYWtlIHN1cmUgZXZlcnl0aGluZyB3YXMgcmVhZCBpbiBjb3JyZWN0bHkuIApgYGAKCgojIFNwbGl0IHRoZSBTYW1wbGU6IFRyYWluaW5nIGFuZCB0ZXN0IGRhdGEKCkJlZm9yZSBldmVudCBsb29raW5nIGF0IHRoZSBkYXRhLCBsZXQncyBzcGxpdCB0aGUgc2FtcGxlIHVwIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0LiBXZSdsbCBjb21wbGV0ZWx5IGhvbGQgb2ZmIG9uIHZpZXdpbmcgdGhlIHRlc3QgZGF0YSwgc28gYXMgbm90IHRvIGJpYXMgb3VyIGRldmVsb3BtZW50IG9mIHRoZSBsZWFybmluZyBtb2RlbC4gTm90ZSB0aGF0IGBzdHJhdGE9YCBhcmd1bWVudCBlbnN1cmVzIHRoYXQgd2UgaGF2ZSBhIHNpbWlsYXIgcHJvcG9ydGlvbiBvZiBjb3ZlcmVkIGFuZCBub3QgY292ZXJlZCBpbmRpdmlkdWFscyBpbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4gCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpzcGxpdHMgPSBpbml0aWFsX3NwbGl0KGRhdCxwcm9wID0gLjgsc3RyYXRhID0gY292ZXJhZ2UpCnRyYWluX2RhdGEgPSB0cmFpbmluZyhzcGxpdHMpICMgVXNlIDgwJSBvZiB0aGUgZGF0YSBhcyB0cmFpbmluZyBkYXRhIAp0ZXN0X2RhdGEgPSB0ZXN0aW5nKHNwbGl0cykgIyBob2xkb3V0IDIwJSBhcyB0ZXN0IGRhdGEgCgpkaW0odHJhaW5fZGF0YSkKZGltKHRlc3RfZGF0YSkgCmBgYAoKCgojIEV4YW1pbmUgdGhlIGRhdGEgCgo+IE5PVEU6IGBza2ltcmAgcHJvdmlkZXMgYSB2ZXJ5IG5pY2Ugc3VtbWFyeSBvZiB0aGUgZGF0YSwgYnV0IHRoZSBtaW5pLWhpc3RvZ3JhbXMgd2lsbCBjYXVzZSBhIGxvdCBvZiBncmllZiBpZiB5b3UncmUgdHJ5aW5nIHRvIGtuaXQgdG8gUERGLiBGZWVsIGZyZWUgdG8gdXNlIGBza2ltcmAgaW50ZXJhY3RpdmVseSBidXQgbm90IGlmIHlvdSdyZSBrbml0dGluZyB0byBQREYuIAoKYGBge3J9CnNraW1yOjpza2ltKHRyYWluX2RhdGEpCmBgYAoKClZpc3VhbGl6ZSB0aGUgZGlzdHJpYnV0aW9uIGZvciBlYWNoIHZhcmlhYmxlLgoKRmlyc3QsIGxldCdzIGxvb2sgYXQgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4KYGBge3IsZmlnLmhlaWdodD02LGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9MTB9CnRyYWluX2RhdGEgJT4lIAogIHNlbGVjdF9pZihpcy5mYWN0b3IpICU+JSAKICBnYXRoZXIodmFyLHZhbCkgJT4lIAogIGdncGxvdChhZXModmFsKSkgKwogIGdlb21fYmFyKCkgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgZmFjZXRfd3JhcCh+dmFyLHNjYWxlcz0iZnJlZV95IixuY29sPTEpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKHRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTYpKQpgYGAKCkZldyB0aGluZ3MgdG8gbm90ZTogCgotIEJhbGFuY2VkIGNsYXNzZXMgb24gdGhlIG91dGNvbWUgKG1vcmUgb3IgbGVzcykKLSBPdmVyIHJlcHJlc2VudGF0aXZlbmVzcyBvZiBzb21lIHJhY2lhbCBncm91cHMgaW4gdGhlIGRhdGEuIAotIEdvb2QgdmFyaWF0aW9uIG9uIG1hcml0YWwgb3V0Y29tZXMuCgpTZWNvbmQsIGxldCdzIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY29udGludW91cyB2YXJpYWJsZXMKCmBgYHtyfQp0cmFpbl9kYXRhICU+JSAKICBzZWxlY3RfaWYoaXMubnVtZXJpYykgJT4lIAogIGdhdGhlcih2YXIsdmFsKSAlPiUgCiAgZ2dwbG90KGFlcyh2YWwpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDc1KSArCiAgZmFjZXRfd3JhcCh+dmFyLHNjYWxlcz0iZnJlZSIsbmNvbD0xKSAKYGBgCgpUaGluZ3MgdG8gbm90ZToKCi0gV2FnZSBpcyByaWdodCBza2V3ZWQuIFRoZXJlIGFwcGVhcnMgdG8gYW4gb3V0bGllciBtYWtpbmcgYSBsb3Qgb2YgdGhlIG1vbmV5LiBUaGlzIG91dGxpZXIgY291bGQgY2F1c2UgcHJvYmxlbXMuIAotIEdvb2QgZGlzdHJpYnV0aW9uIG9uIHRoZSBhZ2UgdmFyaWFibGUuCi0gTWFqb3JpdHkgb2YgdGhlIG9ic2VydmF0aW9ucyBvbmx5IGhhdmUgYSBoaWdoIHNjaG9vbCBlZHVjYXRpb24uIAotIFRoZSBzY2FsZXMgZm9yIHRoZXNlIHZhcmlhYmxlcyB3aWxsIGhhdmUgdG8gYmUgYWRqdXN0ZWQuIAoKTGV0J3MgcGVlayBtb3JlIGNsb3NlbHkgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB3ZWFsdGggaW4gdGhlIHNhbXBsZS4gCgoKYGBge3IsZmlnLmhlaWdodD01LGZpZy53aWR0aD0xMH0KdHJhaW5fZGF0YSAlPiUgCiAgbXV0YXRlKHdlYWx0aF9iaW5zID0gY2FzZV93aGVuKAogICAgd2FnZSA9PSAwIH4gIlVuZW1wbG95ZWQiLAogICAgd2FnZSA+IDAgJiB3YWdlPD0gMzAwMDAgfiAiTG93IiwKICAgIHdhZ2UgPiAzMDAwMCAmIHdhZ2U8PSA3NTAwMCB+ICJNaWQiLAogICAgd2FnZSA+IDc1MDAwICYgd2FnZTw9IDIwMDAwMCB+ICJIaWdoIiwKICAgIHdhZ2UgPiAyMDAwMDAgfiAiSmVlei4uLiEiLAogICkpICU+JSAKICBtdXRhdGUod2VhbHRoX2JpbnMgPSBmY3RfaW5mcmVxKHdlYWx0aF9iaW5zKSkgJT4lCiAgZ2dwbG90KGFlcyh3ZWFsdGhfYmlucykpICsKICBnZW9tX2JhcigpCmBgYAoKClRoZXJlIGFyZSBhIGxvdCBvZiBpbmRpdmlkdWFscyB0aGF0IHJlcG9ydCAwIGluY29tZSAoVW5lbXBsb3llZD8pLiBXaGF0IGlzIHRoZSBhZ2UgZGlzdHJpYnV0aW9uIGZvciB0aG9zZSBpbmRpdmlkdWFscz8KCmBgYHtyLGZpZy5oZWlnaHQ9NSxmaWcud2lkdGg9MTB9CnRyYWluX2RhdGEgJT4lIAogIGZpbHRlcih3YWdlPT0wKSAlPiUgCiAgZ2dwbG90KGFlcyhhZ2UpKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGw9InN0ZWVsYmx1ZSIsYWxwaGE9LjUsY29sb3I9IndoaXRlIikKYGBgCgojIFByZS1wcm9jZXNzIHRoZSBEYXRhIAoKV2hhdCBkbyB3ZSBuZWVkIHRvIGRvPwoKLSBDYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gZHVtbWllcyAKLSBXZSdsbCBsaWtlbHkgbmVlZCB0byBsb2cgd2FnZSB0byBkZWFsIHdpdGggc2tldy4gCi0gU2NhbGUgYWdlIGFuZCB3YWdlIChzbyB0aGF0IHRoZXkgZmFsbCBpbnRvIGEgMCB0byAxIHJhbmdlKQotIE5vIG5lZWQgdG8gaW1wdXRlIG1pc3NpbmcgdmFsdWVzLiBBbGwgdGhlIGRhdGEgaXMgdGhlcmUuIAoKCmBgYHtyfQpyY3AgPC0gCiAgcmVjaXBlKGNvdmVyYWdlIH4gIC4sdHJhaW5fZGF0YSkgJT4lIAogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwtY292ZXJhZ2UpICU+JSAjIFdoeSBleGNsdWRlIG91dGNvbWUgdmFyaWFibGU/CiAgc3RlcF9sb2cod2FnZSxvZmZzZXQgPSAxKSAlPiUgIyBMb2cgdGhlIHNrZXdlZCB3YWdlIHZhcmlhYmxlCiAgc3RlcF9yYW5nZShhbGxfbnVtZXJpYygpKSAlPiUgICMgTm9ybWFsaXplIHNjYWxlCiAgcHJlcCgpCgoKIyBBcHBseSB0aGUgcmVjaXBlIHRvIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCnRyYWluX2RhdGEyIDwtIGJha2UocmNwLHRyYWluX2RhdGEpCnRlc3RfZGF0YTIgPC0gYmFrZShyY3AsdGVzdF9kYXRhKSAKYGBgCgoKQ2hlY2sgdGhhdCBvdXIgcHJlLXByb2Nlc3Npbmcgd29ya2VkLiAKCmBgYHtyfQpoZWFkKHRyYWluX2RhdGEyKQpgYGAKCmBgYHtyfQp0cmFpbl9kYXRhMiAlPiUgCiAgc2VsZWN0KHdhZ2UsYWdlKSAlPiUgCiAgZ2F0aGVyKHZhcix2YWwpICU+JSAKICBnZ3Bsb3QoYWVzKHZhbCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNzUpICsKICBmYWNldF93cmFwKH52YXIsc2NhbGVzPSJmcmVlIixuY29sPTEpCmBgYAoKCiMgQ3Jvc3MtdmFsaWRhdGlvbgoKV2hlbiBjb21wYXJpbmcgZGlmZmVyZW50IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSB3ZSdyZSBtYWtpbmcgYSBmYWlyIGFuZCBlcXVhbCBjb21wYXJpc29uLiBPbmUgd2F5IHRoYXQgcmFuZG9tIGNoYW5jZSBjYW4gc25lYWsgaW50byBvdXIgYXNzZXNzbWVudHMgb2YgbW9kZWwgZml0IGlzIHRocm91Z2ggY3Jvc3MtdmFsaWRhdGlvbi4gV2Ugd2FudCB0byBtYWtlIHN1cmUgdGhhdCB3ZSdyZSBjcm9zcy12YWxpZGF0aW5nIHRoZSBkYXRhIG9uIHRoZSBleGFjdCBzYW1lIGRhdGEgcGFydGl0aW9ucy4gCgpgY2FyZXRgIG1ha2VzIHRoaXMgZWFzZSB0byBkby4gTGV0J3MgdXNlIHRoZSBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBtZXRob2Qgd2l0aCAxMCBmb2xkcyBpbiB0aGUgZGF0YS4gCmBgYHtyfQpzZXQuc2VlZCgxOTg4KSAjIHNldCBhIHNlZWQgZm9yIHJlcGxpY2F0aW9uIHB1cnBvc2VzIAoKIyBQYXJ0aXRpb24gdGhlIGRhdGEgaW50byA1IGVxdWFsIGZvbGRzCmZvbGRzIDwtIGNyZWF0ZUZvbGRzKHRyYWluX2RhdGEyJGNvdmVyYWdlLCBrID0gNSkgCgpzYXBwbHkoZm9sZHMsbGVuZ3RoKQpgYGAKCk5vdywgbGV0J3MgdXNlIHRoZSBgdHJhaW5Db250cm9sKClgIGZ1bmN0aW9uIGZyb20gYGNhcmV0YCB0byBzZXQgdXAgb3VyIHZhbGlkYXRpb24gY29uZGl0aW9ucy4gQW4gaW1wb3J0YW50IGNoYW5nZXMgZnJvbSBsYXN0IHdlZWs6IHdlIGhhdmUgdG8gdGVsbCBgY2FyZXRgIHRoYXQgd2UgYXJlIGRlYWxpbmcgd2l0aCBhIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0uIFdlIGNhbiBkbyB0aGlzIGJ5IGFkZGluZyBgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5YCBhbmQgYGNsYXNzUHJvYnMgPSBUUlVFYCB0byB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBmdW5jdGlvbi4gCgoKYGBge3J9CmNvbnRyb2xfY29uZGl0aW9ucyA8LSAKICB0cmFpbkNvbnRyb2wobWV0aG9kPSdjdicsICMgSy1mb2xkIGNyb3NzIHZhbGlkYXRpb24KICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LCAjIE5lZWQgdGhpcyBiL2MgaXQncyBhIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0KICAgICAgICAgICAgICAgY2xhc3NQcm9icyA9IFRSVUUsICMgTmVlZCB0aGlzIGIvYyBpdCdzIGEgY2xhc3NpZmljYXRpb24gcHJvYmxlbQogICAgICAgICAgICAgICBpbmRleCA9IGZvbGRzICMgVGhlIGluZGljZXMgZm9yIG91ciBmb2xkcyAoc28gdGhleSBhcmUgYWx3YXlzIHRoZSBzYW1lKQogICkKYGBgCgpXZSdsbCBub3cgdXNlIHRoaXMgc2FtZSBjcm9zcy12YWxpZGF0aW9uIG9iamVjdCBmb3IgZXZlcnl0aGluZyB0aGF0IHdlIGRvLiAKCiMgTW9kZWxzCgpMZXQncyBleHBsb3JlIHRoZSBkaWZmZXJlbnQgbW9kZWxzIHRoYXQgd2UgY292ZXJlZCBpbiB0aGUgbGVjdHVyZSB1c2luZyB0aGUgc2FtZSBwYWNrYWdlIGZyYW1ld29yay4gQXMgd2Ugc2F3IGxhc3QgdGltZSBpbiBjbGFzcywgYGNhcmV0YCBmYWNpbGl0YXRlcyB0aGlzIHRhc2sgbmljZWx5LiAKCiMjIExvZ2lzdGljIFJlZ3Jlc3Npb24KCmBgYHtyfQptb2RfbG9naXQgPC0KICB0cmFpbihjb3ZlcmFnZSB+IC4sIAogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAiZ2xtIiwgIyBsb2dpdCBmdW5jdGlvbgogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQptb2RfbG9naXQKYGBgCgojIyBLLU5lYXJlc3QgTmVpZ2hib3JzCgo+IFJlbWVtYmVyOiB0aGVzZSBhbGdvcml0aG1zIGFyZSBjb21wdXRhdGlvbmFsbHkgZGVtYW5kaW5nLCBzbyB0aGV5IGNhbiB0YWtlIGEgbGl0dGxlIHRpbWUgdG8gcnVuIGRlcGVuZGluZyBvbiB0aGUgcG93ZXIgb2YgeW91ciBtYWNoaW5lCgpgYGB7cn0KbW9kX2tubiA8LQogIHRyYWluKGNvdmVyYWdlIH4gLiwgIyBFcXVhdGlvbiAob3V0Y29tZSBhbmQgZXZlcnl0aGluZyBlbHNlKQogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAia25uIiwgIyBLLU5lYXJlc3QgTmVpZ2hib3JzIEFsZ29yaXRobQogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIHBlcmZvcm1hbmNlIHBsb3RzLiBXaGF0IGRvIHdlIHNlZT8KCmBgYHtyfQptb2Rfa25uCmBgYAoKCi0gYGNhcmV0YCBoYXMgZGVmYXVsdCBzZXR0aW5ncyB0aGF0IGF1dG8gZXhwbG9yZSBkaWZmZXJlbnQgdHVubmluZyBwYXJhbWV0ZXJzIGZvciB0aGUgbW9kZWwuIAotIFdlIGNhbiBlYXNpbHkgcGxvdCB0aGVzZSB0dW5pbmcgZmVhdHVyZXMgdXNpbmcgdGhlIHN0YW5kYXJkIHBsb3QgZnVuY3Rpb25zICh0aGVzZSBmdW5jdGlvbiBoYXZlIGJlZW4gb3Zlci1yaWRkZW4gaW4gUikKCmBgYHtyfQpwbG90KG1vZF9rbm4pCmBgYAoKCk5vdywgbGV0J3Mgc2F5IHdlIHdhbnRlZCB0byBhZGp1c3QgdGhlIHR1bm5pbmcgcGFyYW1ldGVycy4gV2UgY2FuIGV4cGxvcmUgZGlmZmVyZW50IG1vZGVsIHR1bmluZ3MgYnkgdXNpbmcgdGhlIGB0dW5lR3JpZGAgYXJndW1lbnQuIAoKYGBge3J9Cmtubl90dW5lID0gZXhwYW5kLmdyaWQoayA9IGMoMSwzLDEwLDUwKSkKa25uX3R1bmUKYGBgCgoKCmBgYHtyfQptb2Rfa25uMiA8LQogIHRyYWluKGNvdmVyYWdlIH4gLiwgIyBFcXVhdGlvbiAob3V0Y29tZSBhbmQgZXZlcnl0aGluZyBlbHNlKQogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAia25uIiwgIyBLLU5lYXJlc3QgTmVpZ2hib3JzIEFsZ29yaXRobQogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHVuZUdyaWQgPSBrbm5fdHVuZSwgIyBhZGQgdGhlIHR1bmluZyBwYXJhbWV0ZXJzIGhlcmUgCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCmBgYHtyfQpwbG90KG1vZF9rbm4yKQpgYGAKCgojIyBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVlcyAoQ0FSVCkKCmBgYHtyfQptb2RfY2FydCA8LQogIHRyYWluKGNvdmVyYWdlIH4gLiwgIyBFcXVhdGlvbiAob3V0Y29tZSBhbmQgZXZlcnl0aGluZyBlbHNlKQogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAicnBhcnQiLCAjIENsYXNzaWZpY2F0aW9uIFRyZWUKICAgICAgICBtZXRyaWMgPSAiUk9DIiwgIyBhcmVhIHVuZGVyIHRoZSBjdXJ2ZQogICAgICAgIHRyQ29udHJvbCA9IGNvbnRyb2xfY29uZGl0aW9ucwogICkKYGBgCgoKYGBge3J9CnBsb3QobW9kX2NhcnQpCmBgYAoKU2hhbGxvd2VyIHRyZWVzIGNvbnZlcmdlIHRvIGEgY29pbiBmbGlwLiBUaGUgZGVlcGVzdCB0cmVlIGFwcGVhcnMgdG8gYmUgdGhlIGJldHMKCkxldCdzIHZpc3VhbGl6ZSB0aGUgZGVjaXNpb24gdHJlZS4uLgoKYGBge3J9CiMgVGhpcyB0cmVlIGdvZXMgcmVhbGx5IGRlZXAKZmFuY3lScGFydFBsb3QobW9kX2NhcnQkZmluYWxNb2RlbCkKYGBgCgpXZSBjYW4gYWN0dWFsbHkgcHJpbnQgb3V0IHRoZSBsYXJnZXIgZGVjaXNpb24gdHJlZSB0byBsb29rIGF0IGl0IGFzIGEgcHJpbnQgb3V0Li4uLiBhcyB3ZSBjYW4gc2VlIHRoaXMgaXMgYSBMT1QuCmBgYHtyfQpwcmludChtb2RfY2FydCRmaW5hbE1vZGVsKQpgYGAKCgojIyBSYW5kb20gRm9yZXN0CgpgYGB7cn0KbW9kX3JmIDwtCiAgdHJhaW4oY292ZXJhZ2UgfiAuLCAjIEVxdWF0aW9uIChvdXRjb21lIGFuZCBldmVyeXRoaW5nIGVsc2UpCiAgICAgICAgZGF0YT10cmFpbl9kYXRhMiwgIyBUcmFpbmluZyBkYXRhIAogICAgICAgIG1ldGhvZCA9ICJyYW5nZXIiLCAjIHJhbmRvbSBmb3Jlc3QgKHJhbmdlciBpcyBtdWNoIGZhc3RlciB0aGFuIHJmKQogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCgpUaGVyZSBhcmUgdGhyZWUgdHVubmluZyBwYXJhbWV0ZXJzIGluIHRoaXMgbW9kZWw6CgotIGBtdHJ5YDogdGhlIG51bWJlciBvZiBwcmVkaWN0b3JzIHRoYXQgd2UnbGwgcmFuZG9tbHkgc2VsZWN0LgotIGBzcGxpdHJ1bGVgOiB0aGUgd2F5IHdlIGRldGVybWluZSBob3cgdGhlIG5vZGVzIHNob3VsZCBiZSBzcGxpdAogIC0gYGdpbmlgID0gbm9kZSBwdXJpdHkgKHNlZSBjbGFzcyBsZWN0dXJlKQogIC0gYGV4dHJhdHJlZXNgID0gZG9lc24ndCBiYWcsIHJhbmRvbWx5IHNlbGVjdCB2YXJzLCBidXQganVzdCByYW5kb21seSBzcGxpdCwgYWNjZXB0IHRoZSBiZXN0IHNwbGl0LiAoU2hvcnQgZm9yICJFeHRyZW1lbHkgUmFuZG9taXplIFRyZWVzIikKLSAnbWluLm5vZGUuc2l6ZSc6IHRoZSBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgdGhhdCBjYW4gYmUgaW4gZWFjaCB0ZXJtaW5hbCBub2RlIChkZWZhdWx0IGZpeGVzIHRoaXMgYXQgMSkKCmBgYHtyfQpwbG90KG1vZF9yZikKYGBgCgoKIyMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZQoKTGV0J3MgZXhwbG9yZSB0aHJlZSB2ZXJzaW9ucyBvZiB0aGUgc3VwcG9ydCB2ZWN0b3IgYWxnb3JpdGhtOiBsaW5lYXIgYm91bmRhcnksIGEgcG9seW5vbWlhbCAoa2VybmVsKSBib3VuZGFyeSwgYW5kIGEgcmFkaWFsIGJvdW5kYXJ5LiBMaWtlIHRoZSBMb2dpc3RpYyByZWdyZXNzaW9uLCBTVk0gd2lsbCBjb21wbGFpbiB3aGVuIHRoZXJlIGlzbid0IHN1ZmZpY2llbnQgdmFyaWF0aW9uIHdpdGhpbiBhIHZhcmlhYmxlLiAgCgoKIyMjIExpbmVhciBCb3VuZGFyeQpgYGB7cn0KbW9kX3N2bV9saW5lYXIgPC0KICB0cmFpbihjb3ZlcmFnZSB+IC4gLAogICAgICAgIGRhdGE9dHJhaW5fZGF0YTIsICMgVHJhaW5pbmcgZGF0YSAKICAgICAgICBtZXRob2QgPSAic3ZtTGluZWFyIiwgIyBTVk0gd2l0aCBhIHBvbHlub21pYWwgS2VybmVsCiAgICAgICAgbWV0cmljID0gIlJPQyIsICMgYXJlYSB1bmRlciB0aGUgY3VydmUKICAgICAgICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKEMgPSBjKC41LDEpKSwgIyBBZGQgdHdvIHR1bmluZyBwYXJhbWV0ZXJzCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCmBgYHtyfQptb2Rfc3ZtX2xpbmVhcgpgYGAKCgojIyMgUG9seW5vbWlhbCBCb3VuZGFyeQoKYGBge3IsY29tbWVudD1GLGVycm9yPUYsd2FybmluZz1GfQptb2Rfc3ZtX3BvbHkgPC0KICB0cmFpbihjb3ZlcmFnZSB+IC4sCiAgICAgICAgZGF0YT10cmFpbl9kYXRhMiwgIyBUcmFpbmluZyBkYXRhIAogICAgICAgIG1ldGhvZCA9ICJzdm1Qb2x5IiwgIyBTVk0gd2l0aCBhIHBvbHlub21pYWwgS2VybmVsCiAgICAgICAgbWV0cmljID0gIlJPQyIsICMgYXJlYSB1bmRlciB0aGUgY3VydmUKICAgICAgICB0ckNvbnRyb2wgPSBjb250cm9sX2NvbmRpdGlvbnMKICApCmBgYCAKCmBgYHtyLH0KcGxvdChtb2Rfc3ZtX3BvbHkpCmBgYAoKCiMjIyBSYWRpYWwgQm91bmRhcnkKCmBgYHtyfQptb2Rfc3ZtX3JhZGlhbCA8LQogIHRyYWluKGNvdmVyYWdlIH4gLiwKICAgICAgICBkYXRhPXRyYWluX2RhdGEyLCAjIFRyYWluaW5nIGRhdGEgCiAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLCAjIFNWTSB3aXRoIGEgUmFkaWFsIEtlcm5lbAogICAgICAgIG1ldHJpYyA9ICJST0MiLCAjIGFyZWEgdW5kZXIgdGhlIGN1cnZlCiAgICAgICAgdHJDb250cm9sID0gY29udHJvbF9jb25kaXRpb25zCiAgKQpgYGAKCgpgYGB7cn0KcGxvdChtb2Rfc3ZtX3JhZGlhbCkKYGBgCgoKIyBNb2RlbCBDb21wYXJpc29uCgpIb3cgZGlkIHRoZSBkaWZmZXJlbnQgbWV0aG9kcyBwZXJmb3JtPyBXaGljaCBvbmUgZGlkIHRoZSBiZXN0PwoKYGBge3IsZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9NH0KIyBPcmdhbml6ZSBhbGwgbW9kZWwgaW1wdXRzIGFzIGEgbGlzdC4KbW9kX2xpc3QgPC0KICBsaXN0KAogICAgZ2xtPW1vZF9sb2dpdCwKICAgIGtubjEgPSBtb2Rfa25uLAogICAga25uMiA9IG1vZF9rbm4yLAogICAgY2FydCA9IG1vZF9jYXJ0LAogICAgcmYgPSBtb2RfcmYsCiAgICBzdm1fbGluZWFyID0gbW9kX3N2bV9saW5lYXIsCiAgICBzdm1fcG9seSA9IG1vZF9zdm1fcG9seSwgIAogICAgc3ZtX3JhZGlhbCA9IG1vZF9zdm1fcmFkaWFsIAogICkKCiMgR2VuZXJhdGUgUGxvdCB0byBjb21wYXJlIG91dHB1dC4gCmRvdHBsb3QocmVzYW1wbGVzKG1vZF9saXN0KSxtZXRyaWMgPSAiUk9DIikKYGBgCgoKIyBQcmVkaWN0aXZlIFBlcmZvcm1hbmNlCgpFeGFtaW5lIHRoZSBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9mIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwuIAoKYGBge3J9CnByZWQgPC0gcHJlZGljdChtb2RfcmYsIG5ld2RhdGEgPSB0ZXN0X2RhdGEyKQpjb25mdXNpb25NYXRyaXgodGFibGUocHJlZCx0ZXN0X2RhdGEyJGNvdmVyYWdlKSkKYGBgIAoKV2UgY2FuIGFsc28gdXNlIHRoZSBgeWFyZHN0aWNrYCBwYWNrYWdlIHRvIGdlbmVyYXRlIHNwZWNpZmljIHBlcmZvcm1hbmNlIG1ldHJpY3MuCgpgYGB7cn0KIyBHZW5lcmF0ZXMgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIGJvdGggMSAoIkNvdmVyYWdlIikgYW5kIDAgKCJObyBDb3ZlcmFnZSIpCnByZWRfcHJvYmFiaWxpdHkgPC0gcHJlZGljdChtb2RfcmYsbmV3ZGF0YSA9IHRlc3RfZGF0YTIsdHlwZT0icHJvYiIpCnByZWQgPC0gcHJlZGljdChtb2RfcmYsbmV3ZGF0YSA9IHRlc3RfZGF0YTIsdHlwZT0icmF3IikKCiMgT3JnYW5pemUgYXMgYSBkYXRhIGZyYW1lCnByZWZvcm1hbmNlIDwtIHRpYmJsZSh0cnV0aCA9IHRlc3RfZGF0YTIkY292ZXJhZ2UsCiAgICAgICAgICAgICAgICAgICAgICBwcm9iID0gcHJlZF9wcm9iYWJpbGl0eSRDb3ZlcmFnZSwKICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBwcmVkKQoKIyBDYWxjdWxhdGUgcGVyZm9ybWFuY2UgbWV0cmljcyAKYmluZF9yb3dzKAogIHByZWZvcm1hbmNlICU+JSByb2NfYXVjKHRydXRoLHByb2IpLAogIHByZWZvcm1hbmNlICU+JSBhY2N1cmFjeSh0cnV0aCxwcmVkKSAgCikKYGBgCgo=