My motivation and code I used in here is an adaptation from Dr.Julia Silage’s blog bird-baths. I’ve applied the similar modeling process to Default dataset from {ISLR} package.

The goal is to build logistic regression model to predict default status.

Explore Data

library(tidyverse)
library(ISLR)
theme_set(theme_bw())

Let’s take a look at the Default data set. It has 2 numeric variables: balance and income; and 2 factor variables: default and student

summary(Default)
##  default    student       balance           income     
##  No :9667   No :7056   Min.   :   0.0   Min.   :  772  
##  Yes: 333   Yes:2944   1st Qu.: 481.7   1st Qu.:21340  
##                        Median : 823.6   Median :34553  
##                        Mean   : 835.4   Mean   :33517  
##                        3rd Qu.:1166.3   3rd Qu.:43808  
##                        Max.   :2654.3   Max.   :73554

Detailed summary can be done with skimr::skim()

skimr::skim(Default)
Data summary
Name Default
Number of rows 10000
Number of columns 4
_______________________
Column type frequency:
factor 2
numeric 2
________________________
Group variables None

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
default 0 1 FALSE 2 No: 9667, Yes: 333
student 0 1 FALSE 2 No: 7056, Yes: 2944

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
balance 0 1 835.37 483.71 0.00 481.73 823.64 1166.31 2654.32 ▆▇▅▁▁
income 0 1 33516.98 13336.64 771.97 21340.46 34552.64 43807.73 73554.23 ▂▇▇▅▁

Please note that there are no missing values, which is great!

Goal

The goal here is to build a logistic regression model to predict default.

Plot Relationship

I will explore the relationship between default and other variables (income, balace, student). Let’s make some plots!

Default %>% 
  ggplot(aes(balance, income, color = default)) + 
  geom_point(alpha = 0.4) +
  scale_color_brewer(palette = "Set1", direction = -1) +
  labs(title = "Default status by income and balance") 

By visual inspection, balance looks like a better predictor for default than income.

p1 <- Default %>% 
  ggplot(aes(balance, default, fill = student)) +
  geom_boxplot(alpha = 0.8) +
  scale_fill_brewer(palette = "Dark2") +
  labs(title = "Distribution of default",
       subtitle = "by balance and student status",
       caption = "Data from ISLR package") 
  
p1 

This plot shows that balance and student seem to be a decent predictor of default.

Build Model

Goal: Outcome variable = default (factor)

Simple Example

First, I will use glm() function to build logistic regression model using all predictors.

glm_fit_all <- glm(default ~ ., data = Default, family = binomial)
summary(glm_fit_all)
## 
## Call:
## glm(formula = default ~ ., family = binomial, data = Default)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.4691  -0.1418  -0.0557  -0.0203   3.7383  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -1.087e+01  4.923e-01 -22.080  < 2e-16 ***
## studentYes  -6.468e-01  2.363e-01  -2.738  0.00619 ** 
## balance      5.737e-03  2.319e-04  24.738  < 2e-16 ***
## income       3.033e-06  8.203e-06   0.370  0.71152    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 2920.6  on 9999  degrees of freedom
## Residual deviance: 1571.5  on 9996  degrees of freedom
## AIC: 1579.5
## 
## Number of Fisher Scoring iterations: 8

Coefficient of income is not significant (p = 0.7115203). Let’s try remove income out.

glm_fit_st_bal <- glm(default ~ student + balance, 
    data = Default, 
    family = binomial)
glm_fit_st_bal
## 
## Call:  glm(formula = default ~ student + balance, family = binomial, 
##     data = Default)
## 
## Coefficients:
## (Intercept)   studentYes      balance  
##  -10.749496    -0.714878     0.005738  
## 
## Degrees of Freedom: 9999 Total (i.e. Null);  9997 Residual
## Null Deviance:       2921 
## Residual Deviance: 1572  AIC: 1578

Model that include only student and balance has lower estimation of test error (i.e., AIC & BIC).

list("All Predictors" = glm_fit_all,
     "student + balance" = glm_fit_st_bal) %>% 
  map_dfr( 
    broom::glance, 
    .id = "Model") %>% 
  select(Model, AIC, BIC)

Modeling Process

Now, let’s apply full modeling process with tidymodels.

library(tidymodels)

Split Data

First, we need to spending data budgets in 2 portions: training and testing data.

set.seed(123)
# Split to Test and Train
Default_split <- initial_split(Default, strata = default)

Default_train <- training(Default_split) # 75% to traning data
Default_test <- testing(Default_split) # 25% to test data

Default_split
## <Analysis/Assess/Total>
## <7500/2500/10000>

Resampling Method

I will use 10-Fold Cross-Validation to resample the training data.

set.seed(234)

Default_folds <- vfold_cv(Default_train, v = 10, strata = default)
Default_folds 

Note that I use strata = default because the frequency of each class is quite difference.

Default %>% count(default)

For each folds, 6750 rows will spend on fitting models and 750 spend on analysis of model performance:

Default_folds$splits[[1]] 
## <Analysis/Assess/Total>
## <6750/750/7500>

Specification

Model Specification

glm_spec <- logistic_reg()
glm_spec
## Logistic Regression Model Specification (classification)
## 
## Computational engine: glm

Feature Engineering Specification

rec_basic <- recipe(default ~ ., data = Default) %>% 
  step_dummy(all_nominal_predictors())

summary(rec_basic)

Preview of engineered training data

You can see that student was replaced by student_Yes with 0-1 encoding. What does it mean?

rec_basic %>% 
  prep(log_change = T) %>% 
  bake(new_data = Default_train)
## step_dummy (dummy_72FQt): 
##  new (1): student_Yes
##  removed (1): student

From contrast() function we can see the dummy encoding of the student (factor) variable: No = 0, Yes = 1.

contrasts(Default$student)
##     Yes
## No    0
## Yes   1

Workflow

workflow() will bundle blueprint of feature engineering and model specification together.

wf_basic <- workflow(rec_basic, glm_spec)
wf_basic
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 1 Recipe Step
## 
## • step_dummy()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
## 
## Computational engine: glm

Fit Model to Resampled Data

doParallel::registerDoParallel()

ctrl_preds <- control_resamples(save_pred = TRUE)
## Fit
rs_basic <- fit_resamples(wf_basic, resamples =  Default_folds, control = ctrl_preds)

head(rs_basic)

ROC Curve

From ROC curve we can see that it has pretty good upper-left bulging curve.

augment(rs_basic) %>% 
  roc_curve(truth = default, .pred_No) %>% 
  autoplot()

This would result in AUC (area under the ROC curve) and accuracy close to 1.

collect_metrics(rs_basic)

Improve the Model

As we can see from the beginning, removing income from predictor result in better estimation of test error by AIC and BIC.

Now, I will remove income from the recipes:

rec_simple <- rec_basic %>% 
  remove_role(income, old_role = "predictor")

summary(rec_simple)

And I will update the WorkFlow

wf_simple <- workflow(rec_simple, glm_spec)

Then the rest is the same. So, I wrote a simple wrapper function to do it.

update_workflow <- function(wf) {

  ctrl_preds <- control_resamples(save_pred = TRUE)
  rs <- fit_resamples(wf, resamples = Default_folds, control = ctrl_preds)
  rs
  
}

rs_simple <- update_workflow(wf_simple)
rs_ls <- list("All Predictors" = rs_basic,
              "student + balance" = rs_simple)
roc_df <- rs_ls %>% 
  map_dfr(~augment(.x) %>% roc_curve(truth = default, .pred_No),
      .id = "Predictors"
      )

ROC curve of the improved model

This plot show comparison of ROC curves of the 2 logistic regression models.

  • Red line shows model that use all predictors: student, balance and income.
  • Blue line shows model that use 2 predictor: student and balance.
roc_df %>% 
  ggplot(aes(1-specificity, sensitivity, color = Predictors)) +
  geom_line(size = 1, alpha = 0.6, linetype = "dashed") +
  geom_abline(intercept = 0, slope = 1, linetype = "dotted")

rs_ls %>% 
  map_dfr(
    collect_metrics, .id = "Features"
  )

You can see that a simpler model result in a similar (or may be slightly improved) AUC. So it’s reasonable to prefer it over more complicated model.

Evaluate Model

In this section, I will evaluate the simpler model with 2 predictors (student, balance).

Fit to Training data

First, fit the model to training data.

Default_fit <- fit(wf_simple, Default_train)
Default_fit
## ══ Workflow [trained] ══════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 1 Recipe Step
## 
## • step_dummy()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## 
## Call:  stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)
## 
## Coefficients:
## (Intercept)      balance  student_Yes  
##   -10.85340      0.00578     -0.71313  
## 
## Degrees of Freedom: 7499 Total (i.e. Null);  7497 Residual
## Null Deviance:       2158 
## Residual Deviance: 1139  AIC: 1145

Use Testing data to Predict

Then, use test data to predict default.

Default_pred <- 
  augment(Default_fit, Default_test) %>% 
  bind_cols(
    predict(Default_fit, Default_test, type = "conf_int")
  )
library(latex2exp)
p2 <- Default_pred %>% 
  ggplot(aes(balance, .pred_Yes, fill = student)) +
  geom_line(aes(color = student)) +
  geom_ribbon(aes(ymin = .pred_lower_Yes, ymax = .pred_upper_Yes),
              alpha = 0.3) +
  scale_fill_brewer(palette = "Dark2") +
  scale_color_brewer(palette = "Dark2") +
  labs(y = TeX("$\\hat{p}(default)$"),
       caption = "Area indicate 95% CI of the estimated probability") +
  labs(title = "Predicted probability of default", 
       subtitle = "by logistic regression with 2 predictors")

p2 

This plot show estimated probability of default if we know the values of predictors: student and balance by using logistic regression model fitted on training data. The prediction was made by plugging test data to the model.

Summary Plot

library(patchwork)
p1 + p2 

The left-sided plot showed default status by balance and student status as observed in the Default data set. After multivariate logistic regression model (default on balance and student) was fitted to the training data, the predicted probability of default using the model was shown in the right-sided plot.

The End

If you have any comment or noticed any mistake, please feel free to share it with me in the comment section.

Thanks a lot for reading.

devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value                       
##  version  R version 4.1.0 (2021-05-18)
##  os       macOS Big Sur 10.16         
##  system   x86_64, darwin17.0          
##  ui       X11                         
##  language (EN)                        
##  collate  en_US.UTF-8                 
##  ctype    en_US.UTF-8                 
##  tz       Asia/Bangkok                
##  date     2021-10-11                  
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package      * version    date       lib source                        
##  assertthat     0.2.1      2019-03-21 [1] CRAN (R 4.1.0)                
##  backports      1.2.1      2020-12-09 [1] CRAN (R 4.1.0)                
##  base64enc      0.1-3      2015-07-28 [1] CRAN (R 4.1.0)                
##  broom        * 0.7.9      2021-07-27 [1] CRAN (R 4.1.0)                
##  bslib          0.2.5.1    2021-05-18 [1] CRAN (R 4.1.0)                
##  cachem         1.0.5      2021-05-15 [1] CRAN (R 4.1.0)                
##  callr          3.7.0      2021-04-20 [1] CRAN (R 4.1.0)                
##  cellranger     1.1.0      2016-07-27 [1] CRAN (R 4.1.0)                
##  class          7.3-19     2021-05-03 [1] CRAN (R 4.1.0)                
##  cli            3.0.1      2021-07-17 [1] CRAN (R 4.1.0)                
##  codetools      0.2-18     2020-11-04 [1] CRAN (R 4.1.0)                
##  colorspace     2.0-2      2021-06-24 [1] CRAN (R 4.1.0)                
##  crayon         1.4.1      2021-02-08 [1] CRAN (R 4.1.0)                
##  DBI            1.1.1      2021-01-15 [1] CRAN (R 4.1.0)                
##  dbplyr         2.1.1      2021-04-06 [1] CRAN (R 4.1.0)                
##  desc           1.3.0      2021-03-05 [1] CRAN (R 4.1.0)                
##  devtools       2.4.2      2021-06-07 [1] CRAN (R 4.1.0)                
##  dials        * 0.0.10     2021-09-10 [1] CRAN (R 4.1.0)                
##  DiceDesign     1.9        2021-02-13 [1] CRAN (R 4.1.0)                
##  digest         0.6.28     2021-09-23 [1] CRAN (R 4.1.0)                
##  doParallel     1.0.16     2020-10-16 [1] CRAN (R 4.1.0)                
##  dplyr        * 1.0.7      2021-06-18 [1] CRAN (R 4.1.0)                
##  ellipsis       0.3.2      2021-04-29 [1] CRAN (R 4.1.0)                
##  evaluate       0.14       2019-05-28 [1] CRAN (R 4.1.0)                
##  fansi          0.5.0      2021-05-25 [1] CRAN (R 4.1.0)                
##  farver         2.1.0      2021-02-28 [1] CRAN (R 4.1.0)                
##  fastmap        1.1.0      2021-01-25 [1] CRAN (R 4.1.0)                
##  forcats      * 0.5.1      2021-01-27 [1] CRAN (R 4.1.0)                
##  foreach        1.5.1      2020-10-15 [1] CRAN (R 4.1.0)                
##  fs             1.5.0      2020-07-31 [1] CRAN (R 4.1.0)                
##  furrr          0.2.3      2021-06-25 [1] CRAN (R 4.1.0)                
##  future         1.22.1     2021-08-25 [1] CRAN (R 4.1.0)                
##  generics       0.1.0      2020-10-31 [1] CRAN (R 4.1.0)                
##  ggplot2      * 3.3.5      2021-06-25 [1] CRAN (R 4.1.0)                
##  globals        0.14.0     2020-11-22 [1] CRAN (R 4.1.0)                
##  glue           1.4.2      2020-08-27 [1] CRAN (R 4.1.0)                
##  gower          0.2.2      2020-06-23 [1] CRAN (R 4.1.0)                
##  GPfit          1.0-8      2019-02-08 [1] CRAN (R 4.1.0)                
##  gtable         0.3.0      2019-03-25 [1] CRAN (R 4.1.0)                
##  hardhat        0.1.6      2021-07-14 [1] CRAN (R 4.1.0)                
##  haven          2.4.3      2021-08-04 [1] CRAN (R 4.1.0)                
##  here         * 1.0.1      2020-12-13 [1] CRAN (R 4.1.0)                
##  highr          0.9        2021-04-16 [1] CRAN (R 4.1.0)                
##  hms            1.1.0      2021-05-17 [1] CRAN (R 4.1.0)                
##  htmltools      0.5.2      2021-08-25 [1] CRAN (R 4.1.0)                
##  httr           1.4.2      2020-07-20 [1] CRAN (R 4.1.0)                
##  infer        * 1.0.0      2021-08-13 [1] CRAN (R 4.1.0)                
##  ipred          0.9-11     2021-03-12 [1] CRAN (R 4.1.0)                
##  ISLR         * 1.2        2017-10-20 [1] CRAN (R 4.1.0)                
##  iterators      1.0.13     2020-10-15 [1] CRAN (R 4.1.0)                
##  jquerylib      0.1.4      2021-04-26 [1] CRAN (R 4.1.0)                
##  jsonlite       1.7.2      2020-12-09 [1] CRAN (R 4.1.0)                
##  knitr          1.34       2021-09-09 [1] CRAN (R 4.1.0)                
##  labeling       0.4.2      2020-10-20 [1] CRAN (R 4.1.0)                
##  latex2exp    * 0.5.0      2021-03-18 [1] CRAN (R 4.1.0)                
##  lattice        0.20-44    2021-05-02 [1] CRAN (R 4.1.0)                
##  lava           1.6.9      2021-03-11 [1] CRAN (R 4.1.0)                
##  lhs            1.1.3      2021-09-08 [1] CRAN (R 4.1.0)                
##  lifecycle      1.0.1      2021-09-24 [1] CRAN (R 4.1.0)                
##  listenv        0.8.0      2019-12-05 [1] CRAN (R 4.1.0)                
##  lubridate      1.7.10     2021-02-26 [1] CRAN (R 4.1.0)                
##  magrittr       2.0.1      2020-11-17 [1] CRAN (R 4.1.0)                
##  MASS           7.3-54     2021-05-03 [1] CRAN (R 4.1.0)                
##  Matrix         1.3-3      2021-05-04 [1] CRAN (R 4.1.0)                
##  memoise        2.0.0      2021-01-26 [1] CRAN (R 4.1.0)                
##  modeldata    * 0.1.1      2021-07-14 [1] CRAN (R 4.1.0)                
##  modelr         0.1.8      2020-05-19 [1] CRAN (R 4.1.0)                
##  munsell        0.5.0      2018-06-12 [1] CRAN (R 4.1.0)                
##  nnet           7.3-16     2021-05-03 [1] CRAN (R 4.1.0)                
##  parallelly     1.28.1     2021-09-09 [1] CRAN (R 4.1.0)                
##  parsnip      * 0.1.7      2021-07-21 [1] CRAN (R 4.1.0)                
##  patchwork    * 1.1.1      2020-12-17 [1] CRAN (R 4.1.0)                
##  pillar         1.6.2      2021-07-29 [1] CRAN (R 4.1.0)                
##  pkgbuild       1.2.0      2020-12-15 [1] CRAN (R 4.1.0)                
##  pkgconfig      2.0.3      2019-09-22 [1] CRAN (R 4.1.0)                
##  pkgload        1.2.1      2021-04-06 [1] CRAN (R 4.1.0)                
##  plyr           1.8.6      2020-03-03 [1] CRAN (R 4.1.0)                
##  prettyunits    1.1.1      2020-01-24 [1] CRAN (R 4.1.0)                
##  pROC           1.17.0.1   2021-01-13 [1] CRAN (R 4.1.0)                
##  processx       3.5.2      2021-04-30 [1] CRAN (R 4.1.0)                
##  prodlim        2019.11.13 2019-11-17 [1] CRAN (R 4.1.0)                
##  ps             1.6.0      2021-02-28 [1] CRAN (R 4.1.0)                
##  purrr        * 0.3.4      2020-04-17 [1] CRAN (R 4.1.0)                
##  R6             2.5.1      2021-08-19 [1] CRAN (R 4.1.0)                
##  RColorBrewer   1.1-2      2014-12-07 [1] CRAN (R 4.1.0)                
##  Rcpp           1.0.7      2021-07-07 [1] CRAN (R 4.1.0)                
##  readr        * 2.0.1      2021-08-10 [1] CRAN (R 4.1.0)                
##  readxl         1.3.1      2019-03-13 [1] CRAN (R 4.1.0)                
##  recipes      * 0.1.16     2021-04-16 [1] CRAN (R 4.1.0)                
##  remotes        2.4.0      2021-06-02 [1] CRAN (R 4.1.0)                
##  repr           1.1.3      2021-01-21 [1] CRAN (R 4.1.0)                
##  reprex         2.0.1      2021-08-05 [1] CRAN (R 4.1.0)                
##  rlang        * 0.4.11     2021-04-30 [1] CRAN (R 4.1.0)                
##  rmarkdown      2.11       2021-09-14 [1] CRAN (R 4.1.0)                
##  rpart          4.1-15     2019-04-12 [1] CRAN (R 4.1.0)                
##  rprojroot      2.0.2      2020-11-15 [1] CRAN (R 4.1.0)                
##  rsample      * 0.1.0      2021-05-08 [1] CRAN (R 4.1.0)                
##  rstudioapi     0.13       2020-11-12 [1] CRAN (R 4.1.0)                
##  rvest          1.0.1      2021-07-26 [1] CRAN (R 4.1.0)                
##  sass           0.4.0      2021-05-12 [1] CRAN (R 4.1.0)                
##  scales       * 1.1.1      2020-05-11 [1] CRAN (R 4.1.0)                
##  sessioninfo    1.1.1      2018-11-05 [1] CRAN (R 4.1.0)                
##  skimr          2.1.3      2021-03-07 [1] CRAN (R 4.1.0)                
##  stringi        1.7.4      2021-08-25 [1] CRAN (R 4.1.0)                
##  stringr      * 1.4.0      2019-02-10 [1] CRAN (R 4.1.0)                
##  survival       3.2-11     2021-04-26 [1] CRAN (R 4.1.0)                
##  testthat       3.0.4      2021-07-01 [1] CRAN (R 4.1.0)                
##  tibble       * 3.1.4      2021-08-25 [1] CRAN (R 4.1.0)                
##  tidymodels   * 0.1.3      2021-04-19 [1] CRAN (R 4.1.0)                
##  tidyr        * 1.1.3      2021-03-03 [1] CRAN (R 4.1.0)                
##  tidyselect     1.1.1      2021-04-30 [1] CRAN (R 4.1.0)                
##  tidyverse    * 1.3.1      2021-04-15 [1] CRAN (R 4.1.0)                
##  timeDate       3043.102   2018-02-21 [1] CRAN (R 4.1.0)                
##  tune         * 0.1.6      2021-07-21 [1] CRAN (R 4.1.0)                
##  tzdb           0.1.2      2021-07-20 [1] CRAN (R 4.1.0)                
##  usethis        2.0.1.9000 2021-09-23 [1] Github (r-lib/usethis@3385e14)
##  utf8           1.2.2      2021-07-24 [1] CRAN (R 4.1.0)                
##  vctrs        * 0.3.8      2021-04-29 [1] CRAN (R 4.1.0)                
##  withr          2.4.2      2021-04-18 [1] CRAN (R 4.1.0)                
##  workflows    * 0.2.3      2021-07-16 [1] CRAN (R 4.1.0)                
##  workflowsets * 0.1.0      2021-07-22 [1] CRAN (R 4.1.0)                
##  xfun           0.26       2021-09-14 [1] CRAN (R 4.1.0)                
##  xml2           1.3.2      2020-04-23 [1] CRAN (R 4.1.0)                
##  yaml           2.2.1      2020-02-01 [1] CRAN (R 4.1.0)                
##  yardstick    * 0.0.8      2021-03-28 [1] CRAN (R 4.1.0)                
## 
## [1] /Library/Frameworks/R.framework/Versions/4.1/Resources/library
LS0tCnRpdGxlOiAiTG9naXN0aWMgUmVncmVzc2lvbiBvZiBEZWZhdWx0IERhdGEiCmF1dGhvcjogIktpdHRpcG9zIFNpcml2b25ncnVuZ3NvbiIKZGF0ZTogJ2ByIFN5cy5EYXRlKClgJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiB1bml0ZWQKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9mb2xkaW5nOiAic2hvdyIKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gcnByb2pyb290OjpmaW5kX3JzdHVkaW9fcm9vdF9maWxlKCkpICMgU2V0IFdEIHRvIFJvb3QKaGVyZTo6aV9hbSgibGFiX21vZC9jaDQtbG9ncmVnLWRlZmF1bHQuUm1kIikKbGlicmFyeShoZXJlKQpgYGAKCgpNeSBtb3RpdmF0aW9uIGFuZCBjb2RlIEkgdXNlZCBpbiBoZXJlIGlzIGFuIGFkYXB0YXRpb24gZnJvbSBEci5KdWxpYSBTaWxhZ2UncyBibG9nIFtiaXJkLWJhdGhzXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvYmlyZC1iYXRocy8pLiBJJ3ZlIGFwcGxpZWQgdGhlIHNpbWlsYXIgbW9kZWxpbmcgcHJvY2VzcyB0byBgRGVmYXVsdGAgZGF0YXNldCBmcm9tIGB7SVNMUn1gIHBhY2thZ2UuCgpUaGUgZ29hbCBpcyB0byBidWlsZCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgYGRlZmF1bHRgIHN0YXR1cy4KCgojIEV4cGxvcmUgRGF0YQoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoSVNMUikKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCmBgYAoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGBEZWZhdWx0YCBkYXRhIHNldC4gCkl0IGhhcyAyIG51bWVyaWMgdmFyaWFibGVzOiBgYmFsYW5jZWAgYW5kIGBpbmNvbWVgOyBhbmQgMiBmYWN0b3IgdmFyaWFibGVzOiBgZGVmYXVsdGAgYW5kIGBzdHVkZW50YAoKYGBge3J9CnN1bW1hcnkoRGVmYXVsdCkKYGBgCgpEZXRhaWxlZCBzdW1tYXJ5IGNhbiBiZSBkb25lIHdpdGggYHNraW1yOjpza2ltKClgCgpgYGB7cn0Kc2tpbXI6OnNraW0oRGVmYXVsdCkKYGBgCgpQbGVhc2Ugbm90ZSB0aGF0IHRoZXJlIGFyZSBubyBtaXNzaW5nIHZhbHVlcywgd2hpY2ggaXMgZ3JlYXQhCgojIyMgR29hbAoKKipUaGUgZ29hbCoqIGhlcmUgaXMgdG8gYnVpbGQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgYGRlZmF1bHRgLgoKIyMjIFBsb3QgUmVsYXRpb25zaGlwCgpJIHdpbGwgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYGRlZmF1bHRgIGFuZCBvdGhlciB2YXJpYWJsZXMgKGBpbmNvbWVgLCBgYmFsYWNlYCwgYHN0dWRlbnRgKS4gTGV0J3MgbWFrZSBzb21lIHBsb3RzIQoKCmBgYHtyfQpEZWZhdWx0ICU+JSAKICBnZ3Bsb3QoYWVzKGJhbGFuY2UsIGluY29tZSwgY29sb3IgPSBkZWZhdWx0KSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC40KSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIsIGRpcmVjdGlvbiA9IC0xKSArCiAgbGFicyh0aXRsZSA9ICJEZWZhdWx0IHN0YXR1cyBieSBpbmNvbWUgYW5kIGJhbGFuY2UiKSAKYGBgCgpCeSB2aXN1YWwgaW5zcGVjdGlvbiwgYGJhbGFuY2VgIGxvb2tzIGxpa2UgYSBiZXR0ZXIgcHJlZGljdG9yIGZvciBgZGVmYXVsdGAgdGhhbiBgaW5jb21lYC4KCgpgYGB7ciBwMX0KcDEgPC0gRGVmYXVsdCAlPiUgCiAgZ2dwbG90KGFlcyhiYWxhbmNlLCBkZWZhdWx0LCBmaWxsID0gc3R1ZGVudCkpICsKICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIGRlZmF1bHQiLAogICAgICAgc3VidGl0bGUgPSAiYnkgYmFsYW5jZSBhbmQgc3R1ZGVudCBzdGF0dXMiLAogICAgICAgY2FwdGlvbiA9ICJEYXRhIGZyb20gSVNMUiBwYWNrYWdlIikgCiAgCnAxIApgYGAKCgpUaGlzIHBsb3Qgc2hvd3MgdGhhdCBgYmFsYW5jZWAgYW5kIGBzdHVkZW50YCBzZWVtIHRvIGJlIGEgZGVjZW50IHByZWRpY3RvciBvZiBgZGVmYXVsdGAuCgoKIyBCdWlsZCBNb2RlbAoKKipHb2FsOioqIE91dGNvbWUgdmFyaWFibGUgPSBgZGVmYXVsdGAgKGZhY3RvcikKCiMgU2ltcGxlIEV4YW1wbGUKCkZpcnN0LCBJIHdpbGwgdXNlIGBnbG0oKWAgZnVuY3Rpb24gdG8gYnVpbGQgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB1c2luZyBhbGwgcHJlZGljdG9ycy4KCmBgYHtyIGdsbV9maXRfc2ltcGxlfQpnbG1fZml0X2FsbCA8LSBnbG0oZGVmYXVsdCB+IC4sIGRhdGEgPSBEZWZhdWx0LCBmYW1pbHkgPSBiaW5vbWlhbCkKc3VtbWFyeShnbG1fZml0X2FsbCkKYGBgCgpgYGB7ciBwLnZhbHVlX2luY29tZSwgaW5jbHVkZT1GQUxTRX0KcC52YWx1ZV9pbmNvbWUgPC0gYnJvb206OnRpZHkoZ2xtX2ZpdF9hbGwpJHAudmFsdWVbNF0KYGBgCgoKQ29lZmZpY2llbnQgb2YgYGluY29tZWAgaXMgbm90IHNpZ25pZmljYW50IChwID0gYHIgcC52YWx1ZV9pbmNvbWVgKS4gTGV0J3MgdHJ5IHJlbW92ZSBgaW5jb21lYCBvdXQuCgpgYGB7cn0KZ2xtX2ZpdF9zdF9iYWwgPC0gZ2xtKGRlZmF1bHQgfiBzdHVkZW50ICsgYmFsYW5jZSwgCiAgICBkYXRhID0gRGVmYXVsdCwgCiAgICBmYW1pbHkgPSBiaW5vbWlhbCkKZ2xtX2ZpdF9zdF9iYWwKYGBgCgpNb2RlbCB0aGF0IGluY2x1ZGUgb25seSBgc3R1ZGVudGAgYW5kIGBiYWxhbmNlYCBoYXMgKipsb3dlcioqIGVzdGltYXRpb24gb2YgdGVzdCBlcnJvciAoaS5lLiwgQUlDICYgQklDKS4KCmBgYHtyfQpsaXN0KCJBbGwgUHJlZGljdG9ycyIgPSBnbG1fZml0X2FsbCwKICAgICAic3R1ZGVudCArIGJhbGFuY2UiID0gZ2xtX2ZpdF9zdF9iYWwpICU+JSAKICBtYXBfZGZyKCAKICAgIGJyb29tOjpnbGFuY2UsIAogICAgLmlkID0gIk1vZGVsIikgJT4lIAogIHNlbGVjdChNb2RlbCwgQUlDLCBCSUMpCmBgYAoKIyBNb2RlbGluZyBQcm9jZXNzCgpOb3csIGxldCdzIGFwcGx5IGZ1bGwgbW9kZWxpbmcgcHJvY2VzcyB3aXRoIGB0aWR5bW9kZWxzYC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHltb2RlbHMpCmBgYAoKCiMjIFNwbGl0IERhdGEKCkZpcnN0LCB3ZSBuZWVkIHRvIHNwZW5kaW5nIGRhdGEgYnVkZ2V0cyBpbiAyIHBvcnRpb25zOiB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhLgoKYGBge3IgRGVmYXVsdF9zcGxpdH0Kc2V0LnNlZWQoMTIzKQojIFNwbGl0IHRvIFRlc3QgYW5kIFRyYWluCkRlZmF1bHRfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChEZWZhdWx0LCBzdHJhdGEgPSBkZWZhdWx0KQoKRGVmYXVsdF90cmFpbiA8LSB0cmFpbmluZyhEZWZhdWx0X3NwbGl0KSAjIDc1JSB0byB0cmFuaW5nIGRhdGEKRGVmYXVsdF90ZXN0IDwtIHRlc3RpbmcoRGVmYXVsdF9zcGxpdCkgIyAyNSUgdG8gdGVzdCBkYXRhCgpEZWZhdWx0X3NwbGl0CmBgYAoKIyMgUmVzYW1wbGluZyBNZXRob2QKCkkgd2lsbCB1c2UgKioxMC1Gb2xkIENyb3NzLVZhbGlkYXRpb24qKiB0byByZXNhbXBsZSB0aGUgdHJhaW5pbmcgZGF0YS4KCmBgYHtyIERlZmF1bHRfZm9sZHN9CnNldC5zZWVkKDIzNCkKCkRlZmF1bHRfZm9sZHMgPC0gdmZvbGRfY3YoRGVmYXVsdF90cmFpbiwgdiA9IDEwLCBzdHJhdGEgPSBkZWZhdWx0KQpEZWZhdWx0X2ZvbGRzIApgYGAKTm90ZSB0aGF0IEkgdXNlIGBzdHJhdGEgPSBkZWZhdWx0YCBiZWNhdXNlIHRoZSBmcmVxdWVuY3kgb2YgZWFjaCBjbGFzcyBpcyBxdWl0ZSBkaWZmZXJlbmNlLgoKYGBge3J9CkRlZmF1bHQgJT4lIGNvdW50KGRlZmF1bHQpCmBgYAoKRm9yIGVhY2ggZm9sZHMsIDY3NTAgcm93cyB3aWxsIHNwZW5kIG9uIGZpdHRpbmcgbW9kZWxzIGFuZCA3NTAgc3BlbmQgb24gYW5hbHlzaXMgb2YgbW9kZWwgcGVyZm9ybWFuY2U6CgpgYGB7cn0KRGVmYXVsdF9mb2xkcyRzcGxpdHNbWzFdXSAKYGBgCgoKIyMgU3BlY2lmaWNhdGlvbgoKIyMjIE1vZGVsIFNwZWNpZmljYXRpb24KCmBgYHtyIGdsbV9zcGVjfQpnbG1fc3BlYyA8LSBsb2dpc3RpY19yZWcoKQpnbG1fc3BlYwpgYGAKCiMjIyBGZWF0dXJlIEVuZ2luZWVyaW5nIFNwZWNpZmljYXRpb24KCmBgYHtyIHJlY19iYXNpY30KcmVjX2Jhc2ljIDwtIHJlY2lwZShkZWZhdWx0IH4gLiwgZGF0YSA9IERlZmF1bHQpICU+JSAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKCnN1bW1hcnkocmVjX2Jhc2ljKQpgYGAKCioqUHJldmlldyBvZiBlbmdpbmVlcmVkIHRyYWluaW5nIGRhdGEqKgoKWW91IGNhbiBzZWUgdGhhdCBgc3R1ZGVudGAgd2FzIHJlcGxhY2VkIGJ5IGBzdHVkZW50X1llc2Agd2l0aCAwLTEgZW5jb2RpbmcuCldoYXQgZG9lcyBpdCBtZWFuPwoKYGBge3J9CnJlY19iYXNpYyAlPiUgCiAgcHJlcChsb2dfY2hhbmdlID0gVCkgJT4lIAogIGJha2UobmV3X2RhdGEgPSBEZWZhdWx0X3RyYWluKQpgYGAKRnJvbSBgY29udHJhc3QoKWAgZnVuY3Rpb24gd2UgY2FuIHNlZSB0aGUgZHVtbXkgZW5jb2Rpbmcgb2YgdGhlIGBzdHVkZW50YCAoZmFjdG9yKSB2YXJpYWJsZTogTm8gPSAwLCBZZXMgPSAxLgoKYGBge3J9CmNvbnRyYXN0cyhEZWZhdWx0JHN0dWRlbnQpCmBgYAoKIyMjIFdvcmtmbG93Cgpgd29ya2Zsb3coKWAgd2lsbCBidW5kbGUgYmx1ZXByaW50IG9mIGZlYXR1cmUgZW5naW5lZXJpbmcgYW5kIG1vZGVsIHNwZWNpZmljYXRpb24gdG9nZXRoZXIuCgpgYGB7ciB3Zl9iYXNpY30Kd2ZfYmFzaWMgPC0gd29ya2Zsb3cocmVjX2Jhc2ljLCBnbG1fc3BlYykKd2ZfYmFzaWMKYGBgCgojIyBGaXQgTW9kZWwgdG8gUmVzYW1wbGVkIERhdGEKCmBgYHtyIHJzX2Jhc2ljfQpkb1BhcmFsbGVsOjpyZWdpc3RlckRvUGFyYWxsZWwoKQoKY3RybF9wcmVkcyA8LSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFKQojIyBGaXQKcnNfYmFzaWMgPC0gZml0X3Jlc2FtcGxlcyh3Zl9iYXNpYywgcmVzYW1wbGVzID0gIERlZmF1bHRfZm9sZHMsIGNvbnRyb2wgPSBjdHJsX3ByZWRzKQoKaGVhZChyc19iYXNpYykKYGBgCgoKCiMjIFJPQyBDdXJ2ZQoKRnJvbSBST0MgY3VydmUgd2UgY2FuIHNlZSB0aGF0IGl0IGhhcyBwcmV0dHkgZ29vZCB1cHBlci1sZWZ0IGJ1bGdpbmcgY3VydmUuCgpgYGB7cn0KYXVnbWVudChyc19iYXNpYykgJT4lIAogIHJvY19jdXJ2ZSh0cnV0aCA9IGRlZmF1bHQsIC5wcmVkX05vKSAlPiUgCiAgYXV0b3Bsb3QoKQpgYGAKClRoaXMgd291bGQgcmVzdWx0IGluIEFVQyAoYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlKSBhbmQgYWNjdXJhY3kgY2xvc2UgdG8gMS4KCmBgYHtyfQpjb2xsZWN0X21ldHJpY3MocnNfYmFzaWMpCmBgYAoKCiMgSW1wcm92ZSB0aGUgTW9kZWwKCkFzIHdlIGNhbiBzZWUgZnJvbSB0aGUgYmVnaW5uaW5nLCByZW1vdmluZyBgaW5jb21lYCBmcm9tIHByZWRpY3RvciByZXN1bHQgaW4gYmV0dGVyIGVzdGltYXRpb24gb2YgdGVzdCBlcnJvciBieSBBSUMgYW5kIEJJQy4KCk5vdywgSSB3aWxsIHJlbW92ZSBgaW5jb21lYCBmcm9tIHRoZSByZWNpcGVzOgoKYGBge3IgcmVjX3NpbXBsZX0KcmVjX3NpbXBsZSA8LSByZWNfYmFzaWMgJT4lIAogIHJlbW92ZV9yb2xlKGluY29tZSwgb2xkX3JvbGUgPSAicHJlZGljdG9yIikKCnN1bW1hcnkocmVjX3NpbXBsZSkKYGBgCgpBbmQgSSB3aWxsIHVwZGF0ZSB0aGUgV29ya0Zsb3cKCmBgYHtyIHdmX3NpbXBsZX0Kd2Zfc2ltcGxlIDwtIHdvcmtmbG93KHJlY19zaW1wbGUsIGdsbV9zcGVjKQpgYGAKClRoZW4gdGhlIHJlc3QgaXMgdGhlIHNhbWUuIFNvLCBJIHdyb3RlIGEgc2ltcGxlIHdyYXBwZXIgZnVuY3Rpb24gdG8gZG8gaXQuCgoKYGBge3IgdXBkYXRlX3dvcmtmbG93fQp1cGRhdGVfd29ya2Zsb3cgPC0gZnVuY3Rpb24od2YpIHsKCiAgY3RybF9wcmVkcyA8LSBjb250cm9sX3Jlc2FtcGxlcyhzYXZlX3ByZWQgPSBUUlVFKQogIHJzIDwtIGZpdF9yZXNhbXBsZXMod2YsIHJlc2FtcGxlcyA9IERlZmF1bHRfZm9sZHMsIGNvbnRyb2wgPSBjdHJsX3ByZWRzKQogIHJzCiAgCn0KCnJzX3NpbXBsZSA8LSB1cGRhdGVfd29ya2Zsb3cod2Zfc2ltcGxlKQpgYGAKCmBgYHtyIHJzX2xzfQpyc19scyA8LSBsaXN0KCJBbGwgUHJlZGljdG9ycyIgPSByc19iYXNpYywKICAgICAgICAgICAgICAic3R1ZGVudCArIGJhbGFuY2UiID0gcnNfc2ltcGxlKQpgYGAKCmBgYHtyIHJvY19kZn0Kcm9jX2RmIDwtIHJzX2xzICU+JSAKICBtYXBfZGZyKH5hdWdtZW50KC54KSAlPiUgcm9jX2N1cnZlKHRydXRoID0gZGVmYXVsdCwgLnByZWRfTm8pLAogICAgICAuaWQgPSAiUHJlZGljdG9ycyIKICAgICAgKQpgYGAKCiMjIFJPQyBjdXJ2ZSBvZiB0aGUgaW1wcm92ZWQgbW9kZWwKClRoaXMgcGxvdCBzaG93ICoqY29tcGFyaXNvbiBvZiBST0MgY3VydmVzKiogb2YgdGhlIDIgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMuCgotICAgUmVkIGxpbmUgc2hvd3MgbW9kZWwgdGhhdCB1c2UgYWxsIHByZWRpY3RvcnM6IGBzdHVkZW50YCwgYGJhbGFuY2VgIGFuZCBgaW5jb21lYC4KLSAgIEJsdWUgbGluZSBzaG93cyBtb2RlbCB0aGF0IHVzZSAyIHByZWRpY3RvcjogYHN0dWRlbnRgIGFuZCBgYmFsYW5jZWAuCgpgYGB7cn0Kcm9jX2RmICU+JSAKICBnZ3Bsb3QoYWVzKDEtc3BlY2lmaWNpdHksIHNlbnNpdGl2aXR5LCBjb2xvciA9IFByZWRpY3RvcnMpKSArCiAgZ2VvbV9saW5lKHNpemUgPSAxLCBhbHBoYSA9IDAuNiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZG90dGVkIikKYGBgCgoKYGBge3IgfQpyc19scyAlPiUgCiAgbWFwX2RmcigKICAgIGNvbGxlY3RfbWV0cmljcywgLmlkID0gIkZlYXR1cmVzIgogICkKYGBgCgpZb3UgY2FuIHNlZSB0aGF0IGEgc2ltcGxlciBtb2RlbCByZXN1bHQgaW4gYSBzaW1pbGFyIChvciBtYXkgYmUgc2xpZ2h0bHkgaW1wcm92ZWQpIEFVQy4KU28gaXQncyByZWFzb25hYmxlIHRvIHByZWZlciBpdCBvdmVyIG1vcmUgY29tcGxpY2F0ZWQgbW9kZWwuIAoKIyBFdmFsdWF0ZSBNb2RlbCAKCkluIHRoaXMgc2VjdGlvbiwgSSB3aWxsIGV2YWx1YXRlIHRoZSBzaW1wbGVyIG1vZGVsIHdpdGggMiBwcmVkaWN0b3JzIChgc3R1ZGVudGAsIGBiYWxhbmNlYCkuCgojIyMgRml0IHRvIFRyYWluaW5nIGRhdGEKCkZpcnN0LCBmaXQgdGhlIG1vZGVsIHRvIHRyYWluaW5nIGRhdGEuCgoKYGBge3IgRGVmYXVsdF9maXR9CkRlZmF1bHRfZml0IDwtIGZpdCh3Zl9zaW1wbGUsIERlZmF1bHRfdHJhaW4pCkRlZmF1bHRfZml0CmBgYAoKIyMjIFVzZSBUZXN0aW5nIGRhdGEgdG8gUHJlZGljdAoKVGhlbiwgdXNlIHRlc3QgZGF0YSB0byBwcmVkaWN0IGBkZWZhdWx0YC4KCgpgYGB7ciBEZWZhdWx0X3ByZWR9CkRlZmF1bHRfcHJlZCA8LSAKICBhdWdtZW50KERlZmF1bHRfZml0LCBEZWZhdWx0X3Rlc3QpICU+JSAKICBiaW5kX2NvbHMoCiAgICBwcmVkaWN0KERlZmF1bHRfZml0LCBEZWZhdWx0X3Rlc3QsIHR5cGUgPSAiY29uZl9pbnQiKQogICkKYGBgCgpgYGB7ciBwMn0KbGlicmFyeShsYXRleDJleHApCnAyIDwtIERlZmF1bHRfcHJlZCAlPiUgCiAgZ2dwbG90KGFlcyhiYWxhbmNlLCAucHJlZF9ZZXMsIGZpbGwgPSBzdHVkZW50KSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSBzdHVkZW50KSkgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gLnByZWRfbG93ZXJfWWVzLCB5bWF4ID0gLnByZWRfdXBwZXJfWWVzKSwKICAgICAgICAgICAgICBhbHBoYSA9IDAuMykgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiKSArCiAgbGFicyh5ID0gVGVYKCIkXFxoYXR7cH0oZGVmYXVsdCkkIiksCiAgICAgICBjYXB0aW9uID0gIkFyZWEgaW5kaWNhdGUgOTUlIENJIG9mIHRoZSBlc3RpbWF0ZWQgcHJvYmFiaWxpdHkiKSArCiAgbGFicyh0aXRsZSA9ICJQcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgZGVmYXVsdCIsIAogICAgICAgc3VidGl0bGUgPSAiYnkgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIDIgcHJlZGljdG9ycyIpCgpwMiAKYGBgClRoaXMgcGxvdCBzaG93IGVzdGltYXRlZCBwcm9iYWJpbGl0eSBvZiBgZGVmYXVsdGAgaWYgd2Uga25vdyB0aGUgdmFsdWVzIG9mIHByZWRpY3RvcnM6IGBzdHVkZW50YCBhbmQgYGJhbGFuY2VgIGJ5IHVzaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgZml0dGVkIG9uIHRyYWluaW5nIGRhdGEuIFRoZSBwcmVkaWN0aW9uIHdhcyBtYWRlIGJ5IHBsdWdnaW5nIHRlc3QgZGF0YSB0byB0aGUgbW9kZWwuCgoKIyBTdW1tYXJ5IFBsb3QKCmBgYHtyfQpsaWJyYXJ5KHBhdGNod29yaykKcDEgKyBwMiAKYGBgCgpUaGUgbGVmdC1zaWRlZCBwbG90IHNob3dlZCBkZWZhdWx0IHN0YXR1cyBieSBiYWxhbmNlIGFuZCBzdHVkZW50IHN0YXR1cyBhcyBvYnNlcnZlZCBpbiB0aGUgYERlZmF1bHRgIGRhdGEgc2V0LiBBZnRlciBtdWx0aXZhcmlhdGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCAoYGRlZmF1bHRgIG9uIGBiYWxhbmNlYCBhbmQgYHN0dWRlbnRgKSB3YXMgZml0dGVkIHRvIHRoZSB0cmFpbmluZyBkYXRhLCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGRlZmF1bHQgdXNpbmcgdGhlIG1vZGVsIHdhcyBzaG93biBpbiB0aGUgcmlnaHQtc2lkZWQgcGxvdC4KCiMjIyBUaGUgRW5kCgpJZiB5b3UgaGF2ZSBhbnkgY29tbWVudCBvciBub3RpY2VkIGFueSBtaXN0YWtlLCBwbGVhc2UgZmVlbCBmcmVlIHRvIHNoYXJlIGl0IHdpdGggbWUgaW4gdGhlIGNvbW1lbnQgc2VjdGlvbi4KClRoYW5rcyBhIGxvdCBmb3IgcmVhZGluZy4KCmBgYHtyfQpkZXZ0b29sczo6c2Vzc2lvbl9pbmZvKCkKYGBgCgo=