Chapter 6 #2, 9, 11
- For parts (a) through (c), indicate which of i. through iv. is
correct. Justify your answer.
- The lasso, relative to least squares, is:
- More flexible and hence will give improved prediction accuracy when
its increase in bias is less than its decrease in variance.
FALSE
- More flexible and hence will give improved prediction accuracy when
its increase in variance is less than its decrease in bias.
FALSE
- Less flexible and hence will give improved prediction accuracy when
its increase in bias is less than its decrease in variance.
TRUE
- Less flexible and hence will give improved prediction accuracy when
its increase in variance is less than its decrease in bias.
FALSE
- Repeat (a) for ridge regression relative to least squares.
FALSE
FALSE
TRUE
FALSE
- Repeat (a) for non-linear methods relative to least squares.
FALSE
TRUE
FALSE
FALSE
- In this exercise, we will predict the number of applications
received using the other variables in the College data set.
- Split the data set into a training set and a test set.
install.packages("pacman")
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.5/pacman_0.5.1.zip'
Content type 'application/zip' length 395200 bytes (385 KB)
downloaded 385 KB
package ‘pacman’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\aliso\AppData\Local\Temp\RtmpiauuDM\downloaded_packages
library("pacman")
p_load(ggplot2, dplyr, tidyr, tidyverse, ISLR, ISLR2, tibble, readr, purrr,stringr, forcats, lubridate, glmnet, leaps, caret, klaR, pls, mlbench)
# load the College data set and inspect
data("College")
str(College)
'data.frame': 777 obs. of 18 variables:
$ Private : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...
$ Apps : num 1660 2186 1428 417 193 ...
$ Accept : num 1232 1924 1097 349 146 ...
$ Enroll : num 721 512 336 137 55 158 103 489 227 172 ...
$ Top10perc : num 23 16 22 60 16 38 17 37 30 21 ...
$ Top25perc : num 52 29 50 89 44 62 45 68 63 44 ...
$ F.Undergrad: num 2885 2683 1036 510 249 ...
$ P.Undergrad: num 537 1227 99 63 869 ...
$ Outstate : num 7440 12280 11250 12960 7560 ...
$ Room.Board : num 3300 6450 3750 5450 4120 ...
$ Books : num 450 750 400 450 800 500 500 450 300 660 ...
$ Personal : num 2200 1500 1165 875 1500 ...
$ PhD : num 70 29 53 92 76 67 90 89 79 40 ...
$ Terminal : num 78 30 66 97 72 73 93 100 84 41 ...
$ S.F.Ratio : num 18.1 12.2 12.9 7.7 11.9 9.4 11.5 13.7 11.3 11.5 ...
$ perc.alumni: num 12 16 30 37 2 11 26 37 23 15 ...
$ Expend : num 7041 10527 8735 19016 10922 ...
$ Grad.Rate : num 60 56 54 59 15 55 63 73 80 52 ...
# define an 80%/20% train/test split of the data set
set.seed(997)
split=0.80
trainIndex <- createDataPartition(College$Apps, p=split, list=FALSE)
data_train <- College[trainIndex, ]
data_test <- College[-trainIndex, ]
- Fit a linear model using least squares on the training set, and
report the test error obtained.
# train a linear model using least squares
College_lm_model <- lm(Apps ~., data = data_train)
summary(College_lm_model)
Call:
lm(formula = Apps ~ ., data = data_train)
Residuals:
Min 1Q Median 3Q Max
-3609.2 -420.8 -51.4 273.8 7426.4
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -155.38283 418.34753 -0.371 0.71045
PrivateYes -704.24008 146.11853 -4.820 1.82e-06 ***
Accept 1.35999 0.05214 26.081 < 2e-16 ***
Enroll -0.44770 0.19118 -2.342 0.01951 *
Top10perc 41.90177 5.72564 7.318 8.01e-13 ***
Top25perc -10.28007 4.55448 -2.257 0.02436 *
F.Undergrad 0.06222 0.03244 1.918 0.05559 .
P.Undergrad 0.03826 0.03130 1.222 0.22203
Outstate -0.04971 0.02066 -2.406 0.01641 *
Room.Board 0.13239 0.05128 2.581 0.01007 *
Books 0.11964 0.26386 0.453 0.65042
Personal -0.03928 0.06510 -0.603 0.54649
PhD -6.79702 4.74604 -1.432 0.15262
Terminal -3.94673 5.22479 -0.755 0.45031
S.F.Ratio 2.54414 13.73904 0.185 0.85315
perc.alumni -6.17206 4.35038 -1.419 0.15649
Expend 0.06692 0.01353 4.947 9.78e-07 ***
Grad.Rate 9.50104 3.03593 3.130 0.00183 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 970.3 on 606 degrees of freedom
Multiple R-squared: 0.9284, Adjusted R-squared: 0.9264
F-statistic: 462 on 17 and 606 DF, p-value: < 2.2e-16
College_appPredict <- predict(College_lm_model, newdata = data_test)
MSE_lm <- mean((data_test$Apps - College_appPredict )^2)
print(sprintf("Linear model test MSE= %10.3f", MSE_lm))
[1] "Linear model test MSE= 1954809.607"
- Fit a ridge regression model on the training set, with λ chosen by
cross-validation. Report the test error obtained.
Xtrain <- model.matrix(Apps~., data = data_train)[,-1]
Ytrain <- data_train$Apps
Xtest <- model.matrix(Apps~., data = data_test)[,-1]
Ytest <- data_test$Apps
ridge_cv <- cv.glmnet(Xtrain, Ytrain, alpha = 0)
ridge_best_lambda <- ridge_cv$lambda.min
ridge_preds <- predict(ridge_cv, s = ridge_best_lambda, newx = Xtest)
mse_ridge <- mean((Ytest - ridge_preds)^2)
mse_ridge
[1] 3756604
- Fit a lasso model on the training set, with λ chosen by
crossvalidation. Report the test error obtained, along with the number
of non-zero coefficient estimates.
set.seed(997)
cv_lasso <- cv.glmnet(Xtrain, Ytrain, alpha = 1)
best_lasso <- cv_lasso$lambda.min
lasso.pred <- predict(cv_lasso, s = best_lasso, newx = Xtest)
mean((Ytest - lasso.pred)^2)
[1] 2155835
lasso_coef <- predict(cv_lasso, s = best_lasso, type = "coefficients")
sum(lasso_coef != 0)
[1] 15
- Fit a PCR model on the training set, with M chosen by
crossvalidation. Report the test error obtained, along with the value of
M selected by cross-validation.
set.seed(997)
pcr.fit = pcr(Apps~., data = data_train, scale = TRUE, validation = "CV")
summary(pcr.fit)
Data: X dimension: 624 17
Y dimension: 624 1
Fit method: svdpc
Number of components considered: 17
VALIDATION: RMSEP
Cross-validated using 10 random segments.
(Intercept) 1 comps 2 comps 3 comps 4 comps 5 comps 6 comps 7 comps
CV 3578 3548 1727 1712 1535 1370 1288 1258
adjCV 3578 3550 1724 1712 1530 1354 1284 1253
8 comps 9 comps 10 comps 11 comps 12 comps 13 comps 14 comps 15 comps
CV 1234 1229 1228 1229 1224 1224 1228 1229
adjCV 1228 1226 1225 1226 1221 1221 1225 1227
16 comps 17 comps
CV 1033 1018
adjCV 1030 1015
TRAINING: % variance explained
1 comps 2 comps 3 comps 4 comps 5 comps 6 comps 7 comps 8 comps 9 comps
X 31.990 57.73 64.67 70.36 75.80 80.76 84.37 87.78 90.76
Apps 2.361 77.49 77.94 82.81 86.49 87.88 88.61 88.95 89.21
10 comps 11 comps 12 comps 13 comps 14 comps 15 comps 16 comps 17 comps
X 93.11 95.12 96.89 97.95 98.87 99.42 99.83 100.00
Apps 89.22 89.24 89.34 89.37 89.37 89.54 92.46 92.84
validationplot(pcr.fit,val.type="MSEP")

NA
NA
pcr.pred <- predict(pcr.fit, newdata = data_test, ncomp = 17)
mean((data_test$Apps - pcr.pred)^2)
[1] 1954810
- Fit a PLS model on the training set, with M chosen by
crossvalidation. Report the test error obtained, along with the value of
M selected by cross-validation.
set.seed(997)
pls.fit=plsr(Apps~., data=data_train, scale=TRUE,validation ="CV")
summary (pls.fit)
Data: X dimension: 624 17
Y dimension: 624 1
Fit method: kernelpls
Number of components considered: 17
VALIDATION: RMSEP
Cross-validated using 10 random segments.
(Intercept) 1 comps 2 comps 3 comps 4 comps 5 comps 6 comps 7 comps
CV 3578 1557 1289 1184 1149 1096 1034 1023
adjCV 3578 1554 1292 1182 1144 1090 1030 1020
8 comps 9 comps 10 comps 11 comps 12 comps 13 comps 14 comps 15 comps
CV 1021 1020 1018 1017 1018 1018 1018 1018
adjCV 1018 1017 1015 1014 1015 1015 1015 1015
16 comps 17 comps
CV 1018 1018
adjCV 1015 1015
TRAINING: % variance explained
1 comps 2 comps 3 comps 4 comps 5 comps 6 comps 7 comps 8 comps 9 comps
X 25.90 41.71 63.11 66.9 70.57 74.11 77.98 81.08 83.95
Apps 81.83 87.62 89.84 90.9 92.02 92.63 92.72 92.76 92.78
10 comps 11 comps 12 comps 13 comps 14 comps 15 comps 16 comps 17 comps
X 86.84 89.67 91.10 93.57 95.34 96.96 98.98 100.00
Apps 92.81 92.83 92.83 92.84 92.84 92.84 92.84 92.84
validationplot(pls.fit,val.type="MSEP")

NA
NA
pls.pred=predict(pls.fit, newdata = data_test, ncomp =17)
mean((data_test$Apps - pls.pred)^2)
[1] 1954810
- Comment on the results obtained. How accurately can we predict the
number of college applications received? Is there much difference among
the test errors resulting from these five approaches?
#Least Square model
test.avg <- mean(data_test$Apps)
lm.r2 <- 1 - mean((College_appPredict - data_test$Apps)^2) / mean((test.avg - data_test$Apps)^2)
#Ridge model
ridge.r2 <- 1 - mean((ridge_preds - data_test$Apps)^2) / mean((test.avg - data_test$Apps)^2)
#Lasso model
lasso.r2 <- 1 - mean((lasso.pred - data_test$Apps)^2) / mean((test.avg - data_test$Apps)^2)
#PCR model
pcr.r2 <- 1 - mean((pcr.pred - data_test$Apps)^2) / mean((test.avg - data_test$Apps)^2)
#PLS model
pls.r2 <- 1 - mean((pls.pred - data_test$Apps)^2) / mean((test.avg - data_test$Apps)^2)
print(lm.r2)
[1] 0.9182287
print(ridge.r2)
[1] 0.8428582
print(lasso.r2)
[1] 0.9098196
print(pcr.r2)
[1] 0.9182287
print(pls.r2)
[1] 0.9182287
Least Squares, PCR, and PLS all have the same R^2 value. So they are
equal predictability.
- We will now try to predict per capita crime rate in the Boston data
set.
- Try out some of the regression methods explored in this chapter,
such as best subset selection, the lasso, ridge regression, and PCR.
Present and discuss results for the approaches that you consider.
library(pls)
data(Boston)
str(Boston)
attach(Boston)
set.seed(997)
pcr.fit1 = pcr(crim~., data = Boston, scale = TRUE, validation = "CV")
summary(pcr.fit1)
attach(Boston)
set.seed(997)
train <- sample(c(TRUE, FALSE), nrow(Boston), rep = TRUE)
test <- !train
pcr.fit = pcr(crim~., data = Boston[train,], scale = TRUE, validation = "CV")
summary(pcr.fit)
validationplot(pcr.fit, val.type = "MSEP")
set.seed(997)
pcr.fit=pcr(crim~., data=Boston, subset=train, scale=TRUE, validation ="CV")
validationplot(pcr.fit, val.type="MSEP")
cverr <- RMSEP(pcr.fit)$val[1,,-1]
imin <- which.min(cverr) - 1
imin
4 comps
3
set.seed(997)
pcr.pred = predict(pcr.fit, newdata = Boston[test, ], ncomp = 4)
mean((pcr.pred - Boston$crim[test])^2)
[1] 35.15133
best_ncomp <- which.min(pcr.fit$validation$PRESS)
best_ncomp
[1] 4
x_train <- model.matrix(crim ~ ., Boston[train, ])[, -1]
x_test <- model.matrix(crim ~ ., Boston[test, ])[, -1]
y_train <- Boston$crim[train]
y_test <- Boston$crim[test]
cv_ridge <- cv.glmnet(x_train, y_train, alpha = 0)
best_ridge <- cv_ridge$lambda.min
ridge.pred <- predict(cv_ridge, s = best_ridge, newx = x_test)
mean((y_test - ridge.pred)^2)
[1] 34.71123
plot(cv_ridge)
title("Ridge Regression: Cross-Validation Curve", line = 2.5)
set.seed(997)
cv_lasso <- cv.glmnet(x_train, y_train, alpha = 1)
best_lasso <- cv_lasso$lambda.min
lasso.pred <- predict(cv_lasso, s = best_lasso, newx = x_test)
mean((y_test - lasso.pred)^2)
[1] 34.97991
reg.fit.best <- regsubsets(crim ~ ., data = Boston[train, ], nvmax = 13)
test_matrix <- model.matrix(crim ~ ., data = Boston[test, ])
nv <- nrow(summary(reg.fit.best)$which)
val_errors <- rep(NA, nv)
for (i in 1:nv) {
coeficient <- coef(reg.fit.best, id = i)
pred <- test_matrix[, names(coeficient)] %*% coeficient
val_errors[i] <- mean((Boston$crim[test] - pred)^2)
}
min(val_errors)
[1] 35.26505
fitControl <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 3,
verboseIter = TRUE # Show training progress
)
ridgeGrid <- expand.grid(alpha = 0,
lambda = 10^seq(-3, 1, length = 100)) # A range of lambda values
cat("\n--- Training Ridge Regression with caret ---\n")
--- Training Ridge Regression with caret ---
ridge_caret <- train(
crim ~ ., # Formula interface: response ~ all predictors
data = Boston, # Our data frame
method = "glmnet", # Specify the glmnet model
tuneGrid = ridgeGrid, # Our custom tuning grid for alpha and lambda
trControl = fitControl, # Our defined cross-validation control
preProcess = c("center", "scale") # Center and scale predictors (important for regularization)
)
print(ridge_caret)
# Plot the tuning results (RMSE vs lambda) using ggplot2 directly
ridge_plot_data <- ridge_caret$results
print(ggplot(ridge_plot_data, aes(x = lambda, y = RMSE)) +
geom_line() +
geom_point() +
# Add error bars if RMSESD is available in the results
{if("RMSESD" %in% names(ridge_plot_data)) geom_errorbar(aes(ymin = RMSE - RMSESD, ymax = RMSE + RMSESD), width = 0.01)} +
ggplot2::labs(title = "Ridge Regression Tuning (caret)",
x = "Lambda",
y = "RMSE") +
theme_minimal())
cat("Optimal lambda for Ridge (caret):", ridge_caret$bestTune$lambda, "\n")
Optimal lambda for Ridge (caret): 0.4641589
# --- Lasso Regression with caret ---
# For Lasso, we specify a tuneGrid where alpha is fixed at 1.
# caret will then search for the best lambda.
lassoGrid <- expand.grid(alpha = 1,
lambda = 10^seq(-3, 1, length = 100)) # A range of lambda values
cat("\n--- Training Lasso Regression with caret ---\n")
--- Training Lasso Regression with caret ---
lasso_caret <- train(
crim ~ .,
data = Boston,
method = "glmnet",
tuneGrid = lassoGrid,
trControl = fitControl,
preProcess = c("center", "scale")
)
# Print the model results
print(lasso_caret)
# Get the best lambda found by caret
cat("Optimal lambda for Lasso (caret):", lasso_caret$bestTune$lambda, "\n")
Optimal lambda for Lasso (caret): 0.0869749
# Get the coefficients from the best model
coef(lasso_caret$finalModel, s = lasso_caret$bestTune$lambda)
reg.fit.best <- regsubsets(crim ~ ., data = Boston[train, ], nvmax = 13)
test_matrix <- model.matrix(crim ~ ., data = Boston[test, ])
nv <- nrow(summary(reg.fit.best)$which)
val_errors <- rep(NA, nv)
for (i in 1:nv) {
coeficient <- coef(reg.fit.best, id = i)
pred <- test_matrix[, names(coeficient)] %*% coeficient
val_errors[i] <- mean((Boston$crim[test] - pred)^2)
}
min(val_errors)
[1] 35.26505
which.min(val_errors)
[1] 4
plot(val_errors, type = "b", xlab = "Number of Variables", ylab = "Validation MSE")
- Propose a model (or set of models) that seem to perform well on this
data set, and justify your answer. Make sure that you are evaluating
model performance using validation set error, crossvalidation, or some
other reasonable alternative, as opposed to using training error.
Ridge regression had the lowest MSE of 34.71 using cross
validation.
- Does your chosen model involve all of the features in the data set?
Why or why not?
The chosen model does use all features to evaluate the model.
LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgNSBTVEEgNjU0MyBVVFNBIFNNTVIgMjAyNSINCmF1dGhvcjogIkFDIEJhbmQiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KLS0tDQoNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCkNoYXB0ZXIgNiAjMiwgOSwgMTENCg0KMi4gRm9yIHBhcnRzIChhKSB0aHJvdWdoIChjKSwgaW5kaWNhdGUgd2hpY2ggb2YgaS4gdGhyb3VnaCBpdi4gaXMgY29ycmVjdC4NCkp1c3RpZnkgeW91ciBhbnN3ZXIuDQooYSkgVGhlIGxhc3NvLCByZWxhdGl2ZSB0byBsZWFzdCBzcXVhcmVzLCBpczoNCg0KaS4gTW9yZSBmbGV4aWJsZSBhbmQgaGVuY2Ugd2lsbCBnaXZlIGltcHJvdmVkIHByZWRpY3Rpb24gYWNjdXJhY3kNCndoZW4gaXRzIGluY3JlYXNlIGluIGJpYXMgaXMgbGVzcyB0aGFuIGl0cyBkZWNyZWFzZSBpbg0KdmFyaWFuY2UuDQoNCipGQUxTRSoNCg0KaWkuIE1vcmUgZmxleGlibGUgYW5kIGhlbmNlIHdpbGwgZ2l2ZSBpbXByb3ZlZCBwcmVkaWN0aW9uIGFjY3VyYWN5DQp3aGVuIGl0cyBpbmNyZWFzZSBpbiB2YXJpYW5jZSBpcyBsZXNzIHRoYW4gaXRzIGRlY3JlYXNlDQppbiBiaWFzLg0KDQoqRkFMU0UqDQoNCmlpaS4gTGVzcyBmbGV4aWJsZSBhbmQgaGVuY2Ugd2lsbCBnaXZlIGltcHJvdmVkIHByZWRpY3Rpb24gYWNjdXJhY3kNCndoZW4gaXRzIGluY3JlYXNlIGluIGJpYXMgaXMgbGVzcyB0aGFuIGl0cyBkZWNyZWFzZSBpbg0KdmFyaWFuY2UuDQoNCipUUlVFKg0KDQppdi4gTGVzcyBmbGV4aWJsZSBhbmQgaGVuY2Ugd2lsbCBnaXZlIGltcHJvdmVkIHByZWRpY3Rpb24gYWNjdXJhY3kNCndoZW4gaXRzIGluY3JlYXNlIGluIHZhcmlhbmNlIGlzIGxlc3MgdGhhbiBpdHMgZGVjcmVhc2UNCmluIGJpYXMuDQoNCipGQUxTRSoNCg0KKGIpIFJlcGVhdCAoYSkgZm9yIHJpZGdlIHJlZ3Jlc3Npb24gcmVsYXRpdmUgdG8gbGVhc3Qgc3F1YXJlcy4NCg0KKkZBTFNFKg0KDQoqRkFMU0UqDQoNCipUUlVFKg0KDQoqRkFMU0UqDQoNCg0KDQooYykgUmVwZWF0IChhKSBmb3Igbm9uLWxpbmVhciBtZXRob2RzIHJlbGF0aXZlIHRvIGxlYXN0IHNxdWFyZXMuDQoNCipGQUxTRSoNCg0KKlRSVUUqDQoNCipGQUxTRSoNCg0KKkZBTFNFKg0KDQoNCg0KDQoNCjkuIEluIHRoaXMgZXhlcmNpc2UsIHdlIHdpbGwgcHJlZGljdCB0aGUgbnVtYmVyIG9mIGFwcGxpY2F0aW9ucyByZWNlaXZlZA0KdXNpbmcgdGhlIG90aGVyIHZhcmlhYmxlcyBpbiB0aGUgQ29sbGVnZSBkYXRhIHNldC4NCihhKSBTcGxpdCB0aGUgZGF0YSBzZXQgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4NCg0KYGBge3J9DQoNCmluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpsaWJyYXJ5KCJwYWNtYW4iKQ0KDQpgYGANCg0KYGBge3J9DQoNCnBfbG9hZChnZ3Bsb3QyLCBkcGx5ciwgdGlkeXIsIHRpZHl2ZXJzZSwgSVNMUiwgSVNMUjIsIHRpYmJsZSwgcmVhZHIsIHB1cnJyLHN0cmluZ3IsIGZvcmNhdHMsIGx1YnJpZGF0ZSwgZ2xtbmV0LCBsZWFwcywgY2FyZXQsIGtsYVIsIHBscywgbWxiZW5jaCkNCg0KYGBgDQoNCg0KYGBge3J9DQoNCiMgbG9hZCB0aGUgQ29sbGVnZSBkYXRhIHNldCBhbmQgaW5zcGVjdA0KDQpkYXRhKCJDb2xsZWdlIikNCg0Kc3RyKENvbGxlZ2UpDQoNCmBgYA0KYGBge3J9DQojIGRlZmluZSBhbiA4MCUvMjAlIHRyYWluL3Rlc3Qgc3BsaXQgb2YgdGhlIGRhdGEgc2V0DQoNCnNldC5zZWVkKDk5NykNCg0Kc3BsaXQ9MC44MA0KDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oQ29sbGVnZSRBcHBzLCBwPXNwbGl0LCBsaXN0PUZBTFNFKQ0KDQpkYXRhX3RyYWluIDwtIENvbGxlZ2VbdHJhaW5JbmRleCwgXQ0KDQpkYXRhX3Rlc3QgPC0gQ29sbGVnZVstdHJhaW5JbmRleCwgXQ0KDQpgYGANCg0KDQoNCg0KKGIpIEZpdCBhIGxpbmVhciBtb2RlbCB1c2luZyBsZWFzdCBzcXVhcmVzIG9uIHRoZSB0cmFpbmluZyBzZXQsIGFuZA0KcmVwb3J0IHRoZSB0ZXN0IGVycm9yIG9idGFpbmVkLg0KDQpgYGB7cn0NCiMgdHJhaW4gYSBsaW5lYXIgbW9kZWwgdXNpbmcgbGVhc3Qgc3F1YXJlcw0KDQpDb2xsZWdlX2xtX21vZGVsIDwtIGxtKEFwcHMgfi4sIGRhdGEgPSBkYXRhX3RyYWluKQ0KDQpzdW1tYXJ5KENvbGxlZ2VfbG1fbW9kZWwpDQoNCmBgYA0KDQpgYGB7cn0NCg0KQ29sbGVnZV9hcHBQcmVkaWN0IDwtIHByZWRpY3QoQ29sbGVnZV9sbV9tb2RlbCwgbmV3ZGF0YSA9IGRhdGFfdGVzdCkNCg0KTVNFX2xtIDwtIG1lYW4oKGRhdGFfdGVzdCRBcHBzIC0gQ29sbGVnZV9hcHBQcmVkaWN0ICleMikNCg0KcHJpbnQoc3ByaW50ZigiTGluZWFyIG1vZGVsIHRlc3QgTVNFPSAlMTAuM2YiLCBNU0VfbG0pKQ0KDQpgYGANCg0KKGMpIEZpdCBhIHJpZGdlIHJlZ3Jlc3Npb24gbW9kZWwgb24gdGhlIHRyYWluaW5nIHNldCwgd2l0aCDOuyBjaG9zZW4NCmJ5IGNyb3NzLXZhbGlkYXRpb24uIFJlcG9ydCB0aGUgdGVzdCBlcnJvciBvYnRhaW5lZC4NCg0KYGBge3J9DQoNClh0cmFpbiA8LSBtb2RlbC5tYXRyaXgoQXBwc34uLCBkYXRhID0gZGF0YV90cmFpbilbLC0xXQ0KDQpZdHJhaW4gPC0gZGF0YV90cmFpbiRBcHBzDQoNClh0ZXN0IDwtIG1vZGVsLm1hdHJpeChBcHBzfi4sIGRhdGEgPSBkYXRhX3Rlc3QpWywtMV0NCg0KWXRlc3QgPC0gZGF0YV90ZXN0JEFwcHMNCg0KDQpyaWRnZV9jdiA8LSBjdi5nbG1uZXQoWHRyYWluLCBZdHJhaW4sIGFscGhhID0gMCkNCg0KcmlkZ2VfYmVzdF9sYW1iZGEgPC0gcmlkZ2VfY3YkbGFtYmRhLm1pbg0KDQpyaWRnZV9wcmVkcyA8LSBwcmVkaWN0KHJpZGdlX2N2LCBzID0gcmlkZ2VfYmVzdF9sYW1iZGEsIG5ld3ggPSBYdGVzdCkNCg0KbXNlX3JpZGdlIDwtIG1lYW4oKFl0ZXN0IC0gcmlkZ2VfcHJlZHMpXjIpDQoNCm1zZV9yaWRnZQ0KDQpgYGANCg0KDQooZCkgRml0IGEgbGFzc28gbW9kZWwgb24gdGhlIHRyYWluaW5nIHNldCwgd2l0aCDOuyBjaG9zZW4gYnkgY3Jvc3N2YWxpZGF0aW9uLg0KUmVwb3J0IHRoZSB0ZXN0IGVycm9yIG9idGFpbmVkLCBhbG9uZyB3aXRoIHRoZSBudW1iZXINCm9mIG5vbi16ZXJvIGNvZWZmaWNpZW50IGVzdGltYXRlcy4NCg0KYGBge3J9DQoNCnNldC5zZWVkKDk5NykNCg0KY3ZfbGFzc28gPC0gY3YuZ2xtbmV0KFh0cmFpbiwgWXRyYWluLCBhbHBoYSA9IDEpDQpiZXN0X2xhc3NvIDwtIGN2X2xhc3NvJGxhbWJkYS5taW4NCg0KDQpsYXNzby5wcmVkIDwtIHByZWRpY3QoY3ZfbGFzc28sIHMgPSBiZXN0X2xhc3NvLCBuZXd4ID0gWHRlc3QpDQptZWFuKChZdGVzdCAtIGxhc3NvLnByZWQpXjIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQpsYXNzb19jb2VmIDwtIHByZWRpY3QoY3ZfbGFzc28sIHMgPSBiZXN0X2xhc3NvLCB0eXBlID0gImNvZWZmaWNpZW50cyIpDQpzdW0obGFzc29fY29lZiAhPSAwKQ0KDQoNCmBgYA0KDQooZSkgRml0IGEgUENSIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQsIHdpdGggTSBjaG9zZW4gYnkgY3Jvc3N2YWxpZGF0aW9uLg0KUmVwb3J0IHRoZSB0ZXN0IGVycm9yIG9idGFpbmVkLCBhbG9uZyB3aXRoIHRoZSB2YWx1ZQ0Kb2YgTSBzZWxlY3RlZCBieSBjcm9zcy12YWxpZGF0aW9uLg0KDQpgYGB7cn0NCg0Kc2V0LnNlZWQoOTk3KQ0KDQpwY3IuZml0ID0gcGNyKEFwcHN+LiwgZGF0YSA9IGRhdGFfdHJhaW4sIHNjYWxlID0gVFJVRSwgdmFsaWRhdGlvbiA9ICJDViIpDQoNCnN1bW1hcnkocGNyLmZpdCkNCg0KDQoNCmBgYA0KDQpgYGB7cn0NCg0KdmFsaWRhdGlvbnBsb3QocGNyLmZpdCx2YWwudHlwZT0iTVNFUCIpDQoNCg0KYGBgDQpgYGB7cn0NCg0KcGNyLnByZWQgPC0gcHJlZGljdChwY3IuZml0LCBuZXdkYXRhID0gZGF0YV90ZXN0LCBuY29tcCA9IDE3KQ0KDQptZWFuKChkYXRhX3Rlc3QkQXBwcyAtIHBjci5wcmVkKV4yKQ0KDQoNCmBgYA0KDQoNCihmKSBGaXQgYSBQTFMgbW9kZWwgb24gdGhlIHRyYWluaW5nIHNldCwgd2l0aCBNIGNob3NlbiBieSBjcm9zc3ZhbGlkYXRpb24uDQpSZXBvcnQgdGhlIHRlc3QgZXJyb3Igb2J0YWluZWQsIGFsb25nIHdpdGggdGhlIHZhbHVlDQpvZiBNIHNlbGVjdGVkIGJ5IGNyb3NzLXZhbGlkYXRpb24uDQoNCmBgYHtyfQ0KDQpzZXQuc2VlZCg5OTcpDQoNCnBscy5maXQ9cGxzcihBcHBzfi4sIGRhdGE9ZGF0YV90cmFpbiwgc2NhbGU9VFJVRSx2YWxpZGF0aW9uID0iQ1YiKQ0KDQpzdW1tYXJ5IChwbHMuZml0KQ0KDQoNCmBgYA0KYGBge3J9DQoNCnZhbGlkYXRpb25wbG90KHBscy5maXQsdmFsLnR5cGU9Ik1TRVAiKQ0KDQoNCmBgYA0KDQoNCg0KYGBge3J9DQoNCnBscy5wcmVkPXByZWRpY3QocGxzLmZpdCwgbmV3ZGF0YSA9IGRhdGFfdGVzdCwgbmNvbXAgPTE3KQ0KDQptZWFuKChkYXRhX3Rlc3QkQXBwcyAtIHBscy5wcmVkKV4yKQ0KDQpgYGANCg0KKGcpIENvbW1lbnQgb24gdGhlIHJlc3VsdHMgb2J0YWluZWQuIEhvdyBhY2N1cmF0ZWx5IGNhbiB3ZSBwcmVkaWN0DQp0aGUgbnVtYmVyIG9mIGNvbGxlZ2UgYXBwbGljYXRpb25zIHJlY2VpdmVkPyBJcyB0aGVyZSBtdWNoDQpkaWZmZXJlbmNlIGFtb25nIHRoZSB0ZXN0IGVycm9ycyByZXN1bHRpbmcgZnJvbSB0aGVzZSBmaXZlIGFwcHJvYWNoZXM/DQoNCmBgYHtyfQ0KDQojTGVhc3QgU3F1YXJlIG1vZGVsDQoNCnRlc3QuYXZnIDwtIG1lYW4oZGF0YV90ZXN0JEFwcHMpDQoNCmxtLnIyIDwtIDEgLSBtZWFuKChDb2xsZWdlX2FwcFByZWRpY3QgLSBkYXRhX3Rlc3QkQXBwcyleMikgLyBtZWFuKCh0ZXN0LmF2ZyAtIGRhdGFfdGVzdCRBcHBzKV4yKQ0KDQojUmlkZ2UgbW9kZWwNCnJpZGdlLnIyIDwtIDEgLSBtZWFuKChyaWRnZV9wcmVkcyAtIGRhdGFfdGVzdCRBcHBzKV4yKSAvIG1lYW4oKHRlc3QuYXZnIC0gZGF0YV90ZXN0JEFwcHMpXjIpDQoNCiNMYXNzbyBtb2RlbA0KbGFzc28ucjIgPC0gMSAtIG1lYW4oKGxhc3NvLnByZWQgLSBkYXRhX3Rlc3QkQXBwcyleMikgLyBtZWFuKCh0ZXN0LmF2ZyAtIGRhdGFfdGVzdCRBcHBzKV4yKQ0KDQojUENSIG1vZGVsDQpwY3IucjIgPC0gMSAtIG1lYW4oKHBjci5wcmVkIC0gZGF0YV90ZXN0JEFwcHMpXjIpIC8gbWVhbigodGVzdC5hdmcgLSBkYXRhX3Rlc3QkQXBwcyleMikNCg0KI1BMUyBtb2RlbA0KcGxzLnIyIDwtIDEgLSBtZWFuKChwbHMucHJlZCAtIGRhdGFfdGVzdCRBcHBzKV4yKSAvIG1lYW4oKHRlc3QuYXZnIC0gZGF0YV90ZXN0JEFwcHMpXjIpDQoNCg0KcHJpbnQobG0ucjIpDQpwcmludChyaWRnZS5yMikNCnByaW50KGxhc3NvLnIyKQ0KcHJpbnQocGNyLnIyKQ0KcHJpbnQocGxzLnIyKQ0KDQpgYGANCkxlYXN0IFNxdWFyZXMsIFBDUiwgYW5kIFBMUyBhbGwgaGF2ZSB0aGUgc2FtZSBSXjIgdmFsdWUuIFNvIHRoZXkgYXJlIGVxdWFsIHByZWRpY3RhYmlsaXR5Lg0KDQoxMS4gV2Ugd2lsbCBub3cgdHJ5IHRvIHByZWRpY3QgcGVyIGNhcGl0YSBjcmltZSByYXRlIGluIHRoZSBCb3N0b24gZGF0YQ0Kc2V0Lg0KKGEpIFRyeSBvdXQgc29tZSBvZiB0aGUgcmVncmVzc2lvbiBtZXRob2RzIGV4cGxvcmVkIGluIHRoaXMgY2hhcHRlciwNCnN1Y2ggYXMgYmVzdCBzdWJzZXQgc2VsZWN0aW9uLCB0aGUgbGFzc28sIHJpZGdlIHJlZ3Jlc3Npb24sIGFuZA0KUENSLiBQcmVzZW50IGFuZCBkaXNjdXNzIHJlc3VsdHMgZm9yIHRoZSBhcHByb2FjaGVzIHRoYXQgeW91DQpjb25zaWRlci4NCg0KYGBge3J9DQoNCmxpYnJhcnkocGxzKQ0KDQpkYXRhKEJvc3RvbikNCg0Kc3RyKEJvc3RvbikNCg0KDQpgYGANCmBgYHtyfQ0KDQphdHRhY2goQm9zdG9uKQ0KDQpzZXQuc2VlZCg5OTcpDQoNCnBjci5maXQxID0gcGNyKGNyaW1+LiwgZGF0YSA9IEJvc3Rvbiwgc2NhbGUgPSBUUlVFLCB2YWxpZGF0aW9uID0gIkNWIikNCg0Kc3VtbWFyeShwY3IuZml0MSkNCg0KYGBgDQoNCg0KDQoNCg0KYGBge3J9DQoNCmF0dGFjaChCb3N0b24pDQoNCnNldC5zZWVkKDk5NykNCg0KdHJhaW4gPC0gc2FtcGxlKGMoVFJVRSwgRkFMU0UpLCBucm93KEJvc3RvbiksIHJlcCA9IFRSVUUpDQoNCnRlc3QgPC0gIXRyYWluDQoNCnBjci5maXQgPSBwY3IoY3JpbX4uLCBkYXRhID0gQm9zdG9uW3RyYWluLF0sIHNjYWxlID0gVFJVRSwgdmFsaWRhdGlvbiA9ICJDViIpDQoNCnN1bW1hcnkocGNyLmZpdCkNCg0KYGBgDQoNCmBgYHtyfQ0KDQp2YWxpZGF0aW9ucGxvdChwY3IuZml0LCB2YWwudHlwZSA9ICJNU0VQIikNCg0KDQpgYGANCg0KYGBge3J9DQoNCnNldC5zZWVkKDk5NykNCg0KcGNyLmZpdD1wY3IoY3JpbX4uLCBkYXRhPUJvc3Rvbiwgc3Vic2V0PXRyYWluLCBzY2FsZT1UUlVFLCB2YWxpZGF0aW9uID0iQ1YiKQ0KDQp2YWxpZGF0aW9ucGxvdChwY3IuZml0LCB2YWwudHlwZT0iTVNFUCIpDQoNCg0KYGBgDQoNCg0KDQoNCg0KYGBge3J9DQoNCmN2ZXJyIDwtIFJNU0VQKHBjci5maXQpJHZhbFsxLCwtMV0NCg0KaW1pbiA8LSB3aGljaC5taW4oY3ZlcnIpIC0gMQ0KDQppbWluDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpzZXQuc2VlZCg5OTcpDQoNCnBjci5wcmVkID0gcHJlZGljdChwY3IuZml0LCBuZXdkYXRhID0gQm9zdG9uW3Rlc3QsIF0sIG5jb21wID0gNCkNCg0KbWVhbigocGNyLnByZWQgLSBCb3N0b24kY3JpbVt0ZXN0XSleMikNCg0KDQpgYGANCmBgYHtyfQ0KDQpiZXN0X25jb21wIDwtIHdoaWNoLm1pbihwY3IuZml0JHZhbGlkYXRpb24kUFJFU1MpDQoNCmJlc3RfbmNvbXANCg0KYGBgDQpgYGB7cn0NCg0KeF90cmFpbiA8LSBtb2RlbC5tYXRyaXgoY3JpbSB+IC4sIEJvc3Rvblt0cmFpbiwgXSlbLCAtMV0NCg0KeF90ZXN0ICA8LSBtb2RlbC5tYXRyaXgoY3JpbSB+IC4sIEJvc3Rvblt0ZXN0LCBdKVssIC0xXQ0KDQp5X3RyYWluIDwtIEJvc3RvbiRjcmltW3RyYWluXQ0KDQp5X3Rlc3QgIDwtIEJvc3RvbiRjcmltW3Rlc3RdDQoNCg0KY3ZfcmlkZ2UgPC0gY3YuZ2xtbmV0KHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMCkNCg0KYmVzdF9yaWRnZSA8LSBjdl9yaWRnZSRsYW1iZGEubWluDQoNCg0KcmlkZ2UucHJlZCA8LSBwcmVkaWN0KGN2X3JpZGdlLCBzID0gYmVzdF9yaWRnZSwgbmV3eCA9IHhfdGVzdCkNCg0KbWVhbigoeV90ZXN0IC0gcmlkZ2UucHJlZCleMikNCg0KYGBgDQoNCmBgYHtyfQ0KDQpwbG90KGN2X3JpZGdlKQ0KDQp0aXRsZSgiUmlkZ2UgUmVncmVzc2lvbjogQ3Jvc3MtVmFsaWRhdGlvbiBDdXJ2ZSIsIGxpbmUgPSAyLjUpDQpgYGANCmBgYHtyfQ0KDQpzZXQuc2VlZCg5OTcpDQoNCmN2X2xhc3NvIDwtIGN2LmdsbW5ldCh4X3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDEpDQoNCmJlc3RfbGFzc28gPC0gY3ZfbGFzc28kbGFtYmRhLm1pbg0KDQoNCmxhc3NvLnByZWQgPC0gcHJlZGljdChjdl9sYXNzbywgcyA9IGJlc3RfbGFzc28sIG5ld3ggPSB4X3Rlc3QpDQoNCm1lYW4oKHlfdGVzdCAtIGxhc3NvLnByZWQpXjIpDQoNCg0KYGBgDQpgYGB7cn0NCg0KcmVnLmZpdC5iZXN0IDwtIHJlZ3N1YnNldHMoY3JpbSB+IC4sIGRhdGEgPSBCb3N0b25bdHJhaW4sIF0sIG52bWF4ID0gMTMpDQoNCnRlc3RfbWF0cml4IDwtIG1vZGVsLm1hdHJpeChjcmltIH4gLiwgZGF0YSA9IEJvc3Rvblt0ZXN0LCBdKQ0KDQoNCm52IDwtIG5yb3coc3VtbWFyeShyZWcuZml0LmJlc3QpJHdoaWNoKQ0KdmFsX2Vycm9ycyA8LSByZXAoTkEsIG52KQ0KDQpmb3IgKGkgaW4gMTpudikgew0KICBjb2VmaWNpZW50IDwtIGNvZWYocmVnLmZpdC5iZXN0LCBpZCA9IGkpDQogIHByZWQgPC0gdGVzdF9tYXRyaXhbLCBuYW1lcyhjb2VmaWNpZW50KV0gJSolIGNvZWZpY2llbnQNCiAgdmFsX2Vycm9yc1tpXSA8LSBtZWFuKChCb3N0b24kY3JpbVt0ZXN0XSAtIHByZWQpXjIpDQp9DQoNCg0KbWluKHZhbF9lcnJvcnMpDQoNCg0KDQpgYGANCmBgYHtyfQ0KDQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gInJlcGVhdGVkY3YiLA0KICBudW1iZXIgPSAxMCwNCiAgcmVwZWF0cyA9IDMsDQogIHZlcmJvc2VJdGVyID0gVFJVRSAjIFNob3cgdHJhaW5pbmcgcHJvZ3Jlc3MNCikNCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KDQpyaWRnZUdyaWQgPC0gZXhwYW5kLmdyaWQoYWxwaGEgPSAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSA9IDEwXnNlcSgtMywgMSwgbGVuZ3RoID0gMTAwKSkgIyBBIHJhbmdlIG9mIGxhbWJkYSB2YWx1ZXMNCg0KY2F0KCJcbi0tLSBUcmFpbmluZyBSaWRnZSBSZWdyZXNzaW9uIHdpdGggY2FyZXQgLS0tXG4iKQ0KDQoNCmBgYA0KYGBge3J9DQoNCnJpZGdlX2NhcmV0IDwtIHRyYWluKA0KICBjcmltIH4gLiwgICAgICAgICAgICAgICAgIyBGb3JtdWxhIGludGVyZmFjZTogcmVzcG9uc2UgfiBhbGwgcHJlZGljdG9ycw0KICBkYXRhID0gQm9zdG9uLCAgICAgICAgICMgT3VyIGRhdGEgZnJhbWUNCiAgbWV0aG9kID0gImdsbW5ldCIsICAgICAgIyBTcGVjaWZ5IHRoZSBnbG1uZXQgbW9kZWwNCiAgdHVuZUdyaWQgPSByaWRnZUdyaWQsICAgIyBPdXIgY3VzdG9tIHR1bmluZyBncmlkIGZvciBhbHBoYSBhbmQgbGFtYmRhDQogIHRyQ29udHJvbCA9IGZpdENvbnRyb2wsICMgT3VyIGRlZmluZWQgY3Jvc3MtdmFsaWRhdGlvbiBjb250cm9sDQogIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSAjIENlbnRlciBhbmQgc2NhbGUgcHJlZGljdG9ycyAoaW1wb3J0YW50IGZvciByZWd1bGFyaXphdGlvbikNCikNCg0KDQpgYGANCmBgYHtyfQ0KDQpwcmludChyaWRnZV9jYXJldCkNCg0KYGBgDQpgYGB7cn0NCg0KIyBQbG90IHRoZSB0dW5pbmcgcmVzdWx0cyAoUk1TRSB2cyBsYW1iZGEpIHVzaW5nIGdncGxvdDIgZGlyZWN0bHkNCnJpZGdlX3Bsb3RfZGF0YSA8LSByaWRnZV9jYXJldCRyZXN1bHRzDQpwcmludChnZ3Bsb3QocmlkZ2VfcGxvdF9kYXRhLCBhZXMoeCA9IGxhbWJkYSwgeSA9IFJNU0UpKSArDQogICAgICAgIGdlb21fbGluZSgpICsNCiAgICAgICAgZ2VvbV9wb2ludCgpICsNCiAgICAgICAgIyBBZGQgZXJyb3IgYmFycyBpZiBSTVNFU0QgaXMgYXZhaWxhYmxlIGluIHRoZSByZXN1bHRzDQogICAgICAgIHtpZigiUk1TRVNEIiAlaW4lIG5hbWVzKHJpZGdlX3Bsb3RfZGF0YSkpIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBSTVNFIC0gUk1TRVNELCB5bWF4ID0gUk1TRSArIFJNU0VTRCksIHdpZHRoID0gMC4wMSl9ICsNCiAgICAgICAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJSaWRnZSBSZWdyZXNzaW9uIFR1bmluZyAoY2FyZXQpIiwNCiAgICAgICAgICAgICAgICAgICAgICB4ID0gIkxhbWJkYSIsDQogICAgICAgICAgICAgICAgICAgICAgeSA9ICJSTVNFIikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKCkpDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmNhdCgiT3B0aW1hbCBsYW1iZGEgZm9yIFJpZGdlIChjYXJldCk6IiwgcmlkZ2VfY2FyZXQkYmVzdFR1bmUkbGFtYmRhLCAiXG4iKQ0KDQpgYGANCg0KYGBge3J9DQoNCiMgLS0tIExhc3NvIFJlZ3Jlc3Npb24gd2l0aCBjYXJldCAtLS0NCiMgRm9yIExhc3NvLCB3ZSBzcGVjaWZ5IGEgdHVuZUdyaWQgd2hlcmUgYWxwaGEgaXMgZml4ZWQgYXQgMS4NCiMgY2FyZXQgd2lsbCB0aGVuIHNlYXJjaCBmb3IgdGhlIGJlc3QgbGFtYmRhLg0KDQpsYXNzb0dyaWQgPC0gZXhwYW5kLmdyaWQoYWxwaGEgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSA9IDEwXnNlcSgtMywgMSwgbGVuZ3RoID0gMTAwKSkgIyBBIHJhbmdlIG9mIGxhbWJkYSB2YWx1ZXMNCg0KY2F0KCJcbi0tLSBUcmFpbmluZyBMYXNzbyBSZWdyZXNzaW9uIHdpdGggY2FyZXQgLS0tXG4iKQ0KDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmxhc3NvX2NhcmV0IDwtIHRyYWluKA0KIGNyaW0gfiAuLA0KICBkYXRhID0gQm9zdG9uLA0KICBtZXRob2QgPSAiZ2xtbmV0IiwNCiAgdHVuZUdyaWQgPSBsYXNzb0dyaWQsDQogIHRyQ29udHJvbCA9IGZpdENvbnRyb2wsDQogIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKQ0KKQ0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIFByaW50IHRoZSBtb2RlbCByZXN1bHRzDQpwcmludChsYXNzb19jYXJldCkNCg0KDQpgYGANCg0KYGBge3J9DQoNCiMgR2V0IHRoZSBiZXN0IGxhbWJkYSBmb3VuZCBieSBjYXJldA0KY2F0KCJPcHRpbWFsIGxhbWJkYSBmb3IgTGFzc28gKGNhcmV0KToiLCBsYXNzb19jYXJldCRiZXN0VHVuZSRsYW1iZGEsICJcbiIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIEdldCB0aGUgY29lZmZpY2llbnRzIGZyb20gdGhlIGJlc3QgbW9kZWwNCmNvZWYobGFzc29fY2FyZXQkZmluYWxNb2RlbCwgcyA9IGxhc3NvX2NhcmV0JGJlc3RUdW5lJGxhbWJkYSkNCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KcmVnLmZpdC5iZXN0IDwtIHJlZ3N1YnNldHMoY3JpbSB+IC4sIGRhdGEgPSBCb3N0b25bdHJhaW4sIF0sIG52bWF4ID0gMTMpDQoNCnRlc3RfbWF0cml4IDwtIG1vZGVsLm1hdHJpeChjcmltIH4gLiwgZGF0YSA9IEJvc3Rvblt0ZXN0LCBdKQ0KDQoNCm52IDwtIG5yb3coc3VtbWFyeShyZWcuZml0LmJlc3QpJHdoaWNoKQ0KdmFsX2Vycm9ycyA8LSByZXAoTkEsIG52KQ0KDQpmb3IgKGkgaW4gMTpudikgew0KICBjb2VmaWNpZW50IDwtIGNvZWYocmVnLmZpdC5iZXN0LCBpZCA9IGkpDQogIHByZWQgPC0gdGVzdF9tYXRyaXhbLCBuYW1lcyhjb2VmaWNpZW50KV0gJSolIGNvZWZpY2llbnQNCiAgdmFsX2Vycm9yc1tpXSA8LSBtZWFuKChCb3N0b24kY3JpbVt0ZXN0XSAtIHByZWQpXjIpDQp9DQoNCg0KbWluKHZhbF9lcnJvcnMpDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCndoaWNoLm1pbih2YWxfZXJyb3JzKQ0KDQpgYGANCg0KDQpgYGB7cn0NCg0KcGxvdCh2YWxfZXJyb3JzLCB0eXBlID0gImIiLCB4bGFiID0gIk51bWJlciBvZiBWYXJpYWJsZXMiLCB5bGFiID0gIlZhbGlkYXRpb24gTVNFIikNCg0KYGBgDQoNCihiKSBQcm9wb3NlIGEgbW9kZWwgKG9yIHNldCBvZiBtb2RlbHMpIHRoYXQgc2VlbSB0byBwZXJmb3JtIHdlbGwgb24NCnRoaXMgZGF0YSBzZXQsIGFuZCBqdXN0aWZ5IHlvdXIgYW5zd2VyLiBNYWtlIHN1cmUgdGhhdCB5b3UgYXJlDQpldmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlIHVzaW5nIHZhbGlkYXRpb24gc2V0IGVycm9yLCBjcm9zc3ZhbGlkYXRpb24sDQpvciBzb21lIG90aGVyIHJlYXNvbmFibGUgYWx0ZXJuYXRpdmUsIGFzIG9wcG9zZWQgdG8NCnVzaW5nIHRyYWluaW5nIGVycm9yLg0KDQpSaWRnZSByZWdyZXNzaW9uIGhhZCB0aGUgbG93ZXN0IE1TRSBvZiAzNC43MSB1c2luZyBjcm9zcyB2YWxpZGF0aW9uLg0KDQoNCihjKSBEb2VzIHlvdXIgY2hvc2VuIG1vZGVsIGludm9sdmUgYWxsIG9mIHRoZSBmZWF0dXJlcyBpbiB0aGUgZGF0YQ0Kc2V0PyBXaHkgb3Igd2h5IG5vdD8NCg0KVGhlIGNob3NlbiBtb2RlbCBkb2VzIHVzZSBhbGwgZmVhdHVyZXMgdG8gZXZhbHVhdGUgdGhlIG1vZGVsLiANCg==