knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(ggplot2)
library(glmnet)
Packages and data
This notebook uses the Hitters data from either
ISLR2. The Hitters data record Major League
Baseball players from the 1986 season. Salary is the
response variable: annual salary in thousands of dollars.
data("Hitters", package = "ISLR2")
hitters <- na.omit(Hitters)
dim(hitters)
[1] 263 20
summary(hitters$Salary)
Min. 1st Qu. Median Mean 3rd Qu. Max.
67.5 190.0 425.0 535.9 750.0 2460.0
A quick look at the original predictors
There are 19 predictors in the original dataset, and 263 samples.
- Variables such as
AtBat, Hits,
HmRun, Runs, RBI, and
Walks describe recent batting performance.
- Variables beginning with
C are career totals.
PutOuts, Assists, and Errors
describe fielding.
League, Division, and
NewLeague are categorical indicators.
hitters
ggplot(hitters, aes(x = Years, y = Salary, color = League, size = Hits)) +
geom_point(alpha = 0.7) +
labs(
title = "Hitters: salary versus years in the league",
x = "Years",
y = "Salary"
) +
theme_minimal(base_size = 13)

num_vars <- c("Salary", "AtBat", "Hits", "HmRun", "Runs", "RBI", "Walks",
"Years", "CAtBat", "CHits", "CHmRun", "CRuns", "CRBI", "CWalks")
corr_mat <- cor(hitters[, num_vars])
corr_df <- as.data.frame(as.table(corr_mat))
names(corr_df) <- c("Var1", "Var2", "Correlation")
ggplot(corr_df, aes(x = Var1, y = Var2, fill = Correlation)) +
geom_tile() +
scale_fill_gradient2(low = "#2166ac", mid = "white", high = "#b2182b",
midpoint = 0, limits = c(-1, 1)) +
labs(
title = "Correlation structure among selected quantitative variables",
x = NULL,
y = NULL
) +
theme_minimal(base_size = 11) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
panel.grid = element_blank()
)

There is substantial correlation among the career totals, and several
batting variables are also strongly related. This is a good setting for
regularization.
Create a richer design matrix
To make the prediction problem more genuinely high-dimensional, we
add two-way interactions among the quantitative predictors while keeping
the categorical variables as main effects only.
reg_formula <- Salary ~ League + Division + NewLeague +
(AtBat + Hits + HmRun + Runs + RBI + Walks + Years +
CAtBat + CHits + CHmRun + CRuns + CRBI + CWalks +
PutOuts + Assists + Errors)^2
X <- model.matrix(reg_formula, data = hitters)[, -1]
y <- hitters$Salary
dim(X)
[1] 263 139
colnames(X)[1:12]
[1] "LeagueN" "DivisionW" "NewLeagueN" "AtBat" "Hits" "HmRun" "Runs" "RBI" "Walks" "Years" "CAtBat" "CHits"
qr(X)$rank
[1] 139
This creates a design matrix with 139 predictors in total.
Train/test split
set.seed(32950)
n <- nrow(X)
train_id <- sample(seq_len(n), size = floor(0.8 * n))
test_id <- setdiff(seq_len(n), train_id)
X_train <- X[train_id, , drop = FALSE]
X_test <- X[test_id, , drop = FALSE]
y_train <- y[train_id]
y_test <- y[test_id]
length(train_id)
[1] 210
length(test_id)
[1] 53
OLS on the same design
train_dat <- hitters[train_id, ]
test_dat <- hitters[test_id, ]
ols_fit <- lm(reg_formula, data = train_dat)
ols_pred <- predict(ols_fit, newdata = test_dat)
ols_mse <- mean((y_test - ols_pred)^2)
ols_mse
[1] 179377.1
Even without exact degeneracy, this expanded design makes OLS much
less stable. This is exactly the kind of setting where regularization
becomes useful.
Ridge, lasso, and elastic net
We will use the glmnet function to run Lasso, Ridge and
Elastic Net. The glmnet functions will estimate an
intercept automatically and will not penalize it.
We first use cv.glmnet to choose lambda.
The function will then fit the model with the best lambda
on the entire training dataset. By default, cv.glmnet uses
the number of folds K = 10. We will also standardize all
the predictors.
set.seed(32950)
cv_ridge <- cv.glmnet(X_train, y_train, alpha = 0, standardize = TRUE)
set.seed(32950)
cv_lasso <- cv.glmnet(X_train, y_train, alpha = 1, standardize = TRUE)
set.seed(32950)
cv_enet <- cv.glmnet(X_train, y_train, alpha = 0.5, standardize = TRUE)
Cross-validation curves
par(mfrow = c(1, 3))
plot(cv_ridge, main = "Ridge CV")
plot(cv_lasso, main = "Lasso CV")
plot(cv_enet, main = "Elastic net CV")
par(mfrow = c(1, 1))

Test data prediction error
Use the tuning parameters selected by CV on the whole training data,
and then test the models on the test data.
ridge_pred <- predict(cv_ridge, newx = X_test, s = "lambda.min")
lasso_pred <- predict(cv_lasso, newx = X_test, s = "lambda.min")
enet_pred <- predict(cv_enet, newx = X_test, s = "lambda.min")
ridge_mse <- mean((y_test - ridge_pred)^2)
lasso_mse <- mean((y_test - lasso_pred)^2)
enet_mse <- mean((y_test - enet_pred)^2)
results <- data.frame(
Method = c("OLS", "Ridge", "Lasso", "Elastic net"),
Test_MSE = c(ols_mse, ridge_mse, lasso_mse, enet_mse)
)
results[order(results$Test_MSE), ]
Lasso, Elastic net and Ridge all predict the test data much better
than OLS, with Lasso performing the best here.
Coefficient paths
We can also visualize how the coefficients change with
lambda on the training data.
ridge_fit <- glmnet(X_train, y_train, alpha = 0, standardize = TRUE)
lasso_fit <- glmnet(X_train, y_train, alpha = 1, standardize = TRUE)
enet_fit <- glmnet(X_train, y_train, alpha = 0.5, standardize = TRUE)
par(mfrow = c(1, 3))
plot(ridge_fit, xvar = "lambda", label = FALSE, main = "Ridge path")
plot(lasso_fit, xvar = "lambda", label = FALSE, main = "Lasso path")
plot(enet_fit, xvar = "lambda", label = FALSE, main = "Elastic net path")
par(mfrow = c(1, 1))

The Lasso and Elastic net paths actually very similar. The Elastic
net path is just smoother because of the \(\ell_2\) penalty added.
How sparse is the lasso fit?
lasso_coef <- coef(cv_lasso, s = "lambda.min")
enet_coef <- coef(cv_enet, s = "lambda.min")
sum(lasso_coef[-1, 1] != 0)
[1] 53
sum(enet_coef[-1, 1] != 0)
[1] 60
lasso_nonzero <- lasso_coef[lasso_coef[, 1] != 0, , drop = FALSE]
lasso_nonzero
54 x 1 sparse Matrix of class "dgCMatrix"
lambda.min
(Intercept) 5.348107e+02
LeagueN 8.602123e+00
DivisionW -7.185711e+01
NewLeagueN 5.058167e+01
AtBat -1.456531e+00
Hits -2.925791e+00
HmRun 1.870931e+00
Runs -2.139482e+00
RBI -2.298497e+00
Walks -4.853590e+00
CHits 3.329951e-01
CRuns 7.069482e-01
CRBI 1.895966e-01
CWalks 5.206373e-01
PutOuts 3.072278e-01
Assists 2.070375e+00
Errors -7.617525e+00
AtBat:Hits 3.320834e-03
AtBat:Walks 3.748820e-04
AtBat:Years 5.824630e-02
AtBat:CWalks 1.332638e-03
AtBat:Assists -1.280061e-03
Hits:HmRun 1.344806e-01
Hits:Walks 7.223271e-02
Hits:Assists -1.465332e-03
Hits:Errors 2.194851e-05
HmRun:RBI -3.128173e-02
HmRun:CAtBat -5.534970e-03
HmRun:CHmRun 8.095777e-02
HmRun:CRBI 2.288763e-02
HmRun:CWalks -4.280627e-02
HmRun:PutOuts -3.850681e-03
HmRun:Assists -1.492172e-02
HmRun:Errors -3.231415e-01
Runs:CRBI 3.560147e-04
Runs:Errors 6.072413e-02
RBI:CRBI 3.964278e-03
Walks:Years -2.285219e-03
Walks:PutOuts -2.003278e-03
Walks:Assists -6.429953e-03
Walks:Errors 3.312738e-02
Years:CAtBat -1.496781e-02
Years:Errors -2.030175e-01
CAtBat:CHmRun -1.215814e-04
CAtBat:Assists 4.543147e-05
CHits:PutOuts 4.403663e-04
CHits:Errors 6.742580e-05
CHmRun:CRBI 2.900925e-07
CHmRun:PutOuts -1.009738e-03
CHmRun:Errors 9.663381e-03
CRBI:Errors 2.646156e-02
CWalks:Assists -2.295138e-03
PutOuts:Assists -1.357541e-03
Assists:Errors 7.169966e-03
Takeaway
- OLS becomes unstable on the full interaction-expanded design.
- Ridge stabilizes the fit by shrinking all coefficients.
- Lasso performs both shrinkage and variable selection.
- Elastic net gives a compromise between sparsity and stability.
LS0tCnRpdGxlOiAiTGVjdHVyZSAxMCBEZW1vOiBSZWd1bGFyaXphdGlvbiB3aXRoIHRoZSBIaXR0ZXJzIERhdGEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQoKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdsbW5ldCkKYGBgCgojIyBQYWNrYWdlcyBhbmQgZGF0YQoKVGhpcyBub3RlYm9vayB1c2VzIHRoZSBgSGl0dGVyc2AgZGF0YSBmcm9tIGVpdGhlciBgSVNMUjJgLiBUaGUgYEhpdHRlcnNgIGRhdGEgcmVjb3JkIE1ham9yIExlYWd1ZSBCYXNlYmFsbCBwbGF5ZXJzIGZyb20gdGhlIDE5ODYgc2Vhc29uLiBgU2FsYXJ5YCBpcyB0aGUgcmVzcG9uc2UgdmFyaWFibGU6IGFubnVhbCBzYWxhcnkgaW4gdGhvdXNhbmRzIG9mIGRvbGxhcnMuCgpgYGB7cn0KZGF0YSgiSGl0dGVycyIsIHBhY2thZ2UgPSAiSVNMUjIiKQpoaXR0ZXJzIDwtIG5hLm9taXQoSGl0dGVycykKCmRpbShoaXR0ZXJzKQpzdW1tYXJ5KGhpdHRlcnMkU2FsYXJ5KQpgYGAKCgoKIyMjIEEgcXVpY2sgbG9vayBhdCB0aGUgb3JpZ2luYWwgcHJlZGljdG9ycwoKVGhlcmUgYXJlIDE5IHByZWRpY3RvcnMgaW4gdGhlIG9yaWdpbmFsIGRhdGFzZXQsIGFuZCAyNjMgc2FtcGxlcy4gCgotIFZhcmlhYmxlcyBzdWNoIGFzIGBBdEJhdGAsIGBIaXRzYCwgYEhtUnVuYCwgYFJ1bnNgLCBgUkJJYCwgYW5kIGBXYWxrc2AgZGVzY3JpYmUgcmVjZW50IGJhdHRpbmcgcGVyZm9ybWFuY2UuCi0gVmFyaWFibGVzIGJlZ2lubmluZyB3aXRoIGBDYCBhcmUgY2FyZWVyIHRvdGFscy4KLSBgUHV0T3V0c2AsIGBBc3Npc3RzYCwgYW5kIGBFcnJvcnNgIGRlc2NyaWJlIGZpZWxkaW5nLgotIGBMZWFndWVgLCBgRGl2aXNpb25gLCBhbmQgYE5ld0xlYWd1ZWAgYXJlIGNhdGVnb3JpY2FsIGluZGljYXRvcnMuCgpgYGB7cn0KaGl0dGVycwpgYGAKCmBgYHtyfQpnZ3Bsb3QoaGl0dGVycywgYWVzKHggPSBZZWFycywgeSA9IFNhbGFyeSwgY29sb3IgPSBMZWFndWUsIHNpemUgPSBIaXRzKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjcpICsKICBsYWJzKAogICAgdGl0bGUgPSAiSGl0dGVyczogc2FsYXJ5IHZlcnN1cyB5ZWFycyBpbiB0aGUgbGVhZ3VlIiwKICAgIHggPSAiWWVhcnMiLAogICAgeSA9ICJTYWxhcnkiCiAgKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykKYGBgCgpgYGB7cn0KbnVtX3ZhcnMgPC0gYygiU2FsYXJ5IiwgIkF0QmF0IiwgIkhpdHMiLCAiSG1SdW4iLCAiUnVucyIsICJSQkkiLCAiV2Fsa3MiLAogICAgICAgICAgICAgICJZZWFycyIsICJDQXRCYXQiLCAiQ0hpdHMiLCAiQ0htUnVuIiwgIkNSdW5zIiwgIkNSQkkiLCAiQ1dhbGtzIikKCmNvcnJfbWF0IDwtIGNvcihoaXR0ZXJzWywgbnVtX3ZhcnNdKQoKY29ycl9kZiA8LSBhcy5kYXRhLmZyYW1lKGFzLnRhYmxlKGNvcnJfbWF0KSkKbmFtZXMoY29ycl9kZikgPC0gYygiVmFyMSIsICJWYXIyIiwgIkNvcnJlbGF0aW9uIikKCmdncGxvdChjb3JyX2RmLCBhZXMoeCA9IFZhcjEsIHkgPSBWYXIyLCBmaWxsID0gQ29ycmVsYXRpb24pKSArCiAgZ2VvbV90aWxlKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICIjMjE2NmFjIiwgbWlkID0gIndoaXRlIiwgaGlnaCA9ICIjYjIxODJiIiwKICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IDAsIGxpbWl0cyA9IGMoLTEsIDEpKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvcnJlbGF0aW9uIHN0cnVjdHVyZSBhbW9uZyBzZWxlY3RlZCBxdWFudGl0YXRpdmUgdmFyaWFibGVzIiwKICAgIHggPSBOVUxMLAogICAgeSA9IE5VTEwKICApICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDExKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKQogICkKYGBgCgpUaGVyZSBpcyBzdWJzdGFudGlhbCBjb3JyZWxhdGlvbiBhbW9uZyB0aGUgY2FyZWVyIHRvdGFscywgYW5kIHNldmVyYWwgYmF0dGluZyB2YXJpYWJsZXMgYXJlIGFsc28gc3Ryb25nbHkgcmVsYXRlZC4gVGhpcyBpcyBhIGdvb2Qgc2V0dGluZyBmb3IgcmVndWxhcml6YXRpb24uCgojIyMgQ3JlYXRlIGEgcmljaGVyIGRlc2lnbiBtYXRyaXgKClRvIG1ha2UgdGhlIHByZWRpY3Rpb24gcHJvYmxlbSBtb3JlIGdlbnVpbmVseSBoaWdoLWRpbWVuc2lvbmFsLCB3ZSBhZGQgdHdvLXdheSBpbnRlcmFjdGlvbnMgYW1vbmcgdGhlIHF1YW50aXRhdGl2ZSBwcmVkaWN0b3JzIHdoaWxlIGtlZXBpbmcgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBhcyBtYWluIGVmZmVjdHMgb25seS4KCmBgYHtyfQpyZWdfZm9ybXVsYSA8LSBTYWxhcnkgfiBMZWFndWUgKyBEaXZpc2lvbiArIE5ld0xlYWd1ZSArCiAgKEF0QmF0ICsgSGl0cyArIEhtUnVuICsgUnVucyArIFJCSSArIFdhbGtzICsgWWVhcnMgKwogICBDQXRCYXQgKyBDSGl0cyArIENIbVJ1biArIENSdW5zICsgQ1JCSSArIENXYWxrcyArCiAgIFB1dE91dHMgKyBBc3Npc3RzICsgRXJyb3JzKV4yCgpYIDwtIG1vZGVsLm1hdHJpeChyZWdfZm9ybXVsYSwgZGF0YSA9IGhpdHRlcnMpWywgLTFdCnkgPC0gaGl0dGVycyRTYWxhcnkKCmRpbShYKQpgYGAKClRoaXMgY3JlYXRlcyBhIGRlc2lnbiBtYXRyaXggd2l0aCAxMzkgcHJlZGljdG9ycyBpbiB0b3RhbC4gCgoKCiMjIFRyYWluL3Rlc3Qgc3BsaXQKCmBgYHtyfQpzZXQuc2VlZCgzMjk1MCkKbiA8LSBucm93KFgpCnRyYWluX2lkIDwtIHNhbXBsZShzZXFfbGVuKG4pLCBzaXplID0gZmxvb3IoMC44ICogbikpCnRlc3RfaWQgPC0gc2V0ZGlmZihzZXFfbGVuKG4pLCB0cmFpbl9pZCkKClhfdHJhaW4gPC0gWFt0cmFpbl9pZCwgLCBkcm9wID0gRkFMU0VdClhfdGVzdCA8LSBYW3Rlc3RfaWQsICwgZHJvcCA9IEZBTFNFXQp5X3RyYWluIDwtIHlbdHJhaW5faWRdCnlfdGVzdCA8LSB5W3Rlc3RfaWRdCgpsZW5ndGgodHJhaW5faWQpICMgbnVtYmVyIG9mIHRyYWluaW5nIHNhbXBsZXMKbGVuZ3RoKHRlc3RfaWQpICMgbnVtYmVyIG9mIHRlc3Qgc2FtcGxlcwpgYGAKCiMjIyBPTFMgb24gdGhlIHNhbWUgZGVzaWduCgpgYGB7cn0KdHJhaW5fZGF0IDwtIGhpdHRlcnNbdHJhaW5faWQsIF0KdGVzdF9kYXQgPC0gaGl0dGVyc1t0ZXN0X2lkLCBdCgpvbHNfZml0IDwtIGxtKHJlZ19mb3JtdWxhLCBkYXRhID0gdHJhaW5fZGF0KQpvbHNfcHJlZCA8LSBwcmVkaWN0KG9sc19maXQsIG5ld2RhdGEgPSB0ZXN0X2RhdCkKb2xzX21zZSA8LSBtZWFuKCh5X3Rlc3QgLSBvbHNfcHJlZCleMikKCm9sc19tc2UKYGBgCgpFdmVuIHdpdGhvdXQgZXhhY3QgZGVnZW5lcmFjeSwgdGhpcyBleHBhbmRlZCBkZXNpZ24gbWFrZXMgT0xTIG11Y2ggbGVzcyBzdGFibGUuIFRoaXMgaXMgZXhhY3RseSB0aGUga2luZCBvZiBzZXR0aW5nIHdoZXJlIHJlZ3VsYXJpemF0aW9uIGJlY29tZXMgdXNlZnVsLgoKIyMgUmlkZ2UsIGxhc3NvLCBhbmQgZWxhc3RpYyBuZXQKCldlIHdpbGwgdXNlIHRoZSBgZ2xtbmV0YCBmdW5jdGlvbiB0byBydW4gTGFzc28sIFJpZGdlIGFuZCBFbGFzdGljIE5ldC4gVGhlIGBnbG1uZXRgIGZ1bmN0aW9ucyB3aWxsIGVzdGltYXRlIGFuIGludGVyY2VwdCBhdXRvbWF0aWNhbGx5IGFuZCB3aWxsIG5vdCBwZW5hbGl6ZSBpdC4KCldlIGZpcnN0IHVzZSBgY3YuZ2xtbmV0YCB0byBjaG9vc2UgYGxhbWJkYWAuIFRoZSBmdW5jdGlvbiB3aWxsIHRoZW4gZml0IHRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IGBsYW1iZGFgIG9uIHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YXNldC4gQnkgZGVmYXVsdCwgYGN2LmdsbW5ldGAgdXNlcyB0aGUgbnVtYmVyIG9mIGZvbGRzIGBLID0gMTBgLiBXZSB3aWxsIGFsc28gc3RhbmRhcmRpemUgYWxsIHRoZSBwcmVkaWN0b3JzLiAKCmBgYHtyfQpzZXQuc2VlZCgzMjk1MCkKY3ZfcmlkZ2UgPC0gY3YuZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMCwgc3RhbmRhcmRpemUgPSBUUlVFKQoKc2V0LnNlZWQoMzI5NTApCmN2X2xhc3NvIDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDEsIHN0YW5kYXJkaXplID0gVFJVRSkKCnNldC5zZWVkKDMyOTUwKQpjdl9lbmV0IDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDAuNSwgc3RhbmRhcmRpemUgPSBUUlVFKQpgYGAKCiMjIyBDcm9zcy12YWxpZGF0aW9uIGN1cnZlcwoKYGBge3J9CnBhcihtZnJvdyA9IGMoMSwgMykpCnBsb3QoY3ZfcmlkZ2UsIG1haW4gPSAiUmlkZ2UgQ1YiKQpwbG90KGN2X2xhc3NvLCBtYWluID0gIkxhc3NvIENWIikKcGxvdChjdl9lbmV0LCBtYWluID0gIkVsYXN0aWMgbmV0IENWIikKcGFyKG1mcm93ID0gYygxLCAxKSkKYGBgCgojIyMgVGVzdCBkYXRhIHByZWRpY3Rpb24gZXJyb3IKClVzZSB0aGUgdHVuaW5nIHBhcmFtZXRlcnMgc2VsZWN0ZWQgYnkgQ1Ygb24gdGhlIHdob2xlIHRyYWluaW5nIGRhdGEsIGFuZCB0aGVuIHRlc3QgdGhlIG1vZGVscyBvbiB0aGUgdGVzdCBkYXRhLgoKYGBge3J9CnJpZGdlX3ByZWQgPC0gcHJlZGljdChjdl9yaWRnZSwgbmV3eCA9IFhfdGVzdCwgcyA9ICJsYW1iZGEubWluIikKbGFzc29fcHJlZCA8LSBwcmVkaWN0KGN2X2xhc3NvLCBuZXd4ID0gWF90ZXN0LCBzID0gImxhbWJkYS5taW4iKQplbmV0X3ByZWQgPC0gcHJlZGljdChjdl9lbmV0LCBuZXd4ID0gWF90ZXN0LCBzID0gImxhbWJkYS5taW4iKQoKcmlkZ2VfbXNlIDwtIG1lYW4oKHlfdGVzdCAtIHJpZGdlX3ByZWQpXjIpCmxhc3NvX21zZSA8LSBtZWFuKCh5X3Rlc3QgLSBsYXNzb19wcmVkKV4yKQplbmV0X21zZSA8LSBtZWFuKCh5X3Rlc3QgLSBlbmV0X3ByZWQpXjIpCgpyZXN1bHRzIDwtIGRhdGEuZnJhbWUoCiAgTWV0aG9kID0gYygiT0xTIiwgIlJpZGdlIiwgIkxhc3NvIiwgIkVsYXN0aWMgbmV0IiksCiAgVGVzdF9NU0UgPSBjKG9sc19tc2UsIHJpZGdlX21zZSwgbGFzc29fbXNlLCBlbmV0X21zZSkKKQoKcmVzdWx0c1tvcmRlcihyZXN1bHRzJFRlc3RfTVNFKSwgXQpgYGAKTGFzc28sIEVsYXN0aWMgbmV0IGFuZCBSaWRnZSBhbGwgcHJlZGljdCB0aGUgdGVzdCBkYXRhIG11Y2ggYmV0dGVyIHRoYW4gT0xTLCB3aXRoIExhc3NvIHBlcmZvcm1pbmcgdGhlIGJlc3QgaGVyZS4KCgojIyBDb2VmZmljaWVudCBwYXRocwoKV2UgY2FuIGFsc28gdmlzdWFsaXplIGhvdyB0aGUgY29lZmZpY2llbnRzIGNoYW5nZSB3aXRoIGBsYW1iZGFgIG9uIHRoZSB0cmFpbmluZyBkYXRhLgoKYGBge3J9CnJpZGdlX2ZpdCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAwLCBzdGFuZGFyZGl6ZSA9IFRSVUUpCmxhc3NvX2ZpdCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBzdGFuZGFyZGl6ZSA9IFRSVUUpCmVuZXRfZml0IDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDAuNSwgc3RhbmRhcmRpemUgPSBUUlVFKQpgYGAKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDMpKQpwbG90KHJpZGdlX2ZpdCwgeHZhciA9ICJsYW1iZGEiLCBsYWJlbCA9IEZBTFNFLCBtYWluID0gIlJpZGdlIHBhdGgiKQpwbG90KGxhc3NvX2ZpdCwgeHZhciA9ICJsYW1iZGEiLCBsYWJlbCA9IEZBTFNFLCBtYWluID0gIkxhc3NvIHBhdGgiKQpwbG90KGVuZXRfZml0LCB4dmFyID0gImxhbWJkYSIsIGxhYmVsID0gRkFMU0UsIG1haW4gPSAiRWxhc3RpYyBuZXQgcGF0aCIpCnBhcihtZnJvdyA9IGMoMSwgMSkpCmBgYAoKVGhlIExhc3NvIGFuZCBFbGFzdGljIG5ldCBwYXRocyBhY3R1YWxseSB2ZXJ5IHNpbWlsYXIuIFRoZSBFbGFzdGljIG5ldCBwYXRoIGlzIGp1c3Qgc21vb3RoZXIgYmVjYXVzZSBvZiB0aGUgJFxlbGxfMiQgcGVuYWx0eSBhZGRlZC4KCiMjIEhvdyBzcGFyc2UgaXMgdGhlIGxhc3NvIGZpdD8KCmBgYHtyfQpsYXNzb19jb2VmIDwtIGNvZWYoY3ZfbGFzc28sIHMgPSAibGFtYmRhLm1pbiIpCmVuZXRfY29lZiA8LSBjb2VmKGN2X2VuZXQsIHMgPSAibGFtYmRhLm1pbiIpCgpzdW0obGFzc29fY29lZlstMSwgMV0gIT0gMCkgIyByZW1vdmluZyB0aGUgaW50ZXJjZXB0LCBhbmQgY2FsY3VsYXRlIHRoZSBudW1iZXIgb2Ygbm9uemVybyBjb2VmZmljaWVudHMKc3VtKGVuZXRfY29lZlstMSwgMV0gIT0gMCkKYGBgCgpgYGB7cn0KbGFzc29fbm9uemVybyA8LSBsYXNzb19jb2VmW2xhc3NvX2NvZWZbLCAxXSAhPSAwLCAsIGRyb3AgPSBGQUxTRV0KbGFzc29fbm9uemVybwpgYGAKCiMjIFRha2Vhd2F5CgotIE9MUyBiZWNvbWVzIHVuc3RhYmxlIG9uIHRoZSBmdWxsIGludGVyYWN0aW9uLWV4cGFuZGVkIGRlc2lnbi4KLSBSaWRnZSBzdGFiaWxpemVzIHRoZSBmaXQgYnkgc2hyaW5raW5nIGFsbCBjb2VmZmljaWVudHMuCi0gTGFzc28gcGVyZm9ybXMgYm90aCBzaHJpbmthZ2UgYW5kIHZhcmlhYmxlIHNlbGVjdGlvbi4KLSBFbGFzdGljIG5ldCBnaXZlcyBhIGNvbXByb21pc2UgYmV0d2VlbiBzcGFyc2l0eSBhbmQgc3RhYmlsaXR5Lgo=