knitr::opts_chunk$set(echo = TRUE, 
                      message = F,
                      warning = F,
                      fig.align = 'center')

# Loading any packages
pacman::p_load(tidyverse, mlbench, broom, pander)

# Getting the PimaIndiansDiabetes2 data set from mlbench package
data("PimaIndiansDiabetes2", 
      package = "mlbench")


pid2 <- 
  PimaIndiansDiabetes2 %>% 
  filter(complete.cases(.)) |> 
  mutate(diabetes = relevel(diabetes, ref = "neg"))

rm(PimaIndiansDiabetes2)

The pid2 data set has 9 variables on 392 women with no missing values

head(pid2) |> 
  pander()
Table continues below
  pregnant glucose pressure triceps insulin mass pedigree
4 1 89 66 23 94 28.1 0.167
5 0 137 40 35 168 43.1 2.288
7 3 78 50 32 88 31 0.248
9 2 197 70 45 543 30.5 0.158
14 1 189 60 23 846 30.1 0.398
15 5 166 72 19 175 25.8 0.587
  age diabetes
4 21 neg
5 33 pos
7 26 pos
9 53 pos
14 59 pos
15 51 pos

We will use the other 8 variables to predict the probability someone has diabetes.

But which explanatory variables should we include?

0.1 LASSO Regression

LASSO (Least Absolute Shrinkage and Selection Operator) regression can be used to build a simpler model by adding a penalty term determined by how large the model terms, \(\hat{\beta}\), are.

\[h_1(\mathbf{\hat{\beta}}) = -\ell(\hat{\beta}) + \lambda \sum_{i=1}^k|\hat{\beta}_i|\]

where \(\ell(\hat{\beta})\) is the log-likelihood, \(\lambda\) is a penalty parameter, and \(\sum_{i=1}^k|\hat{\beta}_i|\) is the total slope size.

The large \(\lambda\) is, the smaller we’re forcing the \(\hat{\beta}\) to be. How do we decide what \(\lambda\) should be? By testing out a bunch of different ones and seeing which gives us the best results! This is called a grid search.

We’ll start by just fitting a basic logistic regression model and then a logistic regression model with \(\lambda = 0.1\). Why 0.1? Just to use as an example.

To perform LASSO, we need to standardize the explanatory variables first because the size of \(\hat{\beta}\) depends on the scale of the explanatories, as seen below:

# Unscaled explanatory
logit_diabetes <- 
  glm(
    formula = diabetes ~ ., # . means all other columns
    data = pid2,
    family = binomial
  )

# Standardize the data
pid_stan <- 
  pid2 |> 
  mutate(
    across(
      .cols = -diabetes,
      .fns = ~ (. - mean(.)) / sd(.)
    )
  )

logit_diab_scaled <- 
  glm(
    formula = diabetes ~ ., # . means all other columns
    data = pid_stan,
    family = binomial
  )

# Looking at the two estimates
tidy(logit_diabetes) |> 
  dplyr::select(term, estimate) |> 
  # Rounding the estimates and adding the scaled version
  mutate(
    estimate = round(estimate, 4),
    stan_estimate = tidy(logit_diab_scaled) |> dplyr::pull(estimate) |> round(4)
  ) |> 
  pander()
term estimate stan_estimate
(Intercept) -10.04 -1
pregnant 0.0822 0.2638
glucose 0.0383 1.181
pressure -0.0014 -0.0177
triceps 0.0112 0.118
insulin -8e-04 -0.0981
mass 0.0705 0.4957
pedigree 1.141 0.3942
age 0.034 0.3463
# Summing up the estimates

tidy(logit_diabetes) |> 
  dplyr::select(term, estimate) |> 
  
  mutate(
    stan_estimate = tidy(logit_diab_scaled) |> dplyr::pull(estimate)
  ) |> 
  filter(term != '(Intercept)') |> 
  summarize(
    reg_est_total  = sum(estimate),
    stan_est_total = sum(stan_estimate)
  ) |> 
  pander()
reg_est_total stan_est_total
1.375 2.683

0.1.1 Logistic LASSO

The package glmnet has the function glmnet() that will perform LASSO for us. We need to specify the following arguments:

  • x = A matrix of predictors
    • Any factors will need to be dummy coded
  • y = binary response variable
    • Needs to be coded as 0/1
  • family = 'binomial' tells it to do logistic regression
  • alpha = 1 specifies LASSO
    • alpha = 0 specifies Ridge
    • an alpha between (0, 1) will perform elastic net, which is a mix of LASSO and Ridge
  • lambda = what value of \(\lambda\) you want to use
    • If you don’t specify lambda, it will automatically conduct a grid search
library(glmnet)
lasso_0.01 <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    lambda = 0.01
  )

tibble(
  term = coef(lasso_0.01) |> rownames(),
  estimate = coef(lasso_0.01) |> as.vector()
) |> 
  pander()
term estimate
(Intercept) -0.9535
pregnant 0.2027
glucose 1.041
pressure 0
triceps 0.09755
insulin 0
mass 0.3985
pedigree 0.3048
age 0.3191
lasso_0.1 <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    lambda = 0.1
  )

tibble(
  term = coef(lasso_0.1) |> rownames(),
  estimate = coef(lasso_0.1) |> as.vector()
) |> 
  pander()
term estimate
(Intercept) -0.7638
pregnant 0
glucose 0.6355
pressure 0
triceps 0
insulin 0
mass 0
pedigree 0
age 0.08327
lasso_0.2 <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    lambda = 0.2
  )

tibble(
  term = coef(lasso_0.2) |> rownames(),
  estimate = coef(lasso_0.2) |> as.vector()
) |> 
  pander()
term estimate
(Intercept) -0.7067
pregnant 0
glucose 0.1915
pressure 0
triceps 0
insulin 0
mass 0
pedigree 0
age 0

The code chunks above show that as \(\lambda\) increases, the smaller the \(\hat{\beta}\) become, with more being “zeroed” out!

0.1.1.1 Finding the best choice of \(\lambda\)

To find the best value of \(\lambda\), we conduct a grid search. Using glmnet(), it’s easy, just don’t specify lambda!

Or if you want to search across a custom list of \(\lambda\), you can give lambda a vector.

lasso_search <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    lambda = seq(0, 0.25, by = 0.001)
  )

# Data set of AIC and BIC
lambda_grid_search <- 
  data.frame(
    lambda = lasso_search$lambda,
    non_zero_coefs = lasso_search$df,
    deviance = deviance(lasso_search)
  ) |> 
  # Calculating AIC and BIC
  mutate(
    AIC = deviance + 2 * non_zero_coefs,
    BIC = deviance + log(nrow(pid_stan)) * non_zero_coefs
  ) 

# Best (minimum) AIC
lambda_grid_search |> 
  slice_min(AIC, n = 10) |> 
  pander()
lambda non_zero_coefs deviance AIC BIC
0.006 6 344.9 356.9 380.8
0.007 6 345.1 357.1 380.9
0.008 6 345.3 357.3 381.1
0.009 6 345.5 357.5 381.3
0.01 6 345.8 357.8 381.6
0.011 6 346 358 381.8
0.002 7 344.1 358.1 385.9
0.003 7 344.3 358.3 386.1
0.012 6 346.3 358.3 382.1
0.004 7 344.4 358.4 386.2
# Best (minimum) BIC
lambda_grid_search |> 
  slice_min(BIC, n = 10) |> 
  pander()
lambda non_zero_coefs deviance AIC BIC
0.006 6 344.9 356.9 380.8
0.007 6 345.1 357.1 380.9
0.008 6 345.3 357.3 381.1
0.009 6 345.5 357.5 381.3
0.01 6 345.8 357.8 381.6
0.011 6 346 358 381.8
0.012 6 346.3 358.3 382.1
0.013 6 346.6 358.6 382.4
0.014 6 346.9 358.9 382.7
0.015 6 347.2 359.2 383.1

Both AIC and BIC agree that \(\lambda = 0.006\) is the best choice

Alternatively, you can specify nlambda = to tell it how many \(\lambda\) values to test:

lasso_search2 <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    nlambda = 50 # testing 50 different lambda values
  )

# Data set of AIC and BIC
lambda_grid_search2 <- 
  data.frame(
    lambda = lasso_search2$lambda,
    non_zero_coefs = lasso_search2$df,
    deviance = deviance(lasso_search2)
  ) |> 
  # Calculating AIC and BIC
  mutate(
    AIC = deviance + 2 * non_zero_coefs,
    BIC = deviance + log(nrow(pid_stan)) * non_zero_coefs
  ) 

# Best (minimum) AIC
lambda_grid_search2 |> 
  slice_min(AIC, n = 10) |> 
  pander()
lambda non_zero_coefs deviance AIC BIC
0.006827 6 345.1 357.1 380.9
0.008238 6 345.3 357.3 381.2
0.009942 6 345.7 357.7 381.6
0.001258 7 344.1 358.1 385.9
0.001518 7 344.1 358.1 385.9
0.001831 7 344.1 358.1 385.9
0.00221 7 344.2 358.2 386
0.002667 7 344.2 358.2 386
0.012 6 346.3 358.3 382.1
0.003219 7 344.3 358.3 386.1
# Best (minimum) BIC
lambda_grid_search2 |> 
  slice_min(BIC, n = 10) |> 
  pander()
lambda non_zero_coefs deviance AIC BIC
0.006827 6 345.1 357.1 380.9
0.008238 6 345.3 357.3 381.2
0.009942 6 345.7 357.7 381.6
0.012 6 346.3 358.3 382.1
0.01448 6 347.1 359.1 382.9
0.01747 6 348.2 360.2 384
0.02109 6 349.7 361.7 385.5
0.001258 7 344.1 358.1 385.9
0.001518 7 344.1 358.1 385.9
0.001831 7 344.1 358.1 385.9

Again, both AIC and BIC agree that \(\lambda = 0.0068\) is the best choice. Not the same as our previous search of 0.006, but very close.

However, the results here are going to be a little biased since we’re using the same data to build the model and then evaluate the value of \(\lambda\). If we want to properly conduct our grid search, we should instead use cross-validation.

0.1.1.2 LASSO with cross-validation

How can we use glmnet() to perform our grid search with cross-validation? We can’t use the function directly, but we can use cv.glmnet() to perform k-fold cross-validation! We just need to specify nfolds =, which defaults to 10.

# Using leave-one-out cross-validation for each choice of lambda
lambda_cv <- 
  cv.glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    nlambda = 50,
    nfolds = nrow(pid_stan) # leave-one-out cv
  )

plot(lambda_cv)

What does the plot mean?

When not specifying the values of \(\lambda\) to search, glmnet() will create an equally-spaced vector for \(x = \log(\lambda)\), and the vector it tests is \(\lambda = e^x\), which is why ‘Log(\(\lambda\))’ is shown on the lower x-axis.

tibble(
  log_lambda = log(lambda_cv$lambda),
  space = log_lambda - lag(log_lambda)
) |> 
  head(10) |> 
  pander()
log_lambda space
-1.416 NA
-1.604 -0.188
-1.791 -0.188
-1.979 -0.188
-2.167 -0.188
-2.355 -0.188
-2.543 -0.188
-2.731 -0.188
-2.919 -0.188
-3.107 -0.188

The red dot shows the deviance using cross-validation for the particular value of \(\lambda\) and the error bar is the deviance \(\pm\) 1 standard error of the deviance. The lower the better.

The two dashed lines show the two options for the choice of \(\lambda\):

  • Left-most line will be the value that give the minimum deviance
  • Right-most line will be the largest value of \(\lambda\) (most regularization) such that

\[\text{deviance} < \min(\text{deviance}) + \text{SE(min(deviance))}\]

where \(\text{SE(min(deviance))}\) is the SE of the minimum deviance

# Data frame of lambda, deviance, SE, and number of non-zero slopes
cv_results <- 
  data.frame(
    lambda   = lambda_cv$lambda,
    deviance = lambda_cv$cvm,
    dev_SE   = lambda_cv$cvsd,
    n_preds  = lambda_cv$nzero
  ) 

pander(cv_results)
  lambda deviance dev_SE n_preds
s0 0.2428 1.277 0.03357 0
s1 0.2012 1.196 0.03181 1
s2 0.1667 1.139 0.0322 1
s3 0.1381 1.098 0.03372 1
s4 0.1145 1.069 0.03563 2
s5 0.09486 1.04 0.03745 3
s6 0.0786 1.01 0.03896 3
s7 0.06513 0.9907 0.04094 4
s8 0.05397 0.975 0.04293 6
s9 0.04472 0.9618 0.04493 6
s10 0.03706 0.9499 0.04674 6
s11 0.03071 0.941 0.04845 6
s12 0.02545 0.9346 0.05007 6
s13 0.02109 0.9302 0.05158 6
s14 0.01747 0.9272 0.05296 6
s15 0.01448 0.9253 0.05421 6
s16 0.012 0.9241 0.05533 6
s17 0.009942 0.9233 0.0563 6
s18 0.008238 0.9231 0.05716 6
s19 0.006827 0.9239 0.05794 6
s20 0.005657 0.9265 0.0587 7
s21 0.004688 0.9278 0.05933 7
s22 0.003884 0.9284 0.05985 7
s23 0.003219 0.9289 0.06029 7
s24 0.002667 0.9294 0.06066 7
s25 0.00221 0.93 0.06098 7
s26 0.001831 0.9307 0.06127 7
s27 0.001518 0.9316 0.06152 7
s28 0.001258 0.9324 0.06173 7
s29 0.001042 0.9332 0.06191 8
s30 0.0008635 0.9338 0.06204 8
s31 0.0007155 0.9342 0.06215 8
s32 0.0005929 0.9345 0.06224 8
s33 0.0004913 0.9347 0.06229 8
# Finding the cutoff for the largest "acceptable" deviance
cv_dev_cutoff <- 
  cv_results |> 
  # Min deviance 
  slice_min(order_by = deviance, n = 1) |> 
  # Cutoff = min deviance + SE
  mutate(dev_cutoff = deviance + dev_SE) |> 
  # cut off as a number instead of a data frame
  pull(dev_cutoff)

cv_dev_cutoff
## [1] 0.9802589
cv_results |> 
  # Finding all the values of lambda where min(dev) is in dev +- 1SE
  filter(
    deviance < cv_dev_cutoff
  ) |> 
  # picking the row with the largest lambda
  slice_max(order_by = lambda, n = 1) |> 
  pander()
  lambda deviance dev_SE n_preds
s8 0.05397 0.975 0.04293 6

We can get the two \(\lambda\) values with:

cv_results |> 
  filter(
    lambda %in% c(lambda_cv$lambda.min, lambda_cv$lambda.1se)
  ) |> 
  pander()
  lambda deviance dev_SE n_preds
s8 0.05397 0.975 0.04293 6
s18 0.008238 0.9231 0.05716 6

Which one we use depends on what we want.

If we want to include all potential important variables (less regularization), then we choose lambda.min

If we want to include all variables that are definitely important (more regularization), then we choose lambda.1se.

According to out plot, both will have the same number of predictors: 6!

They are:

data.frame(
  terms = rownames(coef(lambda_cv, s = 'lambda.1se') ),
  dev_min = as.vector(coef(lambda_cv, s = 'lambda.min')),
  dev_1se = as.vector(coef(lambda_cv, s = 'lambda.1se'))
) |> 
  pander()
terms dev_min dev_1se
(Intercept) -0.9612 -0.8262
pregnant 0.2134 0.0004463
glucose 1.055 0.8006
pressure 0 0
triceps 0.1018 0.01661
insulin 0 0
mass 0.4102 0.1799
pedigree 0.3187 0.05661
age 0.3218 0.2567

According to LASSO, we can drop pressure and insulin from the predictors!

0.1.2 What next?

Should we use the LASSO coefficients or should we refit a typical logistic regression model for the predictors with non-zero terms?

Depends on what your goals are!

If you want to make predictions, stick with the resulting LASSO model:

LASSO_model <- 
  glmnet(
    x = as.matrix(pid_stan |> dplyr::select(-diabetes)),
    y = (pid_stan$diabetes == 'pos') * 1,
    family = 'binomial',
    alpha = 1,
    lambda = lambda_cv$lambda.1se # more regularization choice
  )


# Diabetes status and estimated probabilities
data.frame(
  diabetes = pid_stan$diabetes,
  est_prob =
    predict(
      LASSO_model, 
      newx = pid_stan |> dplyr::select(-diabetes) |> as.matrix(),
      type = 'response'
    ) |> as.vector()
) |> 
  head(10) |> 
  pander()
diabetes est_prob
neg 0.105
pos 0.5386
pos 0.09971
pos 0.8262
pos 0.8171
pos 0.6488
pos 0.3578
neg 0.2569
pos 0.2779
neg 0.3478

Or if we want to anaylze the relationships (confidence intervals and p-values), we use unpenalized logistic regression:

# Fitting unregularized logistic model
logit_model <- 
  glm(
    formula = diabetes ~ . - pressure - insulin,
    data = pid2,
    family = binomial
  )

# term table
tidy(logit_model, exponentiate = T, conf.int = T) |> 
  mutate(
    across(
      .cols = -term,
      .fns = ~round(., 4)
    )
  ) |> 
  pander()
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 0 1.09 -9.095 0 0 4e-04
pregnant 1.087 0.0552 1.515 0.1298 0.9763 1.213
glucose 1.037 0.005 7.314 0 1.027 1.048
triceps 1.012 0.0171 0.6794 0.4969 0.9783 1.046
mass 1.069 0.0261 2.566 0.0103 1.017 1.127
pedigree 3.099 0.4257 2.656 0.0079 1.369 7.27
age 1.033 0.018 1.83 0.0673 0.9982 1.071
# Estimated probabilities
data.frame(
  diabetes = pid_stan$diabetes,
  est_prob =
    predict(
      logit_model, 
      type = 'response'
    ) 
) |> 
  head(10) |> 
  pander()
  diabetes est_prob
4 neg 0.02783
5 pos 0.8862
7 pos 0.03795
9 pos 0.8731
14 pos 0.8508
15 pos 0.7009
17 pos 0.4132
19 neg 0.192
20 pos 0.2114
21 neg 0.4324
LS0tDQp0aXRsZTogIkxBU1NPIGZvciBMb2dpc3RpYyBSZWdyZXNzaW9uIg0KYXV0aG9yOiAiQ2hhcHRlciA0Ig0KZGF0ZTogIlNUQVQgNTM1MCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBmaWdfd2lkdGg6IDYNCiAgICBmaWdfaGVpZ2h0OiA2DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9VFJVRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEYsDQogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gJ2NlbnRlcicpDQoNCiMgTG9hZGluZyBhbnkgcGFja2FnZXMNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgbWxiZW5jaCwgYnJvb20sIHBhbmRlcikNCg0KIyBHZXR0aW5nIHRoZSBQaW1hSW5kaWFuc0RpYWJldGVzMiBkYXRhIHNldCBmcm9tIG1sYmVuY2ggcGFja2FnZQ0KZGF0YSgiUGltYUluZGlhbnNEaWFiZXRlczIiLCANCiAgICAgIHBhY2thZ2UgPSAibWxiZW5jaCIpDQoNCg0KcGlkMiA8LSANCiAgUGltYUluZGlhbnNEaWFiZXRlczIgJT4lIA0KICBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpIHw+IA0KICBtdXRhdGUoZGlhYmV0ZXMgPSByZWxldmVsKGRpYWJldGVzLCByZWYgPSAibmVnIikpDQoNCnJtKFBpbWFJbmRpYW5zRGlhYmV0ZXMyKQ0KDQpgYGANCg0KVGhlIGBwaWQyYCBkYXRhIHNldCBoYXMgOSB2YXJpYWJsZXMgb24gMzkyIHdvbWVuIHdpdGggbm8gbWlzc2luZyB2YWx1ZXMNCg0KYGBge3J9DQpoZWFkKHBpZDIpIHw+IA0KICBwYW5kZXIoKQ0KYGBgDQoNCldlIHdpbGwgdXNlIHRoZSBvdGhlciA4IHZhcmlhYmxlcyB0byBwcmVkaWN0IHRoZSBwcm9iYWJpbGl0eSBzb21lb25lIGhhcyBkaWFiZXRlcy4NCg0KQnV0IHdoaWNoIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBzaG91bGQgd2UgaW5jbHVkZT8NCg0KIyMgTEFTU08gUmVncmVzc2lvbg0KDQpMQVNTTyAoTGVhc3QgQWJzb2x1dGUgU2hyaW5rYWdlIGFuZCBTZWxlY3Rpb24gT3BlcmF0b3IpIHJlZ3Jlc3Npb24gY2FuIGJlIHVzZWQgdG8gYnVpbGQgYSBzaW1wbGVyIG1vZGVsIGJ5IGFkZGluZyBhIHBlbmFsdHkgdGVybSBkZXRlcm1pbmVkIGJ5IGhvdyBsYXJnZSB0aGUgbW9kZWwgdGVybXMsICRcaGF0e1xiZXRhfSQsIGFyZS4NCg0KJCRoXzEoXG1hdGhiZntcaGF0e1xiZXRhfX0pID0gLVxlbGwoXGhhdHtcYmV0YX0pICsgXGxhbWJkYSBcc3VtX3tpPTF9Xmt8XGhhdHtcYmV0YX1faXwkJA0KDQp3aGVyZSAkXGVsbChcaGF0e1xiZXRhfSkkIGlzIHRoZSBsb2ctbGlrZWxpaG9vZCwgJFxsYW1iZGEkIGlzIGEgcGVuYWx0eSBwYXJhbWV0ZXIsIGFuZCAkXHN1bV97aT0xfV5rfFxoYXR7XGJldGF9X2l8JCBpcyB0aGUgdG90YWwgc2xvcGUgc2l6ZS4NCg0KVGhlIGxhcmdlICRcbGFtYmRhJCBpcywgdGhlIHNtYWxsZXIgd2UncmUgZm9yY2luZyB0aGUgJFxoYXR7XGJldGF9JCB0byBiZS4gSG93IGRvIHdlIGRlY2lkZSB3aGF0ICRcbGFtYmRhJCBzaG91bGQgYmU/IEJ5IHRlc3Rpbmcgb3V0IGEgYnVuY2ggb2YgZGlmZmVyZW50IG9uZXMgYW5kIHNlZWluZyB3aGljaCBnaXZlcyB1cyB0aGUgYmVzdCByZXN1bHRzISBUaGlzIGlzIGNhbGxlZCBhIGdyaWQgc2VhcmNoLg0KDQpXZSdsbCBzdGFydCBieSBqdXN0IGZpdHRpbmcgYSBiYXNpYyBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGFuZCB0aGVuIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoICRcbGFtYmRhID0gMC4xJC4gV2h5IDAuMT8gSnVzdCB0byB1c2UgYXMgYW4gZXhhbXBsZS4NCg0KVG8gcGVyZm9ybSBMQVNTTywgd2UgbmVlZCB0byBzdGFuZGFyZGl6ZSB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGZpcnN0IGJlY2F1c2UgdGhlIHNpemUgb2YgJFxoYXR7XGJldGF9JCBkZXBlbmRzIG9uIHRoZSBzY2FsZSBvZiB0aGUgZXhwbGFuYXRvcmllcywgYXMgc2VlbiBiZWxvdzoNCg0KYGBge3J9DQojIFVuc2NhbGVkIGV4cGxhbmF0b3J5DQpsb2dpdF9kaWFiZXRlcyA8LSANCiAgZ2xtKA0KICAgIGZvcm11bGEgPSBkaWFiZXRlcyB+IC4sICMgLiBtZWFucyBhbGwgb3RoZXIgY29sdW1ucw0KICAgIGRhdGEgPSBwaWQyLA0KICAgIGZhbWlseSA9IGJpbm9taWFsDQogICkNCg0KIyBTdGFuZGFyZGl6ZSB0aGUgZGF0YQ0KcGlkX3N0YW4gPC0gDQogIHBpZDIgfD4gDQogIG11dGF0ZSgNCiAgICBhY3Jvc3MoDQogICAgICAuY29scyA9IC1kaWFiZXRlcywNCiAgICAgIC5mbnMgPSB+ICguIC0gbWVhbiguKSkgLyBzZCguKQ0KICAgICkNCiAgKQ0KDQpsb2dpdF9kaWFiX3NjYWxlZCA8LSANCiAgZ2xtKA0KICAgIGZvcm11bGEgPSBkaWFiZXRlcyB+IC4sICMgLiBtZWFucyBhbGwgb3RoZXIgY29sdW1ucw0KICAgIGRhdGEgPSBwaWRfc3RhbiwNCiAgICBmYW1pbHkgPSBiaW5vbWlhbA0KICApDQoNCiMgTG9va2luZyBhdCB0aGUgdHdvIGVzdGltYXRlcw0KdGlkeShsb2dpdF9kaWFiZXRlcykgfD4gDQogIGRwbHlyOjpzZWxlY3QodGVybSwgZXN0aW1hdGUpIHw+IA0KICAjIFJvdW5kaW5nIHRoZSBlc3RpbWF0ZXMgYW5kIGFkZGluZyB0aGUgc2NhbGVkIHZlcnNpb24NCiAgbXV0YXRlKA0KICAgIGVzdGltYXRlID0gcm91bmQoZXN0aW1hdGUsIDQpLA0KICAgIHN0YW5fZXN0aW1hdGUgPSB0aWR5KGxvZ2l0X2RpYWJfc2NhbGVkKSB8PiBkcGx5cjo6cHVsbChlc3RpbWF0ZSkgfD4gcm91bmQoNCkNCiAgKSB8PiANCiAgcGFuZGVyKCkNCg0KIyBTdW1taW5nIHVwIHRoZSBlc3RpbWF0ZXMNCg0KdGlkeShsb2dpdF9kaWFiZXRlcykgfD4gDQogIGRwbHlyOjpzZWxlY3QodGVybSwgZXN0aW1hdGUpIHw+IA0KICANCiAgbXV0YXRlKA0KICAgIHN0YW5fZXN0aW1hdGUgPSB0aWR5KGxvZ2l0X2RpYWJfc2NhbGVkKSB8PiBkcGx5cjo6cHVsbChlc3RpbWF0ZSkNCiAgKSB8PiANCiAgZmlsdGVyKHRlcm0gIT0gJyhJbnRlcmNlcHQpJykgfD4gDQogIHN1bW1hcml6ZSgNCiAgICByZWdfZXN0X3RvdGFsICA9IHN1bShlc3RpbWF0ZSksDQogICAgc3Rhbl9lc3RfdG90YWwgPSBzdW0oc3Rhbl9lc3RpbWF0ZSkNCiAgKSB8PiANCiAgcGFuZGVyKCkNCmBgYA0KDQojIyMgTG9naXN0aWMgTEFTU08NCg0KVGhlIHBhY2thZ2UgYGdsbW5ldGAgaGFzIHRoZSBmdW5jdGlvbiBgZ2xtbmV0KClgIHRoYXQgd2lsbCBwZXJmb3JtIExBU1NPIGZvciB1cy4gV2UgbmVlZCB0byBzcGVjaWZ5IHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOg0KDQotICAgYHhgID0gQSBtYXRyaXggb2YgcHJlZGljdG9ycw0KICAgIC0gICBBbnkgZmFjdG9ycyB3aWxsIG5lZWQgdG8gYmUgZHVtbXkgY29kZWQNCi0gICBgeWAgPSBiaW5hcnkgcmVzcG9uc2UgdmFyaWFibGUNCiAgICAtICAgTmVlZHMgdG8gYmUgY29kZWQgYXMgMC8xDQotICAgYGZhbWlseSA9ICdiaW5vbWlhbCdgIHRlbGxzIGl0IHRvIGRvIGxvZ2lzdGljIHJlZ3Jlc3Npb24NCi0gICBgYWxwaGEgPSAxYCBzcGVjaWZpZXMgTEFTU08NCiAgICAtICAgYGFscGhhID0gMGAgc3BlY2lmaWVzIFJpZGdlDQogICAgLSAgIGFuIGBhbHBoYWAgYmV0d2VlbiAoMCwgMSkgd2lsbCBwZXJmb3JtIGVsYXN0aWMgbmV0LCB3aGljaCBpcyBhIG1peCBvZiBMQVNTTyBhbmQgUmlkZ2UNCi0gICBgbGFtYmRhYCA9IHdoYXQgdmFsdWUgb2YgJFxsYW1iZGEkIHlvdSB3YW50IHRvIHVzZQ0KICAgIC0gICBJZiB5b3UgZG9uJ3Qgc3BlY2lmeSBgbGFtYmRhYCwgaXQgd2lsbCBhdXRvbWF0aWNhbGx5IGNvbmR1Y3QgYSBncmlkIHNlYXJjaA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2xtbmV0KQ0KbGFzc29fMC4wMSA8LSANCiAgZ2xtbmV0KA0KICAgIHggPSBhcy5tYXRyaXgocGlkX3N0YW4gfD4gZHBseXI6OnNlbGVjdCgtZGlhYmV0ZXMpKSwNCiAgICB5ID0gKHBpZF9zdGFuJGRpYWJldGVzID09ICdwb3MnKSAqIDEsDQogICAgZmFtaWx5ID0gJ2Jpbm9taWFsJywNCiAgICBhbHBoYSA9IDEsDQogICAgbGFtYmRhID0gMC4wMQ0KICApDQoNCnRpYmJsZSgNCiAgdGVybSA9IGNvZWYobGFzc29fMC4wMSkgfD4gcm93bmFtZXMoKSwNCiAgZXN0aW1hdGUgPSBjb2VmKGxhc3NvXzAuMDEpIHw+IGFzLnZlY3RvcigpDQopIHw+IA0KICBwYW5kZXIoKQ0KYGBgDQoNCmBgYHtyfQ0KbGFzc29fMC4xIDwtIA0KICBnbG1uZXQoDQogICAgeCA9IGFzLm1hdHJpeChwaWRfc3RhbiB8PiBkcGx5cjo6c2VsZWN0KC1kaWFiZXRlcykpLA0KICAgIHkgPSAocGlkX3N0YW4kZGlhYmV0ZXMgPT0gJ3BvcycpICogMSwNCiAgICBmYW1pbHkgPSAnYmlub21pYWwnLA0KICAgIGFscGhhID0gMSwNCiAgICBsYW1iZGEgPSAwLjENCiAgKQ0KDQp0aWJibGUoDQogIHRlcm0gPSBjb2VmKGxhc3NvXzAuMSkgfD4gcm93bmFtZXMoKSwNCiAgZXN0aW1hdGUgPSBjb2VmKGxhc3NvXzAuMSkgfD4gYXMudmVjdG9yKCkNCikgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KYGBge3J9DQpsYXNzb18wLjIgPC0gDQogIGdsbW5ldCgNCiAgICB4ID0gYXMubWF0cml4KHBpZF9zdGFuIHw+IGRwbHlyOjpzZWxlY3QoLWRpYWJldGVzKSksDQogICAgeSA9IChwaWRfc3RhbiRkaWFiZXRlcyA9PSAncG9zJykgKiAxLA0KICAgIGZhbWlseSA9ICdiaW5vbWlhbCcsDQogICAgYWxwaGEgPSAxLA0KICAgIGxhbWJkYSA9IDAuMg0KICApDQoNCnRpYmJsZSgNCiAgdGVybSA9IGNvZWYobGFzc29fMC4yKSB8PiByb3duYW1lcygpLA0KICBlc3RpbWF0ZSA9IGNvZWYobGFzc29fMC4yKSB8PiBhcy52ZWN0b3IoKQ0KKSB8PiANCiAgcGFuZGVyKCkNCmBgYA0KDQpUaGUgY29kZSBjaHVua3MgYWJvdmUgc2hvdyB0aGF0IGFzICRcbGFtYmRhJCBpbmNyZWFzZXMsIHRoZSBzbWFsbGVyIHRoZSAkXGhhdHtcYmV0YX0kIGJlY29tZSwgd2l0aCBtb3JlIGJlaW5nICJ6ZXJvZWQiIG91dCENCg0KIyMjIyBGaW5kaW5nIHRoZSBiZXN0IGNob2ljZSBvZiAkXGxhbWJkYSQNCg0KVG8gZmluZCB0aGUgYmVzdCB2YWx1ZSBvZiAkXGxhbWJkYSQsIHdlIGNvbmR1Y3QgYSBncmlkIHNlYXJjaC4gVXNpbmcgYGdsbW5ldCgpYCwgaXQncyBlYXN5LCBqdXN0IGRvbid0IHNwZWNpZnkgYGxhbWJkYWAhDQoNCk9yIGlmIHlvdSB3YW50IHRvIHNlYXJjaCBhY3Jvc3MgYSBjdXN0b20gbGlzdCBvZiAkXGxhbWJkYSQsIHlvdSBjYW4gZ2l2ZSBgbGFtYmRhYCBhIHZlY3Rvci4NCg0KYGBge3J9DQpsYXNzb19zZWFyY2ggPC0gDQogIGdsbW5ldCgNCiAgICB4ID0gYXMubWF0cml4KHBpZF9zdGFuIHw+IGRwbHlyOjpzZWxlY3QoLWRpYWJldGVzKSksDQogICAgeSA9IChwaWRfc3RhbiRkaWFiZXRlcyA9PSAncG9zJykgKiAxLA0KICAgIGZhbWlseSA9ICdiaW5vbWlhbCcsDQogICAgYWxwaGEgPSAxLA0KICAgIGxhbWJkYSA9IHNlcSgwLCAwLjI1LCBieSA9IDAuMDAxKQ0KICApDQoNCiMgRGF0YSBzZXQgb2YgQUlDIGFuZCBCSUMNCmxhbWJkYV9ncmlkX3NlYXJjaCA8LSANCiAgZGF0YS5mcmFtZSgNCiAgICBsYW1iZGEgPSBsYXNzb19zZWFyY2gkbGFtYmRhLA0KICAgIG5vbl96ZXJvX2NvZWZzID0gbGFzc29fc2VhcmNoJGRmLA0KICAgIGRldmlhbmNlID0gZGV2aWFuY2UobGFzc29fc2VhcmNoKQ0KICApIHw+IA0KICAjIENhbGN1bGF0aW5nIEFJQyBhbmQgQklDDQogIG11dGF0ZSgNCiAgICBBSUMgPSBkZXZpYW5jZSArIDIgKiBub25femVyb19jb2VmcywNCiAgICBCSUMgPSBkZXZpYW5jZSArIGxvZyhucm93KHBpZF9zdGFuKSkgKiBub25femVyb19jb2Vmcw0KICApIA0KDQojIEJlc3QgKG1pbmltdW0pIEFJQw0KbGFtYmRhX2dyaWRfc2VhcmNoIHw+IA0KICBzbGljZV9taW4oQUlDLCBuID0gMTApIHw+IA0KICBwYW5kZXIoKQ0KDQoNCiMgQmVzdCAobWluaW11bSkgQklDDQpsYW1iZGFfZ3JpZF9zZWFyY2ggfD4gDQogIHNsaWNlX21pbihCSUMsIG4gPSAxMCkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KQm90aCBBSUMgYW5kIEJJQyBhZ3JlZSB0aGF0ICRcbGFtYmRhID0gMC4wMDYkIGlzIHRoZSBiZXN0IGNob2ljZQ0KDQpBbHRlcm5hdGl2ZWx5LCB5b3UgY2FuIHNwZWNpZnkgYG5sYW1iZGEgPWAgdG8gdGVsbCBpdCBob3cgbWFueSAkXGxhbWJkYSQgdmFsdWVzIHRvIHRlc3Q6DQoNCmBgYHtyfQ0KbGFzc29fc2VhcmNoMiA8LSANCiAgZ2xtbmV0KA0KICAgIHggPSBhcy5tYXRyaXgocGlkX3N0YW4gfD4gZHBseXI6OnNlbGVjdCgtZGlhYmV0ZXMpKSwNCiAgICB5ID0gKHBpZF9zdGFuJGRpYWJldGVzID09ICdwb3MnKSAqIDEsDQogICAgZmFtaWx5ID0gJ2Jpbm9taWFsJywNCiAgICBhbHBoYSA9IDEsDQogICAgbmxhbWJkYSA9IDUwICMgdGVzdGluZyA1MCBkaWZmZXJlbnQgbGFtYmRhIHZhbHVlcw0KICApDQoNCiMgRGF0YSBzZXQgb2YgQUlDIGFuZCBCSUMNCmxhbWJkYV9ncmlkX3NlYXJjaDIgPC0gDQogIGRhdGEuZnJhbWUoDQogICAgbGFtYmRhID0gbGFzc29fc2VhcmNoMiRsYW1iZGEsDQogICAgbm9uX3plcm9fY29lZnMgPSBsYXNzb19zZWFyY2gyJGRmLA0KICAgIGRldmlhbmNlID0gZGV2aWFuY2UobGFzc29fc2VhcmNoMikNCiAgKSB8PiANCiAgIyBDYWxjdWxhdGluZyBBSUMgYW5kIEJJQw0KICBtdXRhdGUoDQogICAgQUlDID0gZGV2aWFuY2UgKyAyICogbm9uX3plcm9fY29lZnMsDQogICAgQklDID0gZGV2aWFuY2UgKyBsb2cobnJvdyhwaWRfc3RhbikpICogbm9uX3plcm9fY29lZnMNCiAgKSANCg0KIyBCZXN0IChtaW5pbXVtKSBBSUMNCmxhbWJkYV9ncmlkX3NlYXJjaDIgfD4gDQogIHNsaWNlX21pbihBSUMsIG4gPSAxMCkgfD4gDQogIHBhbmRlcigpDQoNCg0KIyBCZXN0IChtaW5pbXVtKSBCSUMNCmxhbWJkYV9ncmlkX3NlYXJjaDIgfD4gDQogIHNsaWNlX21pbihCSUMsIG4gPSAxMCkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KQWdhaW4sIGJvdGggQUlDIGFuZCBCSUMgYWdyZWUgdGhhdCAkXGxhbWJkYSA9IDAuMDA2OCQgaXMgdGhlIGJlc3QgY2hvaWNlLiBOb3QgdGhlIHNhbWUgYXMgb3VyIHByZXZpb3VzIHNlYXJjaCBvZiAwLjAwNiwgYnV0IHZlcnkgY2xvc2UuDQoNCkhvd2V2ZXIsIHRoZSByZXN1bHRzIGhlcmUgYXJlIGdvaW5nIHRvIGJlIGEgbGl0dGxlIGJpYXNlZCBzaW5jZSB3ZSdyZSB1c2luZyB0aGUgc2FtZSBkYXRhIHRvIGJ1aWxkIHRoZSBtb2RlbCBhbmQgdGhlbiBldmFsdWF0ZSB0aGUgdmFsdWUgb2YgJFxsYW1iZGEkLiBJZiB3ZSB3YW50IHRvIHByb3Blcmx5IGNvbmR1Y3Qgb3VyIGdyaWQgc2VhcmNoLCB3ZSBzaG91bGQgaW5zdGVhZCB1c2UgKipjcm9zcy12YWxpZGF0aW9uKiouDQoNCiMjIyMgTEFTU08gd2l0aCBjcm9zcy12YWxpZGF0aW9uDQoNCkhvdyBjYW4gd2UgdXNlIGBnbG1uZXQoKWAgdG8gcGVyZm9ybSBvdXIgZ3JpZCBzZWFyY2ggd2l0aCBjcm9zcy12YWxpZGF0aW9uPyBXZSBjYW4ndCB1c2UgdGhlIGZ1bmN0aW9uIGRpcmVjdGx5LCBidXQgd2UgY2FuIHVzZSBgY3YuZ2xtbmV0KClgIHRvIHBlcmZvcm0gay1mb2xkIGNyb3NzLXZhbGlkYXRpb24hIFdlIGp1c3QgbmVlZCB0byBzcGVjaWZ5IGBuZm9sZHMgPWAsIHdoaWNoIGRlZmF1bHRzIHRvIDEwLg0KDQpgYGB7cn0NCiMgVXNpbmcgbGVhdmUtb25lLW91dCBjcm9zcy12YWxpZGF0aW9uIGZvciBlYWNoIGNob2ljZSBvZiBsYW1iZGENCmxhbWJkYV9jdiA8LSANCiAgY3YuZ2xtbmV0KA0KICAgIHggPSBhcy5tYXRyaXgocGlkX3N0YW4gfD4gZHBseXI6OnNlbGVjdCgtZGlhYmV0ZXMpKSwNCiAgICB5ID0gKHBpZF9zdGFuJGRpYWJldGVzID09ICdwb3MnKSAqIDEsDQogICAgZmFtaWx5ID0gJ2Jpbm9taWFsJywNCiAgICBhbHBoYSA9IDEsDQogICAgbmxhbWJkYSA9IDUwLA0KICAgIG5mb2xkcyA9IG5yb3cocGlkX3N0YW4pICMgbGVhdmUtb25lLW91dCBjdg0KICApDQoNCnBsb3QobGFtYmRhX2N2KQ0KYGBgDQoNCldoYXQgZG9lcyB0aGUgcGxvdCBtZWFuPw0KDQpXaGVuIG5vdCBzcGVjaWZ5aW5nIHRoZSB2YWx1ZXMgb2YgJFxsYW1iZGEkIHRvIHNlYXJjaCwgYGdsbW5ldCgpYCB3aWxsIGNyZWF0ZSBhbiBlcXVhbGx5LXNwYWNlZCB2ZWN0b3IgZm9yICR4ID0gXGxvZyhcbGFtYmRhKSQsIGFuZCB0aGUgdmVjdG9yIGl0IHRlc3RzIGlzICRcbGFtYmRhID0gZV54JCwgd2hpY2ggaXMgd2h5ICdMb2coJFxsYW1iZGEkKScgaXMgc2hvd24gb24gdGhlIGxvd2VyIHgtYXhpcy4NCg0KYGBge3J9DQp0aWJibGUoDQogIGxvZ19sYW1iZGEgPSBsb2cobGFtYmRhX2N2JGxhbWJkYSksDQogIHNwYWNlID0gbG9nX2xhbWJkYSAtIGxhZyhsb2dfbGFtYmRhKQ0KKSB8PiANCiAgaGVhZCgxMCkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KVGhlIHJlZCBkb3Qgc2hvd3MgdGhlIGRldmlhbmNlIHVzaW5nIGNyb3NzLXZhbGlkYXRpb24gZm9yIHRoZSBwYXJ0aWN1bGFyIHZhbHVlIG9mICRcbGFtYmRhJCBhbmQgdGhlIGVycm9yIGJhciBpcyB0aGUgZGV2aWFuY2UgJFxwbSQgMSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgZGV2aWFuY2UuIFRoZSBsb3dlciB0aGUgYmV0dGVyLg0KDQpUaGUgdHdvIGRhc2hlZCBsaW5lcyBzaG93IHRoZSB0d28gb3B0aW9ucyBmb3IgdGhlIGNob2ljZSBvZiAkXGxhbWJkYSQ6DQoNCi0gICBMZWZ0LW1vc3QgbGluZSB3aWxsIGJlIHRoZSB2YWx1ZSB0aGF0IGdpdmUgdGhlIG1pbmltdW0gZGV2aWFuY2UNCi0gICBSaWdodC1tb3N0IGxpbmUgd2lsbCBiZSB0aGUgbGFyZ2VzdCB2YWx1ZSBvZiAkXGxhbWJkYSQgKG1vc3QgcmVndWxhcml6YXRpb24pIHN1Y2ggdGhhdA0KDQokJFx0ZXh0e2RldmlhbmNlfSA8IFxtaW4oXHRleHR7ZGV2aWFuY2V9KSArIFx0ZXh0e1NFKG1pbihkZXZpYW5jZSkpfSQkDQoNCndoZXJlICRcdGV4dHtTRShtaW4oZGV2aWFuY2UpKX0kIGlzIHRoZSBTRSBvZiB0aGUgbWluaW11bSBkZXZpYW5jZQ0KDQpgYGB7cn0NCiMgRGF0YSBmcmFtZSBvZiBsYW1iZGEsIGRldmlhbmNlLCBTRSwgYW5kIG51bWJlciBvZiBub24temVybyBzbG9wZXMNCmN2X3Jlc3VsdHMgPC0gDQogIGRhdGEuZnJhbWUoDQogICAgbGFtYmRhICAgPSBsYW1iZGFfY3YkbGFtYmRhLA0KICAgIGRldmlhbmNlID0gbGFtYmRhX2N2JGN2bSwNCiAgICBkZXZfU0UgICA9IGxhbWJkYV9jdiRjdnNkLA0KICAgIG5fcHJlZHMgID0gbGFtYmRhX2N2JG56ZXJvDQogICkgDQoNCnBhbmRlcihjdl9yZXN1bHRzKQ0KDQojIEZpbmRpbmcgdGhlIGN1dG9mZiBmb3IgdGhlIGxhcmdlc3QgImFjY2VwdGFibGUiIGRldmlhbmNlDQpjdl9kZXZfY3V0b2ZmIDwtIA0KICBjdl9yZXN1bHRzIHw+IA0KICAjIE1pbiBkZXZpYW5jZSANCiAgc2xpY2VfbWluKG9yZGVyX2J5ID0gZGV2aWFuY2UsIG4gPSAxKSB8PiANCiAgIyBDdXRvZmYgPSBtaW4gZGV2aWFuY2UgKyBTRQ0KICBtdXRhdGUoZGV2X2N1dG9mZiA9IGRldmlhbmNlICsgZGV2X1NFKSB8PiANCiAgIyBjdXQgb2ZmIGFzIGEgbnVtYmVyIGluc3RlYWQgb2YgYSBkYXRhIGZyYW1lDQogIHB1bGwoZGV2X2N1dG9mZikNCg0KY3ZfZGV2X2N1dG9mZg0KDQoNCg0KY3ZfcmVzdWx0cyB8PiANCiAgIyBGaW5kaW5nIGFsbCB0aGUgdmFsdWVzIG9mIGxhbWJkYSB3aGVyZSBtaW4oZGV2KSBpcyBpbiBkZXYgKy0gMVNFDQogIGZpbHRlcigNCiAgICBkZXZpYW5jZSA8IGN2X2Rldl9jdXRvZmYNCiAgKSB8PiANCiAgIyBwaWNraW5nIHRoZSByb3cgd2l0aCB0aGUgbGFyZ2VzdCBsYW1iZGENCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gbGFtYmRhLCBuID0gMSkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KV2UgY2FuIGdldCB0aGUgdHdvICRcbGFtYmRhJCB2YWx1ZXMgd2l0aDoNCg0KYGBge3J9DQpjdl9yZXN1bHRzIHw+IA0KICBmaWx0ZXIoDQogICAgbGFtYmRhICVpbiUgYyhsYW1iZGFfY3YkbGFtYmRhLm1pbiwgbGFtYmRhX2N2JGxhbWJkYS4xc2UpDQogICkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KV2hpY2ggb25lIHdlIHVzZSBkZXBlbmRzIG9uIHdoYXQgd2Ugd2FudC4NCg0KSWYgd2Ugd2FudCB0byBpbmNsdWRlIGFsbCAqKnBvdGVudGlhbCoqIGltcG9ydGFudCB2YXJpYWJsZXMgKGxlc3MgcmVndWxhcml6YXRpb24pLCB0aGVuIHdlIGNob29zZSBgbGFtYmRhLm1pbmANCg0KSWYgd2Ugd2FudCB0byBpbmNsdWRlIGFsbCB2YXJpYWJsZXMgdGhhdCBhcmUgZGVmaW5pdGVseSBpbXBvcnRhbnQgKG1vcmUgcmVndWxhcml6YXRpb24pLCB0aGVuIHdlIGNob29zZSBgbGFtYmRhLjFzZWAuDQoNCkFjY29yZGluZyB0byBvdXQgcGxvdCwgYm90aCB3aWxsIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIHByZWRpY3RvcnM6IDYhDQoNClRoZXkgYXJlOg0KDQpgYGB7cn0NCmRhdGEuZnJhbWUoDQogIHRlcm1zID0gcm93bmFtZXMoY29lZihsYW1iZGFfY3YsIHMgPSAnbGFtYmRhLjFzZScpICksDQogIGRldl9taW4gPSBhcy52ZWN0b3IoY29lZihsYW1iZGFfY3YsIHMgPSAnbGFtYmRhLm1pbicpKSwNCiAgZGV2XzFzZSA9IGFzLnZlY3Rvcihjb2VmKGxhbWJkYV9jdiwgcyA9ICdsYW1iZGEuMXNlJykpDQopIHw+IA0KICBwYW5kZXIoKQ0KYGBgDQoNCkFjY29yZGluZyB0byBMQVNTTywgd2UgY2FuIGRyb3AgYHByZXNzdXJlYCBhbmQgYGluc3VsaW5gIGZyb20gdGhlIHByZWRpY3RvcnMhDQoNCiMjIyBXaGF0IG5leHQ/DQoNClNob3VsZCB3ZSB1c2UgdGhlIExBU1NPIGNvZWZmaWNpZW50cyBvciBzaG91bGQgd2UgcmVmaXQgYSB0eXBpY2FsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIHRoZSBwcmVkaWN0b3JzIHdpdGggbm9uLXplcm8gdGVybXM/DQoNCkRlcGVuZHMgb24gd2hhdCB5b3VyIGdvYWxzIGFyZSENCg0KSWYgeW91IHdhbnQgdG8gbWFrZSBwcmVkaWN0aW9ucywgc3RpY2sgd2l0aCB0aGUgcmVzdWx0aW5nIExBU1NPIG1vZGVsOg0KDQpgYGB7cn0NCkxBU1NPX21vZGVsIDwtIA0KICBnbG1uZXQoDQogICAgeCA9IGFzLm1hdHJpeChwaWRfc3RhbiB8PiBkcGx5cjo6c2VsZWN0KC1kaWFiZXRlcykpLA0KICAgIHkgPSAocGlkX3N0YW4kZGlhYmV0ZXMgPT0gJ3BvcycpICogMSwNCiAgICBmYW1pbHkgPSAnYmlub21pYWwnLA0KICAgIGFscGhhID0gMSwNCiAgICBsYW1iZGEgPSBsYW1iZGFfY3YkbGFtYmRhLjFzZSAjIG1vcmUgcmVndWxhcml6YXRpb24gY2hvaWNlDQogICkNCg0KDQojIERpYWJldGVzIHN0YXR1cyBhbmQgZXN0aW1hdGVkIHByb2JhYmlsaXRpZXMNCmRhdGEuZnJhbWUoDQogIGRpYWJldGVzID0gcGlkX3N0YW4kZGlhYmV0ZXMsDQogIGVzdF9wcm9iID0NCiAgICBwcmVkaWN0KA0KICAgICAgTEFTU09fbW9kZWwsIA0KICAgICAgbmV3eCA9IHBpZF9zdGFuIHw+IGRwbHlyOjpzZWxlY3QoLWRpYWJldGVzKSB8PiBhcy5tYXRyaXgoKSwNCiAgICAgIHR5cGUgPSAncmVzcG9uc2UnDQogICAgKSB8PiBhcy52ZWN0b3IoKQ0KKSB8PiANCiAgaGVhZCgxMCkgfD4gDQogIHBhbmRlcigpDQpgYGANCg0KT3IgaWYgd2Ugd2FudCB0byBhbmF5bHplIHRoZSByZWxhdGlvbnNoaXBzIChjb25maWRlbmNlIGludGVydmFscyBhbmQgcC12YWx1ZXMpLCB3ZSB1c2UgdW5wZW5hbGl6ZWQgbG9naXN0aWMgcmVncmVzc2lvbjoNCg0KYGBge3J9DQojIEZpdHRpbmcgdW5yZWd1bGFyaXplZCBsb2dpc3RpYyBtb2RlbA0KbG9naXRfbW9kZWwgPC0gDQogIGdsbSgNCiAgICBmb3JtdWxhID0gZGlhYmV0ZXMgfiAuIC0gcHJlc3N1cmUgLSBpbnN1bGluLA0KICAgIGRhdGEgPSBwaWQyLA0KICAgIGZhbWlseSA9IGJpbm9taWFsDQogICkNCg0KIyB0ZXJtIHRhYmxlDQp0aWR5KGxvZ2l0X21vZGVsLCBleHBvbmVudGlhdGUgPSBULCBjb25mLmludCA9IFQpIHw+IA0KICBtdXRhdGUoDQogICAgYWNyb3NzKA0KICAgICAgLmNvbHMgPSAtdGVybSwNCiAgICAgIC5mbnMgPSB+cm91bmQoLiwgNCkNCiAgICApDQogICkgfD4gDQogIHBhbmRlcigpDQoNCg0KIyBFc3RpbWF0ZWQgcHJvYmFiaWxpdGllcw0KZGF0YS5mcmFtZSgNCiAgZGlhYmV0ZXMgPSBwaWRfc3RhbiRkaWFiZXRlcywNCiAgZXN0X3Byb2IgPQ0KICAgIHByZWRpY3QoDQogICAgICBsb2dpdF9tb2RlbCwgDQogICAgICB0eXBlID0gJ3Jlc3BvbnNlJw0KICAgICkgDQopIHw+IA0KICBoZWFkKDEwKSB8PiANCiAgcGFuZGVyKCkNCmBgYA0K