1. (Exercise 5.4.3) We now review k-fold
cross-validation.
- Explain how k-fold cross-validation is implemented.
The data is divided into \(k\)
subsets (folds). The \(i^{th}\) subset
\((i=1,2,...,k)\) is left out and the
model is trained on the remaining \(k-1\) subsets and then tested on the \(i^{th}\) subset. This is an iterative
process and each fold is left out once to be used to assess the model
which was trained on all of the other folds. In the end, all of the
observations have been used for both training and testing the model.
What are the advantages and disadvantages of k-fold
cross-validation relative to:
- The validation set approach?
Advantages: All of the data are used in both training and testing of
the model which helps to prevent over/underfitting. Also, \(k\)-fold CV typically has lower variability
in test error rates than the validation set approach.
Disadvantages: Greater computational cost.
- LOOCV?
Advantages: Much lower computational cost for large sample size and
lower variance for \(k < n\).
Disadvantages: Introduces greater bias than LOOCV.
2. (Exercise 5.4.5) In Chapter 4, we used logistic regression
to predict the probability of default using
income and balance on the Default
data set. We will now estimate the test error of this logistic
regression model using the validation set approach. Do not forget to set
a random seed before beginning your analysis.
- Fit a logistic regression model that uses
income and
balance to predict default.
library(ISLR2)
data('Default')
set.seed(1)
model.p2 <- glm(default ~ income + balance, data = Default, family = "binomial")
summary(model.p2)
Call:
glm(formula = default ~ income + balance, family = "binomial",
data = Default)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.154e+01 4.348e-01 -26.545 < 2e-16 ***
income 2.081e-05 4.985e-06 4.174 2.99e-05 ***
balance 5.647e-03 2.274e-04 24.836 < 2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2920.6 on 9999 degrees of freedom
Residual deviance: 1579.0 on 9997 degrees of freedom
AIC: 1585
Number of Fisher Scoring iterations: 8
Using the validation set approach, estimate the test error of
this model. In order to do this, you must perform the following
steps:
Split the sample set into a training set and a validation
set.
Fit a multiple logistic regression model using only the training
observations.
Obtain a prediction of default status for each individual in the
validation set by computing the posterior probability of default for
that individual, and classifying the individual to the
default category if the posterior probability is greater
than 0.5.
Compute the validation set error, which is the fraction of the
observations in the validation set that are misclassified.
library(caret)
# (i) split data
set.seed(1)
split.p2 <- createDataPartition(Default$default, p = 0.8, list = FALSE, times = 1)
train.p2 <- Default[split.p2, ]
test.p2 <- Default[-split.p2, ]
# (ii) fit model with training data
model.train.p2 <- glm(default ~ income + balance, data = train.p2, family = 'binomial')
# (iii) get predictions for default status
probs.p2 <- predict(model.train.p2, test.p2, type = "response")
preds.p2 <- rep("No", length(probs.p2))
preds.p2[probs.p2 >= 0.5] <- "Yes"
# (iv) compute validation set error
table(test.p2$default, preds.p2)
preds.p2
No Yes
No 1930 3
Yes 51 15
error.p2 <- mean(test.p2$default != preds.p2)
cat("The validation set error for the first data split is given by:", error.p2, "\n")
The validation set error for the first data split is given by: 0.02701351
- Repeat the process in (b) three times, using three different splits
of the observations into a training set and a validation set. Comment on
the results obtained.
######################### 2ND DATA SPLIT #########################
# (i) split data 70% train/30% test
set.seed(1)
split.2nd <- createDataPartition(Default$default, p = 0.7, list = FALSE, times = 1)
train.2nd <- Default[split.2nd, ]
test.2nd <- Default[-split.2nd, ]
# (ii) fit model with training data
model.train.2nd <- glm(default ~ income + balance, data = train.2nd, family = 'binomial')
# (iii) get predictions for default status
probs.2nd <- predict(model.train.2nd, test.2nd, type = "response")
preds.2nd <- rep("No", length(probs.2nd))
preds.2nd[probs.2nd >= 0.5] <- "Yes"
# (iv) compute validation set error
table(test.2nd$default, preds.2nd)
preds.2nd
No Yes
No 2893 7
Yes 71 28
error.2nd <- mean(test.2nd$default != preds.2nd)
cat("The validation set error for the second data split is given by:", error.2nd, "\n")
The validation set error for the second data split is given by: 0.02600867
######################### 3RD DATA SPLIT #########################
# (i) split data 60% train/40% test
split.3rd <- createDataPartition(Default$default, p = 0.6, list = FALSE, times = 1)
train.3rd <- Default[split.3rd, ]
test.3rd <- Default[-split.3rd, ]
# (ii) fit model with training data
model.train.3rd <- glm(default ~ income + balance, data = train.3rd, family = 'binomial')
# (iii) get predictions for default status
probs.3rd <- predict(model.train.3rd, test.3rd, type = "response")
preds.3rd <- rep("No", length(probs.3rd))
preds.3rd[probs.3rd >= 0.5] <- "Yes"
# (iv) compute validation set error
table(test.3rd$default, preds.3rd)
preds.3rd
No Yes
No 3855 11
Yes 84 49
error.3rd <- mean(test.3rd$default != preds.3rd)
cat("The validation set error for the third data split is given by:", error.3rd, "\n")
The validation set error for the third data split is given by: 0.02375594
######################### 4TH DATA SPLIT #########################
# (i) split data 50% train/50% test
split.4th <- createDataPartition(Default$default, p = 0.5, list = FALSE, times = 1)
train.4th <- Default[split.4th, ]
test.4th <- Default[-split.4th, ]
# (ii) fit model with training data
model.train.4th <- glm(default ~ income + balance, data = train.4th, family = 'binomial')
# (iii) get predictions for default status
probs.4th <- predict(model.train.4th, test.4th, type = "response")
preds.4th <- rep("No", length(probs.4th))
preds.4th[probs.4th >= 0.5] <- "Yes"
# (iv) compute validation set error
table(test.4th$default, preds.4th)
preds.4th
No Yes
No 4813 20
Yes 111 55
error.4th <- mean(test.4th$default != preds.4th)
cat("The validation set error for the fourth data split is given by:", error.4th, "\n")
The validation set error for the fourth data split is given by: 0.02620524
The test error seems to decrease and then increase again as the
amount of data used to train the model decreases from 80% to 50%. This
is not unexpected, since we know that, in general, as model flexibility
increases, training error decreases and testing error decreases, reaches
some minimum point, then increases again.
- Now consider a logistic regression model that predicts the
probability of
default using income,
balance, and a dummy variable for student.
Estimate the test error for this model using the validation set
approach. Comment on whether or not including a dummy variable for
student leads to a reduction in the test error rate.
# code dummy variable for student
Default$studentDummy <- ifelse(Default$student == "Yes", 1, 0)
# fit logreg model with dummy
model.new.p2 <- glm(default ~ income + balance + studentDummy, data = Default,
family = 'binomial')
summary(model.new.p2)
Call:
glm(formula = default ~ income + balance + studentDummy, family = "binomial",
data = Default)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.087e+01 4.923e-01 -22.080 < 2e-16 ***
income 3.033e-06 8.203e-06 0.370 0.71152
balance 5.737e-03 2.319e-04 24.738 < 2e-16 ***
studentDummy -6.468e-01 2.363e-01 -2.738 0.00619 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2920.6 on 9999 degrees of freedom
Residual deviance: 1571.5 on 9996 degrees of freedom
AIC: 1579.5
Number of Fisher Scoring iterations: 8
set.seed(1)
split.new <- createDataPartition(Default$default, p = 0.8, list = FALSE, times = 1)
train.new <- Default[split.new, ]
test.new <- Default[-split.new, ]
model.new.train <- glm(default ~ income + balance + studentDummy,
data = train.new, family = 'binomial')
probs.new <- predict(model.new.train, test.new, type = "response")
preds.new <- rep("No", length(probs.new))
preds.new[probs.new >= 0.5] <- "Yes"
table(test.new$default, preds.new)
preds.new
No Yes
No 1928 5
Yes 50 16
error.new <- mean(test.new$default != preds.new)
cat("The test error for the model with the dummy student variable is:", error.new, "\n")
The test error for the model with the dummy student variable is: 0.02751376
Including a dummy variable for student led to a model
with a higher test error rate than the model which does not include the
dummy variable using the exact same data split.
3. (Exercise 5.4.6) We continue to consider the use of a
logistic regression model to predict the probability of
default using income and balance
on the Default data set. In particular, we will now compute
estimates for the standard errors of the income and
balance logistic regression coefficients in two different
ways: (1) using the bootstrap, and (2) using the standard formula for
computing the standard errors in the glm() function. Do not
forget to set a random seed before beginning your analysis.
- Using the
summary() and glm() functions,
determine the estimated standard errors for the coefficients associated
with income and balance in a multiple logistic
regression model that uses both predictors.
set.seed(20)
p3.fit <- glm(default ~ income + balance, data = Default, family = 'binomial')
summary(p3.fit)
Call:
glm(formula = default ~ income + balance, family = "binomial",
data = Default)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.154e+01 4.348e-01 -26.545 < 2e-16 ***
income 2.081e-05 4.985e-06 4.174 2.99e-05 ***
balance 5.647e-03 2.274e-04 24.836 < 2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2920.6 on 9999 degrees of freedom
Residual deviance: 1579.0 on 9997 degrees of freedom
AIC: 1585
Number of Fisher Scoring iterations: 8
The estimated standard errors for the coefficients associated with
income and balance are 4.985e-06 and 2.274
e-04, respectively.
- Write a function,
boot.fn(), that takes as input the
Default data set as well as an index of the observations,
and that outputs the coefficient estimates for income and
balance in the multiple logistic regression model.
boot.fn <- function(df, x) {
boot.fit <- glm(df[,1] ~ df[,4] + df[,3], data = df, subset = x,
family = 'binomial')
names(boot.fit$coefficients) <- c('Intercept', 'Income', 'Balance')
get.coefs <- coef(boot.fit)
return(get.coefs[2:3])
}
- Use the
boot() function together with your
boot.fn() function to estimate the standard errors of the
logistic regression coefficients for income and
balance.
library(boot)
boot(data = Default, boot.fn, R = 1000)
ORDINARY NONPARAMETRIC BOOTSTRAP
Call:
boot(data = Default, statistic = boot.fn, R = 1000)
Bootstrap Statistics :
original bias std. error
t1* 2.080898e-05 9.312739e-09 5.114083e-06
t2* 5.647103e-03 2.283201e-05 2.231546e-04
- Comment on the estimated standard errors obtained using the
glm() function and using your bootstrap function.
The estimated standard errors are as follows:
glm() |
4.985e-06 |
2.274e-04 |
| bootstrap |
5.114083e-06 |
2.231546e-04 |
Clearly the estimates are very close in value.
4. (Exercise 5.4.9) We will now consider the
Boston housing data set, from the ISLR2
library.
- Based on this data set, provide an estimate for the population mean
of
medv. Call this estimate \(\hat{\mu}\).
library(ISLR2)
data("Boston")
mu.hat <- mean(Boston$medv)
mu.hat
[1] 22.53281
Based on this data set, the average median value of owner-occupied
homes among suburbs of Boston is approximately \(\hat{\mu} = \$22,532.81\).
- Provide an estimate of the standard error of \(\hat{\mu}\). Interpret this result.
Hint: We can compute the standard error of the sample mean by
dividing the sample standard deviation by the square root of the number
of observations.
SE.hat
[1] 0.4088611
The standard error estimate is given by \(\widehat{SE} = 0.4089\). For a sample of
owner-occupied homes in 506 Boston suburbs, the \(\hat{\mu}\) estimate will vary
approximately by \(\pm \,\,\$408.90\),
on average. This is a relatively low standard error, which tells us that
\(\hat{\mu} = \$22,532.81\) is a good
estimate of the population mean of medv; our sample
medv sufficiently represents the true population.
- Now estimate the standard error of \(\hat{\mu}\) using the bootstrap. How does
this compare to your answer from (b)?
set.seed(506)
boot.new <- function(df, x) {
mu.boot <- mean(df[x])
return(mu.boot)
}
boot(Boston$medv, boot.new, R = 1000)
ORDINARY NONPARAMETRIC BOOTSTRAP
Call:
boot(data = Boston$medv, statistic = boot.new, R = 1000)
Bootstrap Statistics :
original bias std. error
t1* 22.53281 -0.01338261 0.4163944
The standard error estimate for \(\hat{\mu}\) from bootstrapping is \(\widehat{SE}_{boot} = 0.4164\). This
estimate is slightly higher than that obtained in part (b), but still
relatively small. We can still conclude that our sample
medv sufficiently represents the true population.
- Based on your bootstrap estimate from (c), provide a 95% confidence
interval for the mean of
medv. Compare it to the results
obtained using t.test(Boston$medv). Hint: You can
approximate a 95% confidence interval using the formula \([\hat{\mu} - 2SE(\hat{\mu}), \hat{\mu} +
2SE(\hat{\mu})]\).
t.test(Boston$medv)
One Sample t-test
data: Boston$medv
t = 55.111, df = 505, p-value < 2.2e-16
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
21.72953 23.33608
sample estimates:
mean of x
22.53281
mu.hat.CI <- function(mu, se) {
CI <- c(mu - 2*se, mu + 2*se)
names(CI) <- c("Lower", "Upper")
return(CI)
}
mu.hat.CI(mu.hat, 0.4163944)
Lower Upper
21.70002 23.36560
The 95% confidence interval based on the bootstrap estimate of
standard error is approximately ($21,700.02, $23,365.60). This interval
is only slightly wider (by about $60.00) than the one obtained using
t.test(Boston$medv) which is approximately ($21,729.53,
$23,336.08).
- Based on this data set, provide an estimate, \(\hat{\mu}_{med}\), for the median value of
medv in the population.
mu.hat.med <- median(Boston$medv)
mu.hat.med
[1] 21.2
The population estimate for the median value of medvis
\(\hat{\mu}_{med} = \$21,200.00\).
- We now would like to estimate the standard error of \(\hat{\mu}_{med}\). Unfortunately, there is
no simple formula for computing the standard error of the median.
Instead, estimate the standard error of the median using the bootstrap.
Comment on your findings.
set.seed(506)
boot.4f <- function(df, x) {
med.boot <- median(df[x])
return(med.boot)
}
boot(Boston$medv, boot.4f, R = 1000)
ORDINARY NONPARAMETRIC BOOTSTRAP
Call:
boot(data = Boston$medv, statistic = boot.4f, R = 1000)
Bootstrap Statistics :
original bias std. error
t1* 21.2 -0.0372 0.3863877
The standard error estimate for \(\hat{\mu}_{med}\) from bootstrapping is
\(\widehat{SE}_{median} = 0.3864\).
Again, this is quite small. We can conclude that \(\hat{\mu}_{med} = \$21,200.00\) is a good
estimate of the true population median of medv.
- Based on this data set, provide an estimate for the tenth percentile
of
medv in Boston census tracts. Call this quantity \(\hat{\mu}_{0.1}\). (You can use the
quantile() function.)
hat.mu10 <- quantile(Boston$medv, probs = 0.1)
hat.mu10
10%
12.75
The 10th percentile estimate for the population of
medvis \(\hat{\mu}_{0.1} =
\$12,750.00\).
- Use the bootstrap to estimate the standard error of \(\hat{\mu}_{0.1}\). Comment on your
findings.
boot.4h <- function(df, x) {
boot10 <- quantile(df[x], probs = 0.1)
return(boot10)
}
boot(Boston$medv, boot.4h, R = 1000)
ORDINARY NONPARAMETRIC BOOTSTRAP
Call:
boot(data = Boston$medv, statistic = boot.4h, R = 1000)
Bootstrap Statistics :
original bias std. error
t1* 12.75 0.0134 0.5027208
The standard error estimate for \(\hat{\mu}_{0.1}\) from bootstrapping is
\(\widehat{SE}_{0.1} = 0.5027\). A bit
higher than previous SE estimates, but still relatively small. We can
conclude that \(\hat{\mu}_{0.1} =
\$12,750.00\) is a good estimate of the true population 10th
percentile of medv.
LS0tCnRpdGxlOiAiU1RBIDY1NDMgQXNzaWdubWVudCA0IgphdXRob3I6ICJBbGx5c3NhIFdlaW5icmVjaHQiCmRhdGU6ICIyMDI1LTAzLTI4IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGNvbGxhcHNlID0gVFJVRSwKICBmaWcuYWxpZ249ImNlbnRlciIsCiAgZmlnLnBvcz0iYiIsCiAgc3RyaXAud2hpdGUgPSBUUlVFCikKYGBgCgoqKjEuIChFeGVyY2lzZSA1LjQuMykgV2Ugbm93IHJldmlldyAqayotZm9sZCBjcm9zcy12YWxpZGF0aW9uLioqCgooYSkgRXhwbGFpbiBob3cgKmsqLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBpcyBpbXBsZW1lbnRlZC4KClRoZSBkYXRhIGlzIGRpdmlkZWQgaW50byAkayQgc3Vic2V0cyAoZm9sZHMpLiBUaGUgJGlee3RofSQgc3Vic2V0ICQoaT0xLDIsLi4uLGspJCBpcyBsZWZ0IG91dCBhbmQgdGhlIG1vZGVsIGlzIHRyYWluZWQgb24gdGhlIHJlbWFpbmluZyAkay0xJCBzdWJzZXRzIGFuZCB0aGVuIHRlc3RlZCBvbiB0aGUgJGlee3RofSQgc3Vic2V0LiBUaGlzIGlzIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzIGFuZCBlYWNoIGZvbGQgaXMgbGVmdCBvdXQgb25jZSB0byBiZSB1c2VkIHRvIGFzc2VzcyB0aGUgbW9kZWwgd2hpY2ggd2FzIHRyYWluZWQgb24gYWxsIG9mIHRoZSBvdGhlciBmb2xkcy4gSW4gdGhlIGVuZCwgYWxsIG9mIHRoZSBvYnNlcnZhdGlvbnMgaGF2ZSBiZWVuIHVzZWQgZm9yIGJvdGggdHJhaW5pbmcgYW5kIHRlc3RpbmcgdGhlIG1vZGVsLgoKKGIpIFdoYXQgYXJlIHRoZSBhZHZhbnRhZ2VzIGFuZCBkaXNhZHZhbnRhZ2VzIG9mICprKi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gcmVsYXRpdmUgdG86CgogICAgKGkpIFRoZSB2YWxpZGF0aW9uIHNldCBhcHByb2FjaD8KICAgIAogICAgQWR2YW50YWdlczogQWxsIG9mIHRoZSBkYXRhIGFyZSB1c2VkIGluIGJvdGggdHJhaW5pbmcgYW5kIHRlc3Rpbmcgb2YgdGhlIG1vZGVsIHdoaWNoIGhlbHBzIHRvIHByZXZlbnQgb3Zlci91bmRlcmZpdHRpbmcuIEFsc28sICRrJC1mb2xkIENWIHR5cGljYWxseSBoYXMgbG93ZXIgdmFyaWFiaWxpdHkgaW4gdGVzdCBlcnJvciByYXRlcyB0aGFuIHRoZSB2YWxpZGF0aW9uIHNldCBhcHByb2FjaC4KICAgIAogICAgRGlzYWR2YW50YWdlczogR3JlYXRlciBjb21wdXRhdGlvbmFsIGNvc3QuCiAgICAKICAgIChpaSkgTE9PQ1Y/CiAgICAKICAgIEFkdmFudGFnZXM6IE11Y2ggbG93ZXIgY29tcHV0YXRpb25hbCBjb3N0IGZvciBsYXJnZSBzYW1wbGUgc2l6ZSBhbmQgbG93ZXIgdmFyaWFuY2UgZm9yICRrIDwgbiQuCiAgICAKICAgIERpc2FkdmFudGFnZXM6IEludHJvZHVjZXMgZ3JlYXRlciBiaWFzIHRoYW4gTE9PQ1YuCgoKKioyLiAoRXhlcmNpc2UgNS40LjUpIEluIENoYXB0ZXIgNCwgd2UgdXNlZCBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIHByZWRpY3QgdGhlIHByb2JhYmlsaXR5IG9mIGBkZWZhdWx0YCB1c2luZyBgaW5jb21lYCBhbmQgYGJhbGFuY2VgIG9uIHRoZSBgRGVmYXVsdGAgZGF0YSBzZXQuIFdlIHdpbGwgbm93IGVzdGltYXRlIHRoZSB0ZXN0IGVycm9yIG9mIHRoaXMgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB1c2luZyB0aGUgdmFsaWRhdGlvbiBzZXQgYXBwcm9hY2guIERvIG5vdCBmb3JnZXQgdG8gc2V0IGEgcmFuZG9tIHNlZWQgYmVmb3JlIGJlZ2lubmluZyB5b3VyIGFuYWx5c2lzLioqCgooYSkgRml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0aGF0IHVzZXMgYGluY29tZWAgYW5kIGBiYWxhbmNlYCB0byBwcmVkaWN0IGBkZWZhdWx0YC4KCmBgYCB7ciBQcm9iMmEsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeShJU0xSMikKZGF0YSgnRGVmYXVsdCcpCgpzZXQuc2VlZCgxKQptb2RlbC5wMiA8LSBnbG0oZGVmYXVsdCB+IGluY29tZSArIGJhbGFuY2UsIGRhdGEgPSBEZWZhdWx0LCBmYW1pbHkgPSAiYmlub21pYWwiKQpzdW1tYXJ5KG1vZGVsLnAyKQpgYGAKCihiKSBVc2luZyB0aGUgdmFsaWRhdGlvbiBzZXQgYXBwcm9hY2gsIGVzdGltYXRlIHRoZSB0ZXN0IGVycm9yIG9mIHRoaXMgbW9kZWwuIEluIG9yZGVyIHRvIGRvIHRoaXMsIHlvdSBtdXN0IHBlcmZvcm0gdGhlIGZvbGxvd2luZyBzdGVwczoKCiAgICAoaSkgU3BsaXQgdGhlIHNhbXBsZSBzZXQgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB2YWxpZGF0aW9uIHNldC4KICAgIAogICAgKGlpKSBGaXQgYSBtdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIG9ubHkgdGhlIHRyYWluaW5nIG9ic2VydmF0aW9ucy4KICAgIAogICAgKGlpaSkgT2J0YWluIGEgcHJlZGljdGlvbiBvZiBkZWZhdWx0IHN0YXR1cyBmb3IgZWFjaCBpbmRpdmlkdWFsIGluIHRoZSB2YWxpZGF0aW9uIHNldCBieSBjb21wdXRpbmcgdGhlIHBvc3RlcmlvciBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0IGZvciB0aGF0IGluZGl2aWR1YWwsIGFuZCBjbGFzc2lmeWluZyB0aGUgaW5kaXZpZHVhbCB0byB0aGUgYGRlZmF1bHRgIGNhdGVnb3J5IGlmIHRoZSBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgaXMgZ3JlYXRlciB0aGFuIDAuNS4KICAgIAogICAgKGl2KSBDb21wdXRlIHRoZSB2YWxpZGF0aW9uIHNldCBlcnJvciwgd2hpY2ggaXMgdGhlIGZyYWN0aW9uIG9mIHRoZSBvYnNlcnZhdGlvbnMgaW4gdGhlIHZhbGlkYXRpb24gc2V0IHRoYXQgYXJlIG1pc2NsYXNzaWZpZWQuCgpgYGAge3IgUHJvYjJiLCB3YXJuaW5nID0gRkFMU0V9CmxpYnJhcnkoY2FyZXQpCgojIChpKSBzcGxpdCBkYXRhIDgwJSB0cmFpbi8yMCUgdGVzdApzZXQuc2VlZCgxKQpzcGxpdC5wMiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKERlZmF1bHQkZGVmYXVsdCwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnRyYWluLnAyIDwtIERlZmF1bHRbc3BsaXQucDIsIF0KdGVzdC5wMiA8LSBEZWZhdWx0Wy1zcGxpdC5wMiwgXQoKIyAoaWkpIGZpdCBtb2RlbCB3aXRoIHRyYWluaW5nIGRhdGEKbW9kZWwudHJhaW4ucDIgPC0gZ2xtKGRlZmF1bHQgfiBpbmNvbWUgKyBiYWxhbmNlLCBkYXRhID0gdHJhaW4ucDIsIGZhbWlseSA9ICdiaW5vbWlhbCcpCgojIChpaWkpIGdldCBwcmVkaWN0aW9ucyBmb3IgZGVmYXVsdCBzdGF0dXMKcHJvYnMucDIgPC0gcHJlZGljdChtb2RlbC50cmFpbi5wMiwgdGVzdC5wMiwgdHlwZSA9ICJyZXNwb25zZSIpCnByZWRzLnAyIDwtIHJlcCgiTm8iLCBsZW5ndGgocHJvYnMucDIpKQpwcmVkcy5wMltwcm9icy5wMiA+PSAwLjVdIDwtICJZZXMiCgojIChpdikgY29tcHV0ZSB2YWxpZGF0aW9uIHNldCBlcnJvcgp0YWJsZSh0ZXN0LnAyJGRlZmF1bHQsIHByZWRzLnAyKQplcnJvci5wMiA8LSBtZWFuKHRlc3QucDIkZGVmYXVsdCAhPSBwcmVkcy5wMikKY2F0KCJUaGUgdmFsaWRhdGlvbiBzZXQgZXJyb3IgZm9yIHRoZSBmaXJzdCBkYXRhIHNwbGl0IGlzIGdpdmVuIGJ5OiIsIGVycm9yLnAyLCAiXG4iKQpgYGAKCihjKSBSZXBlYXQgdGhlIHByb2Nlc3MgaW4gKGIpIHRocmVlIHRpbWVzLCB1c2luZyB0aHJlZSBkaWZmZXJlbnQgc3BsaXRzIG9mIHRoZSBvYnNlcnZhdGlvbnMgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB2YWxpZGF0aW9uIHNldC4gQ29tbWVudCBvbiB0aGUgcmVzdWx0cyBvYnRhaW5lZC4KCmBgYCB7ciBQcm9iMmN9CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMgMk5EIERBVEEgU1BMSVQgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIChpKSBzcGxpdCBkYXRhIDcwJSB0cmFpbi8zMCUgdGVzdApzZXQuc2VlZCgxKQpzcGxpdC4ybmQgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihEZWZhdWx0JGRlZmF1bHQsIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKQp0cmFpbi4ybmQgPC0gRGVmYXVsdFtzcGxpdC4ybmQsIF0KdGVzdC4ybmQgPC0gRGVmYXVsdFstc3BsaXQuMm5kLCBdCgojIChpaSkgZml0IG1vZGVsIHdpdGggdHJhaW5pbmcgZGF0YQptb2RlbC50cmFpbi4ybmQgPC0gZ2xtKGRlZmF1bHQgfiBpbmNvbWUgKyBiYWxhbmNlLCBkYXRhID0gdHJhaW4uMm5kLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKIyAoaWlpKSBnZXQgcHJlZGljdGlvbnMgZm9yIGRlZmF1bHQgc3RhdHVzCnByb2JzLjJuZCA8LSBwcmVkaWN0KG1vZGVsLnRyYWluLjJuZCwgdGVzdC4ybmQsIHR5cGUgPSAicmVzcG9uc2UiKQpwcmVkcy4ybmQgPC0gcmVwKCJObyIsIGxlbmd0aChwcm9icy4ybmQpKQpwcmVkcy4ybmRbcHJvYnMuMm5kID49IDAuNV0gPC0gIlllcyIKCiMgKGl2KSBjb21wdXRlIHZhbGlkYXRpb24gc2V0IGVycm9yCnRhYmxlKHRlc3QuMm5kJGRlZmF1bHQsIHByZWRzLjJuZCkKZXJyb3IuMm5kIDwtIG1lYW4odGVzdC4ybmQkZGVmYXVsdCAhPSBwcmVkcy4ybmQpCmNhdCgiVGhlIHZhbGlkYXRpb24gc2V0IGVycm9yIGZvciB0aGUgc2Vjb25kIGRhdGEgc3BsaXQgaXMgZ2l2ZW4gYnk6IiwgZXJyb3IuMm5kLCAiXG4iKQoKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMgM1JEIERBVEEgU1BMSVQgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIChpKSBzcGxpdCBkYXRhIDYwJSB0cmFpbi80MCUgdGVzdApzcGxpdC4zcmQgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihEZWZhdWx0JGRlZmF1bHQsIHAgPSAwLjYsIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKQp0cmFpbi4zcmQgPC0gRGVmYXVsdFtzcGxpdC4zcmQsIF0KdGVzdC4zcmQgPC0gRGVmYXVsdFstc3BsaXQuM3JkLCBdCgojIChpaSkgZml0IG1vZGVsIHdpdGggdHJhaW5pbmcgZGF0YQptb2RlbC50cmFpbi4zcmQgPC0gZ2xtKGRlZmF1bHQgfiBpbmNvbWUgKyBiYWxhbmNlLCBkYXRhID0gdHJhaW4uM3JkLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKIyAoaWlpKSBnZXQgcHJlZGljdGlvbnMgZm9yIGRlZmF1bHQgc3RhdHVzCnByb2JzLjNyZCA8LSBwcmVkaWN0KG1vZGVsLnRyYWluLjNyZCwgdGVzdC4zcmQsIHR5cGUgPSAicmVzcG9uc2UiKQpwcmVkcy4zcmQgPC0gcmVwKCJObyIsIGxlbmd0aChwcm9icy4zcmQpKQpwcmVkcy4zcmRbcHJvYnMuM3JkID49IDAuNV0gPC0gIlllcyIKCiMgKGl2KSBjb21wdXRlIHZhbGlkYXRpb24gc2V0IGVycm9yCnRhYmxlKHRlc3QuM3JkJGRlZmF1bHQsIHByZWRzLjNyZCkKZXJyb3IuM3JkIDwtIG1lYW4odGVzdC4zcmQkZGVmYXVsdCAhPSBwcmVkcy4zcmQpCmNhdCgiVGhlIHZhbGlkYXRpb24gc2V0IGVycm9yIGZvciB0aGUgdGhpcmQgZGF0YSBzcGxpdCBpcyBnaXZlbiBieToiLCBlcnJvci4zcmQsICJcbiIpCgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyA0VEggREFUQSBTUExJVCAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgKGkpIHNwbGl0IGRhdGEgNTAlIHRyYWluLzUwJSB0ZXN0CnNwbGl0LjR0aCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKERlZmF1bHQkZGVmYXVsdCwgcCA9IDAuNSwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnRyYWluLjR0aCA8LSBEZWZhdWx0W3NwbGl0LjR0aCwgXQp0ZXN0LjR0aCA8LSBEZWZhdWx0Wy1zcGxpdC40dGgsIF0KCiMgKGlpKSBmaXQgbW9kZWwgd2l0aCB0cmFpbmluZyBkYXRhCm1vZGVsLnRyYWluLjR0aCA8LSBnbG0oZGVmYXVsdCB+IGluY29tZSArIGJhbGFuY2UsIGRhdGEgPSB0cmFpbi40dGgsIGZhbWlseSA9ICdiaW5vbWlhbCcpCgojIChpaWkpIGdldCBwcmVkaWN0aW9ucyBmb3IgZGVmYXVsdCBzdGF0dXMKcHJvYnMuNHRoIDwtIHByZWRpY3QobW9kZWwudHJhaW4uNHRoLCB0ZXN0LjR0aCwgdHlwZSA9ICJyZXNwb25zZSIpCnByZWRzLjR0aCA8LSByZXAoIk5vIiwgbGVuZ3RoKHByb2JzLjR0aCkpCnByZWRzLjR0aFtwcm9icy40dGggPj0gMC41XSA8LSAiWWVzIgoKIyAoaXYpIGNvbXB1dGUgdmFsaWRhdGlvbiBzZXQgZXJyb3IKdGFibGUodGVzdC40dGgkZGVmYXVsdCwgcHJlZHMuNHRoKQplcnJvci40dGggPC0gbWVhbih0ZXN0LjR0aCRkZWZhdWx0ICE9IHByZWRzLjR0aCkKY2F0KCJUaGUgdmFsaWRhdGlvbiBzZXQgZXJyb3IgZm9yIHRoZSBmb3VydGggZGF0YSBzcGxpdCBpcyBnaXZlbiBieToiLCBlcnJvci40dGgsICJcbiIpCmBgYApUaGUgdGVzdCBlcnJvciBzZWVtcyB0byBkZWNyZWFzZSBhbmQgdGhlbiBpbmNyZWFzZSBhZ2FpbiBhcyB0aGUgYW1vdW50IG9mIGRhdGEgdXNlZCB0byB0cmFpbiB0aGUgbW9kZWwgZGVjcmVhc2VzIGZyb20gODBcJSB0byA1MFwlLiBUaGlzIGlzIG5vdCB1bmV4cGVjdGVkLCBzaW5jZSB3ZSBrbm93IHRoYXQsIGluIGdlbmVyYWwsIGFzIG1vZGVsIGZsZXhpYmlsaXR5IGluY3JlYXNlcywgdHJhaW5pbmcgZXJyb3IgZGVjcmVhc2VzIGFuZCB0ZXN0aW5nIGVycm9yIGRlY3JlYXNlcywgcmVhY2hlcyBzb21lIG1pbmltdW0gcG9pbnQsIHRoZW4gaW5jcmVhc2VzIGFnYWluLgoKKGQpIE5vdyBjb25zaWRlciBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCBwcmVkaWN0cyB0aGUgcHJvYmFiaWxpdHkgb2YgYGRlZmF1bHRgIHVzaW5nIGBpbmNvbWVgLCBgYmFsYW5jZWAsIGFuZCBhIGR1bW15IHZhcmlhYmxlIGZvciBgc3R1ZGVudGAuIEVzdGltYXRlIHRoZSB0ZXN0IGVycm9yIGZvciB0aGlzIG1vZGVsIHVzaW5nIHRoZSB2YWxpZGF0aW9uIHNldCBhcHByb2FjaC4gQ29tbWVudCBvbiB3aGV0aGVyIG9yIG5vdCBpbmNsdWRpbmcgYSBkdW1teSB2YXJpYWJsZSBmb3IgYHN0dWRlbnRgIGxlYWRzIHRvIGEgcmVkdWN0aW9uIGluIHRoZSB0ZXN0IGVycm9yIHJhdGUuCgpgYGAge3IgUHJvYjJkfQojIGNvZGUgZHVtbXkgdmFyaWFibGUgZm9yIHN0dWRlbnQKRGVmYXVsdCRzdHVkZW50RHVtbXkgPC0gaWZlbHNlKERlZmF1bHQkc3R1ZGVudCA9PSAiWWVzIiwgMSwgMCkKCiMgZml0IGxvZ3JlZyBtb2RlbCB3aXRoIGR1bW15Cm1vZGVsLm5ldy5wMiA8LSBnbG0oZGVmYXVsdCB+IGluY29tZSArIGJhbGFuY2UgKyBzdHVkZW50RHVtbXksIGRhdGEgPSBEZWZhdWx0LAogICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICdiaW5vbWlhbCcpCnN1bW1hcnkobW9kZWwubmV3LnAyKQoKc2V0LnNlZWQoMSkKc3BsaXQubmV3IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oRGVmYXVsdCRkZWZhdWx0LCBwID0gMC44LCBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkKdHJhaW4ubmV3IDwtIERlZmF1bHRbc3BsaXQubmV3LCBdCnRlc3QubmV3IDwtIERlZmF1bHRbLXNwbGl0Lm5ldywgXQoKbW9kZWwubmV3LnRyYWluIDwtIGdsbShkZWZhdWx0IH4gaW5jb21lICsgYmFsYW5jZSArIHN0dWRlbnREdW1teSwgCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLm5ldywgZmFtaWx5ID0gJ2Jpbm9taWFsJykKCnByb2JzLm5ldyA8LSBwcmVkaWN0KG1vZGVsLm5ldy50cmFpbiwgdGVzdC5uZXcsIHR5cGUgPSAicmVzcG9uc2UiKQpwcmVkcy5uZXcgPC0gcmVwKCJObyIsIGxlbmd0aChwcm9icy5uZXcpKQpwcmVkcy5uZXdbcHJvYnMubmV3ID49IDAuNV0gPC0gIlllcyIKCnRhYmxlKHRlc3QubmV3JGRlZmF1bHQsIHByZWRzLm5ldykKZXJyb3IubmV3IDwtIG1lYW4odGVzdC5uZXckZGVmYXVsdCAhPSBwcmVkcy5uZXcpCmNhdCgiVGhlIHRlc3QgZXJyb3IgZm9yIHRoZSBtb2RlbCB3aXRoIHRoZSBkdW1teSBzdHVkZW50IHZhcmlhYmxlIGlzOiIsIGVycm9yLm5ldywgIlxuIikKCmBgYAoKSW5jbHVkaW5nIGEgZHVtbXkgdmFyaWFibGUgZm9yIGBzdHVkZW50YCBsZWQgdG8gYSBtb2RlbCB3aXRoIGEgaGlnaGVyIHRlc3QgZXJyb3IgcmF0ZSB0aGFuIHRoZSBtb2RlbCB3aGljaCBkb2VzIG5vdCBpbmNsdWRlIHRoZSBkdW1teSB2YXJpYWJsZSB1c2luZyB0aGUgZXhhY3Qgc2FtZSBkYXRhIHNwbGl0LiAKCioqMy4gKEV4ZXJjaXNlIDUuNC42KSBXZSBjb250aW51ZSB0byBjb25zaWRlciB0aGUgdXNlIG9mIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0byBwcmVkaWN0IHRoZSBwcm9iYWJpbGl0eSBvZiBgZGVmYXVsdGAgdXNpbmcgYGluY29tZWAgYW5kIGBiYWxhbmNlYCBvbiB0aGUgYERlZmF1bHRgIGRhdGEgc2V0LiBJbiBwYXJ0aWN1bGFyLCB3ZSB3aWxsIG5vdyBjb21wdXRlIGVzdGltYXRlcyBmb3IgdGhlIHN0YW5kYXJkIGVycm9ycyBvZiB0aGUgYGluY29tZWAgYW5kIGBiYWxhbmNlYCBsb2dpc3RpYyByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBpbiB0d28gZGlmZmVyZW50IHdheXM6ICgxKSB1c2luZyB0aGUgYm9vdHN0cmFwLCBhbmQgKDIpIHVzaW5nIHRoZSBzdGFuZGFyZCBmb3JtdWxhIGZvciBjb21wdXRpbmcgdGhlIHN0YW5kYXJkIGVycm9ycyBpbiB0aGUgYGdsbSgpYCBmdW5jdGlvbi4gRG8gbm90IGZvcmdldCB0byBzZXQgYSByYW5kb20gc2VlZCBiZWZvcmUgYmVnaW5uaW5nIHlvdXIgYW5hbHlzaXMuKioKCihhKSBVc2luZyB0aGUgYHN1bW1hcnkoKWAgYW5kIGBnbG0oKWAgZnVuY3Rpb25zLCBkZXRlcm1pbmUgdGhlIGVzdGltYXRlZCBzdGFuZGFyZCBlcnJvcnMgZm9yIHRoZSBjb2XvrIBpY2llbnRzIGFzc29jaWF0ZWQgd2l0aCBgaW5jb21lYCBhbmQgYGJhbGFuY2VgIGluIGEgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB0aGF0IHVzZXMgYm90aCBwcmVkaWN0b3JzLgoKYGBgIHtyIFByb2IzYX0Kc2V0LnNlZWQoMjApCnAzLmZpdCA8LSBnbG0oZGVmYXVsdCB+IGluY29tZSArIGJhbGFuY2UsIGRhdGEgPSBEZWZhdWx0LCBmYW1pbHkgPSAnYmlub21pYWwnKQpzdW1tYXJ5KHAzLmZpdCkKYGBgClRoZSBlc3RpbWF0ZWQgc3RhbmRhcmQgZXJyb3JzIGZvciB0aGUgY29lZmZpY2llbnRzIGFzc29jaWF0ZWQgd2l0aCBgaW5jb21lYCBhbmQgYGJhbGFuY2VgIGFyZSA0Ljk4NWUtMDYgYW5kIDIuMjc0IGUtMDQsIHJlc3BlY3RpdmVseS4KCihiKSBXcml0ZSBhIGZ1bmN0aW9uLCBgYm9vdC5mbigpYCwgdGhhdCB0YWtlcyBhcyBpbnB1dCB0aGUgYERlZmF1bHRgIGRhdGEgc2V0IGFzIHdlbGwgYXMgYW4gaW5kZXggb2YgdGhlIG9ic2VydmF0aW9ucywgYW5kIHRoYXQgb3V0cHV0cyB0aGUgY29l76yAaWNpZW50IGVzdGltYXRlcyBmb3IgYGluY29tZWAgYW5kIGBiYWxhbmNlYCBpbiB0aGUgbXVsdGlwbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4KCmBgYCB7ciBQcm9iM2J9CmJvb3QuZm4gPC0gZnVuY3Rpb24oZGYsIHgpIHsKICBib290LmZpdCA8LSBnbG0oZGZbLDFdIH4gZGZbLDRdICsgZGZbLDNdLCBkYXRhID0gZGYsIHN1YnNldCA9IHgsCiAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICdiaW5vbWlhbCcpCiAgbmFtZXMoYm9vdC5maXQkY29lZmZpY2llbnRzKSA8LSBjKCdJbnRlcmNlcHQnLCAnSW5jb21lJywgJ0JhbGFuY2UnKQogIGdldC5jb2VmcyA8LSBjb2VmKGJvb3QuZml0KQogIHJldHVybihnZXQuY29lZnNbMjozXSkKfQpgYGAKCihjKSBVc2UgdGhlIGBib290KClgIGZ1bmN0aW9uIHRvZ2V0aGVyIHdpdGggeW91ciBgYm9vdC5mbigpYCBmdW5jdGlvbiB0byBlc3RpbWF0ZSB0aGUgc3RhbmRhcmQgZXJyb3JzIG9mIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIGNvZe+sgGljaWVudHMgZm9yIGBpbmNvbWVgIGFuZCBgYmFsYW5jZWAuCgpgYGAge3IgUHJvYjNjLCB3YXJuaW5ncyA9IEZBTFNFfQpsaWJyYXJ5KGJvb3QpCmJvb3QoZGF0YSA9IERlZmF1bHQsIGJvb3QuZm4sIFIgPSAxMDAwKQpgYGAKCihkKSBDb21tZW50IG9uIHRoZSBlc3RpbWF0ZWQgc3RhbmRhcmQgZXJyb3JzIG9idGFpbmVkIHVzaW5nIHRoZSBgZ2xtKClgIGZ1bmN0aW9uIGFuZCB1c2luZyB5b3VyIGJvb3RzdHJhcCBmdW5jdGlvbi4KClRoZSBlc3RpbWF0ZWQgc3RhbmRhcmQgZXJyb3JzIGFyZSBhcyBmb2xsb3dzOgoKICAgTWV0aG9kcyAgIHwgICAgIEluY29tZSAgICB8ICAgICBCYWxhbmNlICAgCi0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tIAogICBgZ2xtKClgICAgfCAgNC45ODVlLTA2ICAgIHwgIDIuMjc0ZS0wNCAgICAKICBib290c3RyYXAgIHwgIDUuMTE0MDgzZS0wNiB8ICAyLjIzMTU0NmUtMDQgCiAgCkNsZWFybHkgdGhlIGVzdGltYXRlcyBhcmUgdmVyeSBjbG9zZSBpbiB2YWx1ZS4KCgoqKjQuIChFeGVyY2lzZSA1LjQuOSkgV2Ugd2lsbCBub3cgY29uc2lkZXIgdGhlIGBCb3N0b25gIGhvdXNpbmcgZGF0YSBzZXQsIGZyb20gdGhlIGBJU0xSMmAgbGlicmFyeS4qKgoKKGEpIEJhc2VkIG9uIHRoaXMgZGF0YSBzZXQsIHByb3ZpZGUgYW4gZXN0aW1hdGUgZm9yIHRoZSBwb3B1bGF0aW9uIG1lYW4gb2YgYG1lZHZgLiBDYWxsIHRoaXMgZXN0aW1hdGUgJFxoYXR7XG11fSQuCgpgYGAge3IgUHJvYjRhLCB3YXJuaW5nID0gRkFMU0V9CmxpYnJhcnkoSVNMUjIpCmRhdGEoIkJvc3RvbiIpCm11LmhhdCA8LSBtZWFuKEJvc3RvbiRtZWR2KQptdS5oYXQKYGBgCkJhc2VkIG9uIHRoaXMgZGF0YSBzZXQsIHRoZSBhdmVyYWdlIG1lZGlhbiB2YWx1ZSBvZiBvd25lci1vY2N1cGllZCBob21lcyBhbW9uZyBzdWJ1cmJzIG9mIEJvc3RvbiBpcyBhcHByb3hpbWF0ZWx5ICRcaGF0e1xtdX0gPSBcJDIyLDUzMi44MSQuCgooYikgUHJvdmlkZSBhbiBlc3RpbWF0ZSBvZiB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgJFxoYXR7XG11fSQuIEludGVycHJldCB0aGlzIHJlc3VsdC4KKkhpbnQ6IFdlIGNhbiBjb21wdXRlIHRoZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgc2FtcGxlIG1lYW4gYnkgZGl2aWRpbmcgdGhlIHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24gYnkgdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLioKCmBgYCB7ciBQcm9iNGJ9ClNFLmhhdCA8LSBzZChCb3N0b24kbWVkdikvc3FydChsZW5ndGgoQm9zdG9uJG1lZHYpKQpTRS5oYXQKYGBgClRoZSBzdGFuZGFyZCBlcnJvciBlc3RpbWF0ZSBpcyBnaXZlbiBieSAkXHdpZGVoYXR7U0V9ID0gMC40MDg5JC4gRm9yIGEgc2FtcGxlIG9mIG93bmVyLW9jY3VwaWVkIGhvbWVzIGluIDUwNiBCb3N0b24gc3VidXJicywgdGhlICRcaGF0e1xtdX0kIGVzdGltYXRlIHdpbGwgdmFyeSBhcHByb3hpbWF0ZWx5IGJ5ICRccG0gXCxcLFwkNDA4LjkwJCwgb24gYXZlcmFnZS4gVGhpcyBpcyBhIHJlbGF0aXZlbHkgbG93IHN0YW5kYXJkIGVycm9yLCB3aGljaCB0ZWxscyB1cyB0aGF0ICRcaGF0e1xtdX0gPSBcJDIyLDUzMi44MSQgaXMgYSBnb29kIGVzdGltYXRlIG9mIHRoZSBwb3B1bGF0aW9uIG1lYW4gb2YgYG1lZHZgOyBvdXIgc2FtcGxlIGBtZWR2YCBzdWZmaWNpZW50bHkgcmVwcmVzZW50cyB0aGUgdHJ1ZSBwb3B1bGF0aW9uLgoKKGMpIE5vdyBlc3RpbWF0ZSB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgJFxoYXR7XG11fSQgdXNpbmcgdGhlIGJvb3RzdHJhcC4gSG93IGRvZXMgdGhpcyBjb21wYXJlIHRvIHlvdXIgYW5zd2VyIGZyb20gKGIpPwoKYGBgIHtyIFByb2I0Y30Kc2V0LnNlZWQoNTA2KQpib290Lm5ldyA8LSBmdW5jdGlvbihkZiwgeCkgewogIG11LmJvb3QgPC0gbWVhbihkZlt4XSkKICByZXR1cm4obXUuYm9vdCkKfQoKYm9vdChCb3N0b24kbWVkdiwgYm9vdC5uZXcsIFIgPSAxMDAwKQpgYGAKVGhlIHN0YW5kYXJkIGVycm9yIGVzdGltYXRlIGZvciAkXGhhdHtcbXV9JCBmcm9tIGJvb3RzdHJhcHBpbmcgaXMgJFx3aWRlaGF0e1NFfV97Ym9vdH0gPSAwLjQxNjQkLiBUaGlzIGVzdGltYXRlIGlzIHNsaWdodGx5IGhpZ2hlciB0aGFuIHRoYXQgb2J0YWluZWQgaW4gcGFydCAoYiksIGJ1dCBzdGlsbCByZWxhdGl2ZWx5IHNtYWxsLiBXZSBjYW4gc3RpbGwgY29uY2x1ZGUgdGhhdCBvdXIgc2FtcGxlIGBtZWR2YCBzdWZmaWNpZW50bHkgcmVwcmVzZW50cyB0aGUgdHJ1ZSBwb3B1bGF0aW9uLgoKKGQpIEJhc2VkIG9uIHlvdXIgYm9vdHN0cmFwIGVzdGltYXRlIGZyb20gKGMpLCBwcm92aWRlIGEgOTVcJSBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgbWVhbiBvZiBgbWVkdmAuIENvbXBhcmUgaXQgdG8gdGhlIHJlc3VsdHMgb2J0YWluZWQgdXNpbmcgYHQudGVzdChCb3N0b24kbWVkdilgLgoqSGludDogWW91IGNhbiBhcHByb3hpbWF0ZSBhIDk1XCUgY29uZmlkZW5jZSBpbnRlcnZhbCB1c2luZyB0aGUgZm9ybXVsYSAkW1xoYXR7XG11fSAtIDJTRShcaGF0e1xtdX0pLCBcaGF0e1xtdX0gKyAyU0UoXGhhdHtcbXV9KV0kLioKCmBgYCB7ciBQcm9iNGR9CnQudGVzdChCb3N0b24kbWVkdikKCm11LmhhdC5DSSA8LSBmdW5jdGlvbihtdSwgc2UpIHsKICBDSSA8LSBjKG11IC0gMipzZSwgbXUgKyAyKnNlKQogIG5hbWVzKENJKSA8LSBjKCJMb3dlciIsICJVcHBlciIpCiAgcmV0dXJuKENJKQp9CgptdS5oYXQuQ0kobXUuaGF0LCAwLjQxNjM5NDQpCmBgYApUaGUgOTVcJSBjb25maWRlbmNlIGludGVydmFsIGJhc2VkIG9uIHRoZSBib290c3RyYXAgZXN0aW1hdGUgb2Ygc3RhbmRhcmQgZXJyb3IgaXMgYXBwcm94aW1hdGVseSAoXCQyMSw3MDAuMDIsIFwkMjMsMzY1LjYwKS4gVGhpcyBpbnRlcnZhbCBpcyBvbmx5IHNsaWdodGx5IHdpZGVyIChieSBhYm91dCBcJDYwLjAwKSB0aGFuIHRoZSBvbmUgb2J0YWluZWQgdXNpbmcgYHQudGVzdChCb3N0b24kbWVkdilgIHdoaWNoIGlzIGFwcHJveGltYXRlbHkgKFwkMjEsNzI5LjUzLCBcJDIzLDMzNi4wOCkuIAoKKGUpIEJhc2VkIG9uIHRoaXMgZGF0YSBzZXQsIHByb3ZpZGUgYW4gZXN0aW1hdGUsICRcaGF0e1xtdX1fe21lZH0kLCBmb3IgdGhlIG1lZGlhbiB2YWx1ZSBvZiBgbWVkdmAgaW4gdGhlIHBvcHVsYXRpb24uCgpgYGAge3IgUHJvYjRlfQptdS5oYXQubWVkIDwtIG1lZGlhbihCb3N0b24kbWVkdikKbXUuaGF0Lm1lZApgYGAKVGhlIHBvcHVsYXRpb24gZXN0aW1hdGUgZm9yIHRoZSBtZWRpYW4gdmFsdWUgb2YgYG1lZHZgaXMgJFxoYXR7XG11fV97bWVkfSA9IFwkMjEsMjAwLjAwJC4KCihmKSBXZSBub3cgd291bGQgbGlrZSB0byBlc3RpbWF0ZSB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgJFxoYXR7XG11fV97bWVkfSQuIFVuZm9ydHVuYXRlbHksIHRoZXJlIGlzIG5vIHNpbXBsZSBmb3JtdWxhIGZvciBjb21wdXRpbmcgdGhlIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBtZWRpYW4uIEluc3RlYWQsIGVzdGltYXRlIHRoZSBzdGFuZGFyZCBlcnJvciBvZiB0aGUgbWVkaWFuIHVzaW5nIHRoZSBib290c3RyYXAuIENvbW1lbnQgb24geW91ciBmaW5kaW5ncy4KCmBgYCB7ciBQcm9iNGZ9CnNldC5zZWVkKDUwNikKYm9vdC40ZiA8LSBmdW5jdGlvbihkZiwgeCkgewogIG1lZC5ib290IDwtIG1lZGlhbihkZlt4XSkKICByZXR1cm4obWVkLmJvb3QpCn0KCmJvb3QoQm9zdG9uJG1lZHYsIGJvb3QuNGYsIFIgPSAxMDAwKQpgYGAKVGhlIHN0YW5kYXJkIGVycm9yIGVzdGltYXRlIGZvciAkXGhhdHtcbXV9X3ttZWR9JCBmcm9tIGJvb3RzdHJhcHBpbmcgaXMgJFx3aWRlaGF0e1NFfV97bWVkaWFufSA9IDAuMzg2NCQuIEFnYWluLCB0aGlzIGlzIHF1aXRlIHNtYWxsLiBXZSBjYW4gY29uY2x1ZGUgdGhhdCAkXGhhdHtcbXV9X3ttZWR9ID0gXCQyMSwyMDAuMDAkIGlzIGEgZ29vZCBlc3RpbWF0ZSBvZiB0aGUgdHJ1ZSBwb3B1bGF0aW9uIG1lZGlhbiBvZiBgbWVkdmAuCgooZykgQmFzZWQgb24gdGhpcyBkYXRhIHNldCwgcHJvdmlkZSBhbiBlc3RpbWF0ZSBmb3IgdGhlIHRlbnRoIHBlcmNlbnRpbGUgb2YgYG1lZHZgIGluIEJvc3RvbiBjZW5zdXMgdHJhY3RzLiBDYWxsIHRoaXMgcXVhbnRpdHkgJFxoYXR7XG11fV97MC4xfSQuIChZb3UgY2FuIHVzZSB0aGUgYHF1YW50aWxlKClgIGZ1bmN0aW9uLikKCmBgYHtyIFByb2I0Z30KaGF0Lm11MTAgPC0gcXVhbnRpbGUoQm9zdG9uJG1lZHYsIHByb2JzID0gMC4xKQpoYXQubXUxMApgYGAKVGhlIDEwdGggcGVyY2VudGlsZSBlc3RpbWF0ZSBmb3IgdGhlIHBvcHVsYXRpb24gb2YgYG1lZHZgaXMgJFxoYXR7XG11fV97MC4xfSA9IFwkMTIsNzUwLjAwJC4KCihoKSBVc2UgdGhlIGJvb3RzdHJhcCB0byBlc3RpbWF0ZSB0aGUgc3RhbmRhcmQgZXJyb3Igb2YgJFxoYXR7XG11fV97MC4xfSQuIENvbW1lbnQgb24geW91ciBmaW5kaW5ncy4KCmBgYCB7ciBQcm9iNGh9CmJvb3QuNGggPC0gZnVuY3Rpb24oZGYsIHgpIHsKICBib290MTAgPC0gcXVhbnRpbGUoZGZbeF0sIHByb2JzID0gMC4xKQogIHJldHVybihib290MTApCn0KCmJvb3QoQm9zdG9uJG1lZHYsIGJvb3QuNGgsIFIgPSAxMDAwKQpgYGAKVGhlIHN0YW5kYXJkIGVycm9yIGVzdGltYXRlIGZvciAkXGhhdHtcbXV9X3swLjF9JCBmcm9tIGJvb3RzdHJhcHBpbmcgaXMgJFx3aWRlaGF0e1NFfV97MC4xfSA9IDAuNTAyNyQuIEEgYml0IGhpZ2hlciB0aGFuIHByZXZpb3VzIFNFIGVzdGltYXRlcywgYnV0IHN0aWxsIHJlbGF0aXZlbHkgc21hbGwuIFdlIGNhbiBjb25jbHVkZSB0aGF0ICRcaGF0e1xtdX1fezAuMX0gPSBcJDEyLDc1MC4wMCQgaXMgYSBnb29kIGVzdGltYXRlIG9mIHRoZSB0cnVlIHBvcHVsYXRpb24gMTB0aCBwZXJjZW50aWxlIG9mIGBtZWR2YC4KCgo=