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