Overview

In this lesson, we will discussed two alternate forms of linear regression called ridge regression and LASSO regression. These two methods are examples of regularization or shrinkage methods, in which model parameters are encouraged to be small. In this Report we will discuss about the change in lambda affects the Regression model

Load Packages

Tools for creating ridge and LASSO models are provided by the glmnet package.

library(glmnet)  
package 㤼㸱glmnet㤼㸲 was built under R version 3.6.3
library(reshape)
package 㤼㸱reshape㤼㸲 was built under R version 3.6.3
library(ggplot2)

Generate Data

In this lesson, we will be working with synthetic data. We will now generate the dataset and use it to create a dataframe called df.

       x                 y           
 Min.   :-3.8870   Min.   :-127.293  
 1st Qu.:-1.1963   1st Qu.: -10.483  
 Median : 0.3596   Median :   9.303  
 Mean   : 0.2668   Mean   :   9.269  
 3rd Qu.: 2.0789   3rd Qu.:  15.789  
 Max.   : 3.7405   Max.   : 114.023  

Plot Data

The cell below generates a scatterplot of the dataset.

g <- ggplot(data = df)

g1 <- g + geom_point(aes(x = x , y = y), col = "blue", alpha =0.7, pch = 19)+
  geom_smooth(aes(x= x, y=y),method ="lm")
g1

Create Training and Validation Sets

We will randomly split the dataset into a training set and a validation set.

set.seed(23)
sel <- sample(1:nrow(df), 0.7*nrow(df))

train <- df[sel, ]
valid <- df[-sel, ]

c(nrow(train), nrow(valid))
[1] 14  6

Polynomial Regression

Motivated by the apparently non-linear relationship exhibited in the scatterplot, we will fit a polynomial model to the training set.

#poly_mod <- lm(y ~ poly(x, 9), df)

poly_mod <- lm(y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5) +
                 I(x^6) + I(x^7) + I(x^8) + I(x^9), train)
summary(poly_mod)

Call:
lm(formula = y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5) + I(x^6) + 
    I(x^7) + I(x^8) + I(x^9), data = train)

Residuals:
      19        8       11        7       13        2        1       17        5 
-0.94511  9.88130  0.10056  2.55222 -7.52030  3.71294  0.09926 -6.84575 -1.00670 
      14       20        4        3        6 
-3.16802  0.24743 11.35406 -9.42186  0.95997 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)  
(Intercept)   8.11633    9.42120   0.861   0.4375  
x            36.69803   16.09769   2.280   0.0848 .
I(x^2)       -2.31905   14.59141  -0.159   0.8814  
I(x^3)      -30.06376   14.18018  -2.120   0.1013  
I(x^4)        2.62536    6.44076   0.408   0.7044  
I(x^5)        7.82333    4.08245   1.916   0.1278  
I(x^6)       -0.54048    0.88043  -0.614   0.5725  
I(x^7)       -0.79316    0.45212  -1.754   0.1542  
I(x^8)        0.02660    0.03520   0.756   0.4919  
I(x^9)        0.02733    0.01616   1.691   0.1660  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 10.63 on 4 degrees of freedom
Multiple R-squared:  0.9887,    Adjusted R-squared:  0.9632 
F-statistic: 38.78 on 9 and 4 DF,  p-value: 0.001547

Plot Fitted Curve

The figure below shows the fitted curve, as well as the residuals for both the training and validation sets.

Calculate Training and Validation r-Squared

We will now calculate the training and validation r-squared values for the polynomial model.

SST_train <- var(train$y) * (nrow(train) - 1)
SST_valid <- var(valid$y) * (nrow(valid) - 1)

SSE_train <- sum((train$y - train_pred_poly)^2)
SSE_valid <- sum((valid$y - valid_pred_poly)^2)

train_r2 <- 1 - SSE_train / SST_train
valid_r2 <- 1 - SSE_valid / SST_valid

cat('Training r-Squared:    ', train_r2, '\n',
    'Validation r-Squared: ', valid_r2, sep='')
Training r-Squared:    0.9886688
Validation r-Squared: -1.739167

Ridge Regression

Ridge regression is a regularization (shrinkage) technique that encourages model coefficients to be small by altering the loss function to include a penalty term that is based on the magnitude of the coeffiecients. The ridge loss function includes a regularization hyperparameter \(aplpha\) that determines how much weight should be placed on the penalty term. Different values of this hyperparameter will result in different models.

Goal: Minimize \(Loss\left(\hat\beta_0, ..., \hat\beta_m\right) = \frac{1}{n} SSE + \lambda \sum_{i=1}^{m} \hat\beta_i^2\)

Create Matrix of Predictors

The glmnet() function that provides the implementation of ridge regression in R does not use the same formula structure as other modeling functions that we have encountered. It requires the predictor values to be stored in a matrix of their own.

#Xmat <- model.matrix(y ~ x + I(x^2) + I(x^3) + I(x^4) + I(x^5) +
#                     I(x^6) + I(x^7) + I(x^8) + I(x^9))[,-1]

Xmat <- cbind(df$x, df$x^2, df$x^3, df$x^4, df$x^5, 
              df$x^6, df$x^7, df$x^8, df$x^9)
colnames(Xmat) <- c('x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9')
Xmat[,c(1,2,8,9)]
              x1         x2           x8            x9
 [1,]  2.5973953  6.7464624 2.071593e+03  5.380746e+03
 [2,] -3.0651918  9.3954010 7.792221e+03 -2.388465e+04
 [3,] -1.6017556  2.5656210 4.332813e+01 -6.940108e+01
 [4,] -1.1475141  1.3167886 3.006521e+00 -3.450025e+00
 [5,]  3.7215598 13.8500075 3.679595e+04  1.369383e+05
 [6,]  3.7404842 13.9912223 3.831975e+04  1.433344e+05
 [7,]  0.2647389  0.0700867 2.412918e-05  6.387932e-06
 [8,] -1.3427559  1.8029935 1.056761e+01 -1.418972e+01
 [9,]  1.2307501  1.5147457 5.264522e+00  6.479310e+00
[10,]  0.9163963  0.8397822 4.973553e-01  4.557745e-01
[11,] -3.8869633 15.1084840 5.210550e+04 -2.025322e+05
[12,]  2.1070352  4.4395972 3.884850e+02  8.185516e+02
[13,] -1.1255583  1.2668814 2.575988e+00 -2.899425e+00
[14,] -3.1891943 10.1709602 1.070158e+04 -3.412941e+04
[15,]  0.4543778  0.2064592 1.816925e-03  8.255703e-04
[16,] -0.6062526  0.3675423 1.824858e-02 -1.106325e-02
[17,] -0.6457376  0.4169770 3.023071e-02 -1.952111e-02
[18,]  3.4672877 12.0220839 2.088907e+04  7.242840e+04
[19,]  1.3765259  1.8948235 1.289066e+01  1.774432e+01
[20,]  2.0695417  4.2830028 3.365065e+02  6.964142e+02

We will use the sel variable created previous to recreate our train/validation split.

Xmat_train <- Xmat[sel,]
Xmat_valid <- Xmat[-sel,]

Create Ridge Models for Several Values of Lambda

When training a ridge model, it is important to select a good value for the regularization hyperparameter lambda. The glmnet() function allows us to specify several lambda values, and then simultaneously train ridge models for each value of lambda provided. We can then evaluate each of these models to identify the best value for lambda.

In the code chunk below, we train 100 ridge models. We will then extract the parameter estimates for each of these models.

# Create grid of lambda values
log_lambda_grid <- seq(6, -2, length=100) # In reverse
lambda_grid <- 10^log_lambda_grid

# Create 100 ridge models
ridge_models <- glmnet(Xmat_train, train$y, alpha=0, lambda=lambda_grid)

# Get coefficients of all 100 models
ridge_coef <- coef(ridge_models)

# Display coefficients for 6 models. 
# Ridge Models are displayed in decreasing order of lambda.
round(ridge_coef[, c(1:3, 98:100)], 6)
10 x 6 sparse Matrix of class "dgCMatrix"
                  s0       s1       s2       s97       s98       s99
(Intercept) 6.416705 6.416650 6.416584  5.227570  5.033764  4.861744
x1          0.000955 0.001150 0.001385  7.511869  7.774842  8.038722
x2          0.000022 0.000027 0.000032  2.663012  2.909190  3.127641
x3          0.000095 0.000115 0.000138 -1.866751 -1.996273 -2.131507
x4          0.000001 0.000001 0.000002 -0.497869 -0.527326 -0.551402
x5          0.000007 0.000009 0.000011  0.094714  0.106327  0.119162
x6          0.000000 0.000000 0.000000 -0.000858 -0.001566 -0.002458
x7          0.000001 0.000001 0.000001  0.005023  0.004964  0.004875
x8          0.000000 0.000000 0.000000  0.001641  0.001753  0.001861
x9          0.000000 0.000000 0.000000  0.000350  0.000336  0.000321

Plot Coefficient Estimates against Lambda

The plot below shows how the model coefficients change with respect to lambda.

Generate Predictions

We can use the predict() function to generate predictions according to each of the ridge models that we have trained.

train_pred_ridge <- predict(ridge_models, Xmat_train)
valid_pred_ridge <- predict(ridge_models, Xmat_valid)

valid_pred_ridge[,c(1,2,99,100)]
           s0       s1       s98       s99
[1,] 6.418116 6.418350 14.405266 14.614793
[2,] 6.417677 6.417821 12.765029 12.904992
[3,] 6.420160 6.420811 11.413877 11.495879
[4,] 6.417152 6.417189  8.959429  8.938939
[5,] 6.416112 6.415937  1.754171  1.528253
[6,] 6.433910 6.437370 48.974687 48.696388

Calculating Training and Validation r-Squared

In the cell below, we will calculate training and validation r-squared scores for each of our ridge models.

# Find SSE
SSE_train <- colSums((train_pred_ridge - train$y)^2)
SSE_valid <- colSums((valid_pred_ridge - valid$y)^2)

# Find r-Squared
r2_train_list <- 1 - SSE_train / SST_train
r2_valid_list <- 1 - SSE_valid / SST_valid

round(r2_valid_list[c(1:3, 98:100)],4)
     s0      s1      s2     s97     s98     s99 
-0.1291 -0.1290 -0.1289  0.6850  0.6785  0.6730 

Plotting Training and Validation r-Squared as a Function of Lambda

The figure below shows how the training and validation r-squared values change with respect to lambda.

plot(log_lambda_grid, r2_train_list, ylim=c(-0.2,1), pch=".", col="salmon", 
     xlab="ln(lambda)", ylab="r-Squared", main="Training and Validation Scores (Ridge)")

lines(log_lambda_grid, r2_train_list, col="salmon", lwd=2)

lines(log_lambda_grid, r2_valid_list, col="cornflowerblue", lwd=2)

legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

Finding Optimal Value of Lambda

We will now determine the value of lambda that results in the highest validation score.

best_valid_r2 <- max(r2_valid_list)
best_valid_r2_ix <- which.max(r2_valid_list)
best_log_lambda <- log_lambda_grid[best_valid_r2_ix]

cat('Index of Optimal r-Squared:    ', best_valid_r2_ix, '\n',
    'Value of Optimal r-Squared:    ', best_valid_r2, '\n',
    'Value of Optimal log(lambda):  ', best_log_lambda, sep='')
Index of Optimal r-Squared:    66
Value of Optimal r-Squared:    0.8700205
Value of Optimal log(lambda):  0.7474747

Plot Fitted Curve

In the figure below, we plot the curve representing the optimal ridge model.

LASSO Regression

Like ridge regression, LASSO regression is a regularization method that encourages small parameter values by adding a penalty term to the loss function. This penalty term has a slightly different form from the one encountered in ridge regression.

Goal: Minimize \(Loss\left(\hat\beta_0, ..., \hat\beta_m\right) = \frac{1}{n} SSE + \lambda \sum_{i=1}^{m} | \hat\beta_i |\)

Create LASSO Models for Several Values of Lambda

In the code chunk below, we train 100 LASSO models. We will then extract the parameter estimates for each of these models.

log_lambda_grid <- seq(2, -4, length=100) # In reverse
lambda_grid <- 10^log_lambda_grid

# Create 100 LASSO models
lasso_models <- glmnet(Xmat_train, train$y, alpha=1, lambda=lambda_grid)

# Get coefficients of all 100 models
lasso_coef <- coef(lasso_models)

# Display coefficients for 6 models. 
# LASSO Models are displayed in decreasing order of lambda.
round(lasso_coef[, c(1:3, 98:100)], 6)
10 x 6 sparse Matrix of class "dgCMatrix"
                  s0       s1       s2       s97       s98       s99
(Intercept) 6.416973 6.416973 6.416973  4.435341  4.436035  4.436568
x1          .        .        .        12.741928 12.742527 12.743195
x2          .        .        .         4.085117  4.084868  4.084699
x3          .        .        .        -5.053629 -5.054243 -5.054863
x4          .        .        .        -0.585410 -0.585359 -0.585312
x5          .        .        .         0.486529  0.486617  0.486705
x6          .        .        .        -0.006752 -0.006759 -0.006766
x7          .        .        .        -0.002574 -0.002577 -0.002580
x8          .        .        .         0.001932  0.001932  0.001932
x9          .        .        .        -0.000063 -0.000063 -0.000063

Plot Coefficient Estimates against Lambda

The plot below shows how the model coefficients change with respect to lambda.

Generate Predictions

We can use the predict() function to generate predictions according to each of the LASSO models that we have trained.

train_pred_lasso <- predict(lasso_models, Xmat_train)
valid_pred_lasso <- predict(lasso_models, Xmat_valid)

valid_pred_lasso[,c(1,2,99,100)]
           s0       s1        s98        s99
[1,] 6.416973 6.416973 16.8902719 16.8905375
[2,] 6.416973 6.416973 15.5512321 15.5518425
[3,] 6.416973 6.416973 10.4447371 10.4436781
[4,] 6.416973 6.416973 10.5795817 10.5803283
[5,] 6.416973 6.416973 -0.7807393 -0.7805362
[6,] 6.416973 6.416973 54.8181886 54.8185283

Calculating Training and Validation r-Squared

In the cell below, we will calculate training and validation r-squared scores for each of our LASSO models.

# Find SSE
SSE_train <- colSums((train_pred_lasso - train$y)^2)
SSE_valid <- colSums((valid_pred_lasso - valid$y)^2)

# Find r-Squared
r2_train_list <- 1 - SSE_train / SST_train
r2_valid_list <- 1 - SSE_valid / SST_valid

round(r2_valid_list[c(1:3, 98:100)],4)
     s0      s1      s2     s97     s98     s99 
-0.1297 -0.1297 -0.1297  0.6803  0.6803  0.6803 

Plotting Training and Validation r-Squared as a Function of Lambda

The figure below shows how the training and validation r-squared values change with respect to lambda.

plot(log_lambda_grid, r2_train_list, ylim=c(-0.2,1), pch=".", col="salmon", 
     xlab="ln(lambda)", ylab="r-Squared", main="Training and Validation Scores (Ridge)")

lines(log_lambda_grid, r2_train_list, col="salmon", lwd=2)

lines(log_lambda_grid, r2_valid_list, col="cornflowerblue", lwd=2)

legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

Finding Optimal Value of Lambda

We will now determine the value of lambda that results in the highest validation score.

best_valid_r2 <- max(r2_valid_list)
best_valid_r2_ix <- which.max(r2_valid_list)
best_log_lambda <- log_lambda_grid[best_valid_r2_ix]

cat('Index of Optimal r-Squared:    ', best_valid_r2_ix, '\n',
    'Value of Optimal r-Squared:    ', best_valid_r2, '\n',
    'Value of Optimal log(lambda):  ', best_log_lambda, sep='')
Index of Optimal r-Squared:    46
Value of Optimal r-Squared:    0.842338
Value of Optimal log(lambda):  -0.7272727

Plot Fitted Curve

In the figure below, we plot the curve representing the optimal LASSO model.

LS0tDQp0aXRsZTogIkxhc3NvIGFuZCBSaWRnZSBSZWdyZXNzaW9uIC0gUmVndWxhcml6YXRpb24gVGVjaG5pcXVlIg0KYXV0aG9yOiAiUHJhdmVlbiBKYWxhamEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICc0Jw0KLS0tDQoNCg0KIyMjICoqT3ZlcnZpZXcqKg0KDQpJbiB0aGlzIGxlc3Nvbiwgd2Ugd2lsbCBkaXNjdXNzZWQgdHdvIGFsdGVybmF0ZSBmb3JtcyBvZiBsaW5lYXIgcmVncmVzc2lvbiBjYWxsZWQgKipyaWRnZSByZWdyZXNzaW9uKiogYW5kICoqTEFTU08gcmVncmVzc2lvbioqLiBUaGVzZSB0d28gbWV0aG9kcyBhcmUgZXhhbXBsZXMgb2YgKipyZWd1bGFyaXphdGlvbioqIG9yICoqc2hyaW5rYWdlKiogbWV0aG9kcywgaW4gd2hpY2ggbW9kZWwgcGFyYW1ldGVycyBhcmUgZW5jb3VyYWdlZCB0byBiZSBzbWFsbC4gSW4gdGhpcyBSZXBvcnQgd2Ugd2lsbCBkaXNjdXNzIGFib3V0IHRoZSBjaGFuZ2UgaW4gbGFtYmRhIGFmZmVjdHMgdGhlIFJlZ3Jlc3Npb24gbW9kZWwNCg0KIyMjICoqTG9hZCBQYWNrYWdlcyoqDQoNClRvb2xzIGZvciBjcmVhdGluZyByaWRnZSBhbmQgTEFTU08gbW9kZWxzIGFyZSBwcm92aWRlZCBieSB0aGUgYGdsbW5ldGAgcGFja2FnZS4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdsbW5ldCkgIA0KbGlicmFyeShyZXNoYXBlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCiMjIyAqKkdlbmVyYXRlIERhdGEqKg0KDQpJbiB0aGlzIGxlc3Nvbiwgd2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggc3ludGhldGljIGRhdGEuIFdlIHdpbGwgbm93IGdlbmVyYXRlIHRoZSBkYXRhc2V0IGFuZCB1c2UgaXQgdG8gY3JlYXRlIGEgZGF0YWZyYW1lIGNhbGxlZCBgZGZgLiANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpzZXQuc2VlZCgxMjUpDQpuIDwtIDIwDQp4IDwtIHJ1bmlmKG4sIC00LCA0KQ0KeSA8LSAzICsgMC41ICogeCArIDAuMDEgKiB4Kio3ICsgcm5vcm0obiwgMCwgMTApDQoNCmRmIDwtIGRhdGEuZnJhbWUoeCwgeSkNCg0Kc3VtbWFyeShkZikNCmBgYA0KDQojIyMgKipQbG90IERhdGEqKg0KDQpUaGUgY2VsbCBiZWxvdyBnZW5lcmF0ZXMgYSBzY2F0dGVycGxvdCBvZiB0aGUgZGF0YXNldC4NCg0KYGBge3J9DQpnIDwtIGdncGxvdChkYXRhID0gZGYpDQoNCmcxIDwtIGcgKyBnZW9tX3BvaW50KGFlcyh4ID0geCAsIHkgPSB5KSwgY29sID0gImJsdWUiLCBhbHBoYSA9MC43LCBwY2ggPSAxOSkrDQogIGdlb21fc21vb3RoKGFlcyh4PSB4LCB5PXkpLG1ldGhvZCA9ImxtIikNCmcxDQpgYGANCg0KIyMjICoqQ3JlYXRlIFRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIFNldHMqKg0KDQpXZSB3aWxsIHJhbmRvbWx5IHNwbGl0IHRoZSBkYXRhc2V0IGludG8gYSB0cmFpbmluZyBzZXQgYW5kIGEgdmFsaWRhdGlvbiBzZXQuIA0KDQpgYGB7cn0NCnNldC5zZWVkKDIzKQ0Kc2VsIDwtIHNhbXBsZSgxOm5yb3coZGYpLCAwLjcqbnJvdyhkZikpDQoNCnRyYWluIDwtIGRmW3NlbCwgXQ0KdmFsaWQgPC0gZGZbLXNlbCwgXQ0KDQpjKG5yb3codHJhaW4pLCBucm93KHZhbGlkKSkNCmBgYA0KDQojIyMgKipQb2x5bm9taWFsIFJlZ3Jlc3Npb24qKg0KDQpNb3RpdmF0ZWQgYnkgdGhlIGFwcGFyZW50bHkgbm9uLWxpbmVhciByZWxhdGlvbnNoaXAgZXhoaWJpdGVkIGluIHRoZSBzY2F0dGVycGxvdCwgd2Ugd2lsbCBmaXQgYSBwb2x5bm9taWFsIG1vZGVsIHRvIHRoZSB0cmFpbmluZyBzZXQuIA0KDQpgYGB7cn0NCiNwb2x5X21vZCA8LSBsbSh5IH4gcG9seSh4LCA5KSwgZGYpDQoNCnBvbHlfbW9kIDwtIGxtKHkgfiB4ICsgSSh4XjIpICsgSSh4XjMpICsgSSh4XjQpICsgSSh4XjUpICsNCiAgICAgICAgICAgICAgICAgSSh4XjYpICsgSSh4XjcpICsgSSh4XjgpICsgSSh4XjkpLCB0cmFpbikNCnN1bW1hcnkocG9seV9tb2QpDQpgYGANCg0KIyMjIyAqKlBsb3QgRml0dGVkIEN1cnZlKioNCg0KVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGUgZml0dGVkIGN1cnZlLCBhcyB3ZWxsIGFzIHRoZSByZXNpZHVhbHMgZm9yIGJvdGggdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KDQp0cmFpbl9wcmVkX3BvbHkgPC0gcHJlZGljdChwb2x5X21vZCwgZGF0YS5mcmFtZSh4ID0gdHJhaW4keCkpDQp2YWxpZF9wcmVkX3BvbHkgPC0gcHJlZGljdChwb2x5X21vZCwgZGF0YS5mcmFtZSh4ID0gdmFsaWQkeCkpDQoNCg0KDQp4ZyA8LSBzZXEoLTQsNCxsZW5ndGg9MTAwKQ0KeWdfcG9seSA8LSBwcmVkaWN0KHBvbHlfbW9kLCBkYXRhLmZyYW1lKHg9eGcpKQ0KDQpwb2x5IDwtIGdncGxvdCgpKw0KICBnZW9tX3BvaW50KGFlcyh4ID14LCB5ID15KSxjb2wgPSAiYmx1ZSIsIGRhdGEgPSB0cmFpbikrDQogIGxpbXMoeSA9IGMoLTgwLDE0MCkpKw0KICBsYWJzKHggPSAieCIsIHkgPSAieSIsIHRpdGxlID0gIlBvbHlub21pbmFsIFBsb3QiKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0geGcgLCB5ID0geWdfcG9seSkpKw0KICBnZW9tX3NlZ21lbnQoYWVzKHggPXgsIHk9IHksIHhlbmQgPSB4ICx5ZW5kID0gdHJhaW5fcHJlZF9wb2x5KSwgZGF0YSA9dHJhaW4sIGNvbCA9ImJsdWUiKStnZW9tX3BvaW50KGFlcyh4ID14ICx5ID15ICksIGRhdGEgPSB2YWxpZCwgY29sPSJyZWQiKSsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID14ICx5ID0geSwgeGVuZCA9IHgsIHllbmQgPSB2YWxpZF9wcmVkX3BvbHkpLCBjb2wgPSAicmVkIiwgZGF0YSA9dmFsaWQpDQoNCg0KcG9seQ0KDQoNCmBgYA0KDQojIyMjICoqQ2FsY3VsYXRlIFRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIHItU3F1YXJlZCoqDQoNCldlIHdpbGwgbm93IGNhbGN1bGF0ZSB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gci1zcXVhcmVkIHZhbHVlcyBmb3IgdGhlIHBvbHlub21pYWwgbW9kZWwuIA0KDQpgYGB7cn0NClNTVF90cmFpbiA8LSB2YXIodHJhaW4keSkgKiAobnJvdyh0cmFpbikgLSAxKQ0KU1NUX3ZhbGlkIDwtIHZhcih2YWxpZCR5KSAqIChucm93KHZhbGlkKSAtIDEpDQoNClNTRV90cmFpbiA8LSBzdW0oKHRyYWluJHkgLSB0cmFpbl9wcmVkX3BvbHkpXjIpDQpTU0VfdmFsaWQgPC0gc3VtKCh2YWxpZCR5IC0gdmFsaWRfcHJlZF9wb2x5KV4yKQ0KDQp0cmFpbl9yMiA8LSAxIC0gU1NFX3RyYWluIC8gU1NUX3RyYWluDQp2YWxpZF9yMiA8LSAxIC0gU1NFX3ZhbGlkIC8gU1NUX3ZhbGlkDQoNCmNhdCgnVHJhaW5pbmcgci1TcXVhcmVkOiAgICAnLCB0cmFpbl9yMiwgJ1xuJywNCiAgICAnVmFsaWRhdGlvbiByLVNxdWFyZWQ6ICcsIHZhbGlkX3IyLCBzZXA9JycpDQpgYGANCg0KDQojIyMgKipSaWRnZSBSZWdyZXNzaW9uKioNCg0KUmlkZ2UgcmVncmVzc2lvbiBpcyBhIHJlZ3VsYXJpemF0aW9uIChzaHJpbmthZ2UpIHRlY2huaXF1ZSB0aGF0IGVuY291cmFnZXMgbW9kZWwgY29lZmZpY2llbnRzIHRvIGJlIHNtYWxsIGJ5IGFsdGVyaW5nIHRoZSBsb3NzIGZ1bmN0aW9uIHRvIGluY2x1ZGUgYSBwZW5hbHR5IHRlcm0gdGhhdCBpcyBiYXNlZCBvbiB0aGUgbWFnbml0dWRlIG9mIHRoZSBjb2VmZmllY2llbnRzLiBUaGUgcmlkZ2UgbG9zcyBmdW5jdGlvbiBpbmNsdWRlcyBhIHJlZ3VsYXJpemF0aW9uIGh5cGVycGFyYW1ldGVyICRhcGxwaGEkIHRoYXQgZGV0ZXJtaW5lcyBob3cgbXVjaCB3ZWlnaHQgc2hvdWxkIGJlIHBsYWNlZCBvbiB0aGUgcGVuYWx0eSB0ZXJtLiBEaWZmZXJlbnQgdmFsdWVzIG9mIHRoaXMgaHlwZXJwYXJhbWV0ZXIgd2lsbCByZXN1bHQgaW4gZGlmZmVyZW50IG1vZGVscy4gDQoNCkdvYWw6IE1pbmltaXplICRMb3NzXGxlZnQoXGhhdFxiZXRhXzAsIC4uLiwgXGhhdFxiZXRhX21ccmlnaHQpID0gXGZyYWN7MX17bn0gU1NFICsgXGxhbWJkYSBcc3VtX3tpPTF9XnttfSBcaGF0XGJldGFfaV4yJA0KDQoNCiMjIyMgKipDcmVhdGUgTWF0cml4IG9mIFByZWRpY3RvcnMqKg0KDQpUaGUgYGdsbW5ldCgpYCBmdW5jdGlvbiB0aGF0IHByb3ZpZGVzIHRoZSBpbXBsZW1lbnRhdGlvbiBvZiByaWRnZSByZWdyZXNzaW9uIGluIFIgZG9lcyBub3QgdXNlIHRoZSBzYW1lIGZvcm11bGEgc3RydWN0dXJlIGFzIG90aGVyIG1vZGVsaW5nIGZ1bmN0aW9ucyB0aGF0IHdlIGhhdmUgZW5jb3VudGVyZWQuIEl0IHJlcXVpcmVzIHRoZSBwcmVkaWN0b3IgdmFsdWVzIHRvIGJlIHN0b3JlZCBpbiBhIG1hdHJpeCBvZiB0aGVpciBvd24uIA0KDQpgYGB7cn0NCiNYbWF0IDwtIG1vZGVsLm1hdHJpeCh5IH4geCArIEkoeF4yKSArIEkoeF4zKSArIEkoeF40KSArIEkoeF41KSArDQojICAgICAgICAgICAgICAgICAgICAgSSh4XjYpICsgSSh4XjcpICsgSSh4XjgpICsgSSh4XjkpKVssLTFdDQoNClhtYXQgPC0gY2JpbmQoZGYkeCwgZGYkeF4yLCBkZiR4XjMsIGRmJHheNCwgZGYkeF41LCANCiAgICAgICAgICAgICAgZGYkeF42LCBkZiR4XjcsIGRmJHheOCwgZGYkeF45KQ0KY29sbmFtZXMoWG1hdCkgPC0gYygneDEnLCAneDInLCAneDMnLCAneDQnLCAneDUnLCAneDYnLCAneDcnLCAneDgnLCAneDknKQ0KWG1hdFssYygxLDIsOCw5KV0NCmBgYA0KDQoNCldlIHdpbGwgdXNlIHRoZSBgc2VsYCB2YXJpYWJsZSBjcmVhdGVkIHByZXZpb3VzIHRvIHJlY3JlYXRlIG91ciB0cmFpbi92YWxpZGF0aW9uIHNwbGl0Lg0KDQpgYGB7cn0NClhtYXRfdHJhaW4gPC0gWG1hdFtzZWwsXQ0KWG1hdF92YWxpZCA8LSBYbWF0Wy1zZWwsXQ0KYGBgDQoNCg0KIyMjIyAqKkNyZWF0ZSBSaWRnZSBNb2RlbHMgZm9yIFNldmVyYWwgVmFsdWVzIG9mIExhbWJkYSoqDQoNCldoZW4gdHJhaW5pbmcgYSByaWRnZSBtb2RlbCwgaXQgaXMgaW1wb3J0YW50IHRvIHNlbGVjdCBhIGdvb2QgdmFsdWUgZm9yIHRoZSByZWd1bGFyaXphdGlvbiBoeXBlcnBhcmFtZXRlciBsYW1iZGEuIFRoZSBgZ2xtbmV0KClgIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBzcGVjaWZ5IHNldmVyYWwgbGFtYmRhIHZhbHVlcywgYW5kIHRoZW4gc2ltdWx0YW5lb3VzbHkgdHJhaW4gcmlkZ2UgbW9kZWxzIGZvciBlYWNoIHZhbHVlIG9mIGxhbWJkYSBwcm92aWRlZC4gV2UgY2FuIHRoZW4gZXZhbHVhdGUgZWFjaCBvZiB0aGVzZSBtb2RlbHMgdG8gaWRlbnRpZnkgdGhlIGJlc3QgdmFsdWUgZm9yIGxhbWJkYS4gDQoNCkluIHRoZSBjb2RlIGNodW5rIGJlbG93LCB3ZSB0cmFpbiAxMDAgcmlkZ2UgbW9kZWxzLiBXZSB3aWxsIHRoZW4gZXh0cmFjdCB0aGUgcGFyYW1ldGVyIGVzdGltYXRlcyBmb3IgZWFjaCBvZiB0aGVzZSBtb2RlbHMuIA0KDQpgYGB7cn0NCiMgQ3JlYXRlIGdyaWQgb2YgbGFtYmRhIHZhbHVlcw0KbG9nX2xhbWJkYV9ncmlkIDwtIHNlcSg2LCAtMiwgbGVuZ3RoPTEwMCkgIyBJbiByZXZlcnNlDQpsYW1iZGFfZ3JpZCA8LSAxMF5sb2dfbGFtYmRhX2dyaWQNCg0KIyBDcmVhdGUgMTAwIHJpZGdlIG1vZGVscw0KcmlkZ2VfbW9kZWxzIDwtIGdsbW5ldChYbWF0X3RyYWluLCB0cmFpbiR5LCBhbHBoYT0wLCBsYW1iZGE9bGFtYmRhX2dyaWQpDQoNCiMgR2V0IGNvZWZmaWNpZW50cyBvZiBhbGwgMTAwIG1vZGVscw0KcmlkZ2VfY29lZiA8LSBjb2VmKHJpZGdlX21vZGVscykNCg0KIyBEaXNwbGF5IGNvZWZmaWNpZW50cyBmb3IgNiBtb2RlbHMuIA0KIyBSaWRnZSBNb2RlbHMgYXJlIGRpc3BsYXllZCBpbiBkZWNyZWFzaW5nIG9yZGVyIG9mIGxhbWJkYS4NCnJvdW5kKHJpZGdlX2NvZWZbLCBjKDE6MywgOTg6MTAwKV0sIDYpDQpgYGANCg0KIyMjIyAqKlBsb3QgQ29lZmZpY2llbnQgRXN0aW1hdGVzIGFnYWluc3QgTGFtYmRhKioNCg0KVGhlIHBsb3QgYmVsb3cgc2hvd3MgaG93IHRoZSBtb2RlbCBjb2VmZmljaWVudHMgY2hhbmdlIHdpdGggcmVzcGVjdCB0byBsYW1iZGEuDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KcmlkZ2VfY29lZl9EZiA8LSBkYXRhLmZyYW1lKGdyaWQgPSBsb2dfbGFtYmRhX2dyaWQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHgxID0gcmlkZ2VfY29lZlsyLF0sIHgyID0gcmlkZ2VfY29lZlszLF0sIHgzID0gcmlkZ2VfY29lZls0LF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHg0ID0gcmlkZ2VfY29lZls1LF0sIHg1ID0gcmlkZ2VfY29lZls2LF0sIHg2ID0gcmlkZ2VfY29lZls3LF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHg3ID0gcmlkZ2VfY29lZls4LF0sIHg4ID0gcmlkZ2VfY29lZls5LF0sIHg5ID0gcmlkZ2VfY29lZlsxMCxdKQ0KcmlkZ2VfbWVsdGVkIDwtIG1lbHQocmlkZ2VfY29lZl9EZiwgaWQudmFycz0iZ3JpZCIpDQpnZ3Bsb3QocmlkZ2VfbWVsdGVkLCBhZXMoZ3JpZCwgdmFsdWUsIGNvbD12YXJpYWJsZSkpICsgZ2VvbV9saW5lKHNpemU9MSkgKw0KICB4bGFiKCdsb2cobGFtYmRhKScpICsgeWxhYignQ29lZmZpY2llbnRzJykgDQpgYGANCg0KIyMjIyAqKkdlbmVyYXRlIFByZWRpY3Rpb25zKioNCg0KV2UgY2FuIHVzZSB0aGUgYHByZWRpY3QoKWAgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgcHJlZGljdGlvbnMgYWNjb3JkaW5nIHRvIGVhY2ggb2YgdGhlIHJpZGdlIG1vZGVscyB0aGF0IHdlIGhhdmUgdHJhaW5lZC4gDQoNCmBgYHtyfQ0KdHJhaW5fcHJlZF9yaWRnZSA8LSBwcmVkaWN0KHJpZGdlX21vZGVscywgWG1hdF90cmFpbikNCnZhbGlkX3ByZWRfcmlkZ2UgPC0gcHJlZGljdChyaWRnZV9tb2RlbHMsIFhtYXRfdmFsaWQpDQoNCnZhbGlkX3ByZWRfcmlkZ2VbLGMoMSwyLDk5LDEwMCldDQpgYGANCg0KIyMjIyAqKkNhbGN1bGF0aW5nIFRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIHItU3F1YXJlZCoqDQoNCkluIHRoZSBjZWxsIGJlbG93LCB3ZSB3aWxsIGNhbGN1bGF0ZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiByLXNxdWFyZWQgc2NvcmVzIGZvciBlYWNoIG9mIG91ciByaWRnZSBtb2RlbHMuIA0KDQpgYGB7cn0NCiMgRmluZCBTU0UNClNTRV90cmFpbiA8LSBjb2xTdW1zKCh0cmFpbl9wcmVkX3JpZGdlIC0gdHJhaW4keSleMikNClNTRV92YWxpZCA8LSBjb2xTdW1zKCh2YWxpZF9wcmVkX3JpZGdlIC0gdmFsaWQkeSleMikNCg0KIyBGaW5kIHItU3F1YXJlZA0KcjJfdHJhaW5fbGlzdCA8LSAxIC0gU1NFX3RyYWluIC8gU1NUX3RyYWluDQpyMl92YWxpZF9saXN0IDwtIDEgLSBTU0VfdmFsaWQgLyBTU1RfdmFsaWQNCg0Kcm91bmQocjJfdmFsaWRfbGlzdFtjKDE6MywgOTg6MTAwKV0sNCkNCmBgYA0KDQojIyMjICoqUGxvdHRpbmcgVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gci1TcXVhcmVkIGFzIGEgRnVuY3Rpb24gb2YgTGFtYmRhKioNCg0KVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyBob3cgdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHItc3F1YXJlZCB2YWx1ZXMgY2hhbmdlIHdpdGggcmVzcGVjdCB0byBsYW1iZGEuDQoNCmBgYHtyfQ0KcGxvdChsb2dfbGFtYmRhX2dyaWQsIHIyX3RyYWluX2xpc3QsIHlsaW09YygtMC4yLDEpLCBwY2g9Ii4iLCBjb2w9InNhbG1vbiIsIA0KICAgICB4bGFiPSJsbihsYW1iZGEpIiwgeWxhYj0ici1TcXVhcmVkIiwgbWFpbj0iVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gU2NvcmVzIChSaWRnZSkiKQ0KDQpsaW5lcyhsb2dfbGFtYmRhX2dyaWQsIHIyX3RyYWluX2xpc3QsIGNvbD0ic2FsbW9uIiwgbHdkPTIpDQoNCmxpbmVzKGxvZ19sYW1iZGFfZ3JpZCwgcjJfdmFsaWRfbGlzdCwgY29sPSJjb3JuZmxvd2VyYmx1ZSIsIGx3ZD0yKQ0KDQpsZWdlbmQoNzUsIDEsIGxlZ2VuZD1jKCJUcmFpbmluZyBBY2MiLCAiVmFsaWRhdGlvbiBBY2MiKSwNCiAgICAgICBjb2w9Yygic2FsbW9uIiwgImNvcm5mbG93ZXJibHVlIiksIGx0eT0xLCBsd2Q9MiwgY2V4PTAuOCkNCmBgYA0KDQojIyMjICoqRmluZGluZyBPcHRpbWFsIFZhbHVlIG9mIExhbWJkYSoqDQoNCldlIHdpbGwgbm93IGRldGVybWluZSB0aGUgdmFsdWUgb2YgbGFtYmRhIHRoYXQgcmVzdWx0cyBpbiB0aGUgaGlnaGVzdCB2YWxpZGF0aW9uIHNjb3JlLg0KDQpgYGB7cn0NCmJlc3RfdmFsaWRfcjIgPC0gbWF4KHIyX3ZhbGlkX2xpc3QpDQpiZXN0X3ZhbGlkX3IyX2l4IDwtIHdoaWNoLm1heChyMl92YWxpZF9saXN0KQ0KYmVzdF9sb2dfbGFtYmRhIDwtIGxvZ19sYW1iZGFfZ3JpZFtiZXN0X3ZhbGlkX3IyX2l4XQ0KDQpjYXQoJ0luZGV4IG9mIE9wdGltYWwgci1TcXVhcmVkOiAgICAnLCBiZXN0X3ZhbGlkX3IyX2l4LCAnXG4nLA0KICAgICdWYWx1ZSBvZiBPcHRpbWFsIHItU3F1YXJlZDogICAgJywgYmVzdF92YWxpZF9yMiwgJ1xuJywNCiAgICAnVmFsdWUgb2YgT3B0aW1hbCBsb2cobGFtYmRhKTogICcsIGJlc3RfbG9nX2xhbWJkYSwgc2VwPScnKQ0KDQpgYGANCg0KIyMjIyAqKlBsb3QgRml0dGVkIEN1cnZlKioNCg0KSW4gdGhlIGZpZ3VyZSBiZWxvdywgd2UgcGxvdCB0aGUgY3VydmUgcmVwcmVzZW50aW5nIHRoZSBvcHRpbWFsIHJpZGdlIG1vZGVsLiANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpYbWF0X2dyaWQgPC0gY2JpbmQoeGcsIHhnXjIsIHhnXjMsIHhnXjQsIHhnXjUsIHhnXjYsIHhnXjcsIHhnXjgsIHhnXjkpDQpjb2xuYW1lcyhYbWF0X2dyaWQpIDwtIGMoJ3gxJywgJ3gyJywgJ3gzJywgJ3g0JywgJ3g1JywgJ3g2JywgJ3g3JywgJ3g4JywgJ3g5JykNCg0KZ3JpZF9wcmVkIDwtIHByZWRpY3QocmlkZ2VfbW9kZWxzLCBYbWF0X2dyaWQpWyxiZXN0X3ZhbGlkX3IyX2l4XQ0KDQpwbG90KHkgfiB4LCB0cmFpbiwgeWxpbT1jKC04MCwgMTQwKSwgeGxhYj0ieCIsIHlsYWI9InkiLCBtYWluPSJSaWRnZSBNb2RlbCIpDQpsaW5lcyh4ZywgZ3JpZF9wcmVkKQ0Kc2VnbWVudHModHJhaW4keCwgdHJhaW4keSwgdHJhaW4keCwgdHJhaW5fcHJlZF9yaWRnZVssYmVzdF92YWxpZF9yMl9peF0pIA0KcG9pbnRzKHZhbGlkJHkgfiB2YWxpZCR4LCBjb2w9InJlZCIpDQpzZWdtZW50cyh2YWxpZCR4LCB2YWxpZCR5LCB2YWxpZCR4LCB2YWxpZF9wcmVkX3JpZGdlWyxiZXN0X3ZhbGlkX3IyX2l4XSwgY29sPSJyZWQiKQ0KYGBgDQoNCiMjIyAqKkxBU1NPIFJlZ3Jlc3Npb24qKg0KDQpMaWtlIHJpZGdlIHJlZ3Jlc3Npb24sIExBU1NPIHJlZ3Jlc3Npb24gaXMgYSByZWd1bGFyaXphdGlvbiBtZXRob2QgdGhhdCBlbmNvdXJhZ2VzIHNtYWxsIHBhcmFtZXRlciB2YWx1ZXMgYnkgYWRkaW5nIGEgcGVuYWx0eSB0ZXJtIHRvIHRoZSBsb3NzIGZ1bmN0aW9uLiBUaGlzIHBlbmFsdHkgdGVybSBoYXMgYSBzbGlnaHRseSBkaWZmZXJlbnQgZm9ybSBmcm9tIHRoZSBvbmUgZW5jb3VudGVyZWQgaW4gcmlkZ2UgcmVncmVzc2lvbi4gDQoNCkdvYWw6IE1pbmltaXplICRMb3NzXGxlZnQoXGhhdFxiZXRhXzAsIC4uLiwgXGhhdFxiZXRhX21ccmlnaHQpID0gXGZyYWN7MX17bn0gU1NFICsgXGxhbWJkYSBcc3VtX3tpPTF9XnttfSB8IFxoYXRcYmV0YV9pIHwkDQoNCiMjIyMgKipDcmVhdGUgTEFTU08gTW9kZWxzIGZvciBTZXZlcmFsIFZhbHVlcyBvZiBMYW1iZGEqKg0KDQpJbiB0aGUgY29kZSBjaHVuayBiZWxvdywgd2UgdHJhaW4gMTAwIExBU1NPIG1vZGVscy4gV2Ugd2lsbCB0aGVuIGV4dHJhY3QgdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgZm9yIGVhY2ggb2YgdGhlc2UgbW9kZWxzLiANCg0KYGBge3J9DQpsb2dfbGFtYmRhX2dyaWQgPC0gc2VxKDIsIC00LCBsZW5ndGg9MTAwKSAjIEluIHJldmVyc2UNCmxhbWJkYV9ncmlkIDwtIDEwXmxvZ19sYW1iZGFfZ3JpZA0KDQojIENyZWF0ZSAxMDAgTEFTU08gbW9kZWxzDQpsYXNzb19tb2RlbHMgPC0gZ2xtbmV0KFhtYXRfdHJhaW4sIHRyYWluJHksIGFscGhhPTEsIGxhbWJkYT1sYW1iZGFfZ3JpZCkNCg0KIyBHZXQgY29lZmZpY2llbnRzIG9mIGFsbCAxMDAgbW9kZWxzDQpsYXNzb19jb2VmIDwtIGNvZWYobGFzc29fbW9kZWxzKQ0KDQojIERpc3BsYXkgY29lZmZpY2llbnRzIGZvciA2IG1vZGVscy4gDQojIExBU1NPIE1vZGVscyBhcmUgZGlzcGxheWVkIGluIGRlY3JlYXNpbmcgb3JkZXIgb2YgbGFtYmRhLg0Kcm91bmQobGFzc29fY29lZlssIGMoMTozLCA5ODoxMDApXSwgNikNCmBgYA0KDQojIyMjICoqUGxvdCBDb2VmZmljaWVudCBFc3RpbWF0ZXMgYWdhaW5zdCBMYW1iZGEqKg0KDQpUaGUgcGxvdCBiZWxvdyBzaG93cyBob3cgdGhlIG1vZGVsIGNvZWZmaWNpZW50cyBjaGFuZ2Ugd2l0aCByZXNwZWN0IHRvIGxhbWJkYS4NCg0KYGBge3IsIGVjaG89RkFMU0V9DQpsYXNzb19jb2VmX0RmIDwtIGRhdGEuZnJhbWUoZ3JpZCA9IGxvZ19sYW1iZGFfZ3JpZCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgeDEgPSBsYXNzb19jb2VmWzIsXSwgeDIgPSBsYXNzb19jb2VmWzMsXSwgeDMgPSBsYXNzb19jb2VmWzQsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgeDQgPSBsYXNzb19jb2VmWzUsXSwgeDUgPSBsYXNzb19jb2VmWzYsXSwgeDYgPSBsYXNzb19jb2VmWzcsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgeDcgPSBsYXNzb19jb2VmWzgsXSwgeDggPSBsYXNzb19jb2VmWzksXSwgeDkgPSBsYXNzb19jb2VmWzEwLF0pDQpsYXNzb19tZWx0ZWQgPC0gbWVsdChsYXNzb19jb2VmX0RmLCBpZC52YXJzPSJncmlkIikNCg0KZ2dwbG90KGxhc3NvX21lbHRlZCwgYWVzKGdyaWQsIHZhbHVlLCBjb2w9dmFyaWFibGUpKSArIGdlb21fbGluZShzaXplPTEpICsNCiAgeGxhYignbG9nKGxhbWJkYSknKSArIHlsYWIoJ0NvZWZmaWNpZW50cycpIA0KYGBgDQoNCiMjIyMgKipHZW5lcmF0ZSBQcmVkaWN0aW9ucyoqDQoNCldlIGNhbiB1c2UgdGhlIGBwcmVkaWN0KClgIGZ1bmN0aW9uIHRvIGdlbmVyYXRlIHByZWRpY3Rpb25zIGFjY29yZGluZyB0byBlYWNoIG9mIHRoZSBMQVNTTyBtb2RlbHMgdGhhdCB3ZSBoYXZlIHRyYWluZWQuIA0KDQpgYGB7cn0NCnRyYWluX3ByZWRfbGFzc28gPC0gcHJlZGljdChsYXNzb19tb2RlbHMsIFhtYXRfdHJhaW4pDQp2YWxpZF9wcmVkX2xhc3NvIDwtIHByZWRpY3QobGFzc29fbW9kZWxzLCBYbWF0X3ZhbGlkKQ0KDQp2YWxpZF9wcmVkX2xhc3NvWyxjKDEsMiw5OSwxMDApXQ0KYGBgDQoNCiMjIyMgKipDYWxjdWxhdGluZyBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiByLVNxdWFyZWQqKg0KDQpJbiB0aGUgY2VsbCBiZWxvdywgd2Ugd2lsbCBjYWxjdWxhdGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gci1zcXVhcmVkIHNjb3JlcyBmb3IgZWFjaCBvZiBvdXIgTEFTU08gbW9kZWxzLiANCg0KYGBge3J9DQojIEZpbmQgU1NFDQpTU0VfdHJhaW4gPC0gY29sU3VtcygodHJhaW5fcHJlZF9sYXNzbyAtIHRyYWluJHkpXjIpDQpTU0VfdmFsaWQgPC0gY29sU3VtcygodmFsaWRfcHJlZF9sYXNzbyAtIHZhbGlkJHkpXjIpDQoNCiMgRmluZCByLVNxdWFyZWQNCnIyX3RyYWluX2xpc3QgPC0gMSAtIFNTRV90cmFpbiAvIFNTVF90cmFpbg0KcjJfdmFsaWRfbGlzdCA8LSAxIC0gU1NFX3ZhbGlkIC8gU1NUX3ZhbGlkDQoNCnJvdW5kKHIyX3ZhbGlkX2xpc3RbYygxOjMsIDk4OjEwMCldLDQpDQpgYGANCg0KIyMjIyAqKlBsb3R0aW5nIFRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIHItU3F1YXJlZCBhcyBhIEZ1bmN0aW9uIG9mIExhbWJkYSoqDQoNClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgaG93IHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiByLXNxdWFyZWQgdmFsdWVzIGNoYW5nZSB3aXRoIHJlc3BlY3QgdG8gbGFtYmRhLg0KDQoNCmBgYHtyfQ0KcGxvdChsb2dfbGFtYmRhX2dyaWQsIHIyX3RyYWluX2xpc3QsIHlsaW09YygtMC4yLDEpLCBwY2g9Ii4iLCBjb2w9InNhbG1vbiIsIA0KICAgICB4bGFiPSJsbihsYW1iZGEpIiwgeWxhYj0ici1TcXVhcmVkIiwgbWFpbj0iVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gU2NvcmVzIChSaWRnZSkiKQ0KDQpsaW5lcyhsb2dfbGFtYmRhX2dyaWQsIHIyX3RyYWluX2xpc3QsIGNvbD0ic2FsbW9uIiwgbHdkPTIpDQoNCmxpbmVzKGxvZ19sYW1iZGFfZ3JpZCwgcjJfdmFsaWRfbGlzdCwgY29sPSJjb3JuZmxvd2VyYmx1ZSIsIGx3ZD0yKQ0KDQpsZWdlbmQoNzUsIDEsIGxlZ2VuZD1jKCJUcmFpbmluZyBBY2MiLCAiVmFsaWRhdGlvbiBBY2MiKSwNCiAgICAgICBjb2w9Yygic2FsbW9uIiwgImNvcm5mbG93ZXJibHVlIiksIGx0eT0xLCBsd2Q9MiwgY2V4PTAuOCkNCmBgYA0KDQojIyMjICoqRmluZGluZyBPcHRpbWFsIFZhbHVlIG9mIExhbWJkYSoqDQoNCldlIHdpbGwgbm93IGRldGVybWluZSB0aGUgdmFsdWUgb2YgbGFtYmRhIHRoYXQgcmVzdWx0cyBpbiB0aGUgaGlnaGVzdCB2YWxpZGF0aW9uIHNjb3JlLg0KDQpgYGB7cn0NCmJlc3RfdmFsaWRfcjIgPC0gbWF4KHIyX3ZhbGlkX2xpc3QpDQpiZXN0X3ZhbGlkX3IyX2l4IDwtIHdoaWNoLm1heChyMl92YWxpZF9saXN0KQ0KYmVzdF9sb2dfbGFtYmRhIDwtIGxvZ19sYW1iZGFfZ3JpZFtiZXN0X3ZhbGlkX3IyX2l4XQ0KDQpjYXQoJ0luZGV4IG9mIE9wdGltYWwgci1TcXVhcmVkOiAgICAnLCBiZXN0X3ZhbGlkX3IyX2l4LCAnXG4nLA0KICAgICdWYWx1ZSBvZiBPcHRpbWFsIHItU3F1YXJlZDogICAgJywgYmVzdF92YWxpZF9yMiwgJ1xuJywNCiAgICAnVmFsdWUgb2YgT3B0aW1hbCBsb2cobGFtYmRhKTogICcsIGJlc3RfbG9nX2xhbWJkYSwgc2VwPScnKQ0KDQpgYGANCg0KIyMjIyAqKlBsb3QgRml0dGVkIEN1cnZlKioNCg0KSW4gdGhlIGZpZ3VyZSBiZWxvdywgd2UgcGxvdCB0aGUgY3VydmUgcmVwcmVzZW50aW5nIHRoZSBvcHRpbWFsIExBU1NPIG1vZGVsLiANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpncmlkX3ByZWQgPC0gcHJlZGljdChsYXNzb19tb2RlbHMsIFhtYXRfZ3JpZClbLGJlc3RfdmFsaWRfcjJfaXhdDQoNCnBsb3QoeSB+IHgsIHRyYWluLCB5bGltPWMoLTgwLCAxNDApLCB4bGFiPSJ4IiwgeWxhYj0ieSIsIG1haW49IkxBU1NPIE1vZGVsIikNCmxpbmVzKHhnLCBncmlkX3ByZWQpDQpzZWdtZW50cyh0cmFpbiR4LCB0cmFpbiR5LCB0cmFpbiR4LCB0cmFpbl9wcmVkX2xhc3NvWyxiZXN0X3ZhbGlkX3IyX2l4XSkgDQpwb2ludHModmFsaWQkeSB+IHZhbGlkJHgsIGNvbD0icmVkIikNCnNlZ21lbnRzKHZhbGlkJHgsIHZhbGlkJHksIHZhbGlkJHgsIHZhbGlkX3ByZWRfbGFzc29bLGJlc3RfdmFsaWRfcjJfaXhdLCBjb2w9InJlZCIpDQoNCmBgYA0K