The Effect of 401(k) Eligibility on Net Financial Assets
Data
hdm
パッケージに含まれており、ダウンロード可能である
# install.packages("renv")
# renv::restore()
# remotes::install_github("mlr-org/mlr3extralearners", force = TRUE) # needed to run boosting
pacman::p_load(
xtable,
hdm,
sandwich,
ggplot2,
randomForest,
data.table,
glmnet,
rpart,
gbm,
DoubleML,
mlr3learners,
mlr3,
data.table,
randomForest,
ranger,
mlr3extralearners,
mboost
)
data(pension)
data <- pension
dim(data)
[1] 9915 44
help(pension)
hist_e401 <- ggplot(data, aes(x = e401, fill = factor(e401))) +
geom_bar()
hist_e401

dens_net_tfa <- ggplot(data, aes(x = net_tfa, color = factor(e401), fill = factor(e401))) +
geom_density() +
xlim(c(-20000, 150000)) +
facet_wrap(. ~ e401)
dens_net_tfa

e1 <- data[data$e401 == 1, ]
e0 <- data[data$e401 == 0, ]
round(mean(e1$net_tfa) - mean(e0$net_tfa), 0)
[1] 19559
p1 <- data[data$p401 == 1, ]
p0 <- data[data$p401 == 0, ]
round(mean(p1$net_tfa) - mean(p0$net_tfa), 0)
[1] 27372
# outcome variable
y <- data[, "net_tfa"]
# treatment variable
D <- data[, "e401"]
D2 <- data[, "p401"]
D3 <- data[, "a401"]
columns_to_drop <- c(
"e401", "p401", "a401", "tw", "tfa", "net_tfa", "tfa_he",
"hval", "hmort", "hequity",
"nifa", "net_nifa", "net_n401", "ira",
"dum91", "icat", "ecat", "zhat",
"i1", "i2", "i3", "i4", "i5", "i6", "i7",
"a1", "a2", "a3", "a4", "a5"
)
# covariates
X <- data[, !(names(data) %in% columns_to_drop)]
# Constructing the controls
x_formula <- paste("~ 0 + poly(age, 6, raw=TRUE) + poly(inc, 8, raw=TRUE) + poly(educ, 4, raw=TRUE) ",
"+ poly(fsize, 2, raw=TRUE) + male + marr + twoearn + db + pira + hown")
X <- as.data.table(model.frame(x_formula, X))
head(X)
Partiall Linear Model (PLM)
dml2_for_plm <- function(x, d, y, dreg, yreg, nfold = 3, method = "regression") {
nobs <- nrow(x) # number of observations
foldid <- rep.int(1:nfold, times = ceiling(nobs / nfold))[sample.int(nobs)] # define folds indices
I <- split(1:nobs, foldid) # split observation indices into folds
ytil <- dtil <- rep(NA, nobs)
cat("fold: ")
for (b in seq_along(I)) {
if (method == "regression") {
dfit <- dreg(x[-I[[b]], ], d[-I[[b]]]) # take a fold out
yfit <- yreg(x[-I[[b]], ], y[-I[[b]]]) # take a foldt out
dhat <- predict(dfit, x[I[[b]], ], type = "response") # predict the left-out fold
yhat <- predict(yfit, x[I[[b]], ], type = "response") # predict the left-out fold
dtil[I[[b]]] <- (d[I[[b]]] - dhat) # record residual for the left-out fold
ytil[I[[b]]] <- (y[I[[b]]] - yhat) # record residial for the left-out fold
} else if (method == "randomforest") {
dfit <- dreg(x[-I[[b]], ], as.factor(d)[-I[[b]]]) # take a fold out
yfit <- yreg(x[-I[[b]], ], y[-I[[b]]]) # take a fold out
dhat <- predict(dfit, x[I[[b]], ], type = "prob")[, 2] # predict the left-out fold
yhat <- predict(yfit, x[I[[b]], ], type = "response") # predict the left-out fold
dtil[I[[b]]] <- (d[I[[b]]] - dhat) # record residual for the left-out fold
ytil[I[[b]]] <- (y[I[[b]]] - yhat) # record residial for the left-out fold
} else if (method == "decisiontrees") {
dfit <- dreg(x[-I[[b]], ], as.factor(d)[-I[[b]]]) # take a fold out
yfit <- yreg(x[-I[[b]], ], y[-I[[b]]]) # take a fold out
dhat <- predict(dfit, x[I[[b]], ])[, 2] # predict the left-out fold
yhat <- predict(yfit, x[I[[b]], ]) # predict the left-out fold
dtil[I[[b]]] <- (d[I[[b]]] - dhat) # record residual for the left-out fold
ytil[I[[b]]] <- (y[I[[b]]] - yhat) # record residial for the left-out fold
} else if (method == "boostedtrees") {
dfit <- dreg(x[-I[[b]], ], d[-I[[b]]]) # take a fold out
yfit <- yreg(x[-I[[b]], ], y[-I[[b]]]) # take a fold out
dhat <- predict(dfit, x[I[[b]], ], type = "response") # predict the left-out fold
yhat <- predict(yfit, x[I[[b]], ], type = "response") # predict the left-out fold
dtil[I[[b]]] <- (d[I[[b]]] - dhat) # record residual for the left-out fold
ytil[I[[b]]] <- (y[I[[b]]] - yhat) # record residial for the left-out fold
}
cat(b, " ")
}
rfit <- lm(ytil ~ dtil) # estimate the main parameter by regressing one residual on the other
coef_est <- coef(rfit)[2] # extract coefficient
se <- sqrt(vcovHC(rfit)[2, 2]) # record robust standard error
cat(sprintf("\ncoef (se) = %g (%g)\n", coef_est, se)) # printing output
return(list(coef_est = coef_est, se = se, dtil = dtil, ytil = ytil)) # save output and residuals
}
summaryPLR <- function(point, stderr, resD, resy, name) {
data <- data.frame(
estimate = point, # point estimate
stderr = stderr, # standard error
lower = point - 1.96 * stderr, # lower end of 95% confidence interval
upper = point + 1.96 * stderr, # upper end of 95% confidence interval
`rmse y` = sqrt(mean(resy^2)), # RMSE of model that predicts outcome y
`rmse D` = sqrt(mean(resD^2)), # RMSE of model that predicts treatment D
`accuracy D` = mean(abs(resD) < 0.5) # binary classification accuracy of model for D
)
rownames(data) <- name
return(data)
}
Double Lasso
- 処置の推定において、関数型を線形確率モデルにする場合
# DML with LassoCV
set.seed(123)
cat(sprintf("\nDML with Lasso CV \n"))
DML with Lasso CV
dreg_lasso_cv <- function(x, d) {
cv.glmnet(x, d, family = "gaussian", alpha = 1, nfolds = 5)
}
yreg_lasso_cv <- function(x, y) {
cv.glmnet(x, y, family = "gaussian", alpha = 1, nfolds = 5)
}
dml2_results <- dml2_for_plm(as.matrix(X), D, y, dreg_lasso_cv, yreg_lasso_cv, nfold = 5)
fold: 1 2 3 4 5
coef (se) = 8954.5 (1483.1)
sum_lasso_cv <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "LassoCV")
tableplr <- data.frame()
tableplr <- rbind(sum_lasso_cv)
tableplr
# Because residuals are output, reconstruct fitted values for use in ensemble
dhat_lasso <- D - dml2_results$dtil
yhat_lasso <- y - dml2_results$ytil
- 処置の推定において、Logistic 回帰を用いる場合
# DML with Lasso/Logistic
set.seed(123)
cat(sprintf("\nDML with Lasso/Logistic \n"))
DML with Lasso/Logistic
dreg_logistic_cv <- function(x, d) {
cv.glmnet(x, d, family = "binomial", alpha = 0, nfolds = 5)
}
yreg_lasso_cv <- function(x, y) {
cv.glmnet(x, y, family = "gaussian", alpha = 1, nfolds = 5)
}
dml2_results <- dml2_for_plm(as.matrix(X), D, y, dreg_logistic_cv, yreg_lasso_cv, nfold = 5)
fold: 1 2 3 4 5
coef (se) = 9418.17 (1476.17)
sum_lasso_logistic_cv <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "LassoCV/LogisticCV")
tableplr <- rbind(tableplr, sum_lasso_logistic_cv)
tableplr
# Because residuals are output, reconstruct fitted values for use in ensemble
dhat_lasso_logistic <- D - dml2_results$dtil
yhat_lasso_logistic <- y - dml2_results$ytil
Random Forest
# DML with Random Forest
set.seed(123)
cat(sprintf("\nDML with Random Forest \n"))
DML with Random Forest
dreg_rf <- function(x, d) {
randomForest(x, d, ntree = 1000, nodesize = 10)
} # ML method=Forest
yreg_rf <- function(x, y) {
randomForest(x, y, ntree = 1000, nodesize = 10)
} # ML method=Forest
dml2_results <- dml2_for_plm(as.matrix(X), D, y, dreg_rf, yreg_rf, nfold = 5, method = "randomforest")
fold: 1 2 3 4 5
coef (se) = 9112 (1281.08)
sum_rf <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "Random Forest")
tableplr <- rbind(tableplr, sum_rf)
tableplr
# Because residuals are output, reconstruct fitted values for use in ensemble
dhat_rf <- D - dml2_results$dtil
dhat_rf <- y - dml2_results$ytil
Decision Trees
# DML with Decision Trees
set.seed(123)
cat(sprintf("\nDML with Decision Trees \n"))
DML with Decision Trees
dreg_tr <- function(x, d) {
rpart(as.formula("D~."), cbind(data.frame(D = d), x), method = "class", minbucket = 10, cp = 0.001)
}
dreg_tr <- function(x, y) {
rpart(as.formula("y~."), cbind(data.frame(y = y), x), minbucket = 10, cp = 0.001)
}
# decision tree takes in X as dataframe, not matrix/array
dml2_results <- dml2_for_plm(X, D, y, dreg_tr, dreg_tr, nfold = 5, method = "decisiontrees")
fold: 1 2 3 4 5
coef (se) = 8633.9 (1302.95)
sum_tr <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "Decision Trees")
tableplr <- rbind(tableplr, sum_tr)
tableplr
# Because residuals are output, reconstruct fitted values for use in ensemble
dhat_tr <- D - dml2_results$dtil
yhat_tr <- y - dml2_results$ytil
Boosted Trees
# DML with Boosted Trees
set.seed(123)
cat(sprintf("\nDML with Boosted Trees \n"))
DML with Boosted Trees
# NB: early stopping cannot easily be implemented with gbm
## set n.trees = best, where best <- gbm.perf(dreg_boost, plot.it = FALSE)
dreg_boost <- function(x, d) {
gbm(as.formula("D~."), cbind(data.frame(D = d), x), distribution = "bernoulli",
interaction.depth = 2, n.trees = 100, shrinkage = .1)
}
yreg_boost <- function(x, y) {
gbm(as.formula("y~."), cbind(data.frame(y = y), x), distribution = "gaussian",
interaction.depth = 2, n.trees = 100, shrinkage = .1)
}
# passing these through regression as type="response", and D should not be factor!
dml2_results <- dml2_for_plm(X, D, y, dreg_boost, yreg_boost, nfold = 5, method = "boostedtrees")
fold:
Using 100 trees...
Using 100 trees...
1
Using 100 trees...
Using 100 trees...
2
Using 100 trees...
Using 100 trees...
3
Using 100 trees...
Using 100 trees...
4
Using 100 trees...
Using 100 trees...
5
coef (se) = 8859.28 (1321.43)
sum_boost <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "Boosted Trees")
tableplr <- rbind(tableplr, sum_boost)
tableplr
# Because residuals are output, reconstruct fitted values for use in ensemble
dhat_boost <- D - dml2_results$dtil
yhat_boost <- y - dml2_results$ytil
Ensemble
# Best fit is boosted trees for both D and Y
sum_best <- summaryPLR(dml2_results$coef_est, dml2_results$se, dml2_results$dtil,
dml2_results$ytil, name = "Best")
tableplr <- rbind(tableplr, sum_best)
tableplr
# Least squares model average
ma_dtil <- lm(D ~ dhat_lasso + dhat_lasso_logistic + dhat_rf + dhat_tr + dhat_boost)$residuals
ma_ytil <- lm(y ~ yhat_lasso + yhat_lasso_logistic + dhat_rf + yhat_tr + yhat_boost)$residuals
rfit <- lm(ma_ytil ~ ma_dtil) # estimate the main parameter by regressing one residual on the other
coef_est <- coef(rfit)[2] # extract coefficient
se <- sqrt(vcovHC(rfit)[2, 2]) # record robust standard error
sum.ma <- summaryPLR(coef_est, se, ma_dtil, ma_ytil, name = "Model Average")
tableplr <- rbind(tableplr, sum.ma)
tableplr
Interactive Regression Model (IRM)
dml2_for_irm <- function(x, d, y, dreg, yreg0, yreg1, trimming = 0.01, nfold = 5, method = "regression") {
yhat0 <- rep(0, length(y))
yhat1 <- rep(0, length(y))
Dhat <- rep(0, length(d))
nobs <- nrow(x) # number of observations
foldid <- rep.int(1:nfold, times = ceiling(nobs / nfold))[sample.int(nobs)] # define folds indices
I <- split(1:nobs, foldid) # split observation indices into folds
ytil <- dtil <- rep(NA, nobs)
cat("fold: ")
for (b in seq_along(I)) {
# define helpful variables
Dnotb <- d[-I[[b]]]
Xb <- X[I[[b]], ]
Xnotb <- X[-I[[b]], ]
# training dfs subsetted on the -I[[b]] fold
XD0 <- X[-I[[b]], ][d[-I[[b]]] == 0]
yD0 <- y[-I[[b]]][d[-I[[b]]] == 0]
XD1 <- X[-I[[b]], ][d[-I[[b]]] == 1]
yD1 <- y[-I[[b]]][d[-I[[b]]] == 1]
if (method == "regression") {
yfit0 <- yreg0(as.matrix(XD0), yD0)
yfit1 <- yreg1(as.matrix(XD1), yD1)
yhat0[I[[b]]] <- predict(yfit0, as.matrix(Xb)) # default is type = "response" for glmnet family gaussian
yhat1[I[[b]]] <- predict(yfit1, as.matrix(Xb))
} else if (method == "randomforest") {
yfit0 <- yreg0(XD0, yD0)
yfit1 <- yreg1(XD1, yD1)
yhat0[I[[b]]] <- predict(yfit0, Xb) # default is type = "response" for rf
yhat1[I[[b]]] <- predict(yfit1, Xb)
} else if (method == "decisiontrees") {
yfit0 <- yreg0(XD0, yD0)
yfit1 <- yreg1(XD1, yD1)
yhat0[I[[b]]] <- predict(yfit0, Xb) # default is type = "vector" for decision
yhat1[I[[b]]] <- predict(yfit1, Xb)
} else if (method == "boostedtrees") {
yfit0 <- yreg0(as.data.frame(XD0), yD0)
yfit1 <- yreg1(as.data.frame(XD1), yD1)
yhat0[I[[b]]] <- predict(yfit0, Xb) # default is type = "response" for boosted
yhat1[I[[b]]] <- predict(yfit1, Xb)
}
# propensity scores:
if (method == "regression") {
dfit_b <- dreg(as.matrix(Xnotb), Dnotb)
dhat_b <- predict(dfit_b, as.matrix(Xb), type = "response") # default is type="link" for family binomial!
} else if (method == "randomforest") {
dfit_b <- dreg(Xnotb, as.factor(Dnotb))
dhat_b <- predict(dfit_b, Xb, type = "prob")[, 2]
} else if (method == "decisiontrees") {
dfit_b <- dreg(Xnotb, Dnotb)
dhat_b <- predict(dfit_b, Xb)[, 2]
} else if (method == "boostedtrees") {
dfit_b <- dreg(as.data.frame(Xnotb), Dnotb)
dhat_b <- predict(dfit_b, Xb, type = "response")
}
dhat_b <- pmax(pmin(dhat_b, 1 - trimming), trimming) # trimming so scores are between [trimming, (1-trimming)]
Dhat[I[[b]]] <- dhat_b
cat(b, " ")
}
# Prediction of treatment and outcome for observed instrument
yhat <- yhat0 * (1 - D) + yhat1 * D
# residuals
ytil <- y - yhat
dtil <- D - Dhat
# doubly robust quantity for every sample
drhat <- yhat1 - yhat0 + (y - yhat) * (D / Dhat - (1 - D) / (1 - Dhat))
coef_est <- mean(drhat)
vari <- var(drhat)
se <- sqrt(vari / nrow(X))
cat("point", coef_est)
cat("se", se)
return(list(coef_est = coef_est, se = se, ytil = ytil, dtil = dtil, drhat = drhat,
yhat0 = yhat0, yhat1 = yhat1, dhat = Dhat, yhat = yhat))
}
summaryIRM <- function(coef_est, se, ytil, dtil, drhat, name) {
summary_data <- data.frame(
estimate = coef_est, # point estimate
se = se, # standard error
lower = coef_est - 1.96 * se, # lower end of 95% confidence interval
upper = coef_est + 1.96 * se, # upper end of 95% confidence interval
rmse_y = sqrt(mean(ytil^2)), # res of model that predicts outcome y
rmse_D = sqrt(mean(dtil^2)), # res of model that predicts treatment D
accuracy_D = mean(abs(dtil) < 0.5) # binary classification accuracy of model for D
)
row.names(summary_data) <- name
return(summary_data)
}
Double Lasso
# DML with Lasso/Logistic
set.seed(123)
cat(sprintf("\nDML with LassoCV/Logistic \n"))
DML with LassoCV/Logistic
dreg_lasso_cv <- function(x, d) {
cv.glmnet(x, d, family = "binomial", alpha = 0, nfolds = 5)
}
yreg0_lasso_cv <- function(x, y) {
cv.glmnet(x, y, family = "gaussian", alpha = 1, nfolds = 5)
}
yreg1_lasso_cv <- function(x, y) {
cv.glmnet(x, y, family = "gaussian", alpha = 1, nfolds = 5)
}
# more folds seems to help stabilize finite sample performance
dml2_results <- dml2_for_irm(X, D, y, dreg_lasso_cv, yreg0_lasso_cv, yreg1_lasso_cv, nfold = 5)
fold: 1 2 3 4 5 point 8860.207se 1347.311
sum_lasso_cv <- summaryIRM(dml2_results$coef_est, dml2_results$se, dml2_results$ytil, dml2_results$dtil,
dml2_results$drhat, name = "LassoCVLogistic")
tableirm <- data.frame()
tableirm <- rbind(sum_lasso_cv)
tableirm
yhat0_lasso <- dml2_results$yhat0
yhat1_lasso <- dml2_results$yhat1
dhat_lasso <- dml2_results$dhat
yhat_lasso <- dml2_results$yhat
Random Forest
# DML with Random Forest
set.seed(123)
cat(sprintf("\nDML with Random Forest \n"))
DML with Random Forest
dreg_rf <- function(x, d) {
randomForest(x, d, ntree = 1000, nodesize = 10)
} # ML method=Forest
yreg0_rf <- function(x, y) {
randomForest(x, y, ntree = 1000, nodesize = 10)
} # ML method=Forest
yreg1_rf <- function(x, y) {
randomForest(x, y, ntree = 1000, nodesize = 10)
} # ML method=Forest
dml2_results <- dml2_for_irm(as.matrix(X), D, y, dreg_rf, yreg0_rf, yreg1_rf, nfold = 5, method = "randomforest")
fold: 1 2 3 4 5 point 8349.154se 1502.248
sum_rf <- summaryIRM(dml2_results$coef_est, dml2_results$se, dml2_results$ytil, dml2_results$dtil,
dml2_results$drhat, name = "Random Forest")
tableirm <- rbind(tableirm, sum_rf)
tableirm
yhat0_rf <- dml2_results$yhat0
yhat1_rf <- dml2_results$yhat1
dhat_rf <- dml2_results$dhat
dhat_rf <- dml2_results$yhat
Decision Trees
# DML with Decision Trees
set.seed(123)
cat(sprintf("\nDML with Decision Trees \n"))
DML with Decision Trees
dreg_tr <- function(x, d) {
rpart(as.formula("D~."), cbind(data.frame(D = d), x), method = "class", minbucket = 10, cp = 0.001)
}
yreg0_tr <- function(x, y) {
rpart(as.formula("y~."), cbind(data.frame(y = y), x), minbucket = 10, cp = 0.001)
}
yreg1_tr <- function(x, y) {
rpart(as.formula("y~."), cbind(data.frame(y = y), x), minbucket = 10, cp = 0.001)
}
dml2_results <- dml2_for_irm(X, D, y, dreg_tr, yreg0_tr, yreg1_tr, nfold = 5, method = "decisiontrees")
fold: 1 2 3 4 5 point 7856.397se 1249.771
sum_tr <- summaryIRM(dml2_results$coef_est, dml2_results$se, dml2_results$ytil, dml2_results$dtil,
dml2_results$drhat, name = "Decision Trees")
tableirm <- rbind(tableirm, sum_tr)
tableirm
yhat0_tr <- dml2_results$yhat0
yhat1_tr <- dml2_results$yhat1
dhat_tr <- dml2_results$dhat
yhat_tr <- dml2_results$yhat
Boosted Trees
# DML with Boosted Trees
set.seed(123)
cat(sprintf("\nDML with Boosted Trees \n"))
DML with Boosted Trees
# NB: early stopping cannot easily be implemented with gbm
## set n.trees = best, where best <- gbm.perf(dreg_boost, plot.it = FALSE)
dreg_boost <- function(x, d) {
gbm(as.formula("D~."), cbind(data.frame(D = d), x), distribution = "bernoulli",
interaction.depth = 2, n.trees = 100, shrinkage = .1)
}
yreg0_boost <- function(x, y) {
gbm(as.formula("y~."), cbind(data.frame(y = y), x), distribution = "gaussian",
interaction.depth = 2, n.trees = 100, shrinkage = .1)
}
yreg1_boost <- function(x, y) {
gbm(as.formula("y~."), cbind(data.frame(y = y), x), distribution = "gaussian",
interaction.depth = 2, n.trees = 100, shrinkage = .1)
}
# passing these through regression as type="response", and D should not be factor!
dml2_results <- dml2_for_irm(X, D, y, dreg_boost, yreg0_boost, yreg1_boost, nfold = 5, method = "boostedtrees")
fold:
Using 100 trees...
Using 100 trees...
Using 100 trees...
1
Using 100 trees...
Using 100 trees...
Using 100 trees...
2
Using 100 trees...
Using 100 trees...
Using 100 trees...
3
Using 100 trees...
Using 100 trees...
Using 100 trees...
4
Using 100 trees...
Using 100 trees...
Using 100 trees...
5 point 7871.119se 1156.693
sum_boost <- summaryIRM(dml2_results$coef_est, dml2_results$se, dml2_results$ytil, dml2_results$dtil,
dml2_results$drhat, name = "Boosted Trees")
tableirm <- rbind(tableirm, sum_boost)
tableirm
yhat0_boost <- dml2_results$yhat0
yhat1_boost <- dml2_results$yhat1
dhat_boost <- dml2_results$dhat
yhat_boost <- dml2_results$yhat
Ensemble
# Ensembles
# Best
# We'll look at model that does best for Y overall. Could also use different model for Y0 and Y1
# Here, the best performance for Y is the random forest and for D the boosted tree
# residuals
ytil <- y - dhat_rf
dtil <- D - dhat_boost
# doubly robust quantity for every sample
drhat <- yhat1_rf - yhat0_rf + (y - dhat_rf) * (D / dhat_boost - (1 - D) / (1 - dhat_boost))
coef_est <- mean(drhat)
vari <- var(drhat)
se <- sqrt(vari / nrow(X))
sum_best <- summaryIRM(coef_est, se, ytil, dtil, drhat, name = "Best")
tableirm <- rbind(tableirm, sum_best)
tableirm
# Least squares model average
# We'll look at weights that do best job for Y overall. Could also use different weights for Y0 and Y1
ma_dw <- lm(D ~ dhat_lasso + dhat_rf + dhat_tr + dhat_boost)$coef
ma_yw <- lm(y ~ yhat_lasso + dhat_rf + yhat_tr + yhat_boost)$coef
Dhats <- cbind(as.matrix(rep(1, nrow(X))), dhat_lasso, dhat_rf, dhat_tr, dhat_boost)
Y0s <- cbind(as.matrix(rep(1, nrow(X))), yhat0_lasso, yhat0_rf, yhat0_tr, yhat0_boost)
Y1s <- cbind(as.matrix(rep(1, nrow(X))), yhat1_lasso, yhat1_rf, yhat1_tr, yhat1_boost)
dhat <- Dhats %*% as.matrix(ma_dw)
yhat0 <- Y0s %*% as.matrix(ma_yw)
yhat1 <- Y1s %*% as.matrix(ma_yw)
# Prediction of treatment and outcome for observed instrument
yhat <- yhat0 * (1 - D) + yhat1 * D
# residuals
ytil <- y - yhat
dtil <- D - dhat
# doubly robust quantity for every sample
drhat <- yhat1 - yhat0 + (y - yhat) * (D / dhat - (1 - D) / (1 - dhat))
coef_est <- mean(drhat)
vari <- var(drhat)
se <- sqrt(vari / nrow(X))
sum.ma <- summaryIRM(coef_est, se, ytil, dtil, drhat, name = "Model Average")
tableirm <- rbind(tableirm, sum.ma)
tableirm
DoubleML package による推定
- 今までは自ら関数を書いていたが、
EconML
(Python) や
DoubleML
(R & Python) などのパッケージが利用可能である
データ作成
# Constructing the data (as DoubleMLData)
formula_flex <- paste("net_tfa ~ e401 + poly(age, 6, raw=TRUE) + poly(inc, 8, raw=TRUE) ",
"+ poly(educ, 4, raw=TRUE) + poly(fsize, 2, raw=TRUE) + marr + twoearn + db + pira + hown")
model_flex <- as.data.table(model.frame(formula_flex, pension))
x_cols <- colnames(model_flex)[-c(1, 2)]
data_ml <- DoubleMLData$new(model_flex, y_col = "net_tfa", d_cols = "e401", x_cols = x_cols)
p <- dim(model_flex)[2] - 2
p
[1] 25
Partiall Linear Model (PLM)
# Estimating the PLR
lgr::get_logger("mlr3")$set_threshold("warn")
lasso <- lrn("regr.cv_glmnet", nfolds = 5, s = "lambda.min")
lasso_class <- lrn("classif.cv_glmnet", nfolds = 5, s = "lambda.min")
dml_plr <- DoubleMLPLR$new(data_ml, ml_l = lasso, ml_m = lasso_class, n_folds = 5)
dml_plr$fit(store_predictions = TRUE)
dml_plr$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 8829 1300 6.793 1.1e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
lasso_plr <- dml_plr$coef
lasso_std_plr <- dml_plr$se
dml_plr$params_names()
[1] "ml_l" "ml_m"
g_hat <- as.matrix(dml_plr$predictions$ml_l) # predictions of g_o
m_hat <- as.matrix(dml_plr$predictions$ml_m) # predictions of m_o
# cross-fitted RMSE: outcome
y <- as.matrix(pension$net_tfa) # true observations
theta <- as.numeric(dml_plr$coef) # estimated regression coefficient
d <- as.matrix(pension$e401)
predictions_y <- as.matrix(d * theta) + g_hat # predictions for y
lasso_y_rmse <- sqrt(mean((y - predictions_y)^2))
lasso_y_rmse
[1] 64018.6
Lasso
# cross-fitted RMSE: treatment
d <- as.matrix(pension$e401)
lasso_d_rmse <- sqrt(mean((d - m_hat)^2))
lasso_d_rmse
[1] 0.4443435
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3143722
Random Forest
# Random Forest
lgr::get_logger("mlr3")$set_threshold("warn")
randomForest <- lrn("regr.ranger")
random_forest_class <- lrn("classif.ranger")
dml_plr <- DoubleMLPLR$new(data_ml, ml_l = randomForest, ml_m = random_forest_class, n_folds = 5)
dml_plr$fit(store_predictions = TRUE) # set store_predictions=TRUE to evaluate the model
dml_plr$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 9461 1338 7.069 1.57e-12 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
forest_plr <- dml_plr$coef
forest_std_plr <- dml_plr$se
# Evaluation predictions
g_hat <- as.matrix(dml_plr$predictions$ml_l) # predictions of g_o
m_hat <- as.matrix(dml_plr$predictions$ml_m) # predictions of m_o
theta <- as.numeric(dml_plr$coef) # estimated regression coefficient
predictions_y <- as.matrix(d * theta) + g_hat # predictions for y
forest_y_rmse <- sqrt(mean((y - predictions_y)^2))
forest_y_rmse
[1] 57156.22
# cross-fitted RMSE: treatment
forest_d_rmse <- sqrt(mean((d - m_hat)^2))
forest_d_rmse
[1] 0.4586097
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3358548
Decision Trees
# Trees
lgr::get_logger("mlr3")$set_threshold("warn")
trees <- lrn("regr.rpart")
trees_class <- lrn("classif.rpart")
dml_plr <- DoubleMLPLR$new(data_ml, ml_l = trees, ml_m = trees_class, n_folds = 5)
dml_plr$fit(store_predictions = TRUE)
dml_plr$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 8390 1313 6.389 1.67e-10 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
tree_plr <- dml_plr$coef
tree_std_plr <- dml_plr$se
# Evaluation predictions
g_hat <- as.matrix(dml_plr$predictions$ml_l) # predictions of g_o
m_hat <- as.matrix(dml_plr$predictions$ml_m) # predictions of m_o
theta <- as.numeric(dml_plr$coef) # estimated regression coefficient
predictions_y <- as.matrix(d * theta) + g_hat # predictions for y
tree_y_rmse <- sqrt(mean((y - predictions_y)^2))
tree_y_rmse
[1] 56589.89
# cross-fitted RMSE: treatment
tree_d_rmse <- sqrt(mean((d - m_hat)^2))
tree_d_rmse
[1] 0.4557193
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3118507
Boosted Trees
# Boosting
boost <- lrn("regr.glmboost")
boost_class <- lrn("classif.glmboost")
dml_plr <- DoubleMLPLR$new(data_ml, ml_l = boost, ml_m = boost_class, n_folds = 5)
dml_plr$fit(store_predictions = TRUE)
dml_plr$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 9257 1320 7.013 2.33e-12 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
boost_plr <- dml_plr$coef
boost_std_plr <- dml_plr$se
# Evaluation predictions
g_hat <- as.matrix(dml_plr$predictions$ml_l) # predictions of g_o
m_hat <- as.matrix(dml_plr$predictions$ml_m) # predictions of m_o
theta <- as.numeric(dml_plr$coef) # estimated regression coefficient
predictions_y <- as.matrix(d * theta) + g_hat # predictions for y
boost_y_rmse <- sqrt(mean((y - predictions_y)^2))
boost_y_rmse
[1] 54047.3
# cross-fitted RMSE: treatment
boost_d_rmse <- sqrt(mean((d - m_hat)^2))
boost_d_rmse
[1] 0.4467967
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3154816
まとめ
table <- matrix(0, 4, 4)
table[1, 1:4] <- c(lasso_plr, forest_plr, tree_plr, boost_plr)
table[2, 1:4] <- c(lasso_std_plr, forest_std_plr, tree_std_plr, boost_std_plr)
table[3, 1:4] <- c(lasso_y_rmse, forest_y_rmse, tree_y_rmse, boost_y_rmse)
table[4, 1:4] <- c(lasso_d_rmse, forest_d_rmse, tree_d_rmse, boost_d_rmse)
rownames(table) <- c("Estimate", "Std.Error", "RMSE Y", "RMSE D")
colnames(table) <- c("Lasso", "Random Forest", "Trees", "Boosting")
tab <- xtable(table, digits = 2)
tab
% latex table generated in R 4.4.2 by xtable 1.8-4 package
% Mon Dec 2 19:01:35 2024
\begin{table}[ht]
\centering
\begin{tabular}{rrrrr}
\hline
& Lasso & Random Forest & Trees & Boosting \\
\hline
Estimate & 8828.75 & 9460.91 & 8389.91 & 9257.29 \\
Std.Error & 1299.62 & 1338.45 & 1313.14 & 1319.97 \\
RMSE Y & 64018.60 & 57156.22 & 56589.89 & 54047.30 \\
RMSE D & 0.44 & 0.46 & 0.46 & 0.45 \\
\hline
\end{tabular}
\end{table}
lasso_plr
e401
8828.752
Interactive Regression Model (IRM)
lgr::get_logger("mlr3")$set_threshold("warn")
dml_irm <- DoubleMLIRM$new(data_ml,
ml_g = lasso,
ml_m = lasso_class,
trimming_threshold = 0.01, n_folds = 5
)
dml_irm$fit(store_predictions = TRUE)
dml_irm$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 7906 1116 7.085 1.39e-12 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
lasso_irm <- dml_irm$coef
lasso_std_irm <- dml_irm$se
# predictions
dml_irm$params_names()
[1] "ml_g0" "ml_g1" "ml_m"
g0_hat <- as.matrix(dml_irm$predictions$ml_g0) # predictions of g_0(D=0, X)
g1_hat <- as.matrix(dml_irm$predictions$ml_g1) # predictions of g_0(D=1, X)
g_hat <- d * g1_hat + (1 - d) * g0_hat # predictions of g_0
m_hat <- as.matrix(dml_irm$predictions$ml_m) # predictions of m_o
# cross-fitted RMSE: outcome
y <- as.matrix(pension$net_tfa) # true observations
d <- as.matrix(pension$e401)
lasso_y_irm <- sqrt(mean((y - g_hat)^2))
lasso_y_irm
[1] 71999.94
# cross-fitted RMSE: treatment
lasso_d_irm <- sqrt(mean((d - m_hat)^2))
lasso_d_irm
[1] 0.4442179
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3143722
##### forest #####
dml_irm <- DoubleMLIRM$new(data_ml,
ml_g = randomForest,
ml_m = random_forest_class,
trimming_threshold = 0.01, n_folds = 5
)
dml_irm$fit(store_predictions = TRUE)
dml_irm$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 7906 1622 4.874 1.09e-06 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
forest_irm <- dml_irm$coef
forest_std_irm <- dml_plr$se
# predictions
g0_hat <- as.matrix(dml_irm$predictions$ml_g0) # predictions of g_0(D=0, X)
g1_hat <- as.matrix(dml_irm$predictions$ml_g1) # predictions of g_0(D=1, X)
g_hat <- d * g1_hat + (1 - d) * g0_hat # predictions of g_0
m_hat <- as.matrix(dml_irm$predictions$ml_m) # predictions of m_0
# cross-fitted RMSE: outcome
y <- as.matrix(pension$net_tfa) # true observations
d <- as.matrix(pension$e401)
forest_y_irm <- sqrt(mean((y - g_hat)^2))
forest_y_irm
[1] 55392.98
# cross-fitted RMSE: treatment
forest_d_irm <- sqrt(mean((d - m_hat)^2))
forest_d_irm
[1] 0.456056
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3350479
##### trees #####
dml_irm <- DoubleMLIRM$new(data_ml,
ml_g = trees, ml_m = trees_class,
trimming_threshold = 0.01, n_folds = 5
)
dml_irm$fit(store_predictions = TRUE)
dml_irm$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 7987 1160 6.887 5.68e-12 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
tree_irm <- dml_irm$coef
tree_std_irm <- dml_irm$se
# predictions
g0_hat <- as.matrix(dml_irm$predictions$ml_g0) # predictions of g_0(D=0, X)
g1_hat <- as.matrix(dml_irm$predictions$ml_g1) # predictions of g_0(D=1, X)
g_hat <- d * g1_hat + (1 - d) * g0_hat # predictions of g_0
m_hat <- as.matrix(dml_irm$predictions$ml_m) # predictions of m_o
# cross-fitted RMSE: outcome
y <- as.matrix(pension$net_tfa) # true observations
d <- as.matrix(pension$e401)
tree_y_irm <- sqrt(mean((y - g_hat)^2))
tree_y_irm
[1] 57873.94
# cross-fitted RMSE: treatment
tree_d_irm <- sqrt(mean((d - m_hat)^2))
tree_d_irm
[1] 0.4551777
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3096319
##### boosting #####
dml_irm <- DoubleMLIRM$new(data_ml,
ml_g = boost, ml_m = boost_class,
trimming_threshold = 0.01, n_folds = 5
)
dml_irm$fit(store_predictions = TRUE)
dml_irm$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 8846 1210 7.309 2.69e-13 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
boost_irm <- dml_irm$coef
boost_std_irm <- dml_irm$se
# predictions
g0_hat <- as.matrix(dml_irm$predictions$ml_g0) # predictions of g_0(D=0, X)
g1_hat <- as.matrix(dml_irm$predictions$ml_g1) # predictions of g_0(D=1, X)
g_hat <- d * g1_hat + (1 - d) * g0_hat # predictions of g_0
m_hat <- as.matrix(dml_irm$predictions$ml_m) # predictions of m_o
# cross-fitted RMSE: outcome
y <- as.matrix(pension$net_tfa) # true observations
d <- as.matrix(pension$e401)
boost_y_irm <- sqrt(mean((y - g_hat)^2))
boost_y_irm
[1] 54969.31
# cross-fitted RMSE: treatment
boost_d_irm <- sqrt(mean((d - m_hat)^2))
boost_d_irm
[1] 0.4468418
# cross-fitted ce: treatment
mean(ifelse(m_hat > 0.5, 1, 0) != d)
[1] 0.3143722
まとめ
table <- matrix(0, 4, 4)
table[1, 1:4] <- c(lasso_irm, forest_irm, tree_irm, boost_irm)
table[2, 1:4] <- c(lasso_std_irm, forest_std_irm, tree_std_irm, boost_std_irm)
table[3, 1:4] <- c(lasso_y_irm, forest_y_irm, tree_y_irm, boost_y_irm)
table[4, 1:4] <- c(lasso_d_irm, forest_d_irm, tree_d_irm, boost_d_irm)
rownames(table) <- c("Estimate", "Std.Error", "RMSE Y", "RMSE D")
colnames(table) <- c("Lasso", "Random Forest", "Trees", "Boosting")
tab <- xtable(table, digits = 2)
tab
% latex table generated in R 4.4.2 by xtable 1.8-4 package
% Mon Dec 2 19:07:53 2024
\begin{table}[ht]
\centering
\begin{tabular}{rrrrr}
\hline
& Lasso & Random Forest & Trees & Boosting \\
\hline
Estimate & 7906.19 & 7905.57 & 7987.06 & 8846.44 \\
Std.Error & 1115.90 & 1319.97 & 1159.67 & 1210.31 \\
RMSE Y & 71999.94 & 55392.98 & 57873.94 & 54969.31 \\
RMSE D & 0.44 & 0.46 & 0.46 & 0.45 \\
\hline
\end{tabular}
\end{table}
lgr::get_logger("mlr3")$set_threshold("warn")
dml_irm <- DoubleMLIRM$new(data_ml,
ml_g = randomForest,
ml_m = lasso_class,
trimming_threshold = 0.01, n_folds = 5
)
dml_irm$fit(store_predictions = TRUE)
dml_irm$summary()
Estimates and significance testing of the effect of target variables
Estimate. Std. Error t value Pr(>|t|)
e401 8744 1114 7.851 4.11e-15 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
best_irm <- dml_irm$coef
best_std_irm <- dml_irm$se
LS0tCnRpdGxlOiAiQ2F1c2FsIE1MIEJvb2sgQ2gxMC4yLTEwLjMgKDIpIgphdXRob3I6ICJSeXVraSBLb2JheWFzaGkiCmRhdGU6ICIyMDI0LzExLzI1IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyMgVGhlIEVmZmVjdCBvZiA0MDEoaykgRWxpZ2liaWxpdHkgb24gTmV0IEZpbmFuY2lhbCBBc3NldHMKCi0g5o6o5a6a44Kz44O844OJ44Go57WQ5p6cCgojIyMjICoqRGF0YSoqCgotIGBoZG1gIOODkeODg+OCseODvOOCuOOBq+WQq+OBvuOCjOOBpuOBiuOCiuOAgeODgOOCpuODs+ODreODvOODieWPr+iDveOBp+OBguOCiwoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygicmVudiIpCiMgcmVudjo6cmVzdG9yZSgpCiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoIm1sci1vcmcvbWxyM2V4dHJhbGVhcm5lcnMiLCBmb3JjZSA9IFRSVUUpICMgbmVlZGVkIHRvIHJ1biBib29zdGluZwoKcGFjbWFuOjpwX2xvYWQoCiAgeHRhYmxlLAogIGhkbSwKICBzYW5kd2ljaCwKICBnZ3Bsb3QyLAogIHJhbmRvbUZvcmVzdCwKICBkYXRhLnRhYmxlLAogIGdsbW5ldCwKICBycGFydCwKICBnYm0sCiAgRG91YmxlTUwsIAogIG1scjNsZWFybmVycywgCiAgbWxyMywgCiAgZGF0YS50YWJsZSwgCiAgcmFuZG9tRm9yZXN0LCAKICByYW5nZXIsCiAgbWxyM2V4dHJhbGVhcm5lcnMsCiAgbWJvb3N0CikKYGBgCgoKYGBge3J9CmRhdGEocGVuc2lvbikKZGF0YSA8LSBwZW5zaW9uCmRpbShkYXRhKQpgYGAKCgpgYGB7cn0KaGVscChwZW5zaW9uKQpgYGAKCgpgYGB7cn0KaGlzdF9lNDAxIDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IGU0MDEsIGZpbGwgPSBmYWN0b3IoZTQwMSkpKSArCiAgZ2VvbV9iYXIoKQpoaXN0X2U0MDEKYGBgCgoKYGBge3J9CmRlbnNfbmV0X3RmYSA8LSBnZ3Bsb3QoZGF0YSwgYWVzKHggPSBuZXRfdGZhLCBjb2xvciA9IGZhY3RvcihlNDAxKSwgZmlsbCA9IGZhY3RvcihlNDAxKSkpICsKICBnZW9tX2RlbnNpdHkoKSArCiAgeGxpbShjKC0yMDAwMCwgMTUwMDAwKSkgKwogIGZhY2V0X3dyYXAoLiB+IGU0MDEpCgpkZW5zX25ldF90ZmEKYGBgCgoKYGBge3J9CmUxIDwtIGRhdGFbZGF0YSRlNDAxID09IDEsIF0KZTAgPC0gZGF0YVtkYXRhJGU0MDEgPT0gMCwgXQpyb3VuZChtZWFuKGUxJG5ldF90ZmEpIC0gbWVhbihlMCRuZXRfdGZhKSwgMCkKYGBgCgoKYGBge3J9CnAxIDwtIGRhdGFbZGF0YSRwNDAxID09IDEsIF0KcDAgPC0gZGF0YVtkYXRhJHA0MDEgPT0gMCwgXQpyb3VuZChtZWFuKHAxJG5ldF90ZmEpIC0gbWVhbihwMCRuZXRfdGZhKSwgMCkKYGBgCgoKYGBge3J9CiMgb3V0Y29tZSB2YXJpYWJsZQp5IDwtIGRhdGFbLCAibmV0X3RmYSJdCiMgdHJlYXRtZW50IHZhcmlhYmxlCkQgPC0gZGF0YVssICJlNDAxIl0KRDIgPC0gZGF0YVssICJwNDAxIl0KRDMgPC0gZGF0YVssICJhNDAxIl0KCmNvbHVtbnNfdG9fZHJvcCA8LSBjKAogICJlNDAxIiwgInA0MDEiLCAiYTQwMSIsICJ0dyIsICJ0ZmEiLCAibmV0X3RmYSIsICJ0ZmFfaGUiLAogICJodmFsIiwgImhtb3J0IiwgImhlcXVpdHkiLAogICJuaWZhIiwgIm5ldF9uaWZhIiwgIm5ldF9uNDAxIiwgImlyYSIsCiAgImR1bTkxIiwgImljYXQiLCAiZWNhdCIsICJ6aGF0IiwKICAiaTEiLCAiaTIiLCAiaTMiLCAiaTQiLCAiaTUiLCAiaTYiLCAiaTciLAogICJhMSIsICJhMiIsICJhMyIsICJhNCIsICJhNSIKKQoKIyBjb3ZhcmlhdGVzClggPC0gZGF0YVssICEobmFtZXMoZGF0YSkgJWluJSBjb2x1bW5zX3RvX2Ryb3ApXQpgYGAKCgpgYGB7cn0KIyBDb25zdHJ1Y3RpbmcgdGhlIGNvbnRyb2xzCnhfZm9ybXVsYSA8LSBwYXN0ZSgifiAwICsgcG9seShhZ2UsIDYsIHJhdz1UUlVFKSArIHBvbHkoaW5jLCA4LCByYXc9VFJVRSkgKyBwb2x5KGVkdWMsIDQsIHJhdz1UUlVFKSAiLAogICAgICAgICAgICAgICAgICAgIisgcG9seShmc2l6ZSwgMiwgcmF3PVRSVUUpICsgbWFsZSArIG1hcnIgKyB0d29lYXJuICsgZGIgKyBwaXJhICsgaG93biIpClggPC0gYXMuZGF0YS50YWJsZShtb2RlbC5mcmFtZSh4X2Zvcm11bGEsIFgpKQpoZWFkKFgpCmBgYAoKLS0tCgojIyMjICoqUGFydGlhbGwgTGluZWFyIE1vZGVsIChQTE0pKioKCmBgYHtyfQpkbWwyX2Zvcl9wbG0gPC0gZnVuY3Rpb24oeCwgZCwgeSwgZHJlZywgeXJlZywgbmZvbGQgPSAzLCBtZXRob2QgPSAicmVncmVzc2lvbiIpIHsKICBub2JzIDwtIG5yb3coeCkgIyBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zCiAgZm9sZGlkIDwtIHJlcC5pbnQoMTpuZm9sZCwgdGltZXMgPSBjZWlsaW5nKG5vYnMgLyBuZm9sZCkpW3NhbXBsZS5pbnQobm9icyldICMgZGVmaW5lIGZvbGRzIGluZGljZXMKICBJIDwtIHNwbGl0KDE6bm9icywgZm9sZGlkKSAjIHNwbGl0IG9ic2VydmF0aW9uIGluZGljZXMgaW50byBmb2xkcwogIHl0aWwgPC0gZHRpbCA8LSByZXAoTkEsIG5vYnMpCiAgY2F0KCJmb2xkOiAiKQogIGZvciAoYiBpbiBzZXFfYWxvbmcoSSkpIHsKICAgIGlmIChtZXRob2QgPT0gInJlZ3Jlc3Npb24iKSB7CiAgICAgIGRmaXQgPC0gZHJlZyh4Wy1JW1tiXV0sIF0sIGRbLUlbW2JdXV0pICMgdGFrZSBhIGZvbGQgb3V0CiAgICAgIHlmaXQgPC0geXJlZyh4Wy1JW1tiXV0sIF0sIHlbLUlbW2JdXV0pICMgdGFrZSBhIGZvbGR0IG91dAogICAgICBkaGF0IDwtIHByZWRpY3QoZGZpdCwgeFtJW1tiXV0sIF0sIHR5cGUgPSAicmVzcG9uc2UiKSAjIHByZWRpY3QgdGhlIGxlZnQtb3V0IGZvbGQKICAgICAgeWhhdCA8LSBwcmVkaWN0KHlmaXQsIHhbSVtbYl1dLCBdLCB0eXBlID0gInJlc3BvbnNlIikgIyBwcmVkaWN0IHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIGR0aWxbSVtbYl1dXSA8LSAoZFtJW1tiXV1dIC0gZGhhdCkgIyByZWNvcmQgcmVzaWR1YWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIHl0aWxbSVtbYl1dXSA8LSAoeVtJW1tiXV1dIC0geWhhdCkgIyByZWNvcmQgcmVzaWRpYWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAicmFuZG9tZm9yZXN0IikgewogICAgICBkZml0IDwtIGRyZWcoeFstSVtbYl1dLCBdLCBhcy5mYWN0b3IoZClbLUlbW2JdXV0pICMgdGFrZSBhIGZvbGQgb3V0CiAgICAgIHlmaXQgPC0geXJlZyh4Wy1JW1tiXV0sIF0sIHlbLUlbW2JdXV0pICMgdGFrZSBhIGZvbGQgb3V0CiAgICAgIGRoYXQgPC0gcHJlZGljdChkZml0LCB4W0lbW2JdXSwgXSwgdHlwZSA9ICJwcm9iIilbLCAyXSAjIHByZWRpY3QgdGhlIGxlZnQtb3V0IGZvbGQKICAgICAgeWhhdCA8LSBwcmVkaWN0KHlmaXQsIHhbSVtbYl1dLCBdLCB0eXBlID0gInJlc3BvbnNlIikgIyBwcmVkaWN0IHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIGR0aWxbSVtbYl1dXSA8LSAoZFtJW1tiXV1dIC0gZGhhdCkgIyByZWNvcmQgcmVzaWR1YWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIHl0aWxbSVtbYl1dXSA8LSAoeVtJW1tiXV1dIC0geWhhdCkgIyByZWNvcmQgcmVzaWRpYWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAiZGVjaXNpb250cmVlcyIpIHsKICAgICAgZGZpdCA8LSBkcmVnKHhbLUlbW2JdXSwgXSwgYXMuZmFjdG9yKGQpWy1JW1tiXV1dKSAjIHRha2UgYSBmb2xkIG91dAogICAgICB5Zml0IDwtIHlyZWcoeFstSVtbYl1dLCBdLCB5Wy1JW1tiXV1dKSAjIHRha2UgYSBmb2xkIG91dAogICAgICBkaGF0IDwtIHByZWRpY3QoZGZpdCwgeFtJW1tiXV0sIF0pWywgMl0gIyBwcmVkaWN0IHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIHloYXQgPC0gcHJlZGljdCh5Zml0LCB4W0lbW2JdXSwgXSkgIyBwcmVkaWN0IHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIGR0aWxbSVtbYl1dXSA8LSAoZFtJW1tiXV1dIC0gZGhhdCkgIyByZWNvcmQgcmVzaWR1YWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIHl0aWxbSVtbYl1dXSA8LSAoeVtJW1tiXV1dIC0geWhhdCkgIyByZWNvcmQgcmVzaWRpYWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAiYm9vc3RlZHRyZWVzIikgewogICAgICBkZml0IDwtIGRyZWcoeFstSVtbYl1dLCBdLCBkWy1JW1tiXV1dKSAjIHRha2UgYSBmb2xkIG91dAogICAgICB5Zml0IDwtIHlyZWcoeFstSVtbYl1dLCBdLCB5Wy1JW1tiXV1dKSAjIHRha2UgYSBmb2xkIG91dAogICAgICBkaGF0IDwtIHByZWRpY3QoZGZpdCwgeFtJW1tiXV0sIF0sIHR5cGUgPSAicmVzcG9uc2UiKSAjIHByZWRpY3QgdGhlIGxlZnQtb3V0IGZvbGQKICAgICAgeWhhdCA8LSBwcmVkaWN0KHlmaXQsIHhbSVtbYl1dLCBdLCB0eXBlID0gInJlc3BvbnNlIikgIyBwcmVkaWN0IHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIGR0aWxbSVtbYl1dXSA8LSAoZFtJW1tiXV1dIC0gZGhhdCkgIyByZWNvcmQgcmVzaWR1YWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICAgIHl0aWxbSVtbYl1dXSA8LSAoeVtJW1tiXV1dIC0geWhhdCkgIyByZWNvcmQgcmVzaWRpYWwgZm9yIHRoZSBsZWZ0LW91dCBmb2xkCiAgICB9CiAgICBjYXQoYiwgIiAiKQogIH0KICByZml0IDwtIGxtKHl0aWwgfiBkdGlsKSAjIGVzdGltYXRlIHRoZSBtYWluIHBhcmFtZXRlciBieSByZWdyZXNzaW5nIG9uZSByZXNpZHVhbCBvbiB0aGUgb3RoZXIKICBjb2VmX2VzdCA8LSBjb2VmKHJmaXQpWzJdICMgZXh0cmFjdCBjb2VmZmljaWVudAogIHNlIDwtIHNxcnQodmNvdkhDKHJmaXQpWzIsIDJdKSAjIHJlY29yZCByb2J1c3Qgc3RhbmRhcmQgZXJyb3IKICBjYXQoc3ByaW50ZigiXG5jb2VmIChzZSkgPSAlZyAoJWcpXG4iLCBjb2VmX2VzdCwgc2UpKSAjIHByaW50aW5nIG91dHB1dAogIHJldHVybihsaXN0KGNvZWZfZXN0ID0gY29lZl9lc3QsIHNlID0gc2UsIGR0aWwgPSBkdGlsLCB5dGlsID0geXRpbCkpICMgc2F2ZSBvdXRwdXQgYW5kIHJlc2lkdWFscwp9CmBgYAoKCmBgYHtyfQpzdW1tYXJ5UExSIDwtIGZ1bmN0aW9uKHBvaW50LCBzdGRlcnIsIHJlc0QsIHJlc3ksIG5hbWUpIHsKICBkYXRhIDwtIGRhdGEuZnJhbWUoCiAgICBlc3RpbWF0ZSA9IHBvaW50LCAjIHBvaW50IGVzdGltYXRlCiAgICBzdGRlcnIgPSBzdGRlcnIsICMgc3RhbmRhcmQgZXJyb3IKICAgIGxvd2VyID0gcG9pbnQgLSAxLjk2ICogc3RkZXJyLCAjIGxvd2VyIGVuZCBvZiA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbAogICAgdXBwZXIgPSBwb2ludCArIDEuOTYgKiBzdGRlcnIsICMgdXBwZXIgZW5kIG9mIDk1JSBjb25maWRlbmNlIGludGVydmFsCiAgICBgcm1zZSB5YCA9IHNxcnQobWVhbihyZXN5XjIpKSwgIyBSTVNFIG9mIG1vZGVsIHRoYXQgcHJlZGljdHMgb3V0Y29tZSB5CiAgICBgcm1zZSBEYCA9IHNxcnQobWVhbihyZXNEXjIpKSwgIyBSTVNFIG9mIG1vZGVsIHRoYXQgcHJlZGljdHMgdHJlYXRtZW50IEQKICAgIGBhY2N1cmFjeSBEYCA9IG1lYW4oYWJzKHJlc0QpIDwgMC41KSAjIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBvZiBtb2RlbCBmb3IgRAogICkKICByb3duYW1lcyhkYXRhKSA8LSBuYW1lCiAgcmV0dXJuKGRhdGEpCn0KYGBgCgoqKkRvdWJsZSBMYXNzbyoqCgotIOWHpue9ruOBruaOqOWumuOBq+OBiuOBhOOBpuOAgemWouaVsOWei+OCkue3muW9oueiuueOh+ODouODh+ODq+OBq+OBmeOCi+WgtOWQiAoKYGBge3J9CiMgRE1MIHdpdGggTGFzc29DVgpzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIExhc3NvIENWIFxuIikpCgpkcmVnX2xhc3NvX2N2IDwtIGZ1bmN0aW9uKHgsIGQpIHsKICBjdi5nbG1uZXQoeCwgZCwgZmFtaWx5ID0gImdhdXNzaWFuIiwgYWxwaGEgPSAxLCBuZm9sZHMgPSA1KQp9CnlyZWdfbGFzc29fY3YgPC0gZnVuY3Rpb24oeCwgeSkgewogIGN2LmdsbW5ldCh4LCB5LCBmYW1pbHkgPSAiZ2F1c3NpYW4iLCBhbHBoYSA9IDEsIG5mb2xkcyA9IDUpCn0KCmRtbDJfcmVzdWx0cyA8LSBkbWwyX2Zvcl9wbG0oYXMubWF0cml4KFgpLCBELCB5LCBkcmVnX2xhc3NvX2N2LCB5cmVnX2xhc3NvX2N2LCBuZm9sZCA9IDUpCgpzdW1fbGFzc29fY3YgPC0gc3VtbWFyeVBMUihkbWwyX3Jlc3VsdHMkY29lZl9lc3QsIGRtbDJfcmVzdWx0cyRzZSwgZG1sMl9yZXN1bHRzJGR0aWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRtbDJfcmVzdWx0cyR5dGlsLCBuYW1lID0gIkxhc3NvQ1YiKQp0YWJsZXBsciA8LSBkYXRhLmZyYW1lKCkKdGFibGVwbHIgPC0gcmJpbmQoc3VtX2xhc3NvX2N2KQp0YWJsZXBscgpgYGAgCgoKYGBge3J9CiMgQmVjYXVzZSByZXNpZHVhbHMgYXJlIG91dHB1dCwgcmVjb25zdHJ1Y3QgZml0dGVkIHZhbHVlcyBmb3IgdXNlIGluIGVuc2VtYmxlCmRoYXRfbGFzc28gPC0gRCAtIGRtbDJfcmVzdWx0cyRkdGlsCnloYXRfbGFzc28gPC0geSAtIGRtbDJfcmVzdWx0cyR5dGlsCmBgYAoKLSDlh6bnva7jga7mjqjlrprjgavjgYrjgYTjgabjgIFMb2dpc3RpYyDlm57luLDjgpLnlKjjgYTjgovloLTlkIgKCmBgYHtyfQojIERNTCB3aXRoIExhc3NvL0xvZ2lzdGljCnNldC5zZWVkKDEyMykKY2F0KHNwcmludGYoIlxuRE1MIHdpdGggTGFzc28vTG9naXN0aWMgXG4iKSkKCmRyZWdfbG9naXN0aWNfY3YgPC0gZnVuY3Rpb24oeCwgZCkgewogIGN2LmdsbW5ldCh4LCBkLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDAsIG5mb2xkcyA9IDUpCn0KeXJlZ19sYXNzb19jdiA8LSBmdW5jdGlvbih4LCB5KSB7CiAgY3YuZ2xtbmV0KHgsIHksIGZhbWlseSA9ICJnYXVzc2lhbiIsIGFscGhhID0gMSwgbmZvbGRzID0gNSkKfQoKZG1sMl9yZXN1bHRzIDwtIGRtbDJfZm9yX3BsbShhcy5tYXRyaXgoWCksIEQsIHksIGRyZWdfbG9naXN0aWNfY3YsIHlyZWdfbGFzc29fY3YsIG5mb2xkID0gNSkKc3VtX2xhc3NvX2xvZ2lzdGljX2N2IDwtIHN1bW1hcnlQTFIoZG1sMl9yZXN1bHRzJGNvZWZfZXN0LCBkbWwyX3Jlc3VsdHMkc2UsIGRtbDJfcmVzdWx0cyRkdGlsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkbWwyX3Jlc3VsdHMkeXRpbCwgbmFtZSA9ICJMYXNzb0NWL0xvZ2lzdGljQ1YiKQp0YWJsZXBsciA8LSByYmluZCh0YWJsZXBsciwgc3VtX2xhc3NvX2xvZ2lzdGljX2N2KQp0YWJsZXBscgpgYGAKCmBgYHtyfQojIEJlY2F1c2UgcmVzaWR1YWxzIGFyZSBvdXRwdXQsIHJlY29uc3RydWN0IGZpdHRlZCB2YWx1ZXMgZm9yIHVzZSBpbiBlbnNlbWJsZQpkaGF0X2xhc3NvX2xvZ2lzdGljIDwtIEQgLSBkbWwyX3Jlc3VsdHMkZHRpbAp5aGF0X2xhc3NvX2xvZ2lzdGljIDwtIHkgLSBkbWwyX3Jlc3VsdHMkeXRpbApgYGAKCgoqKlJhbmRvbSBGb3Jlc3QqKgoKYGBge3J9CiMgRE1MIHdpdGggUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIFJhbmRvbSBGb3Jlc3QgXG4iKSkKCmRyZWdfcmYgPC0gZnVuY3Rpb24oeCwgZCkgewogIHJhbmRvbUZvcmVzdCh4LCBkLCBudHJlZSA9IDEwMDAsIG5vZGVzaXplID0gMTApCn0gIyBNTCBtZXRob2Q9Rm9yZXN0CnlyZWdfcmYgPC0gZnVuY3Rpb24oeCwgeSkgewogIHJhbmRvbUZvcmVzdCh4LCB5LCBudHJlZSA9IDEwMDAsIG5vZGVzaXplID0gMTApCn0gIyBNTCBtZXRob2Q9Rm9yZXN0CgpkbWwyX3Jlc3VsdHMgPC0gZG1sMl9mb3JfcGxtKGFzLm1hdHJpeChYKSwgRCwgeSwgZHJlZ19yZiwgeXJlZ19yZiwgbmZvbGQgPSA1LCBtZXRob2QgPSAicmFuZG9tZm9yZXN0IikKc3VtX3JmIDwtIHN1bW1hcnlQTFIoZG1sMl9yZXN1bHRzJGNvZWZfZXN0LCBkbWwyX3Jlc3VsdHMkc2UsIGRtbDJfcmVzdWx0cyRkdGlsLAogICAgICAgICAgICAgICAgICAgICBkbWwyX3Jlc3VsdHMkeXRpbCwgbmFtZSA9ICJSYW5kb20gRm9yZXN0IikKdGFibGVwbHIgPC0gcmJpbmQodGFibGVwbHIsIHN1bV9yZikKdGFibGVwbHIKYGBgCgoKYGBge3J9CiMgQmVjYXVzZSByZXNpZHVhbHMgYXJlIG91dHB1dCwgcmVjb25zdHJ1Y3QgZml0dGVkIHZhbHVlcyBmb3IgdXNlIGluIGVuc2VtYmxlCmRoYXRfcmYgPC0gRCAtIGRtbDJfcmVzdWx0cyRkdGlsCmRoYXRfcmYgPC0geSAtIGRtbDJfcmVzdWx0cyR5dGlsCmBgYAoKKipEZWNpc2lvbiBUcmVlcyoqCgpgYGB7cn0KIyBETUwgd2l0aCBEZWNpc2lvbiBUcmVlcwpzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIERlY2lzaW9uIFRyZWVzIFxuIikpCgpkcmVnX3RyIDwtIGZ1bmN0aW9uKHgsIGQpIHsKICBycGFydChhcy5mb3JtdWxhKCJEfi4iKSwgY2JpbmQoZGF0YS5mcmFtZShEID0gZCksIHgpLCBtZXRob2QgPSAiY2xhc3MiLCBtaW5idWNrZXQgPSAxMCwgY3AgPSAwLjAwMSkKfQpkcmVnX3RyIDwtIGZ1bmN0aW9uKHgsIHkpIHsKICBycGFydChhcy5mb3JtdWxhKCJ5fi4iKSwgY2JpbmQoZGF0YS5mcmFtZSh5ID0geSksIHgpLCBtaW5idWNrZXQgPSAxMCwgY3AgPSAwLjAwMSkKfQoKIyBkZWNpc2lvbiB0cmVlIHRha2VzIGluIFggYXMgZGF0YWZyYW1lLCBub3QgbWF0cml4L2FycmF5CmRtbDJfcmVzdWx0cyA8LSBkbWwyX2Zvcl9wbG0oWCwgRCwgeSwgZHJlZ190ciwgZHJlZ190ciwgbmZvbGQgPSA1LCBtZXRob2QgPSAiZGVjaXNpb250cmVlcyIpCnN1bV90ciA8LSBzdW1tYXJ5UExSKGRtbDJfcmVzdWx0cyRjb2VmX2VzdCwgZG1sMl9yZXN1bHRzJHNlLCBkbWwyX3Jlc3VsdHMkZHRpbCwKICAgICAgICAgICAgICAgICAgICAgZG1sMl9yZXN1bHRzJHl0aWwsIG5hbWUgPSAiRGVjaXNpb24gVHJlZXMiKQp0YWJsZXBsciA8LSByYmluZCh0YWJsZXBsciwgc3VtX3RyKQp0YWJsZXBscgpgYGAKCmBgYHtyfQojIEJlY2F1c2UgcmVzaWR1YWxzIGFyZSBvdXRwdXQsIHJlY29uc3RydWN0IGZpdHRlZCB2YWx1ZXMgZm9yIHVzZSBpbiBlbnNlbWJsZQpkaGF0X3RyIDwtIEQgLSBkbWwyX3Jlc3VsdHMkZHRpbAp5aGF0X3RyIDwtIHkgLSBkbWwyX3Jlc3VsdHMkeXRpbApgYGAKCioqQm9vc3RlZCBUcmVlcyoqCgpgYGB7cn0KIyBETUwgd2l0aCBCb29zdGVkIFRyZWVzCnNldC5zZWVkKDEyMykKY2F0KHNwcmludGYoIlxuRE1MIHdpdGggQm9vc3RlZCBUcmVlcyBcbiIpKQoKIyBOQjogZWFybHkgc3RvcHBpbmcgY2Fubm90IGVhc2lseSBiZSBpbXBsZW1lbnRlZCB3aXRoIGdibQojIyBzZXQgbi50cmVlcyA9IGJlc3QsIHdoZXJlIGJlc3QgPC0gZ2JtLnBlcmYoZHJlZ19ib29zdCwgcGxvdC5pdCA9IEZBTFNFKQpkcmVnX2Jvb3N0IDwtIGZ1bmN0aW9uKHgsIGQpIHsKICBnYm0oYXMuZm9ybXVsYSgiRH4uIiksIGNiaW5kKGRhdGEuZnJhbWUoRCA9IGQpLCB4KSwgZGlzdHJpYnV0aW9uID0gImJlcm5vdWxsaSIsCiAgICAgIGludGVyYWN0aW9uLmRlcHRoID0gMiwgbi50cmVlcyA9IDEwMCwgc2hyaW5rYWdlID0gLjEpCn0KeXJlZ19ib29zdCA8LSBmdW5jdGlvbih4LCB5KSB7CiAgZ2JtKGFzLmZvcm11bGEoInl+LiIpLCBjYmluZChkYXRhLmZyYW1lKHkgPSB5KSwgeCksIGRpc3RyaWJ1dGlvbiA9ICJnYXVzc2lhbiIsCiAgICAgIGludGVyYWN0aW9uLmRlcHRoID0gMiwgbi50cmVlcyA9IDEwMCwgc2hyaW5rYWdlID0gLjEpCn0KCiMgcGFzc2luZyB0aGVzZSB0aHJvdWdoIHJlZ3Jlc3Npb24gYXMgdHlwZT0icmVzcG9uc2UiLCBhbmQgRCBzaG91bGQgbm90IGJlIGZhY3RvciEKZG1sMl9yZXN1bHRzIDwtIGRtbDJfZm9yX3BsbShYLCBELCB5LCBkcmVnX2Jvb3N0LCB5cmVnX2Jvb3N0LCBuZm9sZCA9IDUsIG1ldGhvZCA9ICJib29zdGVkdHJlZXMiKQpzdW1fYm9vc3QgPC0gc3VtbWFyeVBMUihkbWwyX3Jlc3VsdHMkY29lZl9lc3QsIGRtbDJfcmVzdWx0cyRzZSwgZG1sMl9yZXN1bHRzJGR0aWwsCiAgICAgICAgICAgICAgICAgICAgICAgIGRtbDJfcmVzdWx0cyR5dGlsLCBuYW1lID0gIkJvb3N0ZWQgVHJlZXMiKQp0YWJsZXBsciA8LSByYmluZCh0YWJsZXBsciwgc3VtX2Jvb3N0KQp0YWJsZXBscgpgYGAKCmBgYHtyfQojIEJlY2F1c2UgcmVzaWR1YWxzIGFyZSBvdXRwdXQsIHJlY29uc3RydWN0IGZpdHRlZCB2YWx1ZXMgZm9yIHVzZSBpbiBlbnNlbWJsZQpkaGF0X2Jvb3N0IDwtIEQgLSBkbWwyX3Jlc3VsdHMkZHRpbAp5aGF0X2Jvb3N0IDwtIHkgLSBkbWwyX3Jlc3VsdHMkeXRpbApgYGAKCioqRW5zZW1ibGUqKgoKYGBge3J9CiMgQmVzdCBmaXQgaXMgYm9vc3RlZCB0cmVlcyBmb3IgYm90aCBEIGFuZCBZCgpzdW1fYmVzdCA8LSBzdW1tYXJ5UExSKGRtbDJfcmVzdWx0cyRjb2VmX2VzdCwgZG1sMl9yZXN1bHRzJHNlLCBkbWwyX3Jlc3VsdHMkZHRpbCwKICAgICAgICAgICAgICAgICAgICAgICBkbWwyX3Jlc3VsdHMkeXRpbCwgbmFtZSA9ICJCZXN0IikKdGFibGVwbHIgPC0gcmJpbmQodGFibGVwbHIsIHN1bV9iZXN0KQp0YWJsZXBscgpgYGAKCmBgYHtyfQojIExlYXN0IHNxdWFyZXMgbW9kZWwgYXZlcmFnZQoKbWFfZHRpbCA8LSBsbShEIH4gZGhhdF9sYXNzbyArIGRoYXRfbGFzc29fbG9naXN0aWMgKyBkaGF0X3JmICsgZGhhdF90ciArIGRoYXRfYm9vc3QpJHJlc2lkdWFscwptYV95dGlsIDwtIGxtKHkgfiB5aGF0X2xhc3NvICsgeWhhdF9sYXNzb19sb2dpc3RpYyArIGRoYXRfcmYgKyB5aGF0X3RyICsgeWhhdF9ib29zdCkkcmVzaWR1YWxzCgpyZml0IDwtIGxtKG1hX3l0aWwgfiBtYV9kdGlsKSAjIGVzdGltYXRlIHRoZSBtYWluIHBhcmFtZXRlciBieSByZWdyZXNzaW5nIG9uZSByZXNpZHVhbCBvbiB0aGUgb3RoZXIKY29lZl9lc3QgPC0gY29lZihyZml0KVsyXSAjIGV4dHJhY3QgY29lZmZpY2llbnQKc2UgPC0gc3FydCh2Y292SEMocmZpdClbMiwgMl0pICMgcmVjb3JkIHJvYnVzdCBzdGFuZGFyZCBlcnJvcgoKc3VtLm1hIDwtIHN1bW1hcnlQTFIoY29lZl9lc3QsIHNlLCBtYV9kdGlsLCBtYV95dGlsLCBuYW1lID0gIk1vZGVsIEF2ZXJhZ2UiKQp0YWJsZXBsciA8LSByYmluZCh0YWJsZXBsciwgc3VtLm1hKQp0YWJsZXBscgpgYGAKCgotLS0KCiMjIyMgKipJbnRlcmFjdGl2ZSBSZWdyZXNzaW9uIE1vZGVsIChJUk0pKioKCmBgYHtyfQpkbWwyX2Zvcl9pcm0gPC0gZnVuY3Rpb24oeCwgZCwgeSwgZHJlZywgeXJlZzAsIHlyZWcxLCB0cmltbWluZyA9IDAuMDEsIG5mb2xkID0gNSwgbWV0aG9kID0gInJlZ3Jlc3Npb24iKSB7CiAgeWhhdDAgPC0gcmVwKDAsIGxlbmd0aCh5KSkKICB5aGF0MSA8LSByZXAoMCwgbGVuZ3RoKHkpKQogIERoYXQgPC0gcmVwKDAsIGxlbmd0aChkKSkKCiAgbm9icyA8LSBucm93KHgpICMgbnVtYmVyIG9mIG9ic2VydmF0aW9ucwogIGZvbGRpZCA8LSByZXAuaW50KDE6bmZvbGQsIHRpbWVzID0gY2VpbGluZyhub2JzIC8gbmZvbGQpKVtzYW1wbGUuaW50KG5vYnMpXSAjIGRlZmluZSBmb2xkcyBpbmRpY2VzCiAgSSA8LSBzcGxpdCgxOm5vYnMsIGZvbGRpZCkgIyBzcGxpdCBvYnNlcnZhdGlvbiBpbmRpY2VzIGludG8gZm9sZHMKICB5dGlsIDwtIGR0aWwgPC0gcmVwKE5BLCBub2JzKQoKICBjYXQoImZvbGQ6ICIpCiAgZm9yIChiIGluIHNlcV9hbG9uZyhJKSkgewogICAgIyBkZWZpbmUgaGVscGZ1bCB2YXJpYWJsZXMKICAgIERub3RiIDwtIGRbLUlbW2JdXV0KICAgIFhiIDwtIFhbSVtbYl1dLCBdCiAgICBYbm90YiA8LSBYWy1JW1tiXV0sIF0KCiAgICAjIHRyYWluaW5nIGRmcyBzdWJzZXR0ZWQgb24gdGhlIC1JW1tiXV0gZm9sZAogICAgWEQwIDwtIFhbLUlbW2JdXSwgXVtkWy1JW1tiXV1dID09IDBdCiAgICB5RDAgPC0geVstSVtbYl1dXVtkWy1JW1tiXV1dID09IDBdCiAgICBYRDEgPC0gWFstSVtbYl1dLCBdW2RbLUlbW2JdXV0gPT0gMV0KICAgIHlEMSA8LSB5Wy1JW1tiXV1dW2RbLUlbW2JdXV0gPT0gMV0KCiAgICBpZiAobWV0aG9kID09ICJyZWdyZXNzaW9uIikgewogICAgICB5Zml0MCA8LSB5cmVnMChhcy5tYXRyaXgoWEQwKSwgeUQwKQogICAgICB5Zml0MSA8LSB5cmVnMShhcy5tYXRyaXgoWEQxKSwgeUQxKQogICAgICB5aGF0MFtJW1tiXV1dIDwtIHByZWRpY3QoeWZpdDAsIGFzLm1hdHJpeChYYikpICMgZGVmYXVsdCBpcyB0eXBlID0gInJlc3BvbnNlIiBmb3IgZ2xtbmV0IGZhbWlseSBnYXVzc2lhbgogICAgICB5aGF0MVtJW1tiXV1dIDwtIHByZWRpY3QoeWZpdDEsIGFzLm1hdHJpeChYYikpCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAicmFuZG9tZm9yZXN0IikgewogICAgICB5Zml0MCA8LSB5cmVnMChYRDAsIHlEMCkKICAgICAgeWZpdDEgPC0geXJlZzEoWEQxLCB5RDEpCiAgICAgIHloYXQwW0lbW2JdXV0gPC0gcHJlZGljdCh5Zml0MCwgWGIpICMgZGVmYXVsdCBpcyB0eXBlID0gInJlc3BvbnNlIiBmb3IgcmYKICAgICAgeWhhdDFbSVtbYl1dXSA8LSBwcmVkaWN0KHlmaXQxLCBYYikKICAgIH0gZWxzZSBpZiAobWV0aG9kID09ICJkZWNpc2lvbnRyZWVzIikgewogICAgICB5Zml0MCA8LSB5cmVnMChYRDAsIHlEMCkKICAgICAgeWZpdDEgPC0geXJlZzEoWEQxLCB5RDEpCiAgICAgIHloYXQwW0lbW2JdXV0gPC0gcHJlZGljdCh5Zml0MCwgWGIpICMgZGVmYXVsdCBpcyB0eXBlID0gInZlY3RvciIgZm9yIGRlY2lzaW9uCiAgICAgIHloYXQxW0lbW2JdXV0gPC0gcHJlZGljdCh5Zml0MSwgWGIpCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAiYm9vc3RlZHRyZWVzIikgewogICAgICB5Zml0MCA8LSB5cmVnMChhcy5kYXRhLmZyYW1lKFhEMCksIHlEMCkKICAgICAgeWZpdDEgPC0geXJlZzEoYXMuZGF0YS5mcmFtZShYRDEpLCB5RDEpCiAgICAgIHloYXQwW0lbW2JdXV0gPC0gcHJlZGljdCh5Zml0MCwgWGIpICMgZGVmYXVsdCBpcyB0eXBlID0gInJlc3BvbnNlIiBmb3IgYm9vc3RlZAogICAgICB5aGF0MVtJW1tiXV1dIDwtIHByZWRpY3QoeWZpdDEsIFhiKQogICAgfQoKICAgICMgcHJvcGVuc2l0eSBzY29yZXM6CiAgICBpZiAobWV0aG9kID09ICJyZWdyZXNzaW9uIikgewogICAgICBkZml0X2IgPC0gZHJlZyhhcy5tYXRyaXgoWG5vdGIpLCBEbm90YikKICAgICAgZGhhdF9iIDwtIHByZWRpY3QoZGZpdF9iLCBhcy5tYXRyaXgoWGIpLCB0eXBlID0gInJlc3BvbnNlIikgIyBkZWZhdWx0IGlzIHR5cGU9ImxpbmsiIGZvciBmYW1pbHkgYmlub21pYWwhCiAgICB9IGVsc2UgaWYgKG1ldGhvZCA9PSAicmFuZG9tZm9yZXN0IikgewogICAgICBkZml0X2IgPC0gZHJlZyhYbm90YiwgYXMuZmFjdG9yKERub3RiKSkKICAgICAgZGhhdF9iIDwtIHByZWRpY3QoZGZpdF9iLCBYYiwgdHlwZSA9ICJwcm9iIilbLCAyXQogICAgfSBlbHNlIGlmIChtZXRob2QgPT0gImRlY2lzaW9udHJlZXMiKSB7CiAgICAgIGRmaXRfYiA8LSBkcmVnKFhub3RiLCBEbm90YikKICAgICAgZGhhdF9iIDwtIHByZWRpY3QoZGZpdF9iLCBYYilbLCAyXQogICAgfSBlbHNlIGlmIChtZXRob2QgPT0gImJvb3N0ZWR0cmVlcyIpIHsKICAgICAgZGZpdF9iIDwtIGRyZWcoYXMuZGF0YS5mcmFtZShYbm90YiksIERub3RiKQogICAgICBkaGF0X2IgPC0gcHJlZGljdChkZml0X2IsIFhiLCB0eXBlID0gInJlc3BvbnNlIikKICAgIH0KICAgIGRoYXRfYiA8LSBwbWF4KHBtaW4oZGhhdF9iLCAxIC0gdHJpbW1pbmcpLCB0cmltbWluZykgIyB0cmltbWluZyBzbyBzY29yZXMgYXJlIGJldHdlZW4gW3RyaW1taW5nLCAoMS10cmltbWluZyldCiAgICBEaGF0W0lbW2JdXV0gPC0gZGhhdF9iCgogICAgY2F0KGIsICIgIikKICB9CgogICMgUHJlZGljdGlvbiBvZiB0cmVhdG1lbnQgYW5kIG91dGNvbWUgZm9yIG9ic2VydmVkIGluc3RydW1lbnQKICB5aGF0IDwtIHloYXQwICogKDEgLSBEKSArIHloYXQxICogRAogICMgcmVzaWR1YWxzCiAgeXRpbCA8LSB5IC0geWhhdAogIGR0aWwgPC0gRCAtIERoYXQKICAjIGRvdWJseSByb2J1c3QgcXVhbnRpdHkgZm9yIGV2ZXJ5IHNhbXBsZQogIGRyaGF0IDwtIHloYXQxIC0geWhhdDAgKyAoeSAtIHloYXQpICogKEQgLyBEaGF0IC0gKDEgLSBEKSAvICgxIC0gRGhhdCkpCiAgY29lZl9lc3QgPC0gbWVhbihkcmhhdCkKICB2YXJpIDwtIHZhcihkcmhhdCkKICBzZSA8LSBzcXJ0KHZhcmkgLyBucm93KFgpKQogIGNhdCgicG9pbnQiLCBjb2VmX2VzdCkKICBjYXQoInNlIiwgc2UpCiAgcmV0dXJuKGxpc3QoY29lZl9lc3QgPSBjb2VmX2VzdCwgc2UgPSBzZSwgeXRpbCA9IHl0aWwsIGR0aWwgPSBkdGlsLCBkcmhhdCA9IGRyaGF0LAogICAgICAgICAgICAgIHloYXQwID0geWhhdDAsIHloYXQxID0geWhhdDEsIGRoYXQgPSBEaGF0LCB5aGF0ID0geWhhdCkpCn0KCnN1bW1hcnlJUk0gPC0gZnVuY3Rpb24oY29lZl9lc3QsIHNlLCB5dGlsLCBkdGlsLCBkcmhhdCwgbmFtZSkgewogIHN1bW1hcnlfZGF0YSA8LSBkYXRhLmZyYW1lKAogICAgZXN0aW1hdGUgPSBjb2VmX2VzdCwgIyBwb2ludCBlc3RpbWF0ZQogICAgc2UgPSBzZSwgIyBzdGFuZGFyZCBlcnJvcgogICAgbG93ZXIgPSBjb2VmX2VzdCAtIDEuOTYgKiBzZSwgIyBsb3dlciBlbmQgb2YgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwKICAgIHVwcGVyID0gY29lZl9lc3QgKyAxLjk2ICogc2UsICMgdXBwZXIgZW5kIG9mIDk1JSBjb25maWRlbmNlIGludGVydmFsCiAgICBybXNlX3kgPSBzcXJ0KG1lYW4oeXRpbF4yKSksICMgcmVzIG9mIG1vZGVsIHRoYXQgcHJlZGljdHMgb3V0Y29tZSB5CiAgICBybXNlX0QgPSBzcXJ0KG1lYW4oZHRpbF4yKSksICMgcmVzIG9mIG1vZGVsIHRoYXQgcHJlZGljdHMgdHJlYXRtZW50IEQKICAgIGFjY3VyYWN5X0QgPSBtZWFuKGFicyhkdGlsKSA8IDAuNSkgIyBiaW5hcnkgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgbW9kZWwgZm9yIEQKICApCiAgcm93Lm5hbWVzKHN1bW1hcnlfZGF0YSkgPC0gbmFtZQogIHJldHVybihzdW1tYXJ5X2RhdGEpCn0KYGBgCgoqKkRvdWJsZSBMYXNzbyoqCgpgYGB7cn0KIyBETUwgd2l0aCBMYXNzby9Mb2dpc3RpYwpzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIExhc3NvQ1YvTG9naXN0aWMgXG4iKSkKCmRyZWdfbGFzc29fY3YgPC0gZnVuY3Rpb24oeCwgZCkgewogIGN2LmdsbW5ldCh4LCBkLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDAsIG5mb2xkcyA9IDUpCn0KeXJlZzBfbGFzc29fY3YgPC0gZnVuY3Rpb24oeCwgeSkgewogIGN2LmdsbW5ldCh4LCB5LCBmYW1pbHkgPSAiZ2F1c3NpYW4iLCBhbHBoYSA9IDEsIG5mb2xkcyA9IDUpCn0KeXJlZzFfbGFzc29fY3YgPC0gZnVuY3Rpb24oeCwgeSkgewogIGN2LmdsbW5ldCh4LCB5LCBmYW1pbHkgPSAiZ2F1c3NpYW4iLCBhbHBoYSA9IDEsIG5mb2xkcyA9IDUpCn0KCiMgbW9yZSBmb2xkcyBzZWVtcyB0byBoZWxwIHN0YWJpbGl6ZSBmaW5pdGUgc2FtcGxlIHBlcmZvcm1hbmNlCmRtbDJfcmVzdWx0cyA8LSBkbWwyX2Zvcl9pcm0oWCwgRCwgeSwgZHJlZ19sYXNzb19jdiwgeXJlZzBfbGFzc29fY3YsIHlyZWcxX2xhc3NvX2N2LCBuZm9sZCA9IDUpCnN1bV9sYXNzb19jdiA8LSBzdW1tYXJ5SVJNKGRtbDJfcmVzdWx0cyRjb2VmX2VzdCwgZG1sMl9yZXN1bHRzJHNlLCBkbWwyX3Jlc3VsdHMkeXRpbCwgZG1sMl9yZXN1bHRzJGR0aWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRtbDJfcmVzdWx0cyRkcmhhdCwgbmFtZSA9ICJMYXNzb0NWTG9naXN0aWMiKQp0YWJsZWlybSA8LSBkYXRhLmZyYW1lKCkKdGFibGVpcm0gPC0gcmJpbmQoc3VtX2xhc3NvX2N2KQp0YWJsZWlybQoKeWhhdDBfbGFzc28gPC0gZG1sMl9yZXN1bHRzJHloYXQwCnloYXQxX2xhc3NvIDwtIGRtbDJfcmVzdWx0cyR5aGF0MQpkaGF0X2xhc3NvIDwtIGRtbDJfcmVzdWx0cyRkaGF0CnloYXRfbGFzc28gPC0gZG1sMl9yZXN1bHRzJHloYXQKYGBgCgoqKlJhbmRvbSBGb3Jlc3QqKgoKYGBge3J9CiMgRE1MIHdpdGggUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIFJhbmRvbSBGb3Jlc3QgXG4iKSkKCmRyZWdfcmYgPC0gZnVuY3Rpb24oeCwgZCkgewogIHJhbmRvbUZvcmVzdCh4LCBkLCBudHJlZSA9IDEwMDAsIG5vZGVzaXplID0gMTApCn0gIyBNTCBtZXRob2Q9Rm9yZXN0CnlyZWcwX3JmIDwtIGZ1bmN0aW9uKHgsIHkpIHsKICByYW5kb21Gb3Jlc3QoeCwgeSwgbnRyZWUgPSAxMDAwLCBub2Rlc2l6ZSA9IDEwKQp9ICMgTUwgbWV0aG9kPUZvcmVzdAp5cmVnMV9yZiA8LSBmdW5jdGlvbih4LCB5KSB7CiAgcmFuZG9tRm9yZXN0KHgsIHksIG50cmVlID0gMTAwMCwgbm9kZXNpemUgPSAxMCkKfSAjIE1MIG1ldGhvZD1Gb3Jlc3QKCgpkbWwyX3Jlc3VsdHMgPC0gZG1sMl9mb3JfaXJtKGFzLm1hdHJpeChYKSwgRCwgeSwgZHJlZ19yZiwgeXJlZzBfcmYsIHlyZWcxX3JmLCBuZm9sZCA9IDUsIG1ldGhvZCA9ICJyYW5kb21mb3Jlc3QiKQpzdW1fcmYgPC0gc3VtbWFyeUlSTShkbWwyX3Jlc3VsdHMkY29lZl9lc3QsIGRtbDJfcmVzdWx0cyRzZSwgZG1sMl9yZXN1bHRzJHl0aWwsIGRtbDJfcmVzdWx0cyRkdGlsLAogICAgICAgICAgICAgICAgICAgICBkbWwyX3Jlc3VsdHMkZHJoYXQsIG5hbWUgPSAiUmFuZG9tIEZvcmVzdCIpCnRhYmxlaXJtIDwtIHJiaW5kKHRhYmxlaXJtLCBzdW1fcmYpCnRhYmxlaXJtCgp5aGF0MF9yZiA8LSBkbWwyX3Jlc3VsdHMkeWhhdDAKeWhhdDFfcmYgPC0gZG1sMl9yZXN1bHRzJHloYXQxCmRoYXRfcmYgPC0gZG1sMl9yZXN1bHRzJGRoYXQKZGhhdF9yZiA8LSBkbWwyX3Jlc3VsdHMkeWhhdApgYGAKCioqRGVjaXNpb24gVHJlZXMqKgoKYGBge3J9CiMgRE1MIHdpdGggRGVjaXNpb24gVHJlZXMKc2V0LnNlZWQoMTIzKQpjYXQoc3ByaW50ZigiXG5ETUwgd2l0aCBEZWNpc2lvbiBUcmVlcyBcbiIpKQoKZHJlZ190ciA8LSBmdW5jdGlvbih4LCBkKSB7CiAgcnBhcnQoYXMuZm9ybXVsYSgiRH4uIiksIGNiaW5kKGRhdGEuZnJhbWUoRCA9IGQpLCB4KSwgbWV0aG9kID0gImNsYXNzIiwgbWluYnVja2V0ID0gMTAsIGNwID0gMC4wMDEpCn0KeXJlZzBfdHIgPC0gZnVuY3Rpb24oeCwgeSkgewogIHJwYXJ0KGFzLmZvcm11bGEoInl+LiIpLCBjYmluZChkYXRhLmZyYW1lKHkgPSB5KSwgeCksIG1pbmJ1Y2tldCA9IDEwLCBjcCA9IDAuMDAxKQp9CnlyZWcxX3RyIDwtIGZ1bmN0aW9uKHgsIHkpIHsKICBycGFydChhcy5mb3JtdWxhKCJ5fi4iKSwgY2JpbmQoZGF0YS5mcmFtZSh5ID0geSksIHgpLCBtaW5idWNrZXQgPSAxMCwgY3AgPSAwLjAwMSkKfQoKZG1sMl9yZXN1bHRzIDwtIGRtbDJfZm9yX2lybShYLCBELCB5LCBkcmVnX3RyLCB5cmVnMF90ciwgeXJlZzFfdHIsIG5mb2xkID0gNSwgbWV0aG9kID0gImRlY2lzaW9udHJlZXMiKQpzdW1fdHIgPC0gc3VtbWFyeUlSTShkbWwyX3Jlc3VsdHMkY29lZl9lc3QsIGRtbDJfcmVzdWx0cyRzZSwgZG1sMl9yZXN1bHRzJHl0aWwsIGRtbDJfcmVzdWx0cyRkdGlsLAogICAgICAgICAgICAgICAgICAgICBkbWwyX3Jlc3VsdHMkZHJoYXQsIG5hbWUgPSAiRGVjaXNpb24gVHJlZXMiKQp0YWJsZWlybSA8LSByYmluZCh0YWJsZWlybSwgc3VtX3RyKQp0YWJsZWlybQoKeWhhdDBfdHIgPC0gZG1sMl9yZXN1bHRzJHloYXQwCnloYXQxX3RyIDwtIGRtbDJfcmVzdWx0cyR5aGF0MQpkaGF0X3RyIDwtIGRtbDJfcmVzdWx0cyRkaGF0CnloYXRfdHIgPC0gZG1sMl9yZXN1bHRzJHloYXQKYGBgCgoqKkJvb3N0ZWQgVHJlZXMqKgoKYGBge3J9CiMgRE1MIHdpdGggQm9vc3RlZCBUcmVlcwpzZXQuc2VlZCgxMjMpCmNhdChzcHJpbnRmKCJcbkRNTCB3aXRoIEJvb3N0ZWQgVHJlZXMgXG4iKSkKCiMgTkI6IGVhcmx5IHN0b3BwaW5nIGNhbm5vdCBlYXNpbHkgYmUgaW1wbGVtZW50ZWQgd2l0aCBnYm0KIyMgc2V0IG4udHJlZXMgPSBiZXN0LCB3aGVyZSBiZXN0IDwtIGdibS5wZXJmKGRyZWdfYm9vc3QsIHBsb3QuaXQgPSBGQUxTRSkKZHJlZ19ib29zdCA8LSBmdW5jdGlvbih4LCBkKSB7CiAgZ2JtKGFzLmZvcm11bGEoIkR+LiIpLCBjYmluZChkYXRhLmZyYW1lKEQgPSBkKSwgeCksIGRpc3RyaWJ1dGlvbiA9ICJiZXJub3VsbGkiLAogICAgICBpbnRlcmFjdGlvbi5kZXB0aCA9IDIsIG4udHJlZXMgPSAxMDAsIHNocmlua2FnZSA9IC4xKQp9CnlyZWcwX2Jvb3N0IDwtIGZ1bmN0aW9uKHgsIHkpIHsKICBnYm0oYXMuZm9ybXVsYSgieX4uIiksIGNiaW5kKGRhdGEuZnJhbWUoeSA9IHkpLCB4KSwgZGlzdHJpYnV0aW9uID0gImdhdXNzaWFuIiwKICAgICAgaW50ZXJhY3Rpb24uZGVwdGggPSAyLCBuLnRyZWVzID0gMTAwLCBzaHJpbmthZ2UgPSAuMSkKfQp5cmVnMV9ib29zdCA8LSBmdW5jdGlvbih4LCB5KSB7CiAgZ2JtKGFzLmZvcm11bGEoInl+LiIpLCBjYmluZChkYXRhLmZyYW1lKHkgPSB5KSwgeCksIGRpc3RyaWJ1dGlvbiA9ICJnYXVzc2lhbiIsCiAgICAgIGludGVyYWN0aW9uLmRlcHRoID0gMiwgbi50cmVlcyA9IDEwMCwgc2hyaW5rYWdlID0gLjEpCn0KCiMgcGFzc2luZyB0aGVzZSB0aHJvdWdoIHJlZ3Jlc3Npb24gYXMgdHlwZT0icmVzcG9uc2UiLCBhbmQgRCBzaG91bGQgbm90IGJlIGZhY3RvciEKZG1sMl9yZXN1bHRzIDwtIGRtbDJfZm9yX2lybShYLCBELCB5LCBkcmVnX2Jvb3N0LCB5cmVnMF9ib29zdCwgeXJlZzFfYm9vc3QsIG5mb2xkID0gNSwgbWV0aG9kID0gImJvb3N0ZWR0cmVlcyIpCnN1bV9ib29zdCA8LSBzdW1tYXJ5SVJNKGRtbDJfcmVzdWx0cyRjb2VmX2VzdCwgZG1sMl9yZXN1bHRzJHNlLCBkbWwyX3Jlc3VsdHMkeXRpbCwgZG1sMl9yZXN1bHRzJGR0aWwsCiAgICAgICAgICAgICAgICAgICAgICAgIGRtbDJfcmVzdWx0cyRkcmhhdCwgbmFtZSA9ICJCb29zdGVkIFRyZWVzIikKdGFibGVpcm0gPC0gcmJpbmQodGFibGVpcm0sIHN1bV9ib29zdCkKdGFibGVpcm0KCnloYXQwX2Jvb3N0IDwtIGRtbDJfcmVzdWx0cyR5aGF0MAp5aGF0MV9ib29zdCA8LSBkbWwyX3Jlc3VsdHMkeWhhdDEKZGhhdF9ib29zdCA8LSBkbWwyX3Jlc3VsdHMkZGhhdAp5aGF0X2Jvb3N0IDwtIGRtbDJfcmVzdWx0cyR5aGF0CmBgYAoKKipFbnNlbWJsZSoqCgpgYGB7cn0KIyBFbnNlbWJsZXMKCiMgQmVzdAojIFdlJ2xsIGxvb2sgYXQgbW9kZWwgdGhhdCBkb2VzIGJlc3QgZm9yIFkgb3ZlcmFsbC4gQ291bGQgYWxzbyB1c2UgZGlmZmVyZW50IG1vZGVsIGZvciBZMCBhbmQgWTEKIyBIZXJlLCB0aGUgYmVzdCBwZXJmb3JtYW5jZSBmb3IgWSBpcyB0aGUgcmFuZG9tIGZvcmVzdCBhbmQgZm9yIEQgdGhlIGJvb3N0ZWQgdHJlZQoKIyByZXNpZHVhbHMKeXRpbCA8LSB5IC0gZGhhdF9yZgpkdGlsIDwtIEQgLSBkaGF0X2Jvb3N0CiMgZG91Ymx5IHJvYnVzdCBxdWFudGl0eSBmb3IgZXZlcnkgc2FtcGxlCmRyaGF0IDwtIHloYXQxX3JmIC0geWhhdDBfcmYgKyAoeSAtIGRoYXRfcmYpICogKEQgLyBkaGF0X2Jvb3N0IC0gKDEgLSBEKSAvICgxIC0gZGhhdF9ib29zdCkpCmNvZWZfZXN0IDwtIG1lYW4oZHJoYXQpCnZhcmkgPC0gdmFyKGRyaGF0KQpzZSA8LSBzcXJ0KHZhcmkgLyBucm93KFgpKQoKc3VtX2Jlc3QgPC0gc3VtbWFyeUlSTShjb2VmX2VzdCwgc2UsIHl0aWwsIGR0aWwsIGRyaGF0LCBuYW1lID0gIkJlc3QiKQp0YWJsZWlybSA8LSByYmluZCh0YWJsZWlybSwgc3VtX2Jlc3QpCnRhYmxlaXJtCmBgYAoKYGBge3J9CiMgTGVhc3Qgc3F1YXJlcyBtb2RlbCBhdmVyYWdlCiMgV2UnbGwgbG9vayBhdCB3ZWlnaHRzIHRoYXQgZG8gYmVzdCBqb2IgZm9yIFkgb3ZlcmFsbC4gQ291bGQgYWxzbyB1c2UgZGlmZmVyZW50IHdlaWdodHMgZm9yIFkwIGFuZCBZMQoKbWFfZHcgPC0gbG0oRCB+IGRoYXRfbGFzc28gKyBkaGF0X3JmICsgZGhhdF90ciArIGRoYXRfYm9vc3QpJGNvZWYKbWFfeXcgPC0gbG0oeSB+IHloYXRfbGFzc28gKyBkaGF0X3JmICsgeWhhdF90ciArIHloYXRfYm9vc3QpJGNvZWYKCkRoYXRzIDwtIGNiaW5kKGFzLm1hdHJpeChyZXAoMSwgbnJvdyhYKSkpLCBkaGF0X2xhc3NvLCBkaGF0X3JmLCBkaGF0X3RyLCBkaGF0X2Jvb3N0KQpZMHMgPC0gY2JpbmQoYXMubWF0cml4KHJlcCgxLCBucm93KFgpKSksIHloYXQwX2xhc3NvLCB5aGF0MF9yZiwgeWhhdDBfdHIsIHloYXQwX2Jvb3N0KQpZMXMgPC0gY2JpbmQoYXMubWF0cml4KHJlcCgxLCBucm93KFgpKSksIHloYXQxX2xhc3NvLCB5aGF0MV9yZiwgeWhhdDFfdHIsIHloYXQxX2Jvb3N0KQoKZGhhdCA8LSBEaGF0cyAlKiUgYXMubWF0cml4KG1hX2R3KQp5aGF0MCA8LSBZMHMgJSolIGFzLm1hdHJpeChtYV95dykKeWhhdDEgPC0gWTFzICUqJSBhcy5tYXRyaXgobWFfeXcpCgojIFByZWRpY3Rpb24gb2YgdHJlYXRtZW50IGFuZCBvdXRjb21lIGZvciBvYnNlcnZlZCBpbnN0cnVtZW50CnloYXQgPC0geWhhdDAgKiAoMSAtIEQpICsgeWhhdDEgKiBECiMgcmVzaWR1YWxzCnl0aWwgPC0geSAtIHloYXQKZHRpbCA8LSBEIC0gZGhhdAojIGRvdWJseSByb2J1c3QgcXVhbnRpdHkgZm9yIGV2ZXJ5IHNhbXBsZQpkcmhhdCA8LSB5aGF0MSAtIHloYXQwICsgKHkgLSB5aGF0KSAqIChEIC8gZGhhdCAtICgxIC0gRCkgLyAoMSAtIGRoYXQpKQpjb2VmX2VzdCA8LSBtZWFuKGRyaGF0KQp2YXJpIDwtIHZhcihkcmhhdCkKc2UgPC0gc3FydCh2YXJpIC8gbnJvdyhYKSkKCnN1bS5tYSA8LSBzdW1tYXJ5SVJNKGNvZWZfZXN0LCBzZSwgeXRpbCwgZHRpbCwgZHJoYXQsIG5hbWUgPSAiTW9kZWwgQXZlcmFnZSIpCnRhYmxlaXJtIDwtIHJiaW5kKHRhYmxlaXJtLCBzdW0ubWEpCnRhYmxlaXJtCmBgYAoKCi0tLQoKIyMjIERvdWJsZU1MIHBhY2thZ2Ug44Gr44KI44KL5o6o5a6aCgotIOS7iuOBvuOBp+OBr+iHquOCiemWouaVsOOCkuabuOOBhOOBpuOBhOOBn+OBjOOAgWBFY29uTUxgIChQeXRob24pIOOChCBgRG91YmxlTUxgIChSICYgUHl0aG9uKSDjgarjganjga7jg5Hjg4PjgrHjg7zjgrjjgYzliKnnlKjlj6/og73jgafjgYLjgosKICAtIFtEb3VibGVNTCDjg5Hjg4PjgrHjg7zjgrjjga7jgrXjgqTjg4hdKGh0dHBzOi8vZG9jcy5kb3VibGVtbC5vcmcvc3RhYmxlL2luZGV4Lmh0bWwpCiAgLSBbbWwzIOODkeODg+OCseODvOOCuF0oaHR0cHM6Ly9tbHIzYm9vay5tbHItb3JnLmNvbSkg44KS5YaF6YOo44Gn5YuV44GL44GX44Gm44GE44KLCgoqKuODh+ODvOOCv+S9nOaIkCoqCgpgYGB7cn0KIyBDb25zdHJ1Y3RpbmcgdGhlIGRhdGEgKGFzIERvdWJsZU1MRGF0YSkKZm9ybXVsYV9mbGV4IDwtIHBhc3RlKCJuZXRfdGZhIH4gZTQwMSArIHBvbHkoYWdlLCA2LCByYXc9VFJVRSkgKyBwb2x5KGluYywgOCwgcmF3PVRSVUUpICIsCiAgICAgICAgICAgICAgICAgICAgICAiKyBwb2x5KGVkdWMsIDQsIHJhdz1UUlVFKSArIHBvbHkoZnNpemUsIDIsIHJhdz1UUlVFKSArIG1hcnIgKyB0d29lYXJuICsgZGIgKyBwaXJhICsgaG93biIpCm1vZGVsX2ZsZXggPC0gYXMuZGF0YS50YWJsZShtb2RlbC5mcmFtZShmb3JtdWxhX2ZsZXgsIHBlbnNpb24pKQp4X2NvbHMgPC0gY29sbmFtZXMobW9kZWxfZmxleClbLWMoMSwgMildCmRhdGFfbWwgPC0gRG91YmxlTUxEYXRhJG5ldyhtb2RlbF9mbGV4LCB5X2NvbCA9ICJuZXRfdGZhIiwgZF9jb2xzID0gImU0MDEiLCB4X2NvbHMgPSB4X2NvbHMpCgpwIDwtIGRpbShtb2RlbF9mbGV4KVsyXSAtIDIKcApgYGAKCgojIyMjICoqUGFydGlhbGwgTGluZWFyIE1vZGVsIChQTE0pKioKCmBgYHtyfQojIEVzdGltYXRpbmcgdGhlIFBMUgpsZ3I6OmdldF9sb2dnZXIoIm1scjMiKSRzZXRfdGhyZXNob2xkKCJ3YXJuIikKbGFzc28gPC0gbHJuKCJyZWdyLmN2X2dsbW5ldCIsIG5mb2xkcyA9IDUsIHMgPSAibGFtYmRhLm1pbiIpCmxhc3NvX2NsYXNzIDwtIGxybigiY2xhc3NpZi5jdl9nbG1uZXQiLCBuZm9sZHMgPSA1LCBzID0gImxhbWJkYS5taW4iKQoKZG1sX3BsciA8LSBEb3VibGVNTFBMUiRuZXcoZGF0YV9tbCwgbWxfbCA9IGxhc3NvLCBtbF9tID0gbGFzc29fY2xhc3MsIG5fZm9sZHMgPSA1KQpkbWxfcGxyJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9wbHIkc3VtbWFyeSgpCmxhc3NvX3BsciA8LSBkbWxfcGxyJGNvZWYKbGFzc29fc3RkX3BsciA8LSBkbWxfcGxyJHNlCmBgYAoKYGBge3J9CmRtbF9wbHIkcGFyYW1zX25hbWVzKCkKZ19oYXQgPC0gYXMubWF0cml4KGRtbF9wbHIkcHJlZGljdGlvbnMkbWxfbCkgIyBwcmVkaWN0aW9ucyBvZiBnX28KbV9oYXQgPC0gYXMubWF0cml4KGRtbF9wbHIkcHJlZGljdGlvbnMkbWxfbSkgIyBwcmVkaWN0aW9ucyBvZiBtX28KYGBgCgpgYGB7cn0KIyBjcm9zcy1maXR0ZWQgUk1TRTogb3V0Y29tZQp5IDwtIGFzLm1hdHJpeChwZW5zaW9uJG5ldF90ZmEpICMgdHJ1ZSBvYnNlcnZhdGlvbnMKdGhldGEgPC0gYXMubnVtZXJpYyhkbWxfcGxyJGNvZWYpICMgZXN0aW1hdGVkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQKZCA8LSBhcy5tYXRyaXgocGVuc2lvbiRlNDAxKQpwcmVkaWN0aW9uc195IDwtIGFzLm1hdHJpeChkICogdGhldGEpICsgZ19oYXQgIyBwcmVkaWN0aW9ucyBmb3IgeQpsYXNzb195X3Jtc2UgPC0gc3FydChtZWFuKCh5IC0gcHJlZGljdGlvbnNfeSleMikpCmxhc3NvX3lfcm1zZQpgYGAKCioqTGFzc28qKgoKYGBge3J9CiMgY3Jvc3MtZml0dGVkIFJNU0U6IHRyZWF0bWVudApkIDwtIGFzLm1hdHJpeChwZW5zaW9uJGU0MDEpCmxhc3NvX2Rfcm1zZSA8LSBzcXJ0KG1lYW4oKGQgLSBtX2hhdCleMikpCmxhc3NvX2Rfcm1zZQoKIyBjcm9zcy1maXR0ZWQgY2U6IHRyZWF0bWVudAptZWFuKGlmZWxzZShtX2hhdCA+IDAuNSwgMSwgMCkgIT0gZCkKYGBgCgoqKlJhbmRvbSBGb3Jlc3QqKgoKYGBge3J9CiMgUmFuZG9tIEZvcmVzdApsZ3I6OmdldF9sb2dnZXIoIm1scjMiKSRzZXRfdGhyZXNob2xkKCJ3YXJuIikKcmFuZG9tRm9yZXN0IDwtIGxybigicmVnci5yYW5nZXIiKQpyYW5kb21fZm9yZXN0X2NsYXNzIDwtIGxybigiY2xhc3NpZi5yYW5nZXIiKQoKZG1sX3BsciA8LSBEb3VibGVNTFBMUiRuZXcoZGF0YV9tbCwgbWxfbCA9IHJhbmRvbUZvcmVzdCwgbWxfbSA9IHJhbmRvbV9mb3Jlc3RfY2xhc3MsIG5fZm9sZHMgPSA1KQpkbWxfcGxyJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpICMgc2V0IHN0b3JlX3ByZWRpY3Rpb25zPVRSVUUgdG8gZXZhbHVhdGUgdGhlIG1vZGVsCmRtbF9wbHIkc3VtbWFyeSgpCmZvcmVzdF9wbHIgPC0gZG1sX3BsciRjb2VmCmZvcmVzdF9zdGRfcGxyIDwtIGRtbF9wbHIkc2UKYGBgCgoKYGBge3J9CiMgRXZhbHVhdGlvbiBwcmVkaWN0aW9ucwpnX2hhdCA8LSBhcy5tYXRyaXgoZG1sX3BsciRwcmVkaWN0aW9ucyRtbF9sKSAjIHByZWRpY3Rpb25zIG9mIGdfbwptX2hhdCA8LSBhcy5tYXRyaXgoZG1sX3BsciRwcmVkaWN0aW9ucyRtbF9tKSAjIHByZWRpY3Rpb25zIG9mIG1fbwp0aGV0YSA8LSBhcy5udW1lcmljKGRtbF9wbHIkY29lZikgIyBlc3RpbWF0ZWQgcmVncmVzc2lvbiBjb2VmZmljaWVudApwcmVkaWN0aW9uc195IDwtIGFzLm1hdHJpeChkICogdGhldGEpICsgZ19oYXQgIyBwcmVkaWN0aW9ucyBmb3IgeQpmb3Jlc3RfeV9ybXNlIDwtIHNxcnQobWVhbigoeSAtIHByZWRpY3Rpb25zX3kpXjIpKQpmb3Jlc3RfeV9ybXNlCgojIGNyb3NzLWZpdHRlZCBSTVNFOiB0cmVhdG1lbnQKZm9yZXN0X2Rfcm1zZSA8LSBzcXJ0KG1lYW4oKGQgLSBtX2hhdCleMikpCmZvcmVzdF9kX3Jtc2UKCiMgY3Jvc3MtZml0dGVkIGNlOiB0cmVhdG1lbnQKbWVhbihpZmVsc2UobV9oYXQgPiAwLjUsIDEsIDApICE9IGQpCmBgYAoKCioqRGVjaXNpb24gVHJlZXMqKgoKYGBge3J9CiMgVHJlZXMKbGdyOjpnZXRfbG9nZ2VyKCJtbHIzIikkc2V0X3RocmVzaG9sZCgid2FybiIpCgp0cmVlcyA8LSBscm4oInJlZ3IucnBhcnQiKQp0cmVlc19jbGFzcyA8LSBscm4oImNsYXNzaWYucnBhcnQiKQoKZG1sX3BsciA8LSBEb3VibGVNTFBMUiRuZXcoZGF0YV9tbCwgbWxfbCA9IHRyZWVzLCBtbF9tID0gdHJlZXNfY2xhc3MsIG5fZm9sZHMgPSA1KQpkbWxfcGxyJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9wbHIkc3VtbWFyeSgpCnRyZWVfcGxyIDwtIGRtbF9wbHIkY29lZgp0cmVlX3N0ZF9wbHIgPC0gZG1sX3BsciRzZQoKIyBFdmFsdWF0aW9uIHByZWRpY3Rpb25zCmdfaGF0IDwtIGFzLm1hdHJpeChkbWxfcGxyJHByZWRpY3Rpb25zJG1sX2wpICMgcHJlZGljdGlvbnMgb2YgZ19vCm1faGF0IDwtIGFzLm1hdHJpeChkbWxfcGxyJHByZWRpY3Rpb25zJG1sX20pICMgcHJlZGljdGlvbnMgb2YgbV9vCnRoZXRhIDwtIGFzLm51bWVyaWMoZG1sX3BsciRjb2VmKSAjIGVzdGltYXRlZCByZWdyZXNzaW9uIGNvZWZmaWNpZW50CnByZWRpY3Rpb25zX3kgPC0gYXMubWF0cml4KGQgKiB0aGV0YSkgKyBnX2hhdCAjIHByZWRpY3Rpb25zIGZvciB5CnRyZWVfeV9ybXNlIDwtIHNxcnQobWVhbigoeSAtIHByZWRpY3Rpb25zX3kpXjIpKQp0cmVlX3lfcm1zZQoKIyBjcm9zcy1maXR0ZWQgUk1TRTogdHJlYXRtZW50CnRyZWVfZF9ybXNlIDwtIHNxcnQobWVhbigoZCAtIG1faGF0KV4yKSkKdHJlZV9kX3Jtc2UKCiMgY3Jvc3MtZml0dGVkIGNlOiB0cmVhdG1lbnQKbWVhbihpZmVsc2UobV9oYXQgPiAwLjUsIDEsIDApICE9IGQpCmBgYAoKCioqQm9vc3RlZCBUcmVlcyoqCgpgYGB7cn0KIyBCb29zdGluZwpib29zdCA8LSBscm4oInJlZ3IuZ2xtYm9vc3QiKQpib29zdF9jbGFzcyA8LSBscm4oImNsYXNzaWYuZ2xtYm9vc3QiKQoKZG1sX3BsciA8LSBEb3VibGVNTFBMUiRuZXcoZGF0YV9tbCwgbWxfbCA9IGJvb3N0LCBtbF9tID0gYm9vc3RfY2xhc3MsIG5fZm9sZHMgPSA1KQpkbWxfcGxyJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9wbHIkc3VtbWFyeSgpCmJvb3N0X3BsciA8LSBkbWxfcGxyJGNvZWYKYm9vc3Rfc3RkX3BsciA8LSBkbWxfcGxyJHNlCgojIEV2YWx1YXRpb24gcHJlZGljdGlvbnMKZ19oYXQgPC0gYXMubWF0cml4KGRtbF9wbHIkcHJlZGljdGlvbnMkbWxfbCkgIyBwcmVkaWN0aW9ucyBvZiBnX28KbV9oYXQgPC0gYXMubWF0cml4KGRtbF9wbHIkcHJlZGljdGlvbnMkbWxfbSkgIyBwcmVkaWN0aW9ucyBvZiBtX28KdGhldGEgPC0gYXMubnVtZXJpYyhkbWxfcGxyJGNvZWYpICMgZXN0aW1hdGVkIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQKcHJlZGljdGlvbnNfeSA8LSBhcy5tYXRyaXgoZCAqIHRoZXRhKSArIGdfaGF0ICMgcHJlZGljdGlvbnMgZm9yIHkKYm9vc3RfeV9ybXNlIDwtIHNxcnQobWVhbigoeSAtIHByZWRpY3Rpb25zX3kpXjIpKQpib29zdF95X3Jtc2UKCiMgY3Jvc3MtZml0dGVkIFJNU0U6IHRyZWF0bWVudApib29zdF9kX3Jtc2UgPC0gc3FydChtZWFuKChkIC0gbV9oYXQpXjIpKQpib29zdF9kX3Jtc2UKCiMgY3Jvc3MtZml0dGVkIGNlOiB0cmVhdG1lbnQKbWVhbihpZmVsc2UobV9oYXQgPiAwLjUsIDEsIDApICE9IGQpCmBgYAoKKirjgb7jgajjgoEqKgoKYGBge3J9CnRhYmxlIDwtIG1hdHJpeCgwLCA0LCA0KQp0YWJsZVsxLCAxOjRdIDwtIGMobGFzc29fcGxyLCBmb3Jlc3RfcGxyLCB0cmVlX3BsciwgYm9vc3RfcGxyKQp0YWJsZVsyLCAxOjRdIDwtIGMobGFzc29fc3RkX3BsciwgZm9yZXN0X3N0ZF9wbHIsIHRyZWVfc3RkX3BsciwgYm9vc3Rfc3RkX3BscikKdGFibGVbMywgMTo0XSA8LSBjKGxhc3NvX3lfcm1zZSwgZm9yZXN0X3lfcm1zZSwgdHJlZV95X3Jtc2UsIGJvb3N0X3lfcm1zZSkKdGFibGVbNCwgMTo0XSA8LSBjKGxhc3NvX2Rfcm1zZSwgZm9yZXN0X2Rfcm1zZSwgdHJlZV9kX3Jtc2UsIGJvb3N0X2Rfcm1zZSkKcm93bmFtZXModGFibGUpIDwtIGMoIkVzdGltYXRlIiwgIlN0ZC5FcnJvciIsICJSTVNFIFkiLCAiUk1TRSBEIikKY29sbmFtZXModGFibGUpIDwtIGMoIkxhc3NvIiwgIlJhbmRvbSBGb3Jlc3QiLCAiVHJlZXMiLCAiQm9vc3RpbmciKQp0YWIgPC0geHRhYmxlKHRhYmxlLCBkaWdpdHMgPSAyKQp0YWIKCmxhc3NvX3BscgpgYGAKLS0tCgojIyMjICoqSW50ZXJhY3RpdmUgUmVncmVzc2lvbiBNb2RlbCAoSVJNKSoqCgpgYGB7cn0KbGdyOjpnZXRfbG9nZ2VyKCJtbHIzIikkc2V0X3RocmVzaG9sZCgid2FybiIpCmRtbF9pcm0gPC0gRG91YmxlTUxJUk0kbmV3KGRhdGFfbWwsCiAgbWxfZyA9IGxhc3NvLAogIG1sX20gPSBsYXNzb19jbGFzcywKICB0cmltbWluZ190aHJlc2hvbGQgPSAwLjAxLCBuX2ZvbGRzID0gNQopCmRtbF9pcm0kZml0KHN0b3JlX3ByZWRpY3Rpb25zID0gVFJVRSkKZG1sX2lybSRzdW1tYXJ5KCkKbGFzc29faXJtIDwtIGRtbF9pcm0kY29lZgpsYXNzb19zdGRfaXJtIDwtIGRtbF9pcm0kc2UKCgojIHByZWRpY3Rpb25zCmRtbF9pcm0kcGFyYW1zX25hbWVzKCkKZzBfaGF0IDwtIGFzLm1hdHJpeChkbWxfaXJtJHByZWRpY3Rpb25zJG1sX2cwKSAjIHByZWRpY3Rpb25zIG9mIGdfMChEPTAsIFgpCmcxX2hhdCA8LSBhcy5tYXRyaXgoZG1sX2lybSRwcmVkaWN0aW9ucyRtbF9nMSkgIyBwcmVkaWN0aW9ucyBvZiBnXzAoRD0xLCBYKQpnX2hhdCA8LSBkICogZzFfaGF0ICsgKDEgLSBkKSAqIGcwX2hhdCAjIHByZWRpY3Rpb25zIG9mIGdfMAptX2hhdCA8LSBhcy5tYXRyaXgoZG1sX2lybSRwcmVkaWN0aW9ucyRtbF9tKSAjIHByZWRpY3Rpb25zIG9mIG1fbwpgYGAKCmBgYHtyfQojIGNyb3NzLWZpdHRlZCBSTVNFOiBvdXRjb21lCnkgPC0gYXMubWF0cml4KHBlbnNpb24kbmV0X3RmYSkgIyB0cnVlIG9ic2VydmF0aW9ucwpkIDwtIGFzLm1hdHJpeChwZW5zaW9uJGU0MDEpCmxhc3NvX3lfaXJtIDwtIHNxcnQobWVhbigoeSAtIGdfaGF0KV4yKSkKbGFzc29feV9pcm0KCiMgY3Jvc3MtZml0dGVkIFJNU0U6IHRyZWF0bWVudApsYXNzb19kX2lybSA8LSBzcXJ0KG1lYW4oKGQgLSBtX2hhdCleMikpCmxhc3NvX2RfaXJtCgojIGNyb3NzLWZpdHRlZCBjZTogdHJlYXRtZW50Cm1lYW4oaWZlbHNlKG1faGF0ID4gMC41LCAxLCAwKSAhPSBkKQpgYGAKCgoKYGBge3J9CiMjIyMjIGZvcmVzdCAjIyMjIwoKZG1sX2lybSA8LSBEb3VibGVNTElSTSRuZXcoZGF0YV9tbCwKICBtbF9nID0gcmFuZG9tRm9yZXN0LAogIG1sX20gPSByYW5kb21fZm9yZXN0X2NsYXNzLAogIHRyaW1taW5nX3RocmVzaG9sZCA9IDAuMDEsIG5fZm9sZHMgPSA1CikKZG1sX2lybSRmaXQoc3RvcmVfcHJlZGljdGlvbnMgPSBUUlVFKQpkbWxfaXJtJHN1bW1hcnkoKQpmb3Jlc3RfaXJtIDwtIGRtbF9pcm0kY29lZgpmb3Jlc3Rfc3RkX2lybSA8LSBkbWxfcGxyJHNlCgojIHByZWRpY3Rpb25zCmcwX2hhdCA8LSBhcy5tYXRyaXgoZG1sX2lybSRwcmVkaWN0aW9ucyRtbF9nMCkgIyBwcmVkaWN0aW9ucyBvZiBnXzAoRD0wLCBYKQpnMV9oYXQgPC0gYXMubWF0cml4KGRtbF9pcm0kcHJlZGljdGlvbnMkbWxfZzEpICMgcHJlZGljdGlvbnMgb2YgZ18wKEQ9MSwgWCkKZ19oYXQgPC0gZCAqIGcxX2hhdCArICgxIC0gZCkgKiBnMF9oYXQgIyBwcmVkaWN0aW9ucyBvZiBnXzAKbV9oYXQgPC0gYXMubWF0cml4KGRtbF9pcm0kcHJlZGljdGlvbnMkbWxfbSkgIyBwcmVkaWN0aW9ucyBvZiBtXzAKCiMgY3Jvc3MtZml0dGVkIFJNU0U6IG91dGNvbWUKeSA8LSBhcy5tYXRyaXgocGVuc2lvbiRuZXRfdGZhKSAjIHRydWUgb2JzZXJ2YXRpb25zCmQgPC0gYXMubWF0cml4KHBlbnNpb24kZTQwMSkKZm9yZXN0X3lfaXJtIDwtIHNxcnQobWVhbigoeSAtIGdfaGF0KV4yKSkKZm9yZXN0X3lfaXJtCgojIGNyb3NzLWZpdHRlZCBSTVNFOiB0cmVhdG1lbnQKZm9yZXN0X2RfaXJtIDwtIHNxcnQobWVhbigoZCAtIG1faGF0KV4yKSkKZm9yZXN0X2RfaXJtCgojIGNyb3NzLWZpdHRlZCBjZTogdHJlYXRtZW50Cm1lYW4oaWZlbHNlKG1faGF0ID4gMC41LCAxLCAwKSAhPSBkKQoKIyMjIyMgdHJlZXMgIyMjIyMKCmRtbF9pcm0gPC0gRG91YmxlTUxJUk0kbmV3KGRhdGFfbWwsCiAgbWxfZyA9IHRyZWVzLCBtbF9tID0gdHJlZXNfY2xhc3MsCiAgdHJpbW1pbmdfdGhyZXNob2xkID0gMC4wMSwgbl9mb2xkcyA9IDUKKQpkbWxfaXJtJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9pcm0kc3VtbWFyeSgpCnRyZWVfaXJtIDwtIGRtbF9pcm0kY29lZgp0cmVlX3N0ZF9pcm0gPC0gZG1sX2lybSRzZQoKIyBwcmVkaWN0aW9ucwpnMF9oYXQgPC0gYXMubWF0cml4KGRtbF9pcm0kcHJlZGljdGlvbnMkbWxfZzApICMgcHJlZGljdGlvbnMgb2YgZ18wKEQ9MCwgWCkKZzFfaGF0IDwtIGFzLm1hdHJpeChkbWxfaXJtJHByZWRpY3Rpb25zJG1sX2cxKSAjIHByZWRpY3Rpb25zIG9mIGdfMChEPTEsIFgpCmdfaGF0IDwtIGQgKiBnMV9oYXQgKyAoMSAtIGQpICogZzBfaGF0ICMgcHJlZGljdGlvbnMgb2YgZ18wCm1faGF0IDwtIGFzLm1hdHJpeChkbWxfaXJtJHByZWRpY3Rpb25zJG1sX20pICMgcHJlZGljdGlvbnMgb2YgbV9vCgojIGNyb3NzLWZpdHRlZCBSTVNFOiBvdXRjb21lCnkgPC0gYXMubWF0cml4KHBlbnNpb24kbmV0X3RmYSkgIyB0cnVlIG9ic2VydmF0aW9ucwpkIDwtIGFzLm1hdHJpeChwZW5zaW9uJGU0MDEpCnRyZWVfeV9pcm0gPC0gc3FydChtZWFuKCh5IC0gZ19oYXQpXjIpKQp0cmVlX3lfaXJtCgojIGNyb3NzLWZpdHRlZCBSTVNFOiB0cmVhdG1lbnQKdHJlZV9kX2lybSA8LSBzcXJ0KG1lYW4oKGQgLSBtX2hhdCleMikpCnRyZWVfZF9pcm0KCiMgY3Jvc3MtZml0dGVkIGNlOiB0cmVhdG1lbnQKbWVhbihpZmVsc2UobV9oYXQgPiAwLjUsIDEsIDApICE9IGQpCgoKIyMjIyMgYm9vc3RpbmcgIyMjIyMKCmRtbF9pcm0gPC0gRG91YmxlTUxJUk0kbmV3KGRhdGFfbWwsCiAgbWxfZyA9IGJvb3N0LCBtbF9tID0gYm9vc3RfY2xhc3MsCiAgdHJpbW1pbmdfdGhyZXNob2xkID0gMC4wMSwgbl9mb2xkcyA9IDUKKQpkbWxfaXJtJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9pcm0kc3VtbWFyeSgpCmJvb3N0X2lybSA8LSBkbWxfaXJtJGNvZWYKYm9vc3Rfc3RkX2lybSA8LSBkbWxfaXJtJHNlCgojIHByZWRpY3Rpb25zCmcwX2hhdCA8LSBhcy5tYXRyaXgoZG1sX2lybSRwcmVkaWN0aW9ucyRtbF9nMCkgIyBwcmVkaWN0aW9ucyBvZiBnXzAoRD0wLCBYKQpnMV9oYXQgPC0gYXMubWF0cml4KGRtbF9pcm0kcHJlZGljdGlvbnMkbWxfZzEpICMgcHJlZGljdGlvbnMgb2YgZ18wKEQ9MSwgWCkKZ19oYXQgPC0gZCAqIGcxX2hhdCArICgxIC0gZCkgKiBnMF9oYXQgIyBwcmVkaWN0aW9ucyBvZiBnXzAKbV9oYXQgPC0gYXMubWF0cml4KGRtbF9pcm0kcHJlZGljdGlvbnMkbWxfbSkgIyBwcmVkaWN0aW9ucyBvZiBtX28KCiMgY3Jvc3MtZml0dGVkIFJNU0U6IG91dGNvbWUKeSA8LSBhcy5tYXRyaXgocGVuc2lvbiRuZXRfdGZhKSAjIHRydWUgb2JzZXJ2YXRpb25zCmQgPC0gYXMubWF0cml4KHBlbnNpb24kZTQwMSkKYm9vc3RfeV9pcm0gPC0gc3FydChtZWFuKCh5IC0gZ19oYXQpXjIpKQpib29zdF95X2lybQoKIyBjcm9zcy1maXR0ZWQgUk1TRTogdHJlYXRtZW50CmJvb3N0X2RfaXJtIDwtIHNxcnQobWVhbigoZCAtIG1faGF0KV4yKSkKYm9vc3RfZF9pcm0KCiMgY3Jvc3MtZml0dGVkIGNlOiB0cmVhdG1lbnQKbWVhbihpZmVsc2UobV9oYXQgPiAwLjUsIDEsIDApICE9IGQpCmBgYAoKCioq44G+44Go44KBKioKCmBgYHtyfQp0YWJsZSA8LSBtYXRyaXgoMCwgNCwgNCkKdGFibGVbMSwgMTo0XSA8LSBjKGxhc3NvX2lybSwgZm9yZXN0X2lybSwgdHJlZV9pcm0sIGJvb3N0X2lybSkKdGFibGVbMiwgMTo0XSA8LSBjKGxhc3NvX3N0ZF9pcm0sIGZvcmVzdF9zdGRfaXJtLCB0cmVlX3N0ZF9pcm0sIGJvb3N0X3N0ZF9pcm0pCnRhYmxlWzMsIDE6NF0gPC0gYyhsYXNzb195X2lybSwgZm9yZXN0X3lfaXJtLCB0cmVlX3lfaXJtLCBib29zdF95X2lybSkKdGFibGVbNCwgMTo0XSA8LSBjKGxhc3NvX2RfaXJtLCBmb3Jlc3RfZF9pcm0sIHRyZWVfZF9pcm0sIGJvb3N0X2RfaXJtKQpyb3duYW1lcyh0YWJsZSkgPC0gYygiRXN0aW1hdGUiLCAiU3RkLkVycm9yIiwgIlJNU0UgWSIsICJSTVNFIEQiKQpjb2xuYW1lcyh0YWJsZSkgPC0gYygiTGFzc28iLCAiUmFuZG9tIEZvcmVzdCIsICJUcmVlcyIsICJCb29zdGluZyIpCnRhYiA8LSB4dGFibGUodGFibGUsIGRpZ2l0cyA9IDIpCnRhYgpgYGAKCgpgYGB7cn0KbGdyOjpnZXRfbG9nZ2VyKCJtbHIzIikkc2V0X3RocmVzaG9sZCgid2FybiIpCmRtbF9pcm0gPC0gRG91YmxlTUxJUk0kbmV3KGRhdGFfbWwsCiAgbWxfZyA9IHJhbmRvbUZvcmVzdCwKICBtbF9tID0gbGFzc29fY2xhc3MsCiAgdHJpbW1pbmdfdGhyZXNob2xkID0gMC4wMSwgbl9mb2xkcyA9IDUKKQpkbWxfaXJtJGZpdChzdG9yZV9wcmVkaWN0aW9ucyA9IFRSVUUpCmRtbF9pcm0kc3VtbWFyeSgpCmJlc3RfaXJtIDwtIGRtbF9pcm0kY29lZgpiZXN0X3N0ZF9pcm0gPC0gZG1sX2lybSRzZQpgYGAKCi0tLQoKIyMjICoq5YiG5p6Q57WQ5p6c44Go6ICD5a+fKioKCiMjIyMgKipUYWJsZSAxMC40IEVzdGltYXRlZCBFZmZlY3Qgb2YgNDAxKGspIEVsaWdpYmlsaXR5IG9uIE5ldCBGaW5hbmNpYWwgQXNzZXRzKioKCnwgICAgICAgICAgICAgICAgICAgICAgICAgICB8IExhc3NvICB8IFRyZWUgICB8IEZvcmVzdCB8IEJvb3N0ICB8IEJlc3QgICB8IEVuc2VtYmxlIHwKfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tLS0tfAp8ICoqQS4gUGFydGlhbGx5IExpbmVhciBSZWdyZXNzaW9uIE1vZGVsKiogfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgICB8CnwgRXN0aW1hdGUgICAgICAgICAgICAgICAgICB8IDk0MTggICB8IDg2MzQgICB8IDkxMTIgICB8IDg4NTkgICB8IDg4NTkgICB8IDkwNTEgICAgIHwKfCBTdGQuIEVycm9yICAgICAgICAgICAgICAgIHwgKDE0NzYpIHwgKDEzMDMpIHwgKDEyODEpIHwgKDEzMjEpIHwgKDEzMjEpIHwgKDEzMTUpICAgfAp8IFJNU0UgRCAgICAgICAgICAgICAgICAgICAgfCAwLjQ0NyAgfCAwLjQ1NyAgfCAwLjQ1OSAgfCAwLjQ0MyAgfCAwLjQ0MyAgfCAwLjQ0MyAgICB8CnwgUk1TRSBZICAgICAgICAgICAgICAgICAgICB8IDU4MjQyICB8IDU2ODE5ICB8IDU1Mzg1ICB8IDU0MTUzICB8IDU0MTUzICB8IDUzOTE3ICAgIHwKfCAqKkIuIEludGVyYWN0aXZlIFJlZ3Jlc3Npb24gTW9kZWwqKiAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgfCAgICAgICAgICB8CnwgRXN0aW1hdGUgICAgICAgICAgICAgICAgICB8IDg4NjAgICB8IDc4NTYgICB8IDgzNDkgICB8IDc4NzEgICB8IDgyMDQgICB8IDgxNDYgICAgIHwKfCBTdGQuIEVycm9yICAgICAgICAgICAgICAgIHwgKDEzNDcpIHwgKDEyNTApIHwgKDE1MDIpIHwgKDExNTcpIHwgKDExNDQpIHwgKDExNDIpICAgfAp8IFJNU0UgRCAgICAgICAgICAgICAgICAgICAgfCAwLjQ0OCAgfCAwLjQ1NyAgfCAwLjQ1OSAgfCAwLjQ0MyAgfCAwLjQ0MyAgfCAwLjQ0MyAgICB8CnwgUk1TRSBZICAgICAgICAgICAgICAgICAgICB8IDU4MzAwICB8IDU0ODY2ICB8IDU3MjkzICB8IDU1MTEyICB8IDU0ODY2ICB8IDUzODA0ICAgIHwKCi0g5o6o5a6a44Gu6KOc6LazCiAgLSDlgr7lkJHjgrnjgrPjgqLjga7mpbXnq6/jgarlgKTjgavjgojjgovmjqjlrprjgbjjga7lvbHpn7/jgpLpmLLjgZDjgZ/jgoHjgIEoXCgwLjAxXCkg44GoIFwoMC45OVwpKSDjga7nr4Tlm7Ljgafjg4jjg6rjg5/jg7PjgrAKCi0gKirntZDmnpzjga7mpoLopoEqKgogICAtIOWFseWkiemHj+OCkuWQq+OBvuOBquOBhOODouODh+ODq+OBp+OBr+OAgTQwMShrKeOBuOOBruWKoOWFpeizh+agvOOBjOmHkeiejeizh+eUo+OBq+S4juOBiOOCi+W9semfv+OBrzE5LDU1OeODieODq+OBqOaOqOWumgogICAtIOWFseWkiemHj+OCkuOCs+ODs+ODiOODreODvOODq+OBmeOCi+OBk+OBqOOBq+OCiOOCiuOAgeeQhuirlueahOOBquS6iOa4rOmAmuOCiuOAgeato+OBrumBuOaKnuODkOOCpOOCouOCueOBjOijnOato+OBleOCjOOCiwogICAtIFBMTSDjgahJUk0g44Gu57WQ5p6c44Gv5qaC44Gt5LiA6LKr44GX44Gm44GE44KLCiAgIC0gUG90ZXJiYSBldCBhbC4g44Gr44KI44KL44K344Oz44OX44Or44Gq44Kz44Oz44OI44Ot44O844Or44Go57WQ5p6c44Gv6L+R44GP44CB44Gd44KM44Gn5Y2B5YiG44Gn44GC44KL5Y+v6IO95oCn44KS56S65ZSG44GX44Gm44GE44KLCi0gREFHIOOBp+imi+OBn+OCiOOBhuOBquOAgeWboOaenOODgOOCpOOCouOCsOODqeODoOOBruOCj+OBmuOBi+OBquWkieabtOOBjOitmOWIpeOBq+S4juOBiOOCi+W9semfv+OCkuWIhuaekOOBmeOCi+OBruOBr+OAgUNoMTIg44GuIHNlbnNpdGl2aXR5IGFuYWx5c2lzIOOBp+Wun+aWveOBmeOCiwoK