glmnet: library for both lasso (alpha=1) and ridge (alpha=0). Check more glmnet examples at https://web.stanford.edu/~hastie/glmnet/glmnet_beta.html

# install.packages("glmnet")
library(glmnet)

Data

Load Data

The Prostate data can be found [here]. The data is to examine the correlation between the level of prostate-specific antigen and a number of clinical measures in men who were about to receive a radical prostatectomy.

  • lcavol: log cancer volume
  • lweight: log prostate weight
  • age: in years
  • lbph: log of the amount of benign prostatic hyperplasia
  • svi: seminal vesicle invasion
  • lcp: log of capsular penetration
  • gleason: a numeric vector
  • pgg45: percent of Gleason score 4 or 5
  • lpsa: response

The first 8 columns are predictors; column 9 is the outcome/response. We do not use column 10, which indicates which 67 observations were used as the “training set” and which 30 as the test set, as described on page 48 in the ESL book. For convenience, we rename the response column with a generic name Y.

myData = read.table(file = "https://hastie.su.domains/ElemStatLearn/datasets/prostate.data", header = TRUE)
myData = myData[, -10]
names(myData)[9] = 'Y'
names(myData)
[1] "lcavol"  "lweight" "age"     "lbph"    "svi"     "lcp"     "gleason" "pgg45"   "Y"      
n = dim(myData)[1]         # sample size
p = dim(myData)[2] - 1     # number of non-intercept predictors
X = as.matrix(myData[, names(myData) != "Y"]);  # some algorithms need the matrix/vector 
Y = myData$Y;       

Train/Test Split

Split the data into two parts: 80% for training and 20% for testing. Throughout, we will use the the training data to select variables and estimate coefficients and report the average MSE on the test data.

ntest = round(n*0.2)
ntrain = n - ntest;
test.id = sample(1:n, ntest);
Ytest = Y[test.id];

Full Model

Fit an ordinary linear regression model using all variables.

full.model = lm( Y ~ ., data = myData[-test.id, ]);  
Ytest.pred = predict(full.model, newdata= myData[test.id, ]);
sum((Ytest - Ytest.pred)^2)/ntest # averaged MSE on the test set
[1] 0.2199186

Ridge

Fit a set of ridge regression models using glmnet with the default \(\lambda\) sequence.

myridge = glmnet(X[-test.id, ], Y[-test.id], alpha = 0)
plot(myridge, label = TRUE, xvar = "lambda")

Output

Check the output from glmnet for ridge regression.

summary(myridge)

Retrieve the lambda values

length(myridge$lambda)

Retrieve coefficients using two different approaches.

dim(myridge$beta)       # coefficients for  non-intercept predictors
length(myridge$a0)      # intercept


# Retrieve coefficients (including intercept) using
# coef(myridge)
dim(coef(myridge))

The two coefficient matrices should be the same

sum((coef(myridge) - rbind(myridge$a0, myridge$beta))^2)
[1] 0

Note: Ridge regression coefficients may change signs along the path.

round(myridge$beta['age', ], dig = 4)

How are the intercepts computed?

k = 2; 
my.mean = apply(X[-test.id, ], 2, mean)  # 13x1 mean vector for training X
mean(Y[-test.id]) - sum(my.mean * myridge$beta[, k])
myridge$a0[k]  # intercept for lambda = myridge$lambda[k]

Check whether our intercept formula is true for all intercepts

sum((mean(Y[-test.id]) - my.mean %*% myridge$beta  - myridge$a0)^2)

CV & Path Plot

The CV results are stored in

  • cv.out$cvm: mean CV errors
  • cv.out$cvsd: standard errors of cvm
cv.out = cv.glmnet(X[-test.id, ], Y[-test.id], alpha = 0) 
plot(cv.out)

The CV performance continues to drop when \(\lambda\) gets smaller. So, we provide R with a \(\lambda\) sequence dense in [-6, 2].

lam.seq = exp(seq(-6, 2, length=100))
cv.out = cv.glmnet(X[-test.id,], Y[-test.id], alpha=0, lambda=lam.seq)  
plot(cv.out)

Two choices for lambda

  • lambda.min: the value of lambda that achieves the minimum cvm (the left dashed vertical line)
  • lambda.1se: the largest value of lambda (i.e., the largest regularization, the smallest df) whose cvm is within one-standard-error of the cvm of lambda.min. (the left dashed vertical line)

Note: by definition lambda.1se (right dashed vertical line) is always bigger than or equal to lambda.min (left dashed vertical line).

Check how lambda.min and lambda.1se are computed.

names(cv.out)
 [1] "lambda"     "cvm"        "cvsd"       "cvup"       "cvlo"       "nzero"     
 [7] "call"       "name"       "glmnet.fit" "lambda.min" "lambda.1se" "index"     
# Find lambda.min
cv.out$lambda.min
[1] 0.1656332
cv.out$lambda[which.min(cv.out$cvm)]
[1] 0.1656332
# Find lambda.1se
cv.out$lambda.1se
[1] 1.353955
tmp.id = which.min(cv.out$cvm)
max(cv.out$lambda[cv.out$cvm < cv.out$cvm[tmp.id] + cv.out$cvsd[tmp.id]])
[1] 1.353955

Prediction

Next we apply the two ridge regression models—one with lambda.min and one with lambda.1se—on a new test data set and report the corresponding mean squared prediction errors.

myridge = glmnet(X[-test.id,], Y[-test.id], alpha=0, lambda = lam.seq)
Ytest.pred = predict(myridge, s = cv.out$lambda.1se, newx=X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.3779362
Ytest.pred=predict(myridge, s = cv.out$lambda.min, newx=X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.235441

Lasso

Output

Check the output from glmnet for Lasso.

mylasso = glmnet(X[-test.id,], Y[-test.id], alpha = 1)
summary(mylasso)

CV & Path Plot

par(mfrow = c(1, 2))
plot(mylasso, label=TRUE, xvar = "norm")
plot(mylasso, label=TRUE, xvar = "lambda")
par(mfrow=c(1,1))

mylasso$df
 [1] 0 1 1 1 1 1 1 1 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 5 6 6 6 6 6 6 7 7 7 7 7 7 8
[45] 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
cv.out = cv.glmnet(X[-test.id, ], Y[-test.id], alpha = 1)
plot(cv.out)

Try our own lambda sequences.

# lam.seq =  exp(seq(-4, 2, length=100))
# lam.seq =  exp(seq(-6, -1, length=100))
# cv.out = cv.glmnet(X[-test.id,], Y[-test.id], alpha = 1, lambda = l am.seq)
# plot(cv.out)

Check how lambda.min and lambda.1se are computed.

cv.out$lambda.min
tmp.id=which.min(cv.out$cvm)
cv.out$lambda[tmp.id]

cv.out$lambda.1se
max(cv.out$lambda[cv.out$cvm < cv.out$cvm[tmp.id] + cv.out$cvsd[tmp.id]])

Coefficients

How to retrieve Lasso coefficients?

mylasso.coef.min = predict(mylasso, s=cv.out$lambda.min, type="coefficients")
mylasso.coef.1se = predict(mylasso, s=cv.out$lambda.1se, type="coefficients")
cbind(mylasso.coef.min, mylasso.coef.1se)
9 x 2 sparse Matrix of class "dgCMatrix"
                    s1        s1
(Intercept) 0.22065356 1.4530552
lcavol      0.51079974 0.4347692
lweight     0.39003496 0.1021676
age         .          .        
lbph        0.05266784 .        
svi         0.56214447 0.2256772
lcp         .          .        
gleason     .          .        
pgg45       .          .        
# number of variables selected (including the intercept)
sum(mylasso.coef.1se != 0)
[1] 4
# names of selected non-intercept variables
row.names(mylasso.coef.1se)[which(mylasso.coef.1se != 0)[-1]]
[1] "lcavol"  "lweight" "svi"    

Prediction

Apply the two fitted models for prediction on test data.

mylasso = glmnet(X[-test.id, ], Y[-test.id], alpha = 1)
Ytest.pred = predict(mylasso, s = cv.out$lambda.min, newx = X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.2833383
Ytest.pred = predict(mylasso, s = cv.out$lambda.1se, newx = X[test.id,])
mean((Ytest.pred - Y[test.id])^2)
[1] 0.4411785

Refit to Reduce Bias

We refit an ordinary linear regression model using the variables selected by lambda.1se and then use it for prediction.

mylasso.coef.1se = predict(mylasso, s = cv.out$lambda.1se, type="coefficients")
var.sel = row.names(mylasso.coef.1se)[which(mylasso.coef.1se != 0)[-1]]
var.sel; 
[1] "lcavol"  "lweight" "svi"    
tmp.X = X[, colnames(X) %in% var.sel]
mylasso.refit = coef(lm(Y[-test.id] ~ tmp.X[-test.id, ]))
Ytest.pred = mylasso.refit[1] + tmp.X[test.id,] %*% mylasso.refit[-1]
mean((Ytest.pred - Y[test.id])^2)
[1] 0.2601463
LS0tCnRpdGxlOiAiU3RhdDU0MjogUmlkZ2UgYW5kIExhc3NvIgpkYXRlOiAiRmFsbCAyMDIyIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKLS0tCgoKYGdsbW5ldGA6IGxpYnJhcnkgZm9yIGJvdGggbGFzc28gKGFscGhhPTEpIGFuZCByaWRnZSAoYWxwaGE9MCkuIENoZWNrIG1vcmUgZ2xtbmV0IGV4YW1wbGVzIGF0IApodHRwczovL3dlYi5zdGFuZm9yZC5lZHUvfmhhc3RpZS9nbG1uZXQvZ2xtbmV0X2JldGEuaHRtbAoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikKbGlicmFyeShnbG1uZXQpCmBgYAoKIyMgRGF0YQoKIyMjIExvYWQgRGF0YQoKVGhlICoqUHJvc3RhdGUgZGF0YSoqIGNhbiBiZSBmb3VuZCBbW2hlcmVdKGh0dHBzOi8vaGFzdGllLnN1LmRvbWFpbnMvRWxlbVN0YXRMZWFybi9kYXRhLmh0bWwpXS4gVGhlIGRhdGEgaXMgdG8gZXhhbWluZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgbGV2ZWwgb2YgcHJvc3RhdGUtc3BlY2lmaWMgYW50aWdlbiBhbmQgYSBudW1iZXIgb2YgY2xpbmljYWwgbWVhc3VyZXMgaW4gbWVuIHdobyB3ZXJlIGFib3V0IHRvIHJlY2VpdmUgYSByYWRpY2FsIHByb3N0YXRlY3RvbXkuCgotIGBsY2F2b2xgOiAgbG9nIGNhbmNlciB2b2x1bWUKLSBgbHdlaWdodGA6IGxvZyBwcm9zdGF0ZSB3ZWlnaHQKLSBgYWdlYDogaW4geWVhcnMKLSBgbGJwaGA6IGxvZyBvZiB0aGUgYW1vdW50IG9mIGJlbmlnbiBwcm9zdGF0aWMgaHlwZXJwbGFzaWEKLSBgc3ZpYDogc2VtaW5hbCB2ZXNpY2xlIGludmFzaW9uCi0gYGxjcGA6IGxvZyBvZiBjYXBzdWxhciBwZW5ldHJhdGlvbgotIGBnbGVhc29uYDogYSBudW1lcmljIHZlY3RvcgotIGBwZ2c0NWA6IHBlcmNlbnQgb2YgR2xlYXNvbiBzY29yZSA0IG9yIDUKLSBgbHBzYWA6IHJlc3BvbnNlCgpUaGUgZmlyc3QgOCBjb2x1bW5zIGFyZSBwcmVkaWN0b3JzOyBjb2x1bW4gOSBpcyB0aGUgb3V0Y29tZS9yZXNwb25zZS4gV2UgZG8gbm90IHVzZSBjb2x1bW4gMTAsIHdoaWNoIGluZGljYXRlcyB3aGljaCA2NyBvYnNlcnZhdGlvbnMgd2VyZSB1c2VkIGFzIHRoZSAidHJhaW5pbmcgc2V0IiBhbmQgd2hpY2ggMzAgYXMgdGhlIHRlc3Qgc2V0LCBhcyBkZXNjcmliZWQgb24gcGFnZSA0OCBpbiB0aGUgRVNMIGJvb2suIEZvciBjb252ZW5pZW5jZSwgd2UgcmVuYW1lIHRoZSByZXNwb25zZSBjb2x1bW4gd2l0aCBhIGdlbmVyaWMgbmFtZSBZLgoKYGBge3J9Cm15RGF0YSA9IHJlYWQudGFibGUoZmlsZSA9ICJodHRwczovL2hhc3RpZS5zdS5kb21haW5zL0VsZW1TdGF0TGVhcm4vZGF0YXNldHMvcHJvc3RhdGUuZGF0YSIsIGhlYWRlciA9IFRSVUUpCm15RGF0YSA9IG15RGF0YVssIC0xMF0KbmFtZXMobXlEYXRhKVs5XSA9ICdZJwpuYW1lcyhteURhdGEpCmBgYAoKYGBge3J9Cm4gPSBkaW0obXlEYXRhKVsxXSAgICAgICAgICMgc2FtcGxlIHNpemUKcCA9IGRpbShteURhdGEpWzJdIC0gMSAgICAgIyBudW1iZXIgb2Ygbm9uLWludGVyY2VwdCBwcmVkaWN0b3JzClggPSBhcy5tYXRyaXgobXlEYXRhWywgbmFtZXMobXlEYXRhKSAhPSAiWSJdKTsgICMgc29tZSBhbGdvcml0aG1zIG5lZWQgdGhlIG1hdHJpeC92ZWN0b3IgClkgPSBteURhdGEkWTsgICAgICAgCmBgYAoKIyMjIFRyYWluL1Rlc3QgU3BsaXQKClNwbGl0IHRoZSBkYXRhIGludG8gdHdvIHBhcnRzOiA4MCUgZm9yIHRyYWluaW5nIGFuZCAyMCUgZm9yIHRlc3RpbmcuIFRocm91Z2hvdXQsIHdlIHdpbGwgdXNlIHRoZSB0aGUgdHJhaW5pbmcgZGF0YSB0byBzZWxlY3QgdmFyaWFibGVzIGFuZCBlc3RpbWF0ZSBjb2VmZmljaWVudHMgYW5kIHJlcG9ydCB0aGUgYXZlcmFnZSBNU0Ugb24gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpudGVzdCA9IHJvdW5kKG4qMC4yKQpudHJhaW4gPSBuIC0gbnRlc3Q7CnRlc3QuaWQgPSBzYW1wbGUoMTpuLCBudGVzdCk7Cll0ZXN0ID0gWVt0ZXN0LmlkXTsKYGBgCgojIyBGdWxsIE1vZGVsCgpGaXQgYW4gb3JkaW5hcnkgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgYWxsIHZhcmlhYmxlcy4KCmBgYHtyfQpmdWxsLm1vZGVsID0gbG0oIFkgfiAuLCBkYXRhID0gbXlEYXRhWy10ZXN0LmlkLCBdKTsgIApZdGVzdC5wcmVkID0gcHJlZGljdChmdWxsLm1vZGVsLCBuZXdkYXRhPSBteURhdGFbdGVzdC5pZCwgXSk7CnN1bSgoWXRlc3QgLSBZdGVzdC5wcmVkKV4yKS9udGVzdCAjIGF2ZXJhZ2VkIE1TRSBvbiB0aGUgdGVzdCBzZXQKYGBgCgojIyBSaWRnZSAKCkZpdCBhIHNldCBvZiByaWRnZSByZWdyZXNzaW9uIG1vZGVscyB1c2luZyBgZ2xtbmV0YCB3aXRoIHRoZSBkZWZhdWx0ICRcbGFtYmRhJCBzZXF1ZW5jZS4gCgpgYGB7cn0KbXlyaWRnZSA9IGdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAwKQpwbG90KG15cmlkZ2UsIGxhYmVsID0gVFJVRSwgeHZhciA9ICJsYW1iZGEiKQpgYGAKCiMjIyBPdXRwdXQKQ2hlY2sgdGhlIG91dHB1dCBmcm9tIGBnbG1uZXRgIGZvciByaWRnZSByZWdyZXNzaW9uLgoKYGBge3J9CnN1bW1hcnkobXlyaWRnZSkKYGBgCgpSZXRyaWV2ZSB0aGUgbGFtYmRhIHZhbHVlcwpgYGB7cn0KbGVuZ3RoKG15cmlkZ2UkbGFtYmRhKQpgYGAKClJldHJpZXZlIGNvZWZmaWNpZW50cyB1c2luZyB0d28gZGlmZmVyZW50IGFwcHJvYWNoZXMuIApgYGB7cn0KZGltKG15cmlkZ2UkYmV0YSkgICAgICAgIyBjb2VmZmljaWVudHMgZm9yICBub24taW50ZXJjZXB0IHByZWRpY3RvcnMKbGVuZ3RoKG15cmlkZ2UkYTApICAgICAgIyBpbnRlcmNlcHQKCgojIFJldHJpZXZlIGNvZWZmaWNpZW50cyAoaW5jbHVkaW5nIGludGVyY2VwdCkgdXNpbmcKIyBjb2VmKG15cmlkZ2UpCmRpbShjb2VmKG15cmlkZ2UpKQpgYGAKClRoZSB0d28gY29lZmZpY2llbnQgbWF0cmljZXMgc2hvdWxkIGJlIHRoZSBzYW1lCmBgYHtyfQpzdW0oKGNvZWYobXlyaWRnZSkgLSByYmluZChteXJpZGdlJGEwLCBteXJpZGdlJGJldGEpKV4yKQpgYGAKCioqTm90ZSoqOiBSaWRnZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBtYXkgY2hhbmdlIHNpZ25zIGFsb25nIHRoZSBwYXRoLgoKYGBge3J9CnJvdW5kKG15cmlkZ2UkYmV0YVsnYWdlJywgXSwgZGlnID0gNCkKYGBgCgpIb3cgYXJlIHRoZSBpbnRlcmNlcHRzIGNvbXB1dGVkPwoKYGBge3J9CmsgPSAyOyAKbXkubWVhbiA9IGFwcGx5KFhbLXRlc3QuaWQsIF0sIDIsIG1lYW4pICAjIDEzeDEgbWVhbiB2ZWN0b3IgZm9yIHRyYWluaW5nIFgKbWVhbihZWy10ZXN0LmlkXSkgLSBzdW0obXkubWVhbiAqIG15cmlkZ2UkYmV0YVssIGtdKQpteXJpZGdlJGEwW2tdICAjIGludGVyY2VwdCBmb3IgbGFtYmRhID0gbXlyaWRnZSRsYW1iZGFba10KYGBgCgpDaGVjayB3aGV0aGVyIG91ciBpbnRlcmNlcHQgZm9ybXVsYSBpcyB0cnVlIGZvciBhbGwgaW50ZXJjZXB0cyAKYGBge3J9CnN1bSgobWVhbihZWy10ZXN0LmlkXSkgLSBteS5tZWFuICUqJSBteXJpZGdlJGJldGEgIC0gbXlyaWRnZSRhMCleMikKYGBgCgojIyMgQ1YgJiBQYXRoIFBsb3QKClRoZSBDViByZXN1bHRzIGFyZSBzdG9yZWQgaW4gCgotIGBjdi5vdXQkY3ZtYDogbWVhbiBDViBlcnJvcnMKLSBgY3Yub3V0JGN2c2RgOiBzdGFuZGFyZCBlcnJvcnMgb2YgY3ZtCgoKYGBge3J9CmN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAwKSAKcGxvdChjdi5vdXQpCmBgYAoKVGhlIENWIHBlcmZvcm1hbmNlIGNvbnRpbnVlcyB0byBkcm9wIHdoZW4gJFxsYW1iZGEkIGdldHMgc21hbGxlci4gU28sIHdlIHByb3ZpZGUgUiB3aXRoIGEgICRcbGFtYmRhJCBzZXF1ZW5jZSBkZW5zZSBpbiBbLTYsIDJdLgoKYGBge3J9CmxhbS5zZXEgPSBleHAoc2VxKC02LCAyLCBsZW5ndGg9MTAwKSkKY3Yub3V0ID0gY3YuZ2xtbmV0KFhbLXRlc3QuaWQsXSwgWVstdGVzdC5pZF0sIGFscGhhPTAsIGxhbWJkYT1sYW0uc2VxKSAgCnBsb3QoY3Yub3V0KQpgYGAKClR3byBjaG9pY2VzIGZvciBsYW1iZGEKCi0gYGxhbWJkYS5taW5gOiB0aGUgdmFsdWUgb2YgbGFtYmRhIHRoYXQgYWNoaWV2ZXMgdGhlIG1pbmltdW0gY3ZtICh0aGUgbGVmdCBkYXNoZWQgdmVydGljYWwgbGluZSkKLSBgbGFtYmRhLjFzZWA6IHRoZSBsYXJnZXN0IHZhbHVlIG9mIGxhbWJkYSAoaS5lLiwgdGhlIGxhcmdlc3QgcmVndWxhcml6YXRpb24sIHRoZSBzbWFsbGVzdCBkZikgd2hvc2UgY3ZtIGlzIHdpdGhpbiBvbmUtc3RhbmRhcmQtZXJyb3Igb2YgdGhlIGN2bSBvZiBsYW1iZGEubWluLiAgKHRoZSBsZWZ0IGRhc2hlZCB2ZXJ0aWNhbCBsaW5lKQoKKipOb3RlKio6IGJ5IGRlZmluaXRpb24gYGxhbWJkYS4xc2VgIChyaWdodCBkYXNoZWQgdmVydGljYWwgbGluZSkgaXMgYWx3YXlzIGJpZ2dlciB0aGFuIG9yIGVxdWFsIHRvIGBsYW1iZGEubWluYCAobGVmdCBkYXNoZWQgdmVydGljYWwgbGluZSkuIAoKCkNoZWNrIGhvdyBgbGFtYmRhLm1pbmAgYW5kIGBsYW1iZGEuMXNlYCBhcmUgY29tcHV0ZWQuCgpgYGB7cn0KbmFtZXMoY3Yub3V0KQoKIyBGaW5kIGxhbWJkYS5taW4KY3Yub3V0JGxhbWJkYS5taW4KY3Yub3V0JGxhbWJkYVt3aGljaC5taW4oY3Yub3V0JGN2bSldCgojIEZpbmQgbGFtYmRhLjFzZQpjdi5vdXQkbGFtYmRhLjFzZQp0bXAuaWQgPSB3aGljaC5taW4oY3Yub3V0JGN2bSkKbWF4KGN2Lm91dCRsYW1iZGFbY3Yub3V0JGN2bSA8IGN2Lm91dCRjdm1bdG1wLmlkXSArIGN2Lm91dCRjdnNkW3RtcC5pZF1dKQpgYGAKCiMjIyBQcmVkaWN0aW9uCgpOZXh0IHdlIGFwcGx5IHRoZSB0d28gcmlkZ2UgcmVncmVzc2lvbiBtb2RlbHMtLS1vbmUgd2l0aCBgbGFtYmRhLm1pbmAgYW5kIG9uZSB3aXRoIGBsYW1iZGEuMXNlYC0tLW9uIGEgbmV3IHRlc3QgZGF0YSBzZXQgYW5kIHJlcG9ydCB0aGUgY29ycmVzcG9uZGluZyBtZWFuIHNxdWFyZWQgcHJlZGljdGlvbiBlcnJvcnMuIAoKYGBge3J9Cm15cmlkZ2UgPSBnbG1uZXQoWFstdGVzdC5pZCxdLCBZWy10ZXN0LmlkXSwgYWxwaGE9MCwgbGFtYmRhID0gbGFtLnNlcSkKWXRlc3QucHJlZCA9IHByZWRpY3QobXlyaWRnZSwgcyA9IGN2Lm91dCRsYW1iZGEuMXNlLCBuZXd4PVhbdGVzdC5pZCxdKQptZWFuKChZdGVzdC5wcmVkIC0gWVt0ZXN0LmlkXSleMikKWXRlc3QucHJlZD1wcmVkaWN0KG15cmlkZ2UsIHMgPSBjdi5vdXQkbGFtYmRhLm1pbiwgbmV3eD1YW3Rlc3QuaWQsXSkKbWVhbigoWXRlc3QucHJlZCAtIFlbdGVzdC5pZF0pXjIpCmBgYAoKCiMjIExhc3NvCgoKIyMjIE91dHB1dApDaGVjayB0aGUgb3V0cHV0IGZyb20gYGdsbW5ldGAgZm9yIExhc3NvLiAKCmBgYHtyfQpteWxhc3NvID0gZ2xtbmV0KFhbLXRlc3QuaWQsXSwgWVstdGVzdC5pZF0sIGFscGhhID0gMSkKc3VtbWFyeShteWxhc3NvKQpgYGAKCgojIyMgQ1YgJiBQYXRoIFBsb3QKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDIpKQpwbG90KG15bGFzc28sIGxhYmVsPVRSVUUsIHh2YXIgPSAibm9ybSIpCnBsb3QobXlsYXNzbywgbGFiZWw9VFJVRSwgeHZhciA9ICJsYW1iZGEiKQpwYXIobWZyb3c9YygxLDEpKQpgYGAKCmBgYHtyfQpteWxhc3NvJGRmCmN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLCBdLCBZWy10ZXN0LmlkXSwgYWxwaGEgPSAxKQpwbG90KGN2Lm91dCkKYGBgCgpUcnkgb3VyIG93biBsYW1iZGEgc2VxdWVuY2VzLgpgYGB7cn0KIyBsYW0uc2VxID0gIGV4cChzZXEoLTQsIDIsIGxlbmd0aD0xMDApKQojIGxhbS5zZXEgPSAgZXhwKHNlcSgtNiwgLTEsIGxlbmd0aD0xMDApKQojIGN2Lm91dCA9IGN2LmdsbW5ldChYWy10ZXN0LmlkLF0sIFlbLXRlc3QuaWRdLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGwgYW0uc2VxKQojIHBsb3QoY3Yub3V0KQpgYGAKCkNoZWNrIGhvdyBgbGFtYmRhLm1pbmAgYW5kIGBsYW1iZGEuMXNlYCBhcmUgY29tcHV0ZWQuCmBgYHtyfQpjdi5vdXQkbGFtYmRhLm1pbgp0bXAuaWQ9d2hpY2gubWluKGN2Lm91dCRjdm0pCmN2Lm91dCRsYW1iZGFbdG1wLmlkXQoKY3Yub3V0JGxhbWJkYS4xc2UKbWF4KGN2Lm91dCRsYW1iZGFbY3Yub3V0JGN2bSA8IGN2Lm91dCRjdm1bdG1wLmlkXSArIGN2Lm91dCRjdnNkW3RtcC5pZF1dKQpgYGAKCiMjIyBDb2VmZmljaWVudHMKCkhvdyB0byByZXRyaWV2ZSBMYXNzbyBjb2VmZmljaWVudHM/CgpgYGB7cn0KbXlsYXNzby5jb2VmLm1pbiA9IHByZWRpY3QobXlsYXNzbywgcz1jdi5vdXQkbGFtYmRhLm1pbiwgdHlwZT0iY29lZmZpY2llbnRzIikKbXlsYXNzby5jb2VmLjFzZSA9IHByZWRpY3QobXlsYXNzbywgcz1jdi5vdXQkbGFtYmRhLjFzZSwgdHlwZT0iY29lZmZpY2llbnRzIikKY2JpbmQobXlsYXNzby5jb2VmLm1pbiwgbXlsYXNzby5jb2VmLjFzZSkKCiMgbnVtYmVyIG9mIHZhcmlhYmxlcyBzZWxlY3RlZCAoaW5jbHVkaW5nIHRoZSBpbnRlcmNlcHQpCnN1bShteWxhc3NvLmNvZWYuMXNlICE9IDApCiMgbmFtZXMgb2Ygc2VsZWN0ZWQgbm9uLWludGVyY2VwdCB2YXJpYWJsZXMKcm93Lm5hbWVzKG15bGFzc28uY29lZi4xc2UpW3doaWNoKG15bGFzc28uY29lZi4xc2UgIT0gMClbLTFdXQpgYGAKCiMjIyBQcmVkaWN0aW9uCgpBcHBseSB0aGUgdHdvIGZpdHRlZCBtb2RlbHMgZm9yIHByZWRpY3Rpb24gb24gdGVzdCBkYXRhLgoKYGBge3J9Cm15bGFzc28gPSBnbG1uZXQoWFstdGVzdC5pZCwgXSwgWVstdGVzdC5pZF0sIGFscGhhID0gMSkKWXRlc3QucHJlZCA9IHByZWRpY3QobXlsYXNzbywgcyA9IGN2Lm91dCRsYW1iZGEubWluLCBuZXd4ID0gWFt0ZXN0LmlkLF0pCm1lYW4oKFl0ZXN0LnByZWQgLSBZW3Rlc3QuaWRdKV4yKQoKWXRlc3QucHJlZCA9IHByZWRpY3QobXlsYXNzbywgcyA9IGN2Lm91dCRsYW1iZGEuMXNlLCBuZXd4ID0gWFt0ZXN0LmlkLF0pCm1lYW4oKFl0ZXN0LnByZWQgLSBZW3Rlc3QuaWRdKV4yKQpgYGAKCiMjIyBSZWZpdCB0byBSZWR1Y2UgQmlhcwoKV2UgcmVmaXQgYW4gb3JkaW5hcnkgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgdGhlIHZhcmlhYmxlcyBzZWxlY3RlZCBieSBgbGFtYmRhLjFzZWAgYW5kIHRoZW4gdXNlIGl0IGZvciBwcmVkaWN0aW9uLgoKYGBge3J9Cm15bGFzc28uY29lZi4xc2UgPSBwcmVkaWN0KG15bGFzc28sIHMgPSBjdi5vdXQkbGFtYmRhLjFzZSwgdHlwZT0iY29lZmZpY2llbnRzIikKdmFyLnNlbCA9IHJvdy5uYW1lcyhteWxhc3NvLmNvZWYuMXNlKVt3aGljaChteWxhc3NvLmNvZWYuMXNlICE9IDApWy0xXV0KdmFyLnNlbDsgCnRtcC5YID0gWFssIGNvbG5hbWVzKFgpICVpbiUgdmFyLnNlbF0KbXlsYXNzby5yZWZpdCA9IGNvZWYobG0oWVstdGVzdC5pZF0gfiB0bXAuWFstdGVzdC5pZCwgXSkpCll0ZXN0LnByZWQgPSBteWxhc3NvLnJlZml0WzFdICsgdG1wLlhbdGVzdC5pZCxdICUqJSBteWxhc3NvLnJlZml0Wy0xXQptZWFuKChZdGVzdC5wcmVkIC0gWVt0ZXN0LmlkXSleMikKYGBgCgoKCg==