1. (Exercise 4.8.13) This question should be answered using
the Weekly data set, which is part of the
ISLR2 package. This data is similar in nature to the
Smarket data from this chapter’s lab, except that it
contains 1,089 weekly returns for 21 years, from the beginning of 1990
to the end of 2010.
- Produce some numerical and graphical summaries of the
Weekly data. Do there appear to be any patterns?
#download the library create data frame and get data overview
library(ISLR2)
data(Weekly)
#Display histograms of each variable
par(mfrow = c(3,3), mar = c(3.4,3.5,1.2,0), mgp =c(2,1,0))
hist(Weekly$Year, main = "Freq of Years Sampled",
xlab= "Year", col = "lightpink1")
hist(Weekly$Lag1, main = "Prev Week's % Return",
xlab= "Values of Prev Week's Return (%)", col = "palegreen3")
hist(Weekly$Lag2, main = "Prev 2 Weeks' % Return",
xlab= "Values of Prev 2 Weeks' Return (%)", col = "slateblue1")
hist(Weekly$Lag3, main = "Prev 3 Weeks' % Return",
xlab= "Values of Prev 3 Weeks' Return (%)", col = "indianred3")
hist(Weekly$Lag4, main = "Prev 4 Weeks' % Return",
xlab= "Values of Prev 4 Weeks' Return (%)", col = "steelblue2")
hist(Weekly$Lag5, main = "Prev 5 Weeks' % Return",
xlab= "Values of Prev 5 Weeks' Return (%)", col = "thistle2")
hist(Weekly$Volume, main = "Share Trading Volume",
xlab= "Daily Traded Share Avgs (billions)", col = "orange1")
hist(Weekly$Today, main = "Current Week % Return",
xlab= "Values of Current Week Returns (%)", col = "olivedrab3")
plot(Weekly$Direction, main = "Obs Market Direction",
xlab= "Weekly Directional Market Trends", col = "darkorchid2")
library(corrplot)
par(mfrow=c(1,1))

corrplot.mixed(cor(Weekly[,1:8]), order = "hclust", tl.cex = .65, number.cex = 0.75,
lower="number", upper="circle")

NA
Most predictor’s histograms appear approximately normally
distributed, with two notable exceptions:
- The predictor
Year appears to be approximately
uniformly distributed with some slight right skew
- The predictor
Volume has extreme right skew
Additionally, there appears to be a strong-positive correlation
between Year and Volume, which may indicate
multicollinearity.
- Use the full data set to perform a logistic regression with
Direction as the response and the five lag variables plus
Volume as predictors. Use the summary function to print the
results. Do any of the predictors appear to be statistically
significant? If so, which ones?
model.1b <- glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume,
family = binomial, data = Weekly)
summary(model.1b)
Call:
glm(formula = Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 +
Volume, family = binomial, data = Weekly)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) 0.26686 0.08593 3.106 0.0019 **
Lag1 -0.04127 0.02641 -1.563 0.1181
Lag2 0.05844 0.02686 2.175 0.0296 *
Lag3 -0.01606 0.02666 -0.602 0.5469
Lag4 -0.02779 0.02646 -1.050 0.2937
Lag5 -0.01447 0.02638 -0.549 0.5833
Volume -0.02274 0.03690 -0.616 0.5377
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 1496.2 on 1088 degrees of freedom
Residual deviance: 1486.4 on 1082 degrees of freedom
AIC: 1500.4
Number of Fisher Scoring iterations: 4
In this model, only the predictor Lag2 appears to be
statistically significant for \(0.01 <
\alpha < 0.05\).
- Compute the confusion matrix and overall fraction of correct
predictions. Explain what the confusion matrix is telling you about the
types of mistakes made by logistic regression.
probabilities.1b <- predict(model.1b, type = "response")
predictions.1b <- rep("Down", length(probabilities.1b))
predictions.1b[probabilities.1b >= 0.5] <- "Up"
table(Weekly$Direction, predictions.1b)
predictions.1b
Down Up
Down 54 430
Up 48 557
accuracy.1b <- mean(predictions.1b == Weekly$Direction)
cat("The accuracy or fraction of correct predictions is:", accuracy.1b, "\n")
The accuracy or fraction of correct predictions is: 0.5610652
#Type I error (False Positive Rate)
FPR <- 100*(430/1089)
cat("The Type I Error or False Positive Rate is: ", round(FPR, 2), "% \n", sep = "")
The Type I Error or False Positive Rate is: 39.49%
#Type II Error (False Negative Rate)
FNR <- 100*(48/1089)
cat("The Type II Error or False Negative Rate is: ", round(FNR, 2), "% \n", sep = "" )
The Type II Error or False Negative Rate is: 4.41%
This tells us that the model correctly predicted the value of
Direction (either Up or Down)
approximately 56.11% of the time. This would indicate that the
predictive accuracy of our model is not particularly strong.
Additionally, we have the Type I and Type II error rates, which are
the rates of false positive predictions and false negative predictions,
respectively.
- Now fit the logistic regression model using a training data period
from 1990 to 2008, with
Lag2 as the only predictor. Compute
the confusion matrix and the overall fraction of correct predictions for
the held out data (that is, the data from 2009 and 2010).
p1.train <- Weekly[1:985,]
p1.test <- Weekly[986:1089,]
model.1d <- glm(Direction ~ Lag2, data = p1.train, family = 'binomial')
probs.1d <- predict(model.1d, p1.test, type = "response")
preds.1d <- rep("Down", length(probs.1d))
preds.1d[probs.1d >= 0.5] <- "Up"
table(p1.test$Direction, preds.1d)
preds.1d
Down Up
Down 9 34
Up 5 56
accuracy.1d <- mean(preds.1d == p1.test$Direction)
cat("The accuracy or fraction of correct predictions is:", accuracy.1d, "\n")
The accuracy or fraction of correct predictions is: 0.625
- Repeat (d) using LDA.
library(MASS)
model.1e <- lda(Direction ~ Lag2, data = p1.train, family = 'binomial')
Error in eval(expr, p) : object 'p1.train' not found
- Repeat (d) using QDA.
model.1f <- qda(Direction ~ Lag2, data = p1.train, family = 'binomial')
preds.1f <- predict(model.1f, p1.test, type = 'response')
table(p1.test$Direction, preds.1f$class)
Down Up
Down 0 43
Up 0 61
accuracy.1f <- mean(preds.1f$class == p1.test$Direction)
cat("The accuracy or fraction of correct predictions is:", accuracy.1f, "\n")
The accuracy or fraction of correct predictions is: 0.5865385
- Repeat (d) using KNN with \(K=1\).
train2 <- as.matrix(Weekly$Lag2[Weekly$Year < 2009])
test2 <- as.matrix(Weekly$Lag2[Weekly$Year >= 2009])
tr.resp <- Weekly$Direction[Weekly$Year < 2009]
library(class)
model.1g <- knn(train2, test2, tr.resp, k = 1)
table(model.1g, p1.test$Direction)
model.1g Down Up
Down 21 29
Up 22 32
accuracy.1g <- mean(model.1g == p1.test$Direction)
cat("The accuracy or fraction of correct predictions is:", accuracy.1g, "\n")
The accuracy or fraction of correct predictions is: 0.5096154
- Repeat (d) using naive Bayes.
library(e1071)
model.1h <- naiveBayes(Direction ~ Lag2, data = p1.train)
preds.1h <- predict(model.1h, p1.test, type = 'class')
table(p1.test$Direction, preds.1h)
preds.1h
Down Up
Down 0 43
Up 0 61
accuracy.1h <- mean(preds.1h == p1.test$Direction)
cat("The accuracy or fraction of correct predictions is:", accuracy.1h, "\n")
The accuracy or fraction of correct predictions is: 0.5865385
- Which of these methods appears to provide the best results on this
data?
LDA an dLogistic regression are the two methods with the highest
accuracy rate (62.5%).
2. (Exercise 4.8.14) In this problem, you will develop a
model to predict whether a given car gets high or low gas mileage based
on the Auto data set.
- Create a binary variable,
mpg01, that contains a 1 if
mpg contains a value above its median, and a 0 if
mpg contains a value below its median. You can compute the
median using the median() function. Note you may find it
helpful to use the data.frame() function to create a single
data set containing both mpg01 and the other
Auto variables.
data(Auto)
mpg.med <- median(Auto$mpg)
mpg01 <- rep(0, length(Auto$mpg))
mpg01[Auto$mpg >= mpg.med] <- 1
Auto <- data.frame(mpg01, Auto)
- Explore the data graphically in order to investigate the association
between
mpg01 and the other features. Which of the other
features seem most likely to be useful in predicting mpg01?
Scatterplots and boxplots may be useful tools to answer this question.
Describe your findings.
plot(Auto)

library(ggplot2)
ggplot(Auto, aes(x=as.factor(origin), y=mpg, fill=as.factor(cylinders))) +
geom_boxplot()


ggplot(Auto, aes(x=weight, y=mpg, fill=as.factor(cylinders))) +
geom_boxplot()

ggplot(Auto, aes(x=weight, y=horsepower, fill=as.factor(cylinders))) +
geom_boxplot()

ggplot(Auto, aes(x=as.factor(origin), y=mpg, fill=as.factor(year))) +
geom_boxplot()

The value of mpg tends to decrease as the number of
cylinders increases in vehicles of both American and
European origin, but this trend does not hold with
Japanese-made vehicles.
In vehicles with 4 or more cylinders, as the number of
cylinders increases, the vehicle weight
increases and the mpg decreases. Trading mpg
for horsepower we find that the opposite is true:
generally, as weight and cylinders increase,
so does horsepower.
Across all three places of origin, as the vehicle model
year increases (becomes newer), the mpg also
generally tends to increase.
- Split the data into a training set and a test set.
library(caret)
set.seed(4814)
auto.index <- createDataPartition(Auto$mpg01, p = 0.75, list = FALSE)
tr.auto <- Auto[auto.index,]
te.auto <- Auto[-auto.index,]
- Perform LDA on the training data in order to predict
mpg01 using the variables that seemed most associated with
mpg01 in (b). What is the test error of the model
obtained?
lda.auto <- lda(mpg01 ~ cylinders + horsepower + weight + year, data = tr.auto, family = 'binomial')
lda.auto
Call:
lda(mpg01 ~ cylinders + horsepower + weight + year, data = tr.auto,
family = "binomial")
Prior probabilities of groups:
0 1
0.5 0.5
Group means:
cylinders horsepower weight year
0 6.789116 130.55782 3621.898 74.51701
1 4.170068 77.71429 2318.986 77.62585
Coefficients of linear discriminants:
LD1
cylinders -0.435975274
horsepower 0.005803904
weight -0.001161321
year 0.109786589
preds.2d <- predict(lda.auto, te.auto, type = 'response')
table(te.auto$mpg01, preds.2d$class)
0 1
0 43 6
1 2 47
te.error.2d <- mean(preds.2d$class != te.auto$mpg01)
cat("The test error of this LDA model is:", percent(te.error.2d, accuracy = 0.01), "\n")
The test error of this LDA model is: 8.16%
- Perform QDA on the training data in order to predict
mpg01 using the variables that seemed most associated with
mpg01 in (b). What is the test error of the model
obtained?
qda.auto <- qda(mpg01 ~ cylinders + horsepower + weight + year, data = tr.auto, family = 'binomial')
preds.2e <- predict(qda.auto, te.auto, type = 'response')
table(te.auto$mpg01, preds.2e$class)
0 1
0 44 5
1 5 44
te.error.2e <- mean(preds.2e$class != te.auto$mpg01)
cat("The test error of this QDA model is:", percent(te.error.2e, accuracy = 0.01), "\n")
The test error of this QDA model is: 10.20%
The test error of this QDA model is slightly higher than that of the
previous LDA model, fitted with the same predictors.
- Perform logistic regression on the training data in order to predict
mpg01 using the variables that seemed most associated with
mpg01 in (b). What is the test error of the model
obtained?
log.auto <- glm(mpg01 ~ cylinders + horsepower + weight + year, data = tr.auto, family = 'binomial')
summary(log.auto)
Call:
glm(formula = mpg01 ~ cylinders + horsepower + weight + year,
family = "binomial", data = tr.auto)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.185e+01 5.447e+00 -2.175 0.02960 *
cylinders -2.826e-01 2.928e-01 -0.965 0.33458
horsepower -5.736e-02 1.925e-02 -2.980 0.00288 **
weight -3.808e-03 8.699e-04 -4.377 1.20e-05 ***
year 3.858e-01 8.271e-02 4.665 3.09e-06 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 407.57 on 293 degrees of freedom
Residual deviance: 116.36 on 289 degrees of freedom
AIC: 126.36
Number of Fisher Scoring iterations: 8
probs.2f <- predict(log.auto, te.auto, type = "response")
preds.2f <- rep(0, length(probs.2f))
preds.2f[probs.2f >= 0.5] <- 1
table(te.auto$mpg01, preds.2f)
preds.2f
0 1
0 44 5
1 6 43
te.error.2f <- mean(preds.2f != te.auto$mpg01)
cat("The test error of this Logistic model is:", percent(te.error.2f, accuracy = 0.01), "\n")
The test error of this Logistic model is: 11.22%
- Perform naive Bayes on the training data in order to predict
mpg01 using the variables that seemed most associated with
mpg01 in (b). What is the test error of the model
obtained?
nb.auto <- naiveBayes(mpg01 ~ cylinders + horsepower + weight + year, data = tr.auto)
preds.2g <- predict(nb.auto, te.auto, type = 'class')
table(te.auto$mpg01, preds.2g)
preds.2g
0 1
0 43 6
1 4 45
te.error.2g <- mean(preds.2g != te.auto$mpg01)
cat("The test error of this Logistic model is:", percent(te.error.2g, accuracy = 0.01), "\n")
The test error of this Logistic model is: 10.20%
- Perform KNN on the training data, with several values of \(K\), in order to predict
mpg01. Use only the variables that seemed most associated
with mpg01 in (b). What test errors do you obtain? Which
value of \(K\) seems to perform the
best on this data set?
x1.tr <- tr.auto[,c(3,5,6,8)]
y1.tr <- tr.auto[,1]
x1.te <- te.auto[,c(3,5,6,8)]
y1.te <- te.auto[,1]
set.seed(481)
## KNN - K = 1 ##
knn.auto <- knn(x1.tr, x1.te, y1.tr, k = 1)
table(knn.auto, y1.te)
y1.te
knn.auto 0 1
0 46 11
1 3 38
te.error.2h <- mean(knn.auto != y1.te)
cat("The test error of this KNN model with k = 1 is:", percent(te.error.2h, accuracy = 0.01), "\n")
The test error of this KNN model with k = 1 is: 14.29%
## KNN - K = 10 ##
knn.auto <- knn(x1.tr, x1.te, y1.tr, k = 10)
table(knn.auto, y1.te)
y1.te
knn.auto 0 1
0 44 5
1 5 44
te.error.2h <- mean(knn.auto != y1.te)
cat("The test error of this KNN model with k = 10 is:", percent(te.error.2h, accuracy = 0.01), "\n")
The test error of this KNN model with k = 10 is: 10.20%
## KNN - K = 15 ##
knn.auto <- knn(x1.tr, x1.te, y1.tr, k = 15)
table(knn.auto, y1.te)
y1.te
knn.auto 0 1
0 41 3
1 8 46
te.error.2h <- mean(knn.auto != y1.te)
cat("The test error of this KNN model with k = 15 is:", percent(te.error.2h, accuracy = 0.01), "\n")
The test error of this KNN model with k = 15 is: 11.22%
Based on this data set, \(k=10\)
seems to perform the best.
3. (Exercise 4.8.16) Using the Boston data set, fit
classification models in order to predict whether a given census tract
has a crime rate above or below the median. Explore logistic regression,
LDA, naive Bayes, and KNN models using various subsets of the
predictors. Describe your findings.
Hint: You will have to create the response variable yourself,
using the variables that are contained in the Boston data
set.
data(Boston)
library(dplyr)
library(scales)
library(olsrr)
#get median crime rate and create response vector
crim.med <- median(Boston$crim)
response <- rep(0, length(Boston$crim))
response[Boston$crim >= crim.med] <- 1
#attach response vector to data frame
Boston$response <- response
#Split the data into test and train
set.seed(4816)
index.boston <- createDataPartition(Boston$response, p = 0.8, list = FALSE)
tr.bos <- Boston[index.boston,]
te.bos <- Boston[-index.boston,]
## LOGISTIC - FULL ##
log.bos <- glm(response ~ ., data = tr.bos, family = 'binomial')
summary(log.bos)
probs.log <- predict(log.bos, te.bos, type = "response")
preds.log <- rep(0, length(probs.log))
preds.log[probs.log >= 0.5] <- 1
table(te.bos$response, preds.log)
acc.log <- mean(preds.log == te.bos$response)
cat("The prediction accuracy of the full logistic model is:", percent(acc.log, accuracy = 0.01), "\n")
## LOGISTIC - REDUCED ##
#model selection
ols_step_both_p(log.bos, p_enter = 0.05, p_remove = 0.1)
log.red <- glm(response ~ nox + rad + medv + age, data = tr.bos, family = 'binomial')
summary(log.red)
probs.log.red <- predict(log.red, te.bos, type = "response")
preds.log.red <- rep(0, length(probs.log.red))
preds.log.red[probs.log.red >= 0.5] <- 1
table(te.bos$response, preds.log.red)
acc.log.red <- mean(preds.log.red == te.bos$response)
cat("The prediction accuracy of the reduced logistic model is:", percent(acc.log.red, accuracy = 0.01), "\n")
The full logistic regression model outperformed the reduced model,
selected using stepwise regression. Not only did the full model have
higher prediction accuracy, but the AIC was also much lower than that of
the reduced model (28 vs. 209.26), demonstrating that the former has a
much better balance between model fit and complexity.
## LDA - FULL ##
lda.bos <- lda(response ~ ., data = tr.bos, family = 'binomial')
preds.lda <- predict(lda.bos, te.bos, type = 'response')
table(te.bos$response, preds.lda$class)
acc.lda <- mean(preds.lda$class == te.bos$response)
cat("The prediction accuracy of the full LDA model is:", percent(acc.lda, accuracy = 0.01), "\n")
## LDA - REDUCED ##
lda.red <- lda(response ~ nox + age + rad + medv, data = tr.bos, family = 'binomial')
preds.lda.red <- predict(lda.red, te.bos, type = 'response')
table(te.bos$response, preds.lda.red$class)
acc.lda.red <- mean(preds.lda.red$class == te.bos$response)
cat("The prediction accuracy of the reduced LDA model is:", percent(acc.lda.red, accuracy = 0.01), "\n")
The prediction accuracy improved by one percentage point from the
full LDA (83.00%) model to the reduced LDA model (84.00%). Neither LDA
model outperformed the full Logistic model.
## NAIVE BAYES - FULL ##
nb.bos <- naiveBayes(response ~ ., data = tr.bos)
preds.nb <- predict(nb.bos, te.bos, type = 'class')
table(te.bos$response, preds.nb)
acc.nb <- mean(preds.nb == te.bos$response)
cat("The prediction accuracy of the full Naive Bayes model is:", percent(acc.nb, accuracy = 0.01), "\n")
## NAIVE BAYES - REDUCED ##
nb.bos.red <- naiveBayes(response ~ zn + nox + age + rad + ptratio + medv, data = tr.bos)
preds.nb.red <- predict(nb.bos.red, te.bos, type = 'class')
table(te.bos$response, preds.nb.red)
acc.nb.red <- mean(preds.nb.red == te.bos$response)
cat("The prediction accuracy of the reduced Naive Bayes model is:", percent(acc.nb.red, accuracy = 0.01), "\n")
The full Naive Bayes model outperforms the reduced model, but is
still slightly lower in accuracy than the full Logistic.
## KNN - K = 1 ##
x.tr <- tr.bos[,1:13]
y.tr <- tr.bos[,14]
x.te <- te.bos[,1:13]
y.te <- te.bos[, 14]
knn.bos <- knn(x.tr, x.te, y.tr, k = 1)
table(knn.bos, y.te)
acc.knn <- mean(knn.bos == y.te)
cat("The prediction accuracy of the KNN model with k = 1 is:", percent(acc.knn, accuracy = 0.01), "\n")
The full Logisitc regression model remains the highest performing of
those tested here.
LS0tCnRpdGxlOiAiU1RBIDY1NDMgQXNzaWdubWVudCAzIgphdXRob3I6ICJBbGx5c3NhIFdlaW5icmVjaHQiCmRhdGU6ICIyMDI1LTAzLTA3IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGNvbGxhcHNlID0gVFJVRSwKICBmaWcuYWxpZ249ImNlbnRlciIsCiAgZmlnLnBvcz0iYiIsCiAgc3RyaXAud2hpdGUgPSBUUlVFCikKYGBgCgoqKjEuIChFeGVyY2lzZSA0LjguMTMpIFRoaXMgcXVlc3Rpb24gc2hvdWxkIGJlIGFuc3dlcmVkIHVzaW5nIHRoZSBgV2Vla2x5YCBkYXRhIHNldCwgd2hpY2ggaXMgcGFydCBvZiB0aGUgYElTTFIyYCBwYWNrYWdlLiBUaGlzIGRhdGEgaXMgc2ltaWxhciBpbiBuYXR1cmUgdG8gdGhlIGBTbWFya2V0YCBkYXRhIGZyb20gdGhpcyBjaGFwdGVy4oCZcyBsYWIsIGV4Y2VwdCB0aGF0IGl0IGNvbnRhaW5zIDEsMDg5IHdlZWtseSByZXR1cm5zIGZvciAyMSB5ZWFycywgZnJvbSB0aGUgYmVnaW5uaW5nIG9mIDE5OTAgdG8gdGhlIGVuZCBvZiAyMDEwLioqCgooYSkgUHJvZHVjZSBzb21lIG51bWVyaWNhbCBhbmQgZ3JhcGhpY2FsIHN1bW1hcmllcyBvZiB0aGUgYFdlZWtseWAgZGF0YS4gRG8gdGhlcmUgYXBwZWFyIHRvIGJlIGFueSBwYXR0ZXJucz8KCmBgYCB7ciBQcm9iMWF9CiNkb3dubG9hZCB0aGUgbGlicmFyeSBjcmVhdGUgZGF0YSBmcmFtZSBhbmQgZ2V0IGRhdGEgb3ZlcnZpZXcKbGlicmFyeShJU0xSMikKZGF0YShXZWVrbHkpCiAgICAKI0Rpc3BsYXkgaGlzdG9ncmFtcyBvZiBlYWNoIHZhcmlhYmxlCnBhcihtZnJvdyA9IGMoMywzKSwgbWFyID0gYygzLjQsMy41LDEuMiwwKSwgbWdwID1jKDIsMSwwKSkKaGlzdChXZWVrbHkkWWVhciwgbWFpbiA9ICJGcmVxIG9mIFllYXJzIFNhbXBsZWQiLAogICAgICB4bGFiPSAiWWVhciIsIGNvbCA9ICJsaWdodHBpbmsxIikKICAgIApoaXN0KFdlZWtseSRMYWcxLCBtYWluID0gIlByZXYgV2VlaydzICUgUmV0dXJuIiwKICAgICAgeGxhYj0gIlZhbHVlcyBvZiBQcmV2IFdlZWsncyBSZXR1cm4gKCUpIiwgY29sID0gInBhbGVncmVlbjMiKQogICAgCmhpc3QoV2Vla2x5JExhZzIsIG1haW4gPSAiUHJldiAyIFdlZWtzJyAlIFJldHVybiIsCiAgICAgIHhsYWI9ICJWYWx1ZXMgb2YgUHJldiAyIFdlZWtzJyBSZXR1cm4gKCUpIiwgY29sID0gInNsYXRlYmx1ZTEiKQogICAgCmhpc3QoV2Vla2x5JExhZzMsIG1haW4gPSAiUHJldiAzIFdlZWtzJyAlIFJldHVybiIsCiAgICAgIHhsYWI9ICJWYWx1ZXMgb2YgUHJldiAzIFdlZWtzJyBSZXR1cm4gKCUpIiwgY29sID0gImluZGlhbnJlZDMiKQogICAgCmhpc3QoV2Vla2x5JExhZzQsIG1haW4gPSAiUHJldiA0IFdlZWtzJyAlIFJldHVybiIsCiAgICAgIHhsYWI9ICJWYWx1ZXMgb2YgUHJldiA0IFdlZWtzJyBSZXR1cm4gKCUpIiwgY29sID0gInN0ZWVsYmx1ZTIiKQogICAgCmhpc3QoV2Vla2x5JExhZzUsIG1haW4gPSAiUHJldiA1IFdlZWtzJyAlIFJldHVybiIsCiAgICAgIHhsYWI9ICJWYWx1ZXMgb2YgUHJldiA1IFdlZWtzJyBSZXR1cm4gKCUpIiwgY29sID0gInRoaXN0bGUyIikKICAgIApoaXN0KFdlZWtseSRWb2x1bWUsIG1haW4gPSAiU2hhcmUgVHJhZGluZyBWb2x1bWUiLAogICAgICB4bGFiPSAiRGFpbHkgVHJhZGVkIFNoYXJlIEF2Z3MgKGJpbGxpb25zKSIsIGNvbCA9ICJvcmFuZ2UxIikKICAgIApoaXN0KFdlZWtseSRUb2RheSwgbWFpbiA9ICJDdXJyZW50IFdlZWsgJSBSZXR1cm4iLAogICAgICB4bGFiPSAiVmFsdWVzIG9mIEN1cnJlbnQgV2VlayBSZXR1cm5zICglKSIsIGNvbCA9ICJvbGl2ZWRyYWIzIikKICAgIApwbG90KFdlZWtseSREaXJlY3Rpb24sIG1haW4gPSAiT2JzIE1hcmtldCBEaXJlY3Rpb24iLAogICAgICB4bGFiPSAiV2Vla2x5IERpcmVjdGlvbmFsIE1hcmtldCBUcmVuZHMiLCBjb2wgPSAiZGFya29yY2hpZDIiKQoKbGlicmFyeShjb3JycGxvdCkKcGFyKG1mcm93PWMoMSwxKSkKY29ycnBsb3QubWl4ZWQoY29yKFdlZWtseVssMTo4XSksIG9yZGVyID0gImhjbHVzdCIsIHRsLmNleCA9IC42NSwgbnVtYmVyLmNleCA9IDAuNzUsIAogICAgICAgICAgICAgICBsb3dlcj0ibnVtYmVyIiwgdXBwZXI9ImNpcmNsZSIpCiAgICAKYGBgCk1vc3QgcHJlZGljdG9yJ3MgaGlzdG9ncmFtcyBhcHBlYXIgYXBwcm94aW1hdGVseSBub3JtYWxseSBkaXN0cmlidXRlZCwgd2l0aCB0d28gbm90YWJsZSBleGNlcHRpb25zOgoKKiBUaGUgcHJlZGljdG9yIGBZZWFyYCBhcHBlYXJzIHRvIGJlIGFwcHJveGltYXRlbHkgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkIHdpdGggc29tZSBzbGlnaHQgcmlnaHQgc2tldwoqIFRoZSBwcmVkaWN0b3IgYFZvbHVtZWAgaGFzIGV4dHJlbWUgcmlnaHQgc2tldwogIApBZGRpdGlvbmFsbHksIHRoZXJlIGFwcGVhcnMgdG8gYmUgYSBzdHJvbmctcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBgWWVhcmAgYW5kIGBWb2x1bWVgLCB3aGljaCBtYXkgaW5kaWNhdGUgbXVsdGljb2xsaW5lYXJpdHkuCgooYikgVXNlIHRoZSBmdWxsIGRhdGEgc2V0IHRvIHBlcmZvcm0gYSBsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggYERpcmVjdGlvbmAgYXMgdGhlIHJlc3BvbnNlIGFuZCB0aGUgZml2ZSBsYWcgdmFyaWFibGVzIHBsdXMgYFZvbHVtZWAgYXMgcHJlZGljdG9ycy4gVXNlIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHRvIHByaW50IHRoZSByZXN1bHRzLiBEbyBhbnkgb2YgdGhlIHByZWRpY3RvcnMgYXBwZWFyIHRvIGJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQ/IElmIHNvLCB3aGljaCBvbmVzPwoKYGBgIHtyIFByb2IxYn0KbW9kZWwuMWIgPC0gZ2xtKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyICsgTGFnMyArIExhZzQgKyBMYWc1ICsgVm9sdW1lLCAKICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsLCBkYXRhID0gV2Vla2x5KQpzdW1tYXJ5KG1vZGVsLjFiKQpgYGAKSW4gdGhpcyBtb2RlbCwgb25seSB0aGUgcHJlZGljdG9yIGBMYWcyYCBhcHBlYXJzIHRvIGJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZm9yICQwLjAxIDwgXGFscGhhIDwgMC4wNSQuCgooYykgQ29tcHV0ZSB0aGUgY29uZnVzaW9uIG1hdHJpeCBhbmQgb3ZlcmFsbCBmcmFjdGlvbiBvZiBjb3JyZWN0IHByZWRpY3Rpb25zLiBFeHBsYWluIHdoYXQgdGhlIGNvbmZ1c2lvbiBtYXRyaXggaXMgdGVsbGluZyB5b3UgYWJvdXQgdGhlIHR5cGVzIG9mIG1pc3Rha2VzIG1hZGUgYnkgbG9naXN0aWMgcmVncmVzc2lvbi4KCmBgYCB7ciBQcm9iMWN9CnByb2JhYmlsaXRpZXMuMWIgPC0gcHJlZGljdChtb2RlbC4xYiwgdHlwZSA9ICJyZXNwb25zZSIpCnByZWRpY3Rpb25zLjFiIDwtIHJlcCgiRG93biIsIGxlbmd0aChwcm9iYWJpbGl0aWVzLjFiKSkKcHJlZGljdGlvbnMuMWJbcHJvYmFiaWxpdGllcy4xYiA+PSAwLjVdIDwtICJVcCIKdGFibGUoV2Vla2x5JERpcmVjdGlvbiwgcHJlZGljdGlvbnMuMWIpCgphY2N1cmFjeS4xYiA8LSBtZWFuKHByZWRpY3Rpb25zLjFiID09IFdlZWtseSREaXJlY3Rpb24pCmNhdCgiVGhlIGFjY3VyYWN5IG9yIGZyYWN0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMgaXM6IiwgYWNjdXJhY3kuMWIsICJcbiIpCgojVHlwZSBJIGVycm9yIChGYWxzZSBQb3NpdGl2ZSBSYXRlKQpGUFIgPC0gMTAwKig0MzAvMTA4OSkKY2F0KCJUaGUgVHlwZSBJIEVycm9yIG9yIEZhbHNlIFBvc2l0aXZlIFJhdGUgaXM6ICIsIHJvdW5kKEZQUiwgMiksICIlIFxuIiwgc2VwID0gIiIpCgojVHlwZSBJSSBFcnJvciAoRmFsc2UgTmVnYXRpdmUgUmF0ZSkKRk5SIDwtIDEwMCooNDgvMTA4OSkKICBjYXQoIlRoZSBUeXBlIElJIEVycm9yIG9yIEZhbHNlIE5lZ2F0aXZlIFJhdGUgaXM6ICIsIHJvdW5kKEZOUiwgMiksICIlIFxuIiwgc2VwID0gIiIgKQpgYGAKClRoaXMgdGVsbHMgdXMgdGhhdCB0aGUgbW9kZWwgY29ycmVjdGx5IHByZWRpY3RlZCB0aGUgdmFsdWUgb2YgYERpcmVjdGlvbmAgKGVpdGhlciBgVXBgIG9yIGBEb3duYCkgYXBwcm94aW1hdGVseSA1Ni4xMSUgb2YgdGhlIHRpbWUuIFRoaXMgd291bGQgaW5kaWNhdGUgdGhhdCB0aGUgcHJlZGljdGl2ZSBhY2N1cmFjeSBvZiBvdXIgbW9kZWwgaXMgbm90IHBhcnRpY3VsYXJseSBzdHJvbmcuCgpBZGRpdGlvbmFsbHksIHdlIGhhdmUgdGhlIFR5cGUgSSBhbmQgVHlwZSBJSSBlcnJvciByYXRlcywgd2hpY2ggYXJlIHRoZSByYXRlcyBvZiBmYWxzZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyBhbmQgZmFsc2UgbmVnYXRpdmUgcHJlZGljdGlvbnMsIHJlc3BlY3RpdmVseS4KCihkKSBOb3cgZml0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIGEgdHJhaW5pbmcgZGF0YSBwZXJpb2QgZnJvbSAxOTkwIHRvIDIwMDgsIHdpdGggYExhZzJgIGFzIHRoZSBvbmx5IHByZWRpY3Rvci4gQ29tcHV0ZSB0aGUgY29uZnVzaW9uIG1hdHJpeCBhbmQgdGhlIG92ZXJhbGwgZnJhY3Rpb24gb2YgY29ycmVjdCBwcmVkaWN0aW9ucyBmb3IgdGhlIGhlbGQgb3V0IGRhdGEgKHRoYXQgaXMsIHRoZSBkYXRhIGZyb20gMjAwOSBhbmQgMjAxMCkuCgpgYGAge3IgUHJvYjFkfQpwMS50cmFpbiA8LSBXZWVrbHlbMTo5ODUsXQpwMS50ZXN0IDwtIFdlZWtseVs5ODY6MTA4OSxdCgptb2RlbC4xZCA8LSBnbG0oRGlyZWN0aW9uIH4gTGFnMiwgZGF0YSA9IHAxLnRyYWluLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKcHJvYnMuMWQgPC0gcHJlZGljdChtb2RlbC4xZCwgcDEudGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpCnByZWRzLjFkIDwtIHJlcCgiRG93biIsIGxlbmd0aChwcm9icy4xZCkpCnByZWRzLjFkW3Byb2JzLjFkID49IDAuNV0gPC0gIlVwIgp0YWJsZShwMS50ZXN0JERpcmVjdGlvbiwgcHJlZHMuMWQpCgphY2N1cmFjeS4xZCA8LSBtZWFuKHByZWRzLjFkID09IHAxLnRlc3QkRGlyZWN0aW9uKQpjYXQoIlRoZSBhY2N1cmFjeSBvciBmcmFjdGlvbiBvZiBjb3JyZWN0IHByZWRpY3Rpb25zIGlzOiIsIGFjY3VyYWN5LjFkLCAiXG4iKQpgYGAKCihlKSBSZXBlYXQgKGQpIHVzaW5nIExEQS4KCmBgYCB7ciBQcm9iMWUsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeShNQVNTKQptb2RlbC4xZSA8LSBsZGEoRGlyZWN0aW9uIH4gTGFnMiwgZGF0YSA9IHAxLnRyYWluLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKcHJlZHMuMWUgPC0gcHJlZGljdChtb2RlbC4xZSwgcDEudGVzdCwgdHlwZSA9ICdyZXNwb25zZScpCnRhYmxlKHAxLnRlc3QkRGlyZWN0aW9uLCBwcmVkcy4xZSRjbGFzcykKCmFjY3VyYWN5LjFlIDwtIG1lYW4ocHJlZHMuMWUkY2xhc3MgPT0gcDEudGVzdCREaXJlY3Rpb24pCmNhdCgiVGhlIGFjY3VyYWN5IG9yIGZyYWN0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMgaXM6IiwgYWNjdXJhY3kuMWUsICJcbiIpCmBgYAoKKGYpIFJlcGVhdCAoZCkgdXNpbmcgUURBLgoKYGBgIHtyIFByb2IxZn0KbW9kZWwuMWYgPC0gcWRhKERpcmVjdGlvbiB+IExhZzIsIGRhdGEgPSBwMS50cmFpbiwgZmFtaWx5ID0gJ2Jpbm9taWFsJykKCnByZWRzLjFmIDwtIHByZWRpY3QobW9kZWwuMWYsIHAxLnRlc3QsIHR5cGUgPSAncmVzcG9uc2UnKQp0YWJsZShwMS50ZXN0JERpcmVjdGlvbiwgcHJlZHMuMWYkY2xhc3MpCgphY2N1cmFjeS4xZiA8LSBtZWFuKHByZWRzLjFmJGNsYXNzID09IHAxLnRlc3QkRGlyZWN0aW9uKQpjYXQoIlRoZSBhY2N1cmFjeSBvciBmcmFjdGlvbiBvZiBjb3JyZWN0IHByZWRpY3Rpb25zIGlzOiIsIGFjY3VyYWN5LjFmLCAiXG4iKQpgYGAKCihnKSBSZXBlYXQgKGQpIHVzaW5nIEtOTiB3aXRoICRLPTEkLgoKYGBgIHtyIFByb2IxZ30KdHJhaW4yIDwtIGFzLm1hdHJpeChXZWVrbHkkTGFnMltXZWVrbHkkWWVhciA8IDIwMDldKQp0ZXN0MiA8LSBhcy5tYXRyaXgoV2Vla2x5JExhZzJbV2Vla2x5JFllYXIgPj0gMjAwOV0pCnRyLnJlc3AgPC0gV2Vla2x5JERpcmVjdGlvbltXZWVrbHkkWWVhciA8IDIwMDldCgpsaWJyYXJ5KGNsYXNzKQptb2RlbC4xZyA8LSBrbm4odHJhaW4yLCB0ZXN0MiwgdHIucmVzcCwgayA9IDEpCnRhYmxlKG1vZGVsLjFnLCBwMS50ZXN0JERpcmVjdGlvbikKCmFjY3VyYWN5LjFnIDwtIG1lYW4obW9kZWwuMWcgPT0gcDEudGVzdCREaXJlY3Rpb24pCmNhdCgiVGhlIGFjY3VyYWN5IG9yIGZyYWN0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMgaXM6IiwgYWNjdXJhY3kuMWcsICJcbiIpCmBgYAoKKGgpIFJlcGVhdCAoZCkgdXNpbmcgbmFpdmUgQmF5ZXMuCgpgYGAge3IgUHJvYjFofQpsaWJyYXJ5KGUxMDcxKQptb2RlbC4xaCA8LSBuYWl2ZUJheWVzKERpcmVjdGlvbiB+IExhZzIsIGRhdGEgPSBwMS50cmFpbikKCnByZWRzLjFoIDwtIHByZWRpY3QobW9kZWwuMWgsIHAxLnRlc3QsIHR5cGUgPSAnY2xhc3MnKQp0YWJsZShwMS50ZXN0JERpcmVjdGlvbiwgcHJlZHMuMWgpCgphY2N1cmFjeS4xaCA8LSBtZWFuKHByZWRzLjFoID09IHAxLnRlc3QkRGlyZWN0aW9uKQpjYXQoIlRoZSBhY2N1cmFjeSBvciBmcmFjdGlvbiBvZiBjb3JyZWN0IHByZWRpY3Rpb25zIGlzOiIsIGFjY3VyYWN5LjFoLCAiXG4iKQpgYGAKCihpKSBXaGljaCBvZiB0aGVzZSBtZXRob2RzIGFwcGVhcnMgdG8gcHJvdmlkZSB0aGUgYmVzdCByZXN1bHRzIG9uIHRoaXMgZGF0YT8KCkxEQSBhbiBkTG9naXN0aWMgcmVncmVzc2lvbiBhcmUgdGhlIHR3byBtZXRob2RzIHdpdGggdGhlIGhpZ2hlc3QgYWNjdXJhY3kgcmF0ZSAoNjIuNVwlKS4KCgoqKjIuIChFeGVyY2lzZSA0LjguMTQpIEluIHRoaXMgcHJvYmxlbSwgeW91IHdpbGwgZGV2ZWxvcCBhIG1vZGVsIHRvIHByZWRpY3Qgd2hldGhlciBhIGdpdmVuIGNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlIGJhc2VkIG9uIHRoZSBgQXV0b2AgZGF0YSBzZXQuKioKCihhKSBDcmVhdGUgYSBiaW5hcnkgdmFyaWFibGUsIGBtcGcwMWAsIHRoYXQgY29udGFpbnMgYSAxIGlmIGBtcGdgIGNvbnRhaW5zIGEgdmFsdWUgYWJvdmUgaXRzIG1lZGlhbiwgYW5kIGEgMCBpZiBgbXBnYCBjb250YWlucyBhIHZhbHVlIGJlbG93IGl0cyBtZWRpYW4uIFlvdSBjYW4gY29tcHV0ZSB0aGUgbWVkaWFuIHVzaW5nIHRoZSBgbWVkaWFuKClgIGZ1bmN0aW9uLiBOb3RlIHlvdSBtYXkgZmluZCBpdCBoZWxwZnVsIHRvIHVzZSB0aGUgYGRhdGEuZnJhbWUoKWAgZnVuY3Rpb24gdG8gY3JlYXRlIGEgc2luZ2xlIGRhdGEgc2V0IGNvbnRhaW5pbmcgYm90aCBgbXBnMDFgIGFuZCB0aGUgb3RoZXIgYEF1dG9gIHZhcmlhYmxlcy4KCmBgYCB7ciBQcm9iMmF9CmRhdGEoQXV0bykKbXBnLm1lZCA8LSBtZWRpYW4oQXV0byRtcGcpCm1wZzAxIDwtIHJlcCgwLCBsZW5ndGgoQXV0byRtcGcpKQptcGcwMVtBdXRvJG1wZyA+PSBtcGcubWVkXSA8LSAxCgoKQXV0byA8LSBkYXRhLmZyYW1lKG1wZzAxLCBBdXRvKQpgYGAKCihiKSBFeHBsb3JlIHRoZSBkYXRhIGdyYXBoaWNhbGx5IGluIG9yZGVyIHRvIGludmVzdGlnYXRlIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIGBtcGcwMWAgYW5kIHRoZSBvdGhlciBmZWF0dXJlcy4gV2hpY2ggb2YgdGhlIG90aGVyIGZlYXR1cmVzIHNlZW0gbW9zdCBsaWtlbHkgdG8gYmUgdXNlZnVsIGluIHByZWRpY3RpbmcgYG1wZzAxYD8gU2NhdHRlcnBsb3RzIGFuZCBib3hwbG90cyBtYXkgYmUgdXNlZnVsIHRvb2xzIHRvIGFuc3dlciB0aGlzIHF1ZXN0aW9uLiBEZXNjcmliZSB5b3VyIGZpbmRpbmdzLgoKYGBgIHtyIFByb2IyYn0KcGxvdChBdXRvKQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdChBdXRvLCBhZXMoeD1hcy5mYWN0b3Iob3JpZ2luKSwgeT1tcGcsIGZpbGw9YXMuZmFjdG9yKGN5bGluZGVycykpKSArIAogICAgZ2VvbV9ib3hwbG90KCkKZ2dwbG90KEF1dG8sIGFlcyh4PXdlaWdodCwgeT1tcGcsIGZpbGw9YXMuZmFjdG9yKGN5bGluZGVycykpKSArIAogICAgZ2VvbV9ib3hwbG90KCkKZ2dwbG90KEF1dG8sIGFlcyh4PXdlaWdodCwgeT1ob3JzZXBvd2VyLCBmaWxsPWFzLmZhY3RvcihjeWxpbmRlcnMpKSkgKyAKICAgIGdlb21fYm94cGxvdCgpCmdncGxvdChBdXRvLCBhZXMoeD1hcy5mYWN0b3Iob3JpZ2luKSwgeT1tcGcsIGZpbGw9YXMuZmFjdG9yKHllYXIpKSkgKyAKICAgIGdlb21fYm94cGxvdCgpCgpgYGAKVGhlIHZhbHVlIG9mIGBtcGdgIHRlbmRzIHRvIGRlY3JlYXNlIGFzIHRoZSBudW1iZXIgb2YgYGN5bGluZGVyc2AgaW5jcmVhc2VzIGluIHZlaGljbGVzIG9mIGJvdGggQW1lcmljYW4gYW5kIEV1cm9wZWFuIGBvcmlnaW5gLCBidXQgdGhpcyB0cmVuZCBkb2VzIG5vdCBob2xkIHdpdGggSmFwYW5lc2UtbWFkZSB2ZWhpY2xlcy4KCkluIHZlaGljbGVzIHdpdGggNCBvciBtb3JlIGBjeWxpbmRlcnNgLCBhcyB0aGUgbnVtYmVyIG9mIGBjeWxpbmRlcnNgIGluY3JlYXNlcywgdGhlIHZlaGljbGUgYHdlaWdodGAgaW5jcmVhc2VzIGFuZCB0aGUgYG1wZ2AgZGVjcmVhc2VzLiBUcmFkaW5nIGBtcGdgIGZvciBgaG9yc2Vwb3dlcmAgd2UgZmluZCB0aGF0IHRoZSBvcHBvc2l0ZSBpcyB0cnVlOiBnZW5lcmFsbHksIGFzIGB3ZWlnaHRgIGFuZCBgY3lsaW5kZXJzYCBpbmNyZWFzZSwgc28gZG9lcyBgaG9yc2Vwb3dlcmAuCgpBY3Jvc3MgYWxsIHRocmVlIHBsYWNlcyBvZiBgb3JpZ2luYCwgYXMgdGhlIHZlaGljbGUgbW9kZWwgYHllYXJgIGluY3JlYXNlcyAoYmVjb21lcyBuZXdlciksIHRoZSBgbXBnYCBhbHNvIGdlbmVyYWxseSB0ZW5kcyB0byBpbmNyZWFzZS4KCihjKSBTcGxpdCB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0LgoKYGBgIHtyIFByb2IyY30KbGlicmFyeShjYXJldCkKc2V0LnNlZWQoNDgxNCkKYXV0by5pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKEF1dG8kbXBnMDEsIHAgPSAwLjc1LCBsaXN0ID0gRkFMU0UpCnRyLmF1dG8gPC0gQXV0b1thdXRvLmluZGV4LF0KdGUuYXV0byA8LSBBdXRvWy1hdXRvLmluZGV4LF0KYGBgCgooZCkgUGVyZm9ybSBMREEgb24gdGhlIHRyYWluaW5nIGRhdGEgaW4gb3JkZXIgdG8gcHJlZGljdCBgbXBnMDFgIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggYG1wZzAxYCBpbiAoYikuIFdoYXQgaXMgdGhlIHRlc3QgZXJyb3Igb2YgdGhlIG1vZGVsIG9idGFpbmVkPwoKYGBgIHtyIFByb2IyZH0KbGRhLmF1dG8gPC0gbGRhKG1wZzAxIH4gY3lsaW5kZXJzICsgaG9yc2Vwb3dlciArIHdlaWdodCArIHllYXIsIGRhdGEgPSB0ci5hdXRvLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKcHJlZHMuMmQgPC0gcHJlZGljdChsZGEuYXV0bywgdGUuYXV0bywgdHlwZSA9ICdyZXNwb25zZScpCnRhYmxlKHRlLmF1dG8kbXBnMDEsIHByZWRzLjJkJGNsYXNzKQoKdGUuZXJyb3IuMmQgPC0gbWVhbihwcmVkcy4yZCRjbGFzcyAhPSB0ZS5hdXRvJG1wZzAxKQpjYXQoIlRoZSB0ZXN0IGVycm9yIG9mIHRoaXMgTERBIG1vZGVsIGlzOiIsIHBlcmNlbnQodGUuZXJyb3IuMmQsIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCmBgYAoKKGUpIFBlcmZvcm0gUURBIG9uIHRoZSB0cmFpbmluZyBkYXRhIGluIG9yZGVyIHRvIHByZWRpY3QgYG1wZzAxYCB1c2luZyB0aGUgdmFyaWFibGVzIHRoYXQgc2VlbWVkIG1vc3QgYXNzb2NpYXRlZCB3aXRoIGBtcGcwMWAgaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8KCmBgYCB7ciBQcm9iMmV9CnFkYS5hdXRvIDwtIHFkYShtcGcwMSB+IGN5bGluZGVycyArIGhvcnNlcG93ZXIgKyB3ZWlnaHQgKyB5ZWFyLCBkYXRhID0gdHIuYXV0bywgZmFtaWx5ID0gJ2Jpbm9taWFsJykKCnByZWRzLjJlIDwtIHByZWRpY3QocWRhLmF1dG8sIHRlLmF1dG8sIHR5cGUgPSAncmVzcG9uc2UnKQp0YWJsZSh0ZS5hdXRvJG1wZzAxLCBwcmVkcy4yZSRjbGFzcykKCnRlLmVycm9yLjJlIDwtIG1lYW4ocHJlZHMuMmUkY2xhc3MgIT0gdGUuYXV0byRtcGcwMSkKY2F0KCJUaGUgdGVzdCBlcnJvciBvZiB0aGlzIFFEQSBtb2RlbCBpczoiLCBwZXJjZW50KHRlLmVycm9yLjJlLCBhY2N1cmFjeSA9IDAuMDEpLCAiXG4iKQpgYGAKVGhlIHRlc3QgZXJyb3Igb2YgdGhpcyBRREEgbW9kZWwgaXMgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gdGhhdCBvZiB0aGUgcHJldmlvdXMgTERBIG1vZGVsLCBmaXR0ZWQgd2l0aCB0aGUgc2FtZSBwcmVkaWN0b3JzLgoKKGYpIFBlcmZvcm0gbG9naXN0aWMgcmVncmVzc2lvbiBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IGBtcGcwMWAgdXNpbmcgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBgbXBnMDFgIGluIChiKS4gV2hhdCBpcyB0aGUgdGVzdCBlcnJvciBvZiB0aGUgbW9kZWwgb2J0YWluZWQ/CgpgYGAge3IgUHJvYjJmfQpsb2cuYXV0byA8LSBnbG0obXBnMDEgfiBjeWxpbmRlcnMgKyBob3JzZXBvd2VyICsgd2VpZ2h0ICsgeWVhciwgZGF0YSA9IHRyLmF1dG8sIGZhbWlseSA9ICdiaW5vbWlhbCcpCnN1bW1hcnkobG9nLmF1dG8pCgpwcm9icy4yZiA8LSBwcmVkaWN0KGxvZy5hdXRvLCB0ZS5hdXRvLCB0eXBlID0gInJlc3BvbnNlIikKcHJlZHMuMmYgPC0gcmVwKDAsIGxlbmd0aChwcm9icy4yZikpCnByZWRzLjJmW3Byb2JzLjJmID49IDAuNV0gPC0gMQp0YWJsZSh0ZS5hdXRvJG1wZzAxLCBwcmVkcy4yZikKCnRlLmVycm9yLjJmIDwtIG1lYW4ocHJlZHMuMmYgIT0gdGUuYXV0byRtcGcwMSkKY2F0KCJUaGUgdGVzdCBlcnJvciBvZiB0aGlzIExvZ2lzdGljIG1vZGVsIGlzOiIsIHBlcmNlbnQodGUuZXJyb3IuMmYsIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCmBgYAoKKGcpIFBlcmZvcm0gbmFpdmUgQmF5ZXMgb24gdGhlIHRyYWluaW5nIGRhdGEgaW4gb3JkZXIgdG8gcHJlZGljdCBgbXBnMDFgIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggYG1wZzAxYCBpbiAoYikuIFdoYXQgaXMgdGhlIHRlc3QgZXJyb3Igb2YgdGhlIG1vZGVsIG9idGFpbmVkPwoKYGBgIHtyIFByb2IyZ30KbmIuYXV0byA8LSBuYWl2ZUJheWVzKG1wZzAxIH4gY3lsaW5kZXJzICsgaG9yc2Vwb3dlciArIHdlaWdodCArIHllYXIsIGRhdGEgPSB0ci5hdXRvKQoKcHJlZHMuMmcgPC0gcHJlZGljdChuYi5hdXRvLCB0ZS5hdXRvLCB0eXBlID0gJ2NsYXNzJykKdGFibGUodGUuYXV0byRtcGcwMSwgcHJlZHMuMmcpCgp0ZS5lcnJvci4yZyA8LSBtZWFuKHByZWRzLjJnICE9IHRlLmF1dG8kbXBnMDEpCmNhdCgiVGhlIHRlc3QgZXJyb3Igb2YgdGhpcyBOYWl2ZSBCYXllcyBtb2RlbCBpczoiLCBwZXJjZW50KHRlLmVycm9yLjJnLCBhY2N1cmFjeSA9IDAuMDEpLCAiXG4iKQpgYGAKCihoKSBQZXJmb3JtIEtOTiBvbiB0aGUgdHJhaW5pbmcgZGF0YSwgd2l0aCBzZXZlcmFsIHZhbHVlcyBvZiAkSyQsIGluIG9yZGVyIHRvIHByZWRpY3QgYG1wZzAxYC4gVXNlIG9ubHkgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBgbXBnMDFgIGluIChiKS4gV2hhdCB0ZXN0IGVycm9ycyBkbyB5b3Ugb2J0YWluPyBXaGljaCB2YWx1ZSBvZiAkSyQgc2VlbXMgdG8gcGVyZm9ybSB0aGUgYmVzdCBvbiB0aGlzIGRhdGEgc2V0PwoKYGBgIHtyIFByb2IyaH0KeDEudHIgPC0gdHIuYXV0b1ssYygzLDUsNiw4KV0KeTEudHIgPC0gdHIuYXV0b1ssMV0KeDEudGUgPC0gdGUuYXV0b1ssYygzLDUsNiw4KV0KeTEudGUgPC0gdGUuYXV0b1ssMV0KCnNldC5zZWVkKDQ4MSkKIyMgS05OIC0gSyA9IDEgIyMKa25uLmF1dG8gPC0ga25uKHgxLnRyLCB4MS50ZSwgeTEudHIsIGsgPSAxKQp0YWJsZShrbm4uYXV0bywgeTEudGUpCgp0ZS5lcnJvci4yaCA8LSBtZWFuKGtubi5hdXRvICE9IHkxLnRlKQpjYXQoIlRoZSB0ZXN0IGVycm9yIG9mIHRoaXMgS05OIG1vZGVsIHdpdGggayA9IDEgaXM6IiwgcGVyY2VudCh0ZS5lcnJvci4yaCwgYWNjdXJhY3kgPSAwLjAxKSwgIlxuIikKCiMjIEtOTiAtIEsgPSAxMCAjIwprbm4uYXV0byA8LSBrbm4oeDEudHIsIHgxLnRlLCB5MS50ciwgayA9IDEwKQp0YWJsZShrbm4uYXV0bywgeTEudGUpCgp0ZS5lcnJvci4yaCA8LSBtZWFuKGtubi5hdXRvICE9IHkxLnRlKQpjYXQoIlRoZSB0ZXN0IGVycm9yIG9mIHRoaXMgS05OIG1vZGVsIHdpdGggayA9IDEwIGlzOiIsIHBlcmNlbnQodGUuZXJyb3IuMmgsIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCgojIyBLTk4gLSBLID0gMTUgIyMKa25uLmF1dG8gPC0ga25uKHgxLnRyLCB4MS50ZSwgeTEudHIsIGsgPSAxNSkKdGFibGUoa25uLmF1dG8sIHkxLnRlKQoKdGUuZXJyb3IuMmggPC0gbWVhbihrbm4uYXV0byAhPSB5MS50ZSkKY2F0KCJUaGUgdGVzdCBlcnJvciBvZiB0aGlzIEtOTiBtb2RlbCB3aXRoIGsgPSAxNSBpczoiLCBwZXJjZW50KHRlLmVycm9yLjJoLCBhY2N1cmFjeSA9IDAuMDEpLCAiXG4iKQpgYGAKQmFzZWQgb24gdGhpcyBkYXRhIHNldCwgJGs9MTAkIHNlZW1zIHRvIHBlcmZvcm0gdGhlIGJlc3QuCgoqKjMuIChFeGVyY2lzZSA0LjguMTYpIFVzaW5nIHRoZSBCb3N0b24gZGF0YSBzZXQsIGZpdCBjbGFzc2lmaWNhdGlvbiBtb2RlbHMgaW4gb3JkZXIgdG8gcHJlZGljdCB3aGV0aGVyIGEgZ2l2ZW4gY2Vuc3VzIHRyYWN0IGhhcyBhIGNyaW1lIHJhdGUgYWJvdmUgb3IgYmVsb3cgdGhlIG1lZGlhbi4gRXhwbG9yZSBsb2dpc3RpYyByZWdyZXNzaW9uLCBMREEsIG5haXZlIEJheWVzLCBhbmQgS05OIG1vZGVscyB1c2luZyB2YXJpb3VzIHN1YnNldHMgb2YgdGhlIHByZWRpY3RvcnMuIERlc2NyaWJlIHlvdXIgZmluZGluZ3MuKioKCipIaW50OiBZb3Ugd2lsbCBoYXZlIHRvIGNyZWF0ZSB0aGUgcmVzcG9uc2UgdmFyaWFibGUgeW91cnNlbGYsIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBhcmUgY29udGFpbmVkIGluIHRoZSBgQm9zdG9uYCBkYXRhIHNldC4qCgpgYGB7ciBQcm9iMywgbWVzc2FnZT1GQUxTRX0KZGF0YShCb3N0b24pCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KG9sc3JyKQoKI2dldCBtZWRpYW4gY3JpbWUgcmF0ZSBhbmQgY3JlYXRlIHJlc3BvbnNlIHZlY3RvcgpjcmltLm1lZCA8LSBtZWRpYW4oQm9zdG9uJGNyaW0pCnJlc3BvbnNlIDwtIHJlcCgwLCBsZW5ndGgoQm9zdG9uJGNyaW0pKQpyZXNwb25zZVtCb3N0b24kY3JpbSA+PSBjcmltLm1lZF0gPC0gMQoKI2F0dGFjaCByZXNwb25zZSB2ZWN0b3IgdG8gZGF0YSBmcmFtZQpCb3N0b24kcmVzcG9uc2UgPC0gcmVzcG9uc2UKCiNTcGxpdCB0aGUgZGF0YSBpbnRvIHRlc3QgYW5kIHRyYWluCnNldC5zZWVkKDQ4MTYpCmluZGV4LmJvc3RvbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKEJvc3RvbiRyZXNwb25zZSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0ci5ib3MgPC0gQm9zdG9uW2luZGV4LmJvc3RvbixdCnRlLmJvcyA8LSBCb3N0b25bLWluZGV4LmJvc3RvbixdCmBgYAoKYGBgIHtyIFByb2IzTG9nLCB3YXJuaW5nID0gRkFMU0V9CiMjIExPR0lTVElDIC0gRlVMTCAjIwpsb2cuYm9zIDwtIGdsbShyZXNwb25zZSB+IC4sIGRhdGEgPSB0ci5ib3MsIGZhbWlseSA9ICdiaW5vbWlhbCcpCnN1bW1hcnkobG9nLmJvcykKCnByb2JzLmxvZyA8LSBwcmVkaWN0KGxvZy5ib3MsIHRlLmJvcywgdHlwZSA9ICJyZXNwb25zZSIpCnByZWRzLmxvZyA8LSByZXAoMCwgbGVuZ3RoKHByb2JzLmxvZykpCnByZWRzLmxvZ1twcm9icy5sb2cgPj0gMC41XSA8LSAxCnRhYmxlKHRlLmJvcyRyZXNwb25zZSwgcHJlZHMubG9nKQoKYWNjLmxvZyA8LSBtZWFuKHByZWRzLmxvZyA9PSB0ZS5ib3MkcmVzcG9uc2UpCmNhdCgiVGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgb2YgdGhlIGZ1bGwgbG9naXN0aWMgbW9kZWwgaXM6IiwgcGVyY2VudChhY2MubG9nLCBhY2N1cmFjeSA9IDAuMDEpLCAiXG4iKQoKIyMgTE9HSVNUSUMgLSBSRURVQ0VEICMjCiNtb2RlbCBzZWxlY3Rpb24Kb2xzX3N0ZXBfYm90aF9wKGxvZy5ib3MsIHBfZW50ZXIgPSAwLjA1LCBwX3JlbW92ZSA9IDAuMSkKbG9nLnJlZCA8LSBnbG0ocmVzcG9uc2UgfiBub3ggKyByYWQgKyBtZWR2ICsgYWdlLCBkYXRhID0gdHIuYm9zLCBmYW1pbHkgPSAnYmlub21pYWwnKQpzdW1tYXJ5KGxvZy5yZWQpCgpwcm9icy5sb2cucmVkIDwtIHByZWRpY3QobG9nLnJlZCwgdGUuYm9zLCB0eXBlID0gInJlc3BvbnNlIikKcHJlZHMubG9nLnJlZCA8LSByZXAoMCwgbGVuZ3RoKHByb2JzLmxvZy5yZWQpKQpwcmVkcy5sb2cucmVkW3Byb2JzLmxvZy5yZWQgPj0gMC41XSA8LSAxCnRhYmxlKHRlLmJvcyRyZXNwb25zZSwgcHJlZHMubG9nLnJlZCkKCmFjYy5sb2cucmVkIDwtIG1lYW4ocHJlZHMubG9nLnJlZCA9PSB0ZS5ib3MkcmVzcG9uc2UpCmNhdCgiVGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgb2YgdGhlIHJlZHVjZWQgbG9naXN0aWMgbW9kZWwgaXM6IiwgcGVyY2VudChhY2MubG9nLnJlZCwgYWNjdXJhY3kgPSAwLjAxKSwgIlxuIikKYGBgClRoZSBmdWxsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgb3V0cGVyZm9ybWVkIHRoZSByZWR1Y2VkIG1vZGVsLCBzZWxlY3RlZCB1c2luZyBzdGVwd2lzZSByZWdyZXNzaW9uLiBOb3Qgb25seSBkaWQgdGhlIGZ1bGwgbW9kZWwgaGF2ZSBoaWdoZXIgcHJlZGljdGlvbiBhY2N1cmFjeSwgYnV0IHRoZSBBSUMgd2FzIGFsc28gbXVjaCBsb3dlciB0aGFuIHRoYXQgb2YgdGhlIHJlZHVjZWQgbW9kZWwgKDI4IHZzLiAyMDkuMjYpLCBkZW1vbnN0cmF0aW5nIHRoYXQgdGhlIGZvcm1lciBoYXMgYSBtdWNoIGJldHRlciBiYWxhbmNlIGJldHdlZW4gbW9kZWwgZml0IGFuZCBjb21wbGV4aXR5LgoKYGBgIHtyIFByb2IzTERBfQojIyBMREEgLSBGVUxMICMjCmxkYS5ib3MgPC0gbGRhKHJlc3BvbnNlIH4gLiwgZGF0YSA9IHRyLmJvcywgZmFtaWx5ID0gJ2Jpbm9taWFsJykKCnByZWRzLmxkYSA8LSBwcmVkaWN0KGxkYS5ib3MsIHRlLmJvcywgdHlwZSA9ICdyZXNwb25zZScpCnRhYmxlKHRlLmJvcyRyZXNwb25zZSwgcHJlZHMubGRhJGNsYXNzKQoKYWNjLmxkYSA8LSBtZWFuKHByZWRzLmxkYSRjbGFzcyA9PSB0ZS5ib3MkcmVzcG9uc2UpCmNhdCgiVGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgb2YgdGhlIGZ1bGwgTERBIG1vZGVsIGlzOiIsIHBlcmNlbnQoYWNjLmxkYSwgYWNjdXJhY3kgPSAwLjAxKSwgIlxuIikKCiMjIExEQSAtIFJFRFVDRUQgIyMKbGRhLnJlZCA8LSBsZGEocmVzcG9uc2UgfiBub3ggKyBhZ2UgKyByYWQgKyBtZWR2LCBkYXRhID0gdHIuYm9zLCBmYW1pbHkgPSAnYmlub21pYWwnKQoKcHJlZHMubGRhLnJlZCA8LSBwcmVkaWN0KGxkYS5yZWQsIHRlLmJvcywgdHlwZSA9ICdyZXNwb25zZScpCnRhYmxlKHRlLmJvcyRyZXNwb25zZSwgcHJlZHMubGRhLnJlZCRjbGFzcykKCmFjYy5sZGEucmVkIDwtIG1lYW4ocHJlZHMubGRhLnJlZCRjbGFzcyA9PSB0ZS5ib3MkcmVzcG9uc2UpCmNhdCgiVGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgb2YgdGhlIHJlZHVjZWQgTERBIG1vZGVsIGlzOiIsIHBlcmNlbnQoYWNjLmxkYS5yZWQsIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCmBgYApUaGUgcHJlZGljdGlvbiBhY2N1cmFjeSBpbXByb3ZlZCBieSBvbmUgcGVyY2VudGFnZSBwb2ludCBmcm9tIHRoZSBmdWxsIExEQSAoODMuMDBcJSkgbW9kZWwgdG8gdGhlIHJlZHVjZWQgTERBIG1vZGVsICg4NC4wMFwlKS4gTmVpdGhlciBMREEgbW9kZWwgb3V0cGVyZm9ybWVkIHRoZSBmdWxsIExvZ2lzdGljIG1vZGVsLgoKYGBgIHtyIFByb2IzTkJ9CiMjIE5BSVZFIEJBWUVTIC0gRlVMTCAjIwpuYi5ib3MgPC0gbmFpdmVCYXllcyhyZXNwb25zZSB+IC4sIGRhdGEgPSB0ci5ib3MpCgpwcmVkcy5uYiA8LSBwcmVkaWN0KG5iLmJvcywgdGUuYm9zLCB0eXBlID0gJ2NsYXNzJykKdGFibGUodGUuYm9zJHJlc3BvbnNlLCBwcmVkcy5uYikKCmFjYy5uYiA8LSBtZWFuKHByZWRzLm5iID09IHRlLmJvcyRyZXNwb25zZSkKY2F0KCJUaGUgcHJlZGljdGlvbiBhY2N1cmFjeSBvZiB0aGUgZnVsbCBOYWl2ZSBCYXllcyBtb2RlbCBpczoiLCBwZXJjZW50KGFjYy5uYiwgYWNjdXJhY3kgPSAwLjAxKSwgIlxuIikKCiMjIE5BSVZFIEJBWUVTIC0gUkVEVUNFRCAjIwpuYi5ib3MucmVkIDwtIG5haXZlQmF5ZXMocmVzcG9uc2UgfiB6biArIG5veCArIGFnZSArIHJhZCArIHB0cmF0aW8gKyBtZWR2LCBkYXRhID0gdHIuYm9zKQoKcHJlZHMubmIucmVkIDwtIHByZWRpY3QobmIuYm9zLnJlZCwgdGUuYm9zLCB0eXBlID0gJ2NsYXNzJykKdGFibGUodGUuYm9zJHJlc3BvbnNlLCBwcmVkcy5uYi5yZWQpCgphY2MubmIucmVkIDwtIG1lYW4ocHJlZHMubmIucmVkID09IHRlLmJvcyRyZXNwb25zZSkKY2F0KCJUaGUgcHJlZGljdGlvbiBhY2N1cmFjeSBvZiB0aGUgcmVkdWNlZCBOYWl2ZSBCYXllcyBtb2RlbCBpczoiLCBwZXJjZW50KGFjYy5uYi5yZWQsIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCmBgYApUaGUgZnVsbCBOYWl2ZSBCYXllcyBtb2RlbCBvdXRwZXJmb3JtcyB0aGUgcmVkdWNlZCBtb2RlbCwgYnV0IGlzIHN0aWxsIHNsaWdodGx5IGxvd2VyIGluIGFjY3VyYWN5IHRoYW4gdGhlIGZ1bGwgTG9naXN0aWMuCgpgYGAge3IgUHJvYjNLTk59CiMjIEtOTiAtIEsgPSAxICMjCngudHIgPC0gdHIuYm9zWywxOjEzXQp5LnRyIDwtIHRyLmJvc1ssMTRdCngudGUgPC0gdGUuYm9zWywxOjEzXQp5LnRlIDwtIHRlLmJvc1ssIDE0XQoKa25uLmJvcyA8LSBrbm4oeC50ciwgeC50ZSwgeS50ciwgayA9IDEpCnRhYmxlKGtubi5ib3MsIHkudGUpCgphY2Mua25uIDwtIG1lYW4oa25uLmJvcyA9PSB5LnRlKQpjYXQoIlRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG9mIHRoZSBLTk4gbW9kZWwgd2l0aCBrID0gMSBpczoiLCBwZXJjZW50KGFjYy5rbm4sIGFjY3VyYWN5ID0gMC4wMSksICJcbiIpCmBgYApUaGUgZnVsbCBMb2dpc2l0YyByZWdyZXNzaW9uIG1vZGVsIHJlbWFpbnMgdGhlIGhpZ2hlc3QgcGVyZm9ybWluZyBvZiB0aG9zZSB0ZXN0ZWQgaGVyZS4KCgoKCg==