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.

  1. 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.

  1. 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\).

  1. 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.

  1. 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 
  1. 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
  1. 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 
  1. 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 
  1. 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 
  1. 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.

  1. 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)
  1. 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.

  1. 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,]
  1. 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% 
  1. 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.

  1. 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% 
  1. 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% 
  1. 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==