Goal:

  • See the (Post-)Lasso implementations of glmnet and hdm in action


Introducing the data

The Application Notebook builds on the dataset that is kindly provided in the hdm package. The data was used in Chernozhukov and Hansen (2004). Their paper investigates the effect of participation in the employer-sponsored 401(k) retirement savings plan (p401) on net assets (net_tfa). Since then the data was used to showcase many new methods. It is not the most comprehensive dataset with basically ten covariates/regressors/predictors:

  • age: age

  • db: defined benefit pension

  • educ: education (in years)

  • fsize: family size

  • hown: home owner

  • inc: income (in US $)

  • male: male

  • marr: married

  • pira: participation in individual retirement account (IRA)

  • twoearn: two earners

However, it is publicly available and the relatively few covariates ensure that the programs do not run too long.

# Load the packages required for later
if (!require("glmnet")) install.packages("glmnet", dependencies = TRUE); library(glmnet)
if (!require("hdm")) install.packages("hdm", dependencies = TRUE); library(hdm)
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("skimr")) install.packages("skimr", dependencies = TRUE); library(skimr)

set.seed(1234) # for replicability
options(scipen = 999) # Switch off scientific notation

data(pension) # Find variable description if you type ?pension in console
Y = pension$net_tfa
# Create main effect matrix
X1 = model.matrix(~ 0 + age + db + educ + fsize + hown + inc + male + marr + pira + twoearn, data = pension)

Let’s have a quick look at the covariates and the outcome to be predicted:

skim(X1)
── Data Summary ────────────────────────
                           Values
Name                       X1    
Number of rows             9915  
Number of columns          10    
_______________________          
Column type frequency:           
  numeric                  10    
________________________         
Group variables            None  
hist(Y)



First steps with (Post-)Lasso

Lasso using glmnet package

The most mature and probably most popular package for using Lasso is glmnet. It is written by the inventors of Lasso.

As a first step, let’s use Lasso with the ten control variables. That is, we want to predict net assets using the ten main effects.

Remark: Note that most commands we use throughout the course require to first generate a covariate matrix that is then used as input to the functions.

lasso = glmnet(X1,Y)
plot(lasso, xvar = "lambda",label=TRUE)

As the scale of the variables is very different, we standardize them to get a clearer picture.

lasso = glmnet(scale(X1),Y)
plot(lasso, xvar = "lambda",label=TRUE)

Not very surprisingly the income variable (column 6 in the covariate matrix) is selected first as soon as the penalty term is low enough to allow a non-zero coefficient.

Of course using Lasso with only 10 variables is funny, but let’s see what value of the penalty term would be chosen by cross-validation using the cv.glmnet command:

cv_lasso = cv.glmnet(X1,Y)
plot(cv_lasso)

The cross-validated MSE is lowest when the penalty term is also very low. In this case Lasso is basically OLS.

Check the Lasso coefficients at the cross-validated minimum:

coef(cv_lasso,s = "lambda.min")
11 x 1 sparse Matrix of class "dgCMatrix"
                        s1
(Intercept) -32620.7953625
age            612.0306631
db           -3366.9058611
educ          -555.7055501
fsize         -911.8004242
hown          1497.9902393
inc              0.9544221
male          -844.6804354
marr             2.9475481
pira         29559.2002402
twoearn     -18474.8337196

and observe that they are very close to the plain OLS coefficients:

summary( lm(Y ~ X1) )

Call:
lm(formula = Y ~ X1)

Residuals:
    Min      1Q  Median      3Q     Max 
-509020  -17047   -2203   10232 1436184 

Coefficients:
                Estimate   Std. Error t value             Pr(>|t|)    
(Intercept) -31557.69509   4422.53582  -7.136     0.00000000000103 ***
X1age          610.20621     60.05623  10.161 < 0.0000000000000002 ***
X1db         -3517.95396   1331.34350  -2.642              0.00824 ** 
X1educ        -621.90195    228.96898  -2.716              0.00662 ** 
X1fsize      -1092.95457    456.73577  -2.393              0.01673 *  
X1hown        1619.33449   1322.02599   1.225              0.22065    
X1inc            0.96194      0.02989  32.186 < 0.0000000000000002 ***
X1male       -1141.88008   1517.82867  -0.752              0.45188    
X1marr         611.68490   1820.21834   0.336              0.73684    
X1pira       29662.86017   1467.19707  20.217 < 0.0000000000000002 ***
X1twoearn   -19112.13608   1579.31262 -12.102 < 0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 55790 on 9904 degrees of freedom
Multiple R-squared:  0.2295,    Adjusted R-squared:  0.2287 
F-statistic:   295 on 10 and 9904 DF,  p-value: < 0.00000000000000022


Post-Lasso using hdm

Post-Lasso is available in the hdm package. Its rlasso command runs Post-Lasso:

post_lasso = rlasso(X1,Y)
summary(post_lasso)

Call:
rlasso.default(x = X1, y = Y)

Post-Lasso Estimation:  TRUE 

Total number of variables: 10
Number of selected variables: 6 

Residuals: 
    Min      1Q  Median      3Q     Max 
-508713  -17116   -2078   10386 1439229 

              Estimate
(Intercept) -42247.251
age            642.718
db               0.000
educ             0.000
fsize         -544.576
hown          1593.077
inc              0.926
male             0.000
marr             0.000
pira         29324.753
twoearn     -18667.822

Residual standard error: 55810
Multiple R-squared:  0.2281
Adjusted R-squared:  0.2276
Joint significance test:
 the sup score statistic for joint significance test is 6.077e+10 with a p-value of     0

Post-Lasso is more selective. It kicks out 4 variables. However, the remaining 6 coefficients are estimated using plain OLS without any shrinkage as the following exercise where we reestimate plain OLS with the six selected variables confirms:

summary( lm(Y ~ X1[,post_lasso$coefficients[-1] != 0]) )

Call:
lm(formula = Y ~ X1[, post_lasso$coefficients[-1] != 0])

Residuals:
    Min      1Q  Median      3Q     Max 
-508713  -17116   -2078   10386 1439229 

Coefficients:
                                                  Estimate   Std. Error t value            Pr(>|t|)    
(Intercept)                                   -42247.25064   2638.15724 -16.014 <0.0000000000000002 ***
X1[, post_lasso$coefficients[-1] != 0]age        642.71755     57.91604  11.097 <0.0000000000000002 ***
X1[, post_lasso$coefficients[-1] != 0]fsize     -544.57623    390.76494  -1.394               0.163    
X1[, post_lasso$coefficients[-1] != 0]hown      1593.07667   1312.55210   1.214               0.225    
X1[, post_lasso$coefficients[-1] != 0]inc          0.92648      0.02745  33.757 <0.0000000000000002 ***
X1[, post_lasso$coefficients[-1] != 0]pira     29324.75305   1456.83774  20.129 <0.0000000000000002 ***
X1[, post_lasso$coefficients[-1] != 0]twoearn -18667.82240   1354.26057 -13.785 <0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 55830 on 9908 degrees of freedom
Multiple R-squared:  0.2281,    Adjusted R-squared:  0.2276 
F-statistic: 487.9 on 6 and 9908 DF,  p-value: < 0.00000000000000022

Note that the Lasso commands do not provide any standard errors or p-values. This is not a bug. Inference on penalized coefficients like for Lasso or after variable selection like for Post-Lasso is usually not possible. The parameters are tools to get good predictions. That’s it. They are not themselves parameters we care about.



Out-of-sample prediction with (Post-)Lasso

Supervised Machine Learning is mostly used to predict values in a test sample that was not available for estimating/training the model. Let’s check how we can implement this.

First, we split the sample into a training sample with 2/3 of the data and a test sample with 1/3 of the data:

# Create training (2/3) and test (1/3) sample
test_fraction = 1/3
test_size = floor(test_fraction * length(Y))
# Index for test observations
test_ind = sample(1:length(Y), size = test_size)
# Create training and test data
X_tr = X1[-test_ind,]
X_te = X1[test_ind,]
Y_tr = Y[-test_ind]
Y_te = Y[test_ind]

Now we run again cv.glmnet but only using the training data and apply the predict function to get the fitted values for the test data. Under the hood the predict command does nothing else than what you did most likely in your first econometrics exam where you calculated fitted values for an observation by plugging in the covariate values into the estimated linear model:

cv_lasso = cv.glmnet(X_tr,Y_tr)
Y_hat_lasso = predict(cv_lasso, newx = X_te, s = "lambda.min")

This is the distribution of the predicted values in the test set:

hist(Y_hat_lasso)

We can also plot the predictions against the observable outcome:

plot(Y_hat_lasso,Y_te)

We see that extreme wealth values are hard to forecast. Those outliers mask that the model does a decent job in predicting the values out-of-sample with a correlation around 0.45 between predicted and actual values:

cor(Y_hat_lasso,Y_te)
                [,1]
lambda.min 0.4480603

The standard measure to assess out-of-sample prediction quality is the mean-squared error \(MSE^{te} = 1/N^{te}\sum_i(Y_i^{te}-\hat{Y_i})^2\):

mse_lasso = mean( (Y_te - Y_hat_lasso)^2 )
mse_lasso
[1] 4760872819

However, this number is quite useless in terms of interpretation. Thus, we rather calculate the out-of-sample \(R^2 = 1 - MSE^{te} / Var(Y^{te})\). It has the same interpretation as the \(R^2\) you are probably used to. It provides the fraction of variation in the test outcomes that is explained by the model:

1 - mse_lasso / var(Y_te)
[1] 0.1911137

The same can be done with the Post-Lasso model:

Y_hat_plasso = predict(post_lasso, newdata = X_te)
mse_plasso = mean( (Y_te - Y_hat_plasso)^2 )
1 - mse_plasso / var(Y_te)
[1] 0.1970336



Including interactions and compare performance

The previous section illustrated the use of Lasso and Post-Lasso using only the main effects. This is not the setting where they are supposed to provide a big advantage over OLS.

However, there is usually no reason to believe that only main effects are relevant for predicting wealth. In the following I run a computationally expensive experiment (ran on my laptop over night).

We consider four different covariate matrices:

  • X1 with 10 variables: Only the main effects

  • X2 with 88 variables: Second order polynomials of the continuous variables age, education and income as well as first order interactions of all variables

  • X3 with 567 variables: Third order polynomials of the continuous variables age, education and income as well as second order interactions of all variables

  • X4 with 2270 variables: Fourth order polynomials of the continuous variables age, education and income as well as third order interactions of all variables

X2 = model.matrix(~ 0 + (fsize + marr + twoearn + db + pira + hown + male +
                           poly(age,2) + poly(educ,2) + poly(inc,2))^2, data = pension)
dim(X2)
[1] 9915   88
X3 = model.matrix(~ 0 + (fsize + marr + twoearn + db + pira + hown + male +
                           poly(age,3) + poly(educ,3) + poly(inc,3))^3, data = pension)
dim(X3)
[1] 9915  567
X4 = model.matrix(~ 0 + (fsize + marr + twoearn + db + pira + hown + male +
                           poly(age,4) + poly(educ,4) + poly(inc,4))^4, data = pension)
dim(X4)
[1] 9915 2770

We consider 100 random splits into training and test sample to ensure that our results are not an artifact of one particular split and run OLS, Lasso and Post-Lasso with the four different covariate matrices (Post-Lasso with X4 is omitted as it would more then double the computation time).

# Here we define some useful function to keep the code clean
# They run the method in the training sample and calculate the test set R2
ols_oos_r2 = function(x_tr,y_tr,x_te,y_te) {
  ols = lm(y_tr ~ x_tr)
  betas = ols$coefficients
  betas[is.na(betas)] = 0
  y_hat = cbind( rep(1,nrow(x_te)) , x_te ) %*% betas
  mse = mean( (y_te - y_hat)^2 )
  return(1 - mse / var(y_te))
}

lasso_oos_r2 = function(x_tr,y_tr,x_te,y_te,min.lambda = 1e-04) {
  cv_lasso = cv.glmnet(x_tr,y_tr,lambda.min.ratio = min.lambda)
  y_hat = predict(cv_lasso, newx = x_te,s = "lambda.min")
  mse = mean( (y_te - y_hat)^2 )
  return(1 - mse / var(y_te))
}

plasso_oos_r2 = function(x_tr,y_tr,x_te,y_te) {
  plasso = rlasso(x_te,y_te)
  y_hat = predict(plasso, newdata = x_te)
  mse = mean( (y_te - y_hat)^2 )
  return(1 - mse / var(y_te))
}

rep = 100 # number of replications

# Container of the results
results_r2 = matrix(NA,rep,12)
colnames(results_r2) = c("OLS1","OLS2","OLS3","OLS4",
                         "Lasso1","Lasso2","Lasso3","Lasso4",
                         "Post-Lasso1","Post-Lasso2","Post-Lasso3","Post-Lasso4")

# Loop considering different splits
for (i in 1:rep) {
  # Draw index for this round
  temp_ind = sample(1:length(Y), size = test_size)

  # Split into training and test samples
  X_tr1 = X1[-temp_ind,]
  X_te1 = X1[temp_ind,]
  X_tr2 = X2[-temp_ind,]
  X_te2 = X2[temp_ind,]
  X_tr3 = X3[-temp_ind,]
  X_te3 = X3[temp_ind,]
  X_tr4 = X4[-temp_ind,]
  X_te4 = X4[temp_ind,]
  Y_tr = Y[-temp_ind]
  Y_te = Y[temp_ind]
  
  # Get test R2 for method-cov matrix combi
  results_r2[i,1] = ols_oos_r2(X_tr1,Y_tr,X_te1,Y_te)
  results_r2[i,2] = ols_oos_r2(X_tr2,Y_tr,X_te2,Y_te)
  results_r2[i,3] = ols_oos_r2(X_tr3,Y_tr,X_te3,Y_te)
  results_r2[i,4] = ols_oos_r2(X_tr4,Y_tr,X_te4,Y_te)
  results_r2[i,5] = lasso_oos_r2(X_tr1,Y_tr,X_te1,Y_te)
  results_r2[i,6] = lasso_oos_r2(X_tr2,Y_tr,X_te2,Y_te)
  # Increasing min.lambda to speed up computation
  results_r2[i,7] = lasso_oos_r2(X_tr3,Y_tr,X_te3,Y_te,min.lambda = 0.01)
  results_r2[i,8] = lasso_oos_r2(X_tr4,Y_tr,X_te4,Y_te,min.lambda = 0.05)
  results_r2[i,9] = plasso_oos_r2(X_tr1,Y_tr,X_te1,Y_te)
  results_r2[i,10] = plasso_oos_r2(X_tr2,Y_tr,X_te2,Y_te)
  results_r2[i,11] = plasso_oos_r2(X_tr3,Y_tr,X_te3,Y_te)
  # results_r2[i,12] = plasso_oos_r2(X_tr4,Y_tr,X_te4,Y_te)
}
# If you read this and like to parallelize stuff, feel free to send me a fast version ;-)

First, let’s check the mean out-of-sample \(R^2\) for the different methods over the 100 different splits:

t( round(colMeans(results_r2),3) )
      OLS1  OLS2    OLS3       OLS4 Lasso1 Lasso2 Lasso3 Lasso4 Post-Lasso1 Post-Lasso2 Post-Lasso3 Post-Lasso4
[1,] 0.229 0.284 -53.643 -671733924  0.229  0.294  0.269  0.266       0.228       0.265       0.263          NA

Some observations from this specific application:

  • Running OLS with third and fourth order terms is hopeless. The mean \(R^2\) becomes even negative. How can this happen? The \(R^2\) is negative if the prediction model performs worse than just using the outcome mean for prediction. This is exactly the overfitting behaviour that we discuss in the slides.

  • OLS with second order terms is quite competitive.

  • Providing the second order terms improves predictions of all methods. However, higher order terms still improve over just using main effects, but perform worse than second order terms \(\Rightarrow\) just spamming Lasso with interactions not necessarily improves prediction

  • Lasso performs slightly better than Post-Lasso. This is not a general results and can differ for other datasets.

These observations are also visible in the boxplots of the \(R^2\):

as.data.frame(results_r2[,-c(3:4)]) %>% pivot_longer(cols=everything(),names_to = "Method",values_to = "R2") %>%
  ggplot(aes(x = R2, y = Method)) + geom_boxplot()

In general, every dataset is different. There is no rule of thumb which method and specification performs best. However, the good thing is that we can check the performance of different candidates. For the dataset at hand it seems that Lasso with second order terms would be a good choice.



LS0tDQp0aXRsZTogIlN1cGVydmlzZWQgTUw6IExhc3NvIg0Kc3VidGl0bGU6ICJBcHBsaWNhdGlvbiBub3RlYm9vayINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KR29hbDoNCg0KLSBTZWUgdGhlIChQb3N0LSlMYXNzbyBpbXBsZW1lbnRhdGlvbnMgb2YgYGdsbW5ldGAgYW5kIGBoZG1gIGluIGFjdGlvbg0KDQo8YnI+DQoNCiMgSW50cm9kdWNpbmcgdGhlIGRhdGENCg0KVGhlIEFwcGxpY2F0aW9uIE5vdGVib29rIGJ1aWxkcyBvbiB0aGUgZGF0YXNldCB0aGF0IGlzIGtpbmRseSBwcm92aWRlZCBpbiB0aGUgYGhkbWAgcGFja2FnZS4gVGhlIGRhdGEgd2FzIHVzZWQgaW4gW0NoZXJub3podWtvdiBhbmQgSGFuc2VuICgyMDA0KV0oaHR0cHM6Ly9kaXJlY3QubWl0LmVkdS9yZXN0L2FydGljbGUvODYvMy83MzUvNTc1ODYvVGhlLUVmZmVjdHMtb2YtNDAxLUstUGFydGljaXBhdGlvbi1vbi10aGUtV2VhbHRoKS4gVGhlaXIgcGFwZXIgaW52ZXN0aWdhdGVzIHRoZSBlZmZlY3Qgb2YgcGFydGljaXBhdGlvbiBpbiB0aGUgZW1wbG95ZXItc3BvbnNvcmVkIDQwMShrKSByZXRpcmVtZW50IHNhdmluZ3MgcGxhbiAoYHA0MDFgKSBvbiBuZXQgYXNzZXRzIChgbmV0X3RmYWApLiBTaW5jZSB0aGVuIHRoZSBkYXRhIHdhcyB1c2VkIHRvIHNob3djYXNlIG1hbnkgbmV3IG1ldGhvZHMuIEl0IGlzIG5vdCB0aGUgbW9zdCBjb21wcmVoZW5zaXZlIGRhdGFzZXQgd2l0aCBiYXNpY2FsbHkgdGVuIGNvdmFyaWF0ZXMvcmVncmVzc29ycy9wcmVkaWN0b3JzOg0KDQotICphZ2UqOiBhZ2UNCg0KLSAqZGIqOiBkZWZpbmVkIGJlbmVmaXQgcGVuc2lvbg0KDQotICplZHVjKjogZWR1Y2F0aW9uIChpbiB5ZWFycykNCg0KLSAqZnNpemUqOiBmYW1pbHkgc2l6ZQ0KDQotICpob3duKjogaG9tZSBvd25lcg0KDQotICppbmMqOiBpbmNvbWUgKGluIFVTICQpDQoNCi0gKm1hbGUqOiBtYWxlDQoNCi0gKm1hcnIqOiBtYXJyaWVkDQoNCi0gKnBpcmEqOiBwYXJ0aWNpcGF0aW9uIGluIGluZGl2aWR1YWwgcmV0aXJlbWVudCBhY2NvdW50IChJUkEpDQoNCi0gKnR3b2Vhcm4qOiB0d28gZWFybmVycw0KDQpIb3dldmVyLCBpdCBpcyBwdWJsaWNseSBhdmFpbGFibGUgYW5kIHRoZSByZWxhdGl2ZWx5IGZldyBjb3ZhcmlhdGVzIGVuc3VyZSB0aGF0IHRoZSBwcm9ncmFtcyBkbyBub3QgcnVuIHRvbyBsb25nLg0KDQoNCmBgYHtyLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQojIExvYWQgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIGZvciBsYXRlcg0KaWYgKCFyZXF1aXJlKCJnbG1uZXQiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ2xtbmV0KQ0KaWYgKCFyZXF1aXJlKCJoZG0iKSkgaW5zdGFsbC5wYWNrYWdlcygiaGRtIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoaGRtKQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkodGlkeXZlcnNlKQ0KaWYgKCFyZXF1aXJlKCJza2ltciIpKSBpbnN0YWxsLnBhY2thZ2VzKCJza2ltciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHNraW1yKQ0KDQpzZXQuc2VlZCgxMjM0KSAjIGZvciByZXBsaWNhYmlsaXR5DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkgIyBTd2l0Y2ggb2ZmIHNjaWVudGlmaWMgbm90YXRpb24NCg0KZGF0YShwZW5zaW9uKSAjIEZpbmQgdmFyaWFibGUgZGVzY3JpcHRpb24gaWYgeW91IHR5cGUgP3BlbnNpb24gaW4gY29uc29sZQ0KWSA9IHBlbnNpb24kbmV0X3RmYQ0KIyBDcmVhdGUgbWFpbiBlZmZlY3QgbWF0cml4DQpYMSA9IG1vZGVsLm1hdHJpeCh+IDAgKyBhZ2UgKyBkYiArIGVkdWMgKyBmc2l6ZSArIGhvd24gKyBpbmMgKyBtYWxlICsgbWFyciArIHBpcmEgKyB0d29lYXJuLCBkYXRhID0gcGVuc2lvbikNCmBgYA0KDQpMZXQncyBoYXZlIGEgcXVpY2sgbG9vayBhdCB0aGUgY292YXJpYXRlcyBhbmQgdGhlIG91dGNvbWUgdG8gYmUgcHJlZGljdGVkOg0KDQpgYGB7cn0NCnNraW0oWDEpDQpoaXN0KFkpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQoNCiMgRmlyc3Qgc3RlcHMgd2l0aCAoUG9zdC0pTGFzc28NCg0KIyMgTGFzc28gdXNpbmcgYGdsbW5ldGAgcGFja2FnZQ0KDQpUaGUgbW9zdCBtYXR1cmUgYW5kIHByb2JhYmx5IG1vc3QgcG9wdWxhciBwYWNrYWdlIGZvciB1c2luZyBMYXNzbyBpcyBgZ2xtbmV0YC4gSXQgaXMgd3JpdHRlbiBieSB0aGUgaW52ZW50b3JzIG9mIExhc3NvLg0KDQpBcyBhIGZpcnN0IHN0ZXAsIGxldCdzIHVzZSBMYXNzbyB3aXRoIHRoZSB0ZW4gY29udHJvbCB2YXJpYWJsZXMuIFRoYXQgaXMsIHdlIHdhbnQgdG8gcHJlZGljdCBuZXQgYXNzZXRzIHVzaW5nIHRoZSB0ZW4gbWFpbiBlZmZlY3RzLg0KDQoqUmVtYXJrOiogTm90ZSB0aGF0IG1vc3QgY29tbWFuZHMgd2UgdXNlIHRocm91Z2hvdXQgdGhlIGNvdXJzZSByZXF1aXJlIHRvIGZpcnN0IGdlbmVyYXRlIGEgY292YXJpYXRlIG1hdHJpeCB0aGF0IGlzIHRoZW4gdXNlZCBhcyBpbnB1dCB0byB0aGUgZnVuY3Rpb25zLg0KDQpgYGB7cn0NCmxhc3NvID0gZ2xtbmV0KFgxLFkpDQpwbG90KGxhc3NvLCB4dmFyID0gImxhbWJkYSIsbGFiZWw9VFJVRSkNCmBgYA0KDQpBcyB0aGUgc2NhbGUgb2YgdGhlIHZhcmlhYmxlcyBpcyB2ZXJ5IGRpZmZlcmVudCwgd2Ugc3RhbmRhcmRpemUgdGhlbSB0byBnZXQgYSBjbGVhcmVyIHBpY3R1cmUuDQoNCmBgYHtyfQ0KbGFzc28gPSBnbG1uZXQoc2NhbGUoWDEpLFkpDQpwbG90KGxhc3NvLCB4dmFyID0gImxhbWJkYSIsbGFiZWw9VFJVRSkNCmBgYA0KDQoNCk5vdCB2ZXJ5IHN1cnByaXNpbmdseSB0aGUgaW5jb21lIHZhcmlhYmxlIChjb2x1bW4gNiBpbiB0aGUgY292YXJpYXRlIG1hdHJpeCkgaXMgc2VsZWN0ZWQgZmlyc3QgYXMgc29vbiBhcyB0aGUgcGVuYWx0eSB0ZXJtIGlzIGxvdyBlbm91Z2ggdG8gYWxsb3cgYSBub24temVybyBjb2VmZmljaWVudC4NCg0KT2YgY291cnNlIHVzaW5nIExhc3NvIHdpdGggb25seSAxMCB2YXJpYWJsZXMgaXMgZnVubnksIGJ1dCBsZXQncyBzZWUgd2hhdCB2YWx1ZSBvZiB0aGUgcGVuYWx0eSB0ZXJtIHdvdWxkIGJlIGNob3NlbiBieSBjcm9zcy12YWxpZGF0aW9uIHVzaW5nIHRoZSBgY3YuZ2xtbmV0YCBjb21tYW5kOg0KDQpgYGB7cn0NCmN2X2xhc3NvID0gY3YuZ2xtbmV0KFgxLFkpDQpwbG90KGN2X2xhc3NvKQ0KYGBgDQoNClRoZSBjcm9zcy12YWxpZGF0ZWQgTVNFIGlzIGxvd2VzdCB3aGVuIHRoZSBwZW5hbHR5IHRlcm0gaXMgYWxzbyB2ZXJ5IGxvdy4gSW4gdGhpcyBjYXNlIExhc3NvIGlzIGJhc2ljYWxseSBPTFMuDQoNCkNoZWNrIHRoZSBMYXNzbyBjb2VmZmljaWVudHMgYXQgdGhlIGNyb3NzLXZhbGlkYXRlZCBtaW5pbXVtOg0KDQpgYGB7cn0NCmNvZWYoY3ZfbGFzc28scyA9ICJsYW1iZGEubWluIikNCmBgYA0KDQphbmQgb2JzZXJ2ZSB0aGF0IHRoZXkgYXJlIHZlcnkgY2xvc2UgdG8gdGhlIHBsYWluIE9MUyBjb2VmZmljaWVudHM6DQoNCmBgYHtyfQ0Kc3VtbWFyeSggbG0oWSB+IFgxKSApDQpgYGANCg0KPGJyPg0KDQojIyBQb3N0LUxhc3NvIHVzaW5nIGBoZG1gDQoNClBvc3QtTGFzc28gaXMgYXZhaWxhYmxlIGluIHRoZSBgaGRtYCBwYWNrYWdlLiBJdHMgYHJsYXNzb2AgY29tbWFuZCBydW5zIFBvc3QtTGFzc286DQoNCmBgYHtyfQ0KcG9zdF9sYXNzbyA9IHJsYXNzbyhYMSxZKQ0Kc3VtbWFyeShwb3N0X2xhc3NvKQ0KYGBgDQoNClBvc3QtTGFzc28gaXMgbW9yZSBzZWxlY3RpdmUuIEl0IGtpY2tzIG91dCA0IHZhcmlhYmxlcy4gSG93ZXZlciwgdGhlIHJlbWFpbmluZyA2IGNvZWZmaWNpZW50cyBhcmUgZXN0aW1hdGVkIHVzaW5nIHBsYWluIE9MUyB3aXRob3V0IGFueSBzaHJpbmthZ2UgYXMgdGhlIGZvbGxvd2luZyBleGVyY2lzZSB3aGVyZSB3ZSByZWVzdGltYXRlIHBsYWluIE9MUyB3aXRoIHRoZSBzaXggc2VsZWN0ZWQgdmFyaWFibGVzIGNvbmZpcm1zOg0KDQpgYGB7cn0NCnN1bW1hcnkoIGxtKFkgfiBYMVsscG9zdF9sYXNzbyRjb2VmZmljaWVudHNbLTFdICE9IDBdKSApDQpgYGANCg0KTm90ZSB0aGF0IHRoZSBMYXNzbyBjb21tYW5kcyBkbyBub3QgcHJvdmlkZSBhbnkgc3RhbmRhcmQgZXJyb3JzIG9yIHAtdmFsdWVzLiBUaGlzIGlzIG5vdCBhIGJ1Zy4gSW5mZXJlbmNlIG9uIHBlbmFsaXplZCBjb2VmZmljaWVudHMgbGlrZSBmb3IgTGFzc28gb3IgYWZ0ZXIgdmFyaWFibGUgc2VsZWN0aW9uIGxpa2UgZm9yIFBvc3QtTGFzc28gaXMgdXN1YWxseSBub3QgcG9zc2libGUuIFRoZSBwYXJhbWV0ZXJzIGFyZSB0b29scyB0byBnZXQgZ29vZCBwcmVkaWN0aW9ucy4gVGhhdCdzIGl0LiBUaGV5IGFyZSBub3QgdGhlbXNlbHZlcyBwYXJhbWV0ZXJzIHdlIGNhcmUgYWJvdXQuDQoNCjxicj4NCjxicj4NCg0KIyBPdXQtb2Ytc2FtcGxlIHByZWRpY3Rpb24gd2l0aCAoUG9zdC0pTGFzc28NCg0KU3VwZXJ2aXNlZCBNYWNoaW5lIExlYXJuaW5nIGlzIG1vc3RseSB1c2VkIHRvIHByZWRpY3QgdmFsdWVzIGluIGEgdGVzdCBzYW1wbGUgdGhhdCB3YXMgbm90IGF2YWlsYWJsZSBmb3IgZXN0aW1hdGluZy90cmFpbmluZyB0aGUgbW9kZWwuIExldCdzIGNoZWNrIGhvdyB3ZSBjYW4gaW1wbGVtZW50IHRoaXMuDQoNCkZpcnN0LCB3ZSBzcGxpdCB0aGUgc2FtcGxlIGludG8gYSB0cmFpbmluZyBzYW1wbGUgd2l0aCAyLzMgb2YgdGhlIGRhdGEgYW5kIGEgdGVzdCBzYW1wbGUgd2l0aCAxLzMgb2YgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KIyBDcmVhdGUgdHJhaW5pbmcgKDIvMykgYW5kIHRlc3QgKDEvMykgc2FtcGxlDQp0ZXN0X2ZyYWN0aW9uID0gMS8zDQp0ZXN0X3NpemUgPSBmbG9vcih0ZXN0X2ZyYWN0aW9uICogbGVuZ3RoKFkpKQ0KIyBJbmRleCBmb3IgdGVzdCBvYnNlcnZhdGlvbnMNCnRlc3RfaW5kID0gc2FtcGxlKDE6bGVuZ3RoKFkpLCBzaXplID0gdGVzdF9zaXplKQ0KIyBDcmVhdGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YQ0KWF90ciA9IFgxWy10ZXN0X2luZCxdDQpYX3RlID0gWDFbdGVzdF9pbmQsXQ0KWV90ciA9IFlbLXRlc3RfaW5kXQ0KWV90ZSA9IFlbdGVzdF9pbmRdDQpgYGANCg0KTm93IHdlIHJ1biBhZ2FpbiBgY3YuZ2xtbmV0YCBidXQgb25seSB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgYXBwbHkgdGhlIGBwcmVkaWN0YCBmdW5jdGlvbiB0byBnZXQgdGhlIGZpdHRlZCB2YWx1ZXMgZm9yIHRoZSB0ZXN0IGRhdGEuIFVuZGVyIHRoZSBob29kIHRoZSBwcmVkaWN0IGNvbW1hbmQgZG9lcyBub3RoaW5nIGVsc2UgdGhhbiB3aGF0IHlvdSBkaWQgbW9zdCBsaWtlbHkgaW4geW91ciBmaXJzdCBlY29ub21ldHJpY3MgZXhhbSB3aGVyZSB5b3UgY2FsY3VsYXRlZCBmaXR0ZWQgdmFsdWVzIGZvciBhbiBvYnNlcnZhdGlvbiBieSBwbHVnZ2luZyBpbiB0aGUgY292YXJpYXRlIHZhbHVlcyBpbnRvIHRoZSBlc3RpbWF0ZWQgbGluZWFyIG1vZGVsOg0KDQpgYGB7cn0NCmN2X2xhc3NvID0gY3YuZ2xtbmV0KFhfdHIsWV90cikNCllfaGF0X2xhc3NvID0gcHJlZGljdChjdl9sYXNzbywgbmV3eCA9IFhfdGUsIHMgPSAibGFtYmRhLm1pbiIpDQpgYGANCg0KVGhpcyBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGluIHRoZSB0ZXN0IHNldDoNCg0KYGBge3J9DQpoaXN0KFlfaGF0X2xhc3NvKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHBsb3QgdGhlIHByZWRpY3Rpb25zIGFnYWluc3QgdGhlIG9ic2VydmFibGUgb3V0Y29tZToNCg0KYGBge3J9DQpwbG90KFlfaGF0X2xhc3NvLFlfdGUpDQpgYGANCg0KV2Ugc2VlIHRoYXQgZXh0cmVtZSB3ZWFsdGggdmFsdWVzIGFyZSBoYXJkIHRvIGZvcmVjYXN0LiBUaG9zZSBvdXRsaWVycyBtYXNrIHRoYXQgdGhlIG1vZGVsIGRvZXMgYSBkZWNlbnQgam9iIGluIHByZWRpY3RpbmcgdGhlIHZhbHVlcyBvdXQtb2Ytc2FtcGxlIHdpdGggYSBjb3JyZWxhdGlvbiBhcm91bmQgMC40NSBiZXR3ZWVuIHByZWRpY3RlZCBhbmQgYWN0dWFsIHZhbHVlczoNCg0KYGBge3J9DQpjb3IoWV9oYXRfbGFzc28sWV90ZSkNCmBgYA0KDQpUaGUgc3RhbmRhcmQgbWVhc3VyZSB0byBhc3Nlc3Mgb3V0LW9mLXNhbXBsZSBwcmVkaWN0aW9uIHF1YWxpdHkgaXMgdGhlIG1lYW4tc3F1YXJlZCBlcnJvciAkTVNFXnt0ZX0gPSAxL05ee3RlfVxzdW1faShZX2lee3RlfS1caGF0e1lfaX0pXjIkOg0KDQpgYGB7cn0NCm1zZV9sYXNzbyA9IG1lYW4oIChZX3RlIC0gWV9oYXRfbGFzc28pXjIgKQ0KbXNlX2xhc3NvDQpgYGANCg0KSG93ZXZlciwgdGhpcyBudW1iZXIgaXMgcXVpdGUgdXNlbGVzcyBpbiB0ZXJtcyBvZiBpbnRlcnByZXRhdGlvbi4gVGh1cywgd2UgcmF0aGVyIGNhbGN1bGF0ZSB0aGUgb3V0LW9mLXNhbXBsZSAkUl4yID0gMSAtIE1TRV57dGV9IC8gVmFyKFlee3RlfSkkLiBJdCBoYXMgdGhlIHNhbWUgaW50ZXJwcmV0YXRpb24gYXMgdGhlICRSXjIkIHlvdSBhcmUgcHJvYmFibHkgdXNlZCB0by4gSXQgcHJvdmlkZXMgdGhlIGZyYWN0aW9uIG9mIHZhcmlhdGlvbiBpbiB0aGUgdGVzdCBvdXRjb21lcyB0aGF0IGlzIGV4cGxhaW5lZCBieSB0aGUgbW9kZWw6DQoNCmBgYHtyfQ0KMSAtIG1zZV9sYXNzbyAvIHZhcihZX3RlKQ0KYGBgDQoNClRoZSBzYW1lIGNhbiBiZSBkb25lIHdpdGggdGhlIFBvc3QtTGFzc28gbW9kZWw6DQoNCmBgYHtyfQ0KWV9oYXRfcGxhc3NvID0gcHJlZGljdChwb3N0X2xhc3NvLCBuZXdkYXRhID0gWF90ZSkNCm1zZV9wbGFzc28gPSBtZWFuKCAoWV90ZSAtIFlfaGF0X3BsYXNzbyleMiApDQoxIC0gbXNlX3BsYXNzbyAvIHZhcihZX3RlKQ0KYGBgDQoNCg0KPGJyPg0KPGJyPg0KDQojIEluY2x1ZGluZyBpbnRlcmFjdGlvbnMgYW5kIGNvbXBhcmUgcGVyZm9ybWFuY2UNCg0KVGhlIHByZXZpb3VzIHNlY3Rpb24gaWxsdXN0cmF0ZWQgdGhlIHVzZSBvZiBMYXNzbyBhbmQgUG9zdC1MYXNzbyB1c2luZyBvbmx5IHRoZSBtYWluIGVmZmVjdHMuIFRoaXMgaXMgbm90IHRoZSBzZXR0aW5nIHdoZXJlIHRoZXkgYXJlIHN1cHBvc2VkIHRvIHByb3ZpZGUgYSBiaWcgYWR2YW50YWdlIG92ZXIgT0xTLg0KDQpIb3dldmVyLCB0aGVyZSBpcyB1c3VhbGx5IG5vIHJlYXNvbiB0byBiZWxpZXZlIHRoYXQgb25seSBtYWluIGVmZmVjdHMgYXJlIHJlbGV2YW50IGZvciBwcmVkaWN0aW5nIHdlYWx0aC4gSW4gdGhlIGZvbGxvd2luZyBJIHJ1biBhIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUgZXhwZXJpbWVudCAocmFuIG9uIG15IGxhcHRvcCBvdmVyIG5pZ2h0KS4NCg0KV2UgY29uc2lkZXIgZm91ciBkaWZmZXJlbnQgY292YXJpYXRlIG1hdHJpY2VzOg0KDQotICpYMSogd2l0aCAxMCB2YXJpYWJsZXM6IE9ubHkgdGhlIG1haW4gZWZmZWN0cw0KDQotICpYMiogd2l0aCA4OCB2YXJpYWJsZXM6IFNlY29uZCBvcmRlciBwb2x5bm9taWFscyBvZiB0aGUgY29udGludW91cyB2YXJpYWJsZXMgYWdlLCBlZHVjYXRpb24gYW5kIGluY29tZSBhcyB3ZWxsIGFzIGZpcnN0IG9yZGVyIGludGVyYWN0aW9ucyBvZiBhbGwgdmFyaWFibGVzDQoNCi0gKlgzKiB3aXRoIDU2NyB2YXJpYWJsZXM6IFRoaXJkIG9yZGVyIHBvbHlub21pYWxzIG9mIHRoZSBjb250aW51b3VzIHZhcmlhYmxlcyBhZ2UsIGVkdWNhdGlvbiBhbmQgaW5jb21lIGFzIHdlbGwgYXMgc2Vjb25kIG9yZGVyIGludGVyYWN0aW9ucyBvZiBhbGwgdmFyaWFibGVzDQoNCi0gKlg0KiB3aXRoIDIyNzAgdmFyaWFibGVzOiBGb3VydGggb3JkZXIgcG9seW5vbWlhbHMgb2YgdGhlIGNvbnRpbnVvdXMgdmFyaWFibGVzIGFnZSwgZWR1Y2F0aW9uIGFuZCBpbmNvbWUgYXMgd2VsbCBhcyB0aGlyZCBvcmRlciBpbnRlcmFjdGlvbnMgb2YgYWxsIHZhcmlhYmxlcw0KDQoNCmBgYHtyfQ0KWDIgPSBtb2RlbC5tYXRyaXgofiAwICsgKGZzaXplICsgbWFyciArIHR3b2Vhcm4gKyBkYiArIHBpcmEgKyBob3duICsgbWFsZSArDQogICAgICAgICAgICAgICAgICAgICAgICAgICBwb2x5KGFnZSwyKSArIHBvbHkoZWR1YywyKSArIHBvbHkoaW5jLDIpKV4yLCBkYXRhID0gcGVuc2lvbikNCmRpbShYMikNClgzID0gbW9kZWwubWF0cml4KH4gMCArIChmc2l6ZSArIG1hcnIgKyB0d29lYXJuICsgZGIgKyBwaXJhICsgaG93biArIG1hbGUgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9seShhZ2UsMykgKyBwb2x5KGVkdWMsMykgKyBwb2x5KGluYywzKSleMywgZGF0YSA9IHBlbnNpb24pDQpkaW0oWDMpDQpYNCA9IG1vZGVsLm1hdHJpeCh+IDAgKyAoZnNpemUgKyBtYXJyICsgdHdvZWFybiArIGRiICsgcGlyYSArIGhvd24gKyBtYWxlICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvbHkoYWdlLDQpICsgcG9seShlZHVjLDQpICsgcG9seShpbmMsNCkpXjQsIGRhdGEgPSBwZW5zaW9uKQ0KZGltKFg0KQ0KYGBgDQoNCldlIGNvbnNpZGVyIDEwMCByYW5kb20gc3BsaXRzIGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2FtcGxlIHRvIGVuc3VyZSB0aGF0IG91ciByZXN1bHRzIGFyZSBub3QgYW4gYXJ0aWZhY3Qgb2Ygb25lIHBhcnRpY3VsYXIgc3BsaXQgYW5kIHJ1biBPTFMsIExhc3NvIGFuZCBQb3N0LUxhc3NvIHdpdGggdGhlIGZvdXIgZGlmZmVyZW50IGNvdmFyaWF0ZSBtYXRyaWNlcyAoUG9zdC1MYXNzbyB3aXRoICpYNCogaXMgb21pdHRlZCBhcyBpdCB3b3VsZCBtb3JlIHRoZW4gZG91YmxlIHRoZSBjb21wdXRhdGlvbiB0aW1lKS4NCg0KYGBge3J9DQojIEhlcmUgd2UgZGVmaW5lIHNvbWUgdXNlZnVsIGZ1bmN0aW9uIHRvIGtlZXAgdGhlIGNvZGUgY2xlYW4NCiMgVGhleSBydW4gdGhlIG1ldGhvZCBpbiB0aGUgdHJhaW5pbmcgc2FtcGxlIGFuZCBjYWxjdWxhdGUgdGhlIHRlc3Qgc2V0IFIyDQpvbHNfb29zX3IyID0gZnVuY3Rpb24oeF90cix5X3RyLHhfdGUseV90ZSkgew0KICBvbHMgPSBsbSh5X3RyIH4geF90cikNCiAgYmV0YXMgPSBvbHMkY29lZmZpY2llbnRzDQogIGJldGFzW2lzLm5hKGJldGFzKV0gPSAwDQogIHlfaGF0ID0gY2JpbmQoIHJlcCgxLG5yb3coeF90ZSkpICwgeF90ZSApICUqJSBiZXRhcw0KICBtc2UgPSBtZWFuKCAoeV90ZSAtIHlfaGF0KV4yICkNCiAgcmV0dXJuKDEgLSBtc2UgLyB2YXIoeV90ZSkpDQp9DQoNCmxhc3NvX29vc19yMiA9IGZ1bmN0aW9uKHhfdHIseV90cix4X3RlLHlfdGUsbWluLmxhbWJkYSA9IDFlLTA0KSB7DQogIGN2X2xhc3NvID0gY3YuZ2xtbmV0KHhfdHIseV90cixsYW1iZGEubWluLnJhdGlvID0gbWluLmxhbWJkYSkNCiAgeV9oYXQgPSBwcmVkaWN0KGN2X2xhc3NvLCBuZXd4ID0geF90ZSxzID0gImxhbWJkYS5taW4iKQ0KICBtc2UgPSBtZWFuKCAoeV90ZSAtIHlfaGF0KV4yICkNCiAgcmV0dXJuKDEgLSBtc2UgLyB2YXIoeV90ZSkpDQp9DQoNCnBsYXNzb19vb3NfcjIgPSBmdW5jdGlvbih4X3RyLHlfdHIseF90ZSx5X3RlKSB7DQogIHBsYXNzbyA9IHJsYXNzbyh4X3RlLHlfdGUpDQogIHlfaGF0ID0gcHJlZGljdChwbGFzc28sIG5ld2RhdGEgPSB4X3RlKQ0KICBtc2UgPSBtZWFuKCAoeV90ZSAtIHlfaGF0KV4yICkNCiAgcmV0dXJuKDEgLSBtc2UgLyB2YXIoeV90ZSkpDQp9DQoNCnJlcCA9IDEwMCAjIG51bWJlciBvZiByZXBsaWNhdGlvbnMNCg0KIyBDb250YWluZXIgb2YgdGhlIHJlc3VsdHMNCnJlc3VsdHNfcjIgPSBtYXRyaXgoTkEscmVwLDEyKQ0KY29sbmFtZXMocmVzdWx0c19yMikgPSBjKCJPTFMxIiwiT0xTMiIsIk9MUzMiLCJPTFM0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiTGFzc28xIiwiTGFzc28yIiwiTGFzc28zIiwiTGFzc280IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUG9zdC1MYXNzbzEiLCJQb3N0LUxhc3NvMiIsIlBvc3QtTGFzc28zIiwiUG9zdC1MYXNzbzQiKQ0KDQojIExvb3AgY29uc2lkZXJpbmcgZGlmZmVyZW50IHNwbGl0cw0KZm9yIChpIGluIDE6cmVwKSB7DQogICMgRHJhdyBpbmRleCBmb3IgdGhpcyByb3VuZA0KICB0ZW1wX2luZCA9IHNhbXBsZSgxOmxlbmd0aChZKSwgc2l6ZSA9IHRlc3Rfc2l6ZSkNCg0KICAjIFNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2FtcGxlcw0KICBYX3RyMSA9IFgxWy10ZW1wX2luZCxdDQogIFhfdGUxID0gWDFbdGVtcF9pbmQsXQ0KICBYX3RyMiA9IFgyWy10ZW1wX2luZCxdDQogIFhfdGUyID0gWDJbdGVtcF9pbmQsXQ0KICBYX3RyMyA9IFgzWy10ZW1wX2luZCxdDQogIFhfdGUzID0gWDNbdGVtcF9pbmQsXQ0KICBYX3RyNCA9IFg0Wy10ZW1wX2luZCxdDQogIFhfdGU0ID0gWDRbdGVtcF9pbmQsXQ0KICBZX3RyID0gWVstdGVtcF9pbmRdDQogIFlfdGUgPSBZW3RlbXBfaW5kXQ0KICANCiAgIyBHZXQgdGVzdCBSMiBmb3IgbWV0aG9kLWNvdiBtYXRyaXggY29tYmkNCiAgcmVzdWx0c19yMltpLDFdID0gb2xzX29vc19yMihYX3RyMSxZX3RyLFhfdGUxLFlfdGUpDQogIHJlc3VsdHNfcjJbaSwyXSA9IG9sc19vb3NfcjIoWF90cjIsWV90cixYX3RlMixZX3RlKQ0KICByZXN1bHRzX3IyW2ksM10gPSBvbHNfb29zX3IyKFhfdHIzLFlfdHIsWF90ZTMsWV90ZSkNCiAgcmVzdWx0c19yMltpLDRdID0gb2xzX29vc19yMihYX3RyNCxZX3RyLFhfdGU0LFlfdGUpDQogIHJlc3VsdHNfcjJbaSw1XSA9IGxhc3NvX29vc19yMihYX3RyMSxZX3RyLFhfdGUxLFlfdGUpDQogIHJlc3VsdHNfcjJbaSw2XSA9IGxhc3NvX29vc19yMihYX3RyMixZX3RyLFhfdGUyLFlfdGUpDQogICMgSW5jcmVhc2luZyBtaW4ubGFtYmRhIHRvIHNwZWVkIHVwIGNvbXB1dGF0aW9uDQogIHJlc3VsdHNfcjJbaSw3XSA9IGxhc3NvX29vc19yMihYX3RyMyxZX3RyLFhfdGUzLFlfdGUsbWluLmxhbWJkYSA9IDAuMDEpDQogIHJlc3VsdHNfcjJbaSw4XSA9IGxhc3NvX29vc19yMihYX3RyNCxZX3RyLFhfdGU0LFlfdGUsbWluLmxhbWJkYSA9IDAuMDUpDQogIHJlc3VsdHNfcjJbaSw5XSA9IHBsYXNzb19vb3NfcjIoWF90cjEsWV90cixYX3RlMSxZX3RlKQ0KICByZXN1bHRzX3IyW2ksMTBdID0gcGxhc3NvX29vc19yMihYX3RyMixZX3RyLFhfdGUyLFlfdGUpDQogIHJlc3VsdHNfcjJbaSwxMV0gPSBwbGFzc29fb29zX3IyKFhfdHIzLFlfdHIsWF90ZTMsWV90ZSkNCiAgIyByZXN1bHRzX3IyW2ksMTJdID0gcGxhc3NvX29vc19yMihYX3RyNCxZX3RyLFhfdGU0LFlfdGUpDQp9DQojIElmIHlvdSByZWFkIHRoaXMgYW5kIGxpa2UgdG8gcGFyYWxsZWxpemUgc3R1ZmYsIGZlZWwgZnJlZSB0byBzZW5kIG1lIGEgZmFzdCB2ZXJzaW9uIDstKQ0KYGBgDQoNCkZpcnN0LCBsZXQncyBjaGVjayB0aGUgbWVhbiBvdXQtb2Ytc2FtcGxlICRSXjIkIGZvciB0aGUgZGlmZmVyZW50IG1ldGhvZHMgb3ZlciB0aGUgMTAwIGRpZmZlcmVudCBzcGxpdHM6DQoNCmBgYHtyfQ0KdCggcm91bmQoY29sTWVhbnMocmVzdWx0c19yMiksMykgKQ0KYGBgDQoNClNvbWUgb2JzZXJ2YXRpb25zIGZyb20gdGhpcyBzcGVjaWZpYyBhcHBsaWNhdGlvbjoNCg0KLSBSdW5uaW5nIE9MUyB3aXRoIHRoaXJkIGFuZCBmb3VydGggb3JkZXIgdGVybXMgaXMgaG9wZWxlc3MuIFRoZSBtZWFuICRSXjIkIGJlY29tZXMgZXZlbiBuZWdhdGl2ZS4gSG93IGNhbiB0aGlzIGhhcHBlbj8gVGhlICRSXjIkIGlzIG5lZ2F0aXZlIGlmIHRoZSBwcmVkaWN0aW9uIG1vZGVsIHBlcmZvcm1zIHdvcnNlIHRoYW4ganVzdCB1c2luZyB0aGUgb3V0Y29tZSBtZWFuIGZvciBwcmVkaWN0aW9uLiBUaGlzIGlzIGV4YWN0bHkgdGhlIG92ZXJmaXR0aW5nIGJlaGF2aW91ciB0aGF0IHdlIGRpc2N1c3MgaW4gdGhlIHNsaWRlcy4NCg0KLSBPTFMgd2l0aCBzZWNvbmQgb3JkZXIgdGVybXMgaXMgcXVpdGUgY29tcGV0aXRpdmUuDQoNCi0gUHJvdmlkaW5nIHRoZSBzZWNvbmQgb3JkZXIgdGVybXMgaW1wcm92ZXMgcHJlZGljdGlvbnMgb2YgYWxsIG1ldGhvZHMuIEhvd2V2ZXIsIGhpZ2hlciBvcmRlciB0ZXJtcyBzdGlsbCBpbXByb3ZlIG92ZXIganVzdCB1c2luZyBtYWluIGVmZmVjdHMsIGJ1dCBwZXJmb3JtIHdvcnNlIHRoYW4gc2Vjb25kIG9yZGVyIHRlcm1zICRcUmlnaHRhcnJvdyQganVzdCBzcGFtbWluZyBMYXNzbyB3aXRoIGludGVyYWN0aW9ucyBub3QgbmVjZXNzYXJpbHkgaW1wcm92ZXMgcHJlZGljdGlvbg0KDQotIExhc3NvIHBlcmZvcm1zIHNsaWdodGx5IGJldHRlciB0aGFuIFBvc3QtTGFzc28uIFRoaXMgaXMgbm90IGEgZ2VuZXJhbCByZXN1bHRzIGFuZCBjYW4gZGlmZmVyIGZvciBvdGhlciBkYXRhc2V0cy4NCg0KVGhlc2Ugb2JzZXJ2YXRpb25zIGFyZSBhbHNvIHZpc2libGUgaW4gdGhlIGJveHBsb3RzIG9mIHRoZSAkUl4yJDoNCg0KDQpgYGB7cn0NCmFzLmRhdGEuZnJhbWUocmVzdWx0c19yMlssLWMoMzo0KV0pICU+JSBwaXZvdF9sb25nZXIoY29scz1ldmVyeXRoaW5nKCksbmFtZXNfdG8gPSAiTWV0aG9kIix2YWx1ZXNfdG8gPSAiUjIiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gUjIsIHkgPSBNZXRob2QpKSArIGdlb21fYm94cGxvdCgpDQpgYGANCg0KSW4gZ2VuZXJhbCwgZXZlcnkgZGF0YXNldCBpcyBkaWZmZXJlbnQuIFRoZXJlIGlzIG5vIHJ1bGUgb2YgdGh1bWIgd2hpY2ggbWV0aG9kIGFuZCBzcGVjaWZpY2F0aW9uIHBlcmZvcm1zIGJlc3QuIEhvd2V2ZXIsIHRoZSBnb29kIHRoaW5nIGlzIHRoYXQgd2UgY2FuIGNoZWNrIHRoZSBwZXJmb3JtYW5jZSBvZiBkaWZmZXJlbnQgY2FuZGlkYXRlcy4gRm9yIHRoZSBkYXRhc2V0IGF0IGhhbmQgaXQgc2VlbXMgdGhhdCBMYXNzbyB3aXRoIHNlY29uZCBvcmRlciB0ZXJtcyB3b3VsZCBiZSBhIGdvb2QgY2hvaWNlLg0KDQo8YnI+DQo8YnI+DQoNCg0K