Libraries

library(e1071)
Warning: package ‘e1071’ was built under R version 4.4.2
str(Weekly)
'data.frame':   1089 obs. of  9 variables:
 $ Year     : num  1990 1990 1990 1990 1990 1990 1990 1990 1990 1990 ...
 $ Lag1     : num  0.816 -0.27 -2.576 3.514 0.712 ...
 $ Lag2     : num  1.572 0.816 -0.27 -2.576 3.514 ...
 $ Lag3     : num  -3.936 1.572 0.816 -0.27 -2.576 ...
 $ Lag4     : num  -0.229 -3.936 1.572 0.816 -0.27 ...
 $ Lag5     : num  -3.484 -0.229 -3.936 1.572 0.816 ...
 $ Volume   : num  0.155 0.149 0.16 0.162 0.154 ...
 $ Today    : num  -0.27 -2.576 3.514 0.712 1.178 ...
 $ Direction: Factor w/ 2 levels "Down","Up": 1 1 2 2 2 1 2 2 2 1 ...

Problem 13

(a) Produce some numerical and graphical summaries of the Weekly data. Do there appear to be any patterns?

Weekly|> 
  ggplot(aes(x = Direction, Y = as.factor(Year), fill = Direction
         ))+
  geom_bar()+
  theme_minimal()

pairs(Weekly[,2:8])

Weekly|>
  select(Lag1,Lag2,Lag3,Volume,Today)|>
  ggpairs()

Weekly|>
  select(Lag4,Lag5, Volume, Today)|>
  ggpairs()

Weekly|>
  ggscatmat(columns = 2:8, alpha = 0.2)

Week = Weekly|> select(where(is.numeric))
cor = cor(Week)
corrplot(cor,method = "number")

skim(Weekly)
── Data Summary ────────────────────────
                           Values
Name                       Weekly
Number of rows             1089  
Number of columns          9     
_______________________          
Column type frequency:           
  factor                   1     
  numeric                  8     
________________________         
Group variables            None  

Looking at these graphical and numerical summaries, we can see that there is not a lot of correlation between the variables. Year and Volume seem to have a positive correlation with one another. Most of the graphs appear to have no particular patterns and appear to have more of a cluster than anything. The distribution of these variables look to be normally distributed. Volume is right skewed meaning that mean is greater than the median and is pulling the tail to the right.

(b) 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?

glm.fit = glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume, data = Weekly, family = "binomial" )
summary(glm.fit)

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
              
(Intercept) **
Lag1          
Lag2        * 
Lag3          
Lag4          
Lag5          
Volume        
---
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

The only variable that is statistically significant is Lag2. We reject the null hypothesis and state that Lag2 coefficient is not equal to zero.

(c) 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.

pred = predict(glm.fit, Weekly, type = "response")
pred.labels = ifelse(pred > .5, "Up", "Down")
actual.label = factor(Weekly$Direction)
pred.labels = factor(pred.labels)

confusionMatrix(pred.labels, actual.label)
Confusion Matrix and Statistics

          Reference
Prediction Down  Up
      Down   54  48
      Up    430 557
                                         
               Accuracy : 0.5611         
                 95% CI : (0.531, 0.5908)
    No Information Rate : 0.5556         
    P-Value [Acc > NIR] : 0.369          
                                         
                  Kappa : 0.035          
                                         
 Mcnemar's Test P-Value : <2e-16         
                                         
            Sensitivity : 0.11157        
            Specificity : 0.92066        
         Pos Pred Value : 0.52941        
         Neg Pred Value : 0.56434        
             Prevalence : 0.44444        
         Detection Rate : 0.04959        
   Detection Prevalence : 0.09366        
      Balanced Accuracy : 0.51612        
                                         
       'Positive' Class : Down           
                                         

There is a lot of false negatives(430) and a smaller amount of false positives(48). Sensitivity is calculated by (TP TP + FN) $ $ which means \(11.1\%\) of the positive predictions were correctly classified. While the Specificity is the percentage of a models ability to correctly identify negative instances, that is calculated \(\frac{48}{48+557} \approx 92.0\), meaning that \(92\%\) are correctly classified. This confusion matrix is exemplifying that Logistic Regression might not be the best model to use to predict whether the stock of the S&P 500 in a given week.

(d) 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)

train = (Year < 2009 )
test = Weekly[!train,]
dim(test)
[1] 104   9
Direction.2009 = Direction[!train]
glm.fit2 = glm(Direction ~ Lag2, data = Weekly, family = "binomial", subset = train)


glm.probs = predict(glm.fit2, test, type = "response")
glm.pred2 = rep("Down", length(glm.probs))
glm.pred2[glm.probs > .5] = "Up"
table(glm.pred2, Direction.2009)
         Direction.2009
glm.pred2 Down Up
     Down    9  5
     Up     34 56
mean(glm.pred2 == Direction.2009)
[1] 0.625
mean(glm.pred2 != Direction.2009)
[1] 0.375

(e) Repeat (d) using LDA.

lda.model = lda(Direction ~  Lag2, data = Weekly, subset = train)


lda.probs = predict(lda.model,test, type = "response")
lda.pred = rep("Down", 104)
lda.probs_up = lda.probs$posterior[, 2]
lda.pred = rep("Down", length(lda.probs_up))
lda.pred[lda.probs_up > .5] = "Up"
table(lda.pred, Direction.2009)
        Direction.2009
lda.pred Down Up
    Down    9  5
    Up     34 56
mean(lda.pred == Direction.2009)
[1] 0.625
mean(lda.pred != Direction.2009)
[1] 0.375

(f) f) Repeat (d) using QDA

qda.model = qda(Direction ~  Lag2, data = Weekly, subset = train)

qda.pred = predict(qda.model,test)$class

table(qda.pred, Direction.2009)
        Direction.2009
qda.pred Down Up
    Down    0  0
    Up     43 61
mean(qda.pred == Direction.2009)
[1] 0.5865385
mean(qda.pred != Direction.2009)
[1] 0.4134615

(g) Repeat (d) using KNN with K = 1

week.train = as.matrix(Lag2[train])
week.test = as.matrix(Lag2[!train])
train.direction = Direction[train]
set.seed(1)
knn = knn(week.train,week.test,train.direction,k = 1)
table(knn, Direction.2009)
      Direction.2009
knn    Down Up
  Down   21 30
  Up     22 31
mean(knn != Direction.2009)
[1] 0.5

(h) using naive Bayes

bayes = naiveBayes(Direction ~ Lag2, Weekly, subset = train)
bayes.pred = predict(bayes, test)
table(bayes.pred, Direction.2009)
          Direction.2009
bayes.pred Down Up
      Down    0  0
      Up     43 61
mean(bayes.pred == Direction.2009)
[1] 0.5865385
mean(bayes.pred != Direction.2009)
[1] 0.4134615

(i) Which of these methods appears to provide the best results on this data?

Linear Discriminant Analysis has an accuracy of \(62.5\%\) and Logistic Regression \(62.5\%\) are the best methods in predicting the direction of the stock market between 1990 and 2008.

(j) Experiment with different combinations of predictors, including possible transformations and interactions, for each of the methods. Report the variables, method, and associated confusion matrix that appears to provide the best results on the held out data. Note that you should also experiment with values for K in the KNN classifier.

holdout = (Year > 2009)

Logistic Regression Part 2

glm.fit2 = glm(Direction ~ Lag2 + exp(Lag3) + Lag4 + Volume, data = Weekly, family = "binomial", subset = holdout)


glm.probs = predict(glm.fit2, test, type = "response")
glm.pred2 = rep("Down", length(glm.probs))
glm.pred2[glm.probs > .5] = "Up"
table(glm.pred2, Direction.2009)
         Direction.2009
glm.pred2 Down Up
     Down   15 13
     Up     28 48
mean(glm.pred2 == Direction.2009)
[1] 0.6057692
mean(glm.pred2 != Direction.2009)
[1] 0.3942308

Linear Discriminant Analysis Part 2

lda.model = lda(Direction ~ Lag1+ Lag2 + Lag5 + Volume, data = Weekly, subset = holdout)


lda.probs = predict(lda.model,test, type = "response")
lda.pred = rep("Down", 104)
lda.probs_up = lda.probs$posterior[, 2]
lda.pred = rep("Down", length(lda.probs_up))
lda.pred[lda.probs_up > .5] = "Up"
table(lda.pred, Direction.2009)
        Direction.2009
lda.pred Down Up
    Down   16 13
    Up     27 48
mean(lda.pred == Direction.2009)
[1] 0.6153846
mean(lda.pred != Direction.2009)
[1] 0.3846154

QDA Part 2

qda.model = qda(Direction ~  Lag3 + Lag1 + Lag5 + Volume, data = Weekly, subset = holdout)

qda.pred = predict(qda.model,test)$class

table(qda.pred, Direction.2009)
        Direction.2009
qda.pred Down Up
    Down   22 14
    Up     21 47
mean(qda.pred == Direction.2009)
[1] 0.6634615
mean(qda.pred != Direction.2009)
[1] 0.3365385

KNN Part 2

set.seed(1)
knn = knn(week.train,week.test,train.direction,k = 5)
table(knn, Direction.2009)
      Direction.2009
knn    Down Up
  Down   16 21
  Up     27 40
mean(knn == Direction.2009)
[1] 0.5384615

Naive Bayes Part 2

bayes = naiveBayes(Direction ~ Lag2 + Volume + Lag1 + Lag3 + Lag4 + Lag5, Weekly, subset = holdout)
bayes.pred = predict(bayes, test)
table(bayes.pred, Direction.2009)
          Direction.2009
bayes.pred Down Up
      Down   16 16
      Up     27 45
mean(bayes.pred == Direction.2009)
[1] 0.5865385
mean(bayes.pred != Direction.2009)
[1] 0.4134615

The method that has the best results in most accurately predicting the direction of the S&P 500 after the year 2009, is the QDA model with an accuracy of \(66.34\%\).

Problem 14

(a) 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.

mpg01 = ifelse(median(mpg)< Auto$mpg,1, 0)
mpg = data.frame(Auto,mpg01)

(b)

These variables mpg,cylinders,displacement,horsepower,weight,acceleration are not normally distributed, besides acceleration and mpg. There is evidence of high and low values of positive and negative correlation.

MPG, Displacement, Horsepower, Weight are skewed to the right, which again shows that the mean is greater than the median, thus the reason that the distribution of the values are skewed to the right. While Cylinders is not normally distributed and is left tailed.

mpg |>
 ggscatmat(columns = 1:6, alpha = .2)
View(mpg)

par(mfrow = c(3,3))

boxplot(mpg$mpg, main = "Boxplot of MPG")
boxplot(mpg$cylinders, main = "Boxplot of Cylinders")
boxplot(mpg$displacement, main = "Boxplot of Displacement")
boxplot(mpg$horsepower, main = "Boxplot of Horsepower")
boxplot(mpg$weight, main = "Boxplot of Weight")
boxplot(mpg$acceleration, main = "Boxplot of Acceleration")

(c) Split the data into a training set and a test set.

set.seed(123)
train.index = createDataPartition(mpg$mpg01, p = 0.8, list = FALSE)
mpg.train = mpg[train.index,]
mpg.test = mpg[-train.index,]

Formula for problem 14

form14 = mpg01 ~ mpg + cylinders + displacement + weight

(d) 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 Model

This model gave a \(93.59\%\) accuracy!

mpg.lda = lda(form14, data = mpg)
mpg.lda.pred = predict(mpg.lda,mpg.test)$class
table(mpg.lda.pred, mpg.test$mpg01)
            
mpg.lda.pred  0  1
           0 35  1
           1  4 38
mean(mpg.lda.pred == mpg.test$mpg01)
[1] 0.9358974

(e) 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 Model

This model yields an accuracy of \(92.2\%\).

mpg.qda = qda(form14, data = mpg)
mpg.qda.pred = predict(mpg.qda, mpg.test)$class
table(mpg.qda.pred,mpg.test$mpg01)
            
mpg.qda.pred  0  1
           0 34  1
           1  5 38
mean(mpg.qda.pred == mpg.test$mpg01)
[1] 0.9230769

(f) 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?

Logistic Regression Model

Logistic Regression has \(100\%\) accuracy!

mpg.glm = glm(form14, data = mpg, family = "binomial")
mpg.glm.pred = predict(mpg.glm, mpg.test, type = "response")
mpg.glm.pred.class = ifelse(mpg.glm.pred > .5, 1, 0)
table(mpg.glm.pred.class,mpg.test$mpg01)
mean(mpg.glm.pred.class == mpg.test$mpg01)

(g) 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?

Naive Bayes model displayed an accuracy of \(88.46\%\)!

mpg.bayes = naiveBayes(form14, data = mpg)
mpg.bayes.pred = predict(mpg.bayes, mpg.test)
table(mpg.bayes.pred, mpg.test$mpg01)
mean(mpg.bayes.pred == mpg.test$mpg01)

(h) 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?

KNN of 23 clusters has an accuracy of \(86\%\)

set.seed(123)
idx = sample(1:nrow(Auto), 0.8*nrow(Auto))
train.auto = Auto[idx,]
test.auto = Auto[-idx,]
y.train = train.auto$mpg
x.train = train.auto[,-1]
x.test = test.auto[,-1]
x.train = x.train[,-8]
x.test = x.test[,-8]
y.test = ifelse(test.auto$mpg>=23,1,0)
high.low = ifelse(y.train>=23,1,0)
knn.pred <- knn (train=x.train, test=x.test, cl=high.low, k=1)
table(knn.pred, y.test)
mean(knn.pred == y.test)
set.seed(7)
knn.pred <- knn (train=x.train, test=x.test, cl=high.low, k=23)
table(knn.pred, y.test)
mean(knn.pred == y.test)

Problem 16

Crime = ifelse(Boston$crim > median(Boston$crim), 1, 0)
Boston.p2 = data.frame(Boston,Crime)

Visualizaitons

cor(Boston.p2)
               crim          zn       indus         chas
crim     1.00000000 -0.20046922  0.40658341 -0.055891582
zn      -0.20046922  1.00000000 -0.53382819 -0.042696719
indus    0.40658341 -0.53382819  1.00000000  0.062938027
chas    -0.05589158 -0.04269672  0.06293803  1.000000000
nox      0.42097171 -0.51660371  0.76365145  0.091202807
rm      -0.21924670  0.31199059 -0.39167585  0.091251225
age      0.35273425 -0.56953734  0.64477851  0.086517774
dis     -0.37967009  0.66440822 -0.70802699 -0.099175780
rad      0.62550515 -0.31194783  0.59512927 -0.007368241
tax      0.58276431 -0.31456332  0.72076018 -0.035586518
ptratio  0.28994558 -0.39167855  0.38324756 -0.121515174
black   -0.38506394  0.17552032 -0.35697654  0.048788485
lstat    0.45562148 -0.41299457  0.60379972 -0.053929298
medv    -0.38830461  0.36044534 -0.48372516  0.175260177
Crime    0.40939545 -0.43615103  0.60326017  0.070096774
                nox          rm         age         dis
crim     0.42097171 -0.21924670  0.35273425 -0.37967009
zn      -0.51660371  0.31199059 -0.56953734  0.66440822
indus    0.76365145 -0.39167585  0.64477851 -0.70802699
chas     0.09120281  0.09125123  0.08651777 -0.09917578
nox      1.00000000 -0.30218819  0.73147010 -0.76923011
rm      -0.30218819  1.00000000 -0.24026493  0.20524621
age      0.73147010 -0.24026493  1.00000000 -0.74788054
dis     -0.76923011  0.20524621 -0.74788054  1.00000000
rad      0.61144056 -0.20984667  0.45602245 -0.49458793
tax      0.66802320 -0.29204783  0.50645559 -0.53443158
ptratio  0.18893268 -0.35550149  0.26151501 -0.23247054
black   -0.38005064  0.12806864 -0.27353398  0.29151167
lstat    0.59087892 -0.61380827  0.60233853 -0.49699583
medv    -0.42732077  0.69535995 -0.37695457  0.24992873
Crime    0.72323480 -0.15637178  0.61393992 -0.61634164
                 rad         tax    ptratio       black
crim     0.625505145  0.58276431  0.2899456 -0.38506394
zn      -0.311947826 -0.31456332 -0.3916785  0.17552032
indus    0.595129275  0.72076018  0.3832476 -0.35697654
chas    -0.007368241 -0.03558652 -0.1215152  0.04878848
nox      0.611440563  0.66802320  0.1889327 -0.38005064
rm      -0.209846668 -0.29204783 -0.3555015  0.12806864
age      0.456022452  0.50645559  0.2615150 -0.27353398
dis     -0.494587930 -0.53443158 -0.2324705  0.29151167
rad      1.000000000  0.91022819  0.4647412 -0.44441282
tax      0.910228189  1.00000000  0.4608530 -0.44180801
ptratio  0.464741179  0.46085304  1.0000000 -0.17738330
black   -0.444412816 -0.44180801 -0.1773833  1.00000000
lstat    0.488676335  0.54399341  0.3740443 -0.36608690
medv    -0.381626231 -0.46853593 -0.5077867  0.33346082
Crime    0.619786249  0.60874128  0.2535684 -0.35121093
             lstat       medv       Crime
crim     0.4556215 -0.3883046  0.40939545
zn      -0.4129946  0.3604453 -0.43615103
indus    0.6037997 -0.4837252  0.60326017
chas    -0.0539293  0.1752602  0.07009677
nox      0.5908789 -0.4273208  0.72323480
rm      -0.6138083  0.6953599 -0.15637178
age      0.6023385 -0.3769546  0.61393992
dis     -0.4969958  0.2499287 -0.61634164
rad      0.4886763 -0.3816262  0.61978625
tax      0.5439934 -0.4685359  0.60874128
ptratio  0.3740443 -0.5077867  0.25356836
black   -0.3660869  0.3334608 -0.35121093
lstat    1.0000000 -0.7376627  0.45326273
medv    -0.7376627  1.0000000 -0.26301673
Crime    0.4532627 -0.2630167  1.00000000

Splitting the data

set.seed(123)
Boston.index = createDataPartition(Boston.p2$Crime, p = .7, list = F)
Boston.train = Boston.p2[Boston.index,]
Boston.test = Boston.p2[-Boston.index,]
train = 1:(dim(Boston.p2)[1]/2)
test = (dim(Boston.p2)[1]/2 + 1):dim(Boston.p2)[1]
Crime.test = Crime[test]

Logistic Regression Model

This Logistic Regression Model with indus,nox,age,rad,tax has an accuracy of \(90.66\%\).

boston.glm = glm(form16, data = Boston.train,family = "binomial")
boston.glm.probs = predict(boston.glm,Boston.test,type = "response")
boston.glm.pred = ifelse(boston.glm.probs > .5,1,0)
table(boston.glm.pred,Boston.test$Crime)
mean(boston.glm.pred == Boston.test$Crime)

LDA

Linear Discriminant Analysis with indus,nox,age,rad,tax has an accuracy of \(85.33\%\)

boston.lda = lda(form16, data = Boston.train)
boston.lda.pred = predict(boston.lda, Boston.test)$class
table(boston.lda.pred,Boston.test$Crime)
mean(boston.lda.pred == Boston.test$Crime)

Naive Bayes

This model yields an \(84.66\%\) accuracy.

boston.naive = naiveBayes(form16, data = Boston.train)
boston.naive.pred = predict(boston.naive, Boston.test)
table(boston.naive.pred, Boston.test$Crime)
mean(boston.naive.pred == Boston.test$Crime)

KNN

KNN with 5 clusters has an accuracy of \(76.67\%\)

set.seed(2)
train.knn = cbind(indus,nox,age,dis,rad,tax)[train,]
test.knn = cbind(indus,nox,age,dis,rad,tax)[test,]
Bostonknn.pred = knn(train.knn, test.knn, Crime.test, k=10)
table(Bostonknn.pred, Crime.test)
mean(Bostonknn.pred == Crime.test)
LS0tDQp0aXRsZTogIkNsYXNzaWZpY2F0aW9uIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQp0b2M6IFRydWUNCnRvY19mbG9hdDogVHJ1ZQ0KLS0tDQojIExpYnJhcmllcw0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoY2xhc3MpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoc2tpbXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShHR2FsbHkpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShJU0xSMikNCmF0dGFjaChXZWVrbHkpDQpgYGANCmBgYHtyfQ0Kc3RyKFdlZWtseSkNCmBgYA0KIyBQcm9ibGVtIDEzDQojIyMgKGEpIFByb2R1Y2Ugc29tZSBudW1lcmljYWwgYW5kIGdyYXBoaWNhbCBzdW1tYXJpZXMgb2YgdGhlIFdlZWtseSBkYXRhLiBEbyB0aGVyZSBhcHBlYXIgdG8gYmUgYW55IHBhdHRlcm5zPw0KYGBge3J9DQpXZWVrbHl8PiANCiAgZ2dwbG90KGFlcyh4ID0gRGlyZWN0aW9uLCBZID0gYXMuZmFjdG9yKFllYXIpLCBmaWxsID0gRGlyZWN0aW9uDQogICAgICAgICApKSsNCiAgZ2VvbV9iYXIoKSsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCmBgYHtyfQ0KcGFpcnMoV2Vla2x5WywyOjhdKQ0KYGBgDQpgYGB7cn0NCldlZWtseXw+DQogIHNlbGVjdChMYWcxLExhZzIsTGFnMyxWb2x1bWUsVG9kYXkpfD4NCiAgZ2dwYWlycygpDQpgYGANCg0KYGBge3J9DQpXZWVrbHl8Pg0KICBzZWxlY3QoTGFnNCxMYWc1LCBWb2x1bWUsIFRvZGF5KXw+DQogIGdncGFpcnMoKQ0KYGBgDQpgYGB7cn0NCldlZWtseXw+DQogIGdnc2NhdG1hdChjb2x1bW5zID0gMjo4LCBhbHBoYSA9IDAuMikNCmBgYA0KDQpgYGB7cn0NCldlZWsgPSBXZWVrbHl8PiBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpDQpjb3IgPSBjb3IoV2VlaykNCmNvcnJwbG90KGNvcixtZXRob2QgPSAibnVtYmVyIikNCmBgYA0KDQoNCmBgYHtyfQ0Kc2tpbShXZWVrbHkpDQpgYGANCkxvb2tpbmcgYXQgdGhlc2UgZ3JhcGhpY2FsIGFuZCBudW1lcmljYWwgc3VtbWFyaWVzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgbm90IGEgbG90IG9mIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHZhcmlhYmxlcy4gWWVhciBhbmQgVm9sdW1lIHNlZW0gdG8gaGF2ZSBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIHdpdGggb25lIGFub3RoZXIuIE1vc3Qgb2YgdGhlIGdyYXBocyBhcHBlYXIgdG8gaGF2ZSBubyBwYXJ0aWN1bGFyIHBhdHRlcm5zIGFuZCBhcHBlYXIgdG8gaGF2ZSBtb3JlIG9mIGEgY2x1c3RlciB0aGFuIGFueXRoaW5nLiBUaGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyBsb29rIHRvIGJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiBWb2x1bWUgaXMgcmlnaHQgc2tld2VkIG1lYW5pbmcgdGhhdCBtZWFuIGlzIGdyZWF0ZXIgdGhhbiB0aGUgbWVkaWFuIGFuZCBpcyBwdWxsaW5nIHRoZSB0YWlsIHRvIHRoZSByaWdodC4gDQoNCiMjIyAoYikgVXNlIHRoZSBmdWxsIGRhdGEgc2V0IHRvIHBlcmZvcm0gYSBsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggRGlyZWN0aW9uIGFzIHRoZSByZXNwb25zZSBhbmQgdGhlIGZpdmUgbGFnIHZhcmlhYmxlcyBwbHVzIFZvbHVtZSBhcyBwcmVkaWN0b3JzLiBVc2UgdGhlIHN1bW1hcnkgZnVuY3Rpb24gdG8gcHJpbnQgdGhlIHJlc3VsdHMuIERvIGFueSBvZiB0aGUgcHJlZGljdG9ycyBhcHBlYXIgdG8gYmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudD8gSWYgc28sd2hpY2ggb25lcz8NCg0KYGBge3J9DQpnbG0uZml0ID0gZ2xtKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyICsgTGFnMyArIExhZzQgKyBMYWc1ICsgVm9sdW1lLCBkYXRhID0gV2Vla2x5LCBmYW1pbHkgPSAiYmlub21pYWwiICkNCnN1bW1hcnkoZ2xtLmZpdCkNCmBgYA0KVGhlIG9ubHkgdmFyaWFibGUgdGhhdCBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGlzICpMYWcyKi4gV2UgcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgYW5kIHN0YXRlIHRoYXQgKkxhZzIqIGNvZWZmaWNpZW50IGlzIG5vdCBlcXVhbCB0byB6ZXJvLiANCg0KIyMjIChjKSBDb21wdXRlIHRoZSBjb25mdXNpb24gbWF0cml4IGFuZCBvdmVyYWxsIGZyYWN0aW9uIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMuIEV4cGxhaW4gd2hhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCBpcyB0ZWxsaW5nIHlvdSBhYm91dCB0aGUgdHlwZXMgb2YgbWlzdGFrZXMgbWFkZSBieSBsb2dpc3RpYyByZWdyZXNzaW9uLg0KDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KGdsbS5maXQsIFdlZWtseSwgdHlwZSA9ICJyZXNwb25zZSIpDQpwcmVkLmxhYmVscyA9IGlmZWxzZShwcmVkID4gLjUsICJVcCIsICJEb3duIikNCmFjdHVhbC5sYWJlbCA9IGZhY3RvcihXZWVrbHkkRGlyZWN0aW9uKQ0KcHJlZC5sYWJlbHMgPSBmYWN0b3IocHJlZC5sYWJlbHMpDQoNCmNvbmZ1c2lvbk1hdHJpeChwcmVkLmxhYmVscywgYWN0dWFsLmxhYmVsKQ0KYGBgDQpUaGVyZSBpcyBhIGxvdCBvZiBmYWxzZSBuZWdhdGl2ZXMoNDMwKSBhbmQgYSBzbWFsbGVyIGFtb3VudCBvZiBmYWxzZSBwb3NpdGl2ZXMoNDgpLiBTZW5zaXRpdml0eSBpcyBjYWxjdWxhdGVkIGJ5IChUUFwgVFAgKyBGTikgJFxmcmFjezU0fXs1NCs0MzB9IFxhcHByb3ggMTEuMSAkIHdoaWNoIG1lYW5zICQxMS4xXCUkIG9mIHRoZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyB3ZXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkLiBXaGlsZSB0aGUgU3BlY2lmaWNpdHkgaXMgdGhlIHBlcmNlbnRhZ2Ugb2YgYSBtb2RlbHMgYWJpbGl0eSB0byBjb3JyZWN0bHkgaWRlbnRpZnkgbmVnYXRpdmUgaW5zdGFuY2VzLCB0aGF0IGlzIGNhbGN1bGF0ZWQgJFxmcmFjezQ4fXs0OCs1NTd9IFxhcHByb3ggOTIuMCQsIG1lYW5pbmcgdGhhdCAkOTJcJSQgYXJlIGNvcnJlY3RseSBjbGFzc2lmaWVkLiBUaGlzIGNvbmZ1c2lvbiBtYXRyaXggaXMgZXhlbXBsaWZ5aW5nIHRoYXQgTG9naXN0aWMgUmVncmVzc2lvbiBtaWdodCBub3QgYmUgdGhlIGJlc3QgbW9kZWwgdG8gdXNlIHRvIHByZWRpY3Qgd2hldGhlciB0aGUgc3RvY2sgb2YgdGhlIFMmUCAgNTAwIGluIGEgZ2l2ZW4gd2Vlay4gICANCg0KDQojIyMgKGQpIE5vdyBmaXQgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgYSB0cmFpbmluZyBkYXRhIHBlcmlvZCBmcm9tIDE5OTAgdG8gMjAwOCwgd2l0aCBMYWcyIGFzIHRoZSBvbmx5IHByZWRpY3Rvci4gQ29tcHV0ZSB0aGUgY29uZnVzaW9uIG1hdHJpeCBhbmQgdGhlIG92ZXJhbGwgZnJhY3Rpb24gb2YgY29ycmVjdCBwcmVkaWN0aW9ucyBmb3IgdGhlIGhlbGQgb3V0IGRhdGEgKHRoYXQgaXMsIHRoZSBkYXRhIGZyb20gMjAwOSBhbmQgMjAxMCkNCmBgYHtyfQ0KdHJhaW4gPSAoWWVhciA8IDIwMDkgKQ0KdGVzdCA9IFdlZWtseVshdHJhaW4sXQ0KZGltKHRlc3QpDQpEaXJlY3Rpb24uMjAwOSA9IERpcmVjdGlvblshdHJhaW5dDQpgYGANCg0KYGBge3J9DQpnbG0uZml0MiA9IGdsbShEaXJlY3Rpb24gfiBMYWcyLCBkYXRhID0gV2Vla2x5LCBmYW1pbHkgPSAiYmlub21pYWwiLCBzdWJzZXQgPSB0cmFpbikNCg0KDQpnbG0ucHJvYnMgPSBwcmVkaWN0KGdsbS5maXQyLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCmdsbS5wcmVkMiA9IHJlcCgiRG93biIsIGxlbmd0aChnbG0ucHJvYnMpKQ0KZ2xtLnByZWQyW2dsbS5wcm9icyA+IC41XSA9ICJVcCINCnRhYmxlKGdsbS5wcmVkMiwgRGlyZWN0aW9uLjIwMDkpDQptZWFuKGdsbS5wcmVkMiA9PSBEaXJlY3Rpb24uMjAwOSkNCm1lYW4oZ2xtLnByZWQyICE9IERpcmVjdGlvbi4yMDA5KQ0KYGBgDQoNCiMjIyAoZSkgUmVwZWF0IChkKSB1c2luZyBMREEuDQpgYGB7cn0NCmxkYS5tb2RlbCA9IGxkYShEaXJlY3Rpb24gfiAgTGFnMiwgZGF0YSA9IFdlZWtseSwgc3Vic2V0ID0gdHJhaW4pDQoNCg0KbGRhLnByb2JzID0gcHJlZGljdChsZGEubW9kZWwsdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpsZGEucHJlZCA9IHJlcCgiRG93biIsIDEwNCkNCmxkYS5wcm9ic191cCA9IGxkYS5wcm9icyRwb3N0ZXJpb3JbLCAyXQ0KbGRhLnByZWQgPSByZXAoIkRvd24iLCBsZW5ndGgobGRhLnByb2JzX3VwKSkNCmxkYS5wcmVkW2xkYS5wcm9ic191cCA+IC41XSA9ICJVcCINCnRhYmxlKGxkYS5wcmVkLCBEaXJlY3Rpb24uMjAwOSkNCm1lYW4obGRhLnByZWQgPT0gRGlyZWN0aW9uLjIwMDkpDQptZWFuKGxkYS5wcmVkICE9IERpcmVjdGlvbi4yMDA5KQ0KDQpgYGANCiMjIyAoZikgZikgUmVwZWF0IChkKSB1c2luZyBRREENCmBgYHtyfQ0KcWRhLm1vZGVsID0gcWRhKERpcmVjdGlvbiB+ICBMYWcyLCBkYXRhID0gV2Vla2x5LCBzdWJzZXQgPSB0cmFpbikNCg0KcWRhLnByZWQgPSBwcmVkaWN0KHFkYS5tb2RlbCx0ZXN0KSRjbGFzcw0KDQp0YWJsZShxZGEucHJlZCwgRGlyZWN0aW9uLjIwMDkpDQptZWFuKHFkYS5wcmVkID09IERpcmVjdGlvbi4yMDA5KQ0KbWVhbihxZGEucHJlZCAhPSBEaXJlY3Rpb24uMjAwOSkNCmBgYA0KIyMjIChnKSBSZXBlYXQgKGQpIHVzaW5nIEtOTiB3aXRoIEsgPSAxDQpgYGB7cn0NCndlZWsudHJhaW4gPSBhcy5tYXRyaXgoTGFnMlt0cmFpbl0pDQp3ZWVrLnRlc3QgPSBhcy5tYXRyaXgoTGFnMlshdHJhaW5dKQ0KdHJhaW4uZGlyZWN0aW9uID0gRGlyZWN0aW9uW3RyYWluXQ0Kc2V0LnNlZWQoMSkNCmtubiA9IGtubih3ZWVrLnRyYWluLHdlZWsudGVzdCx0cmFpbi5kaXJlY3Rpb24sayA9IDEpDQp0YWJsZShrbm4sIERpcmVjdGlvbi4yMDA5KQ0KYGBgDQoNCmBgYHtyfQ0KbWVhbihrbm4gPT0gRGlyZWN0aW9uLjIwMDkpDQpgYGANCg0KIyMjIChoKSB1c2luZyBuYWl2ZSBCYXllcw0KYGBge3J9DQpiYXllcyA9IG5haXZlQmF5ZXMoRGlyZWN0aW9uIH4gTGFnMiwgV2Vla2x5LCBzdWJzZXQgPSB0cmFpbikNCmJheWVzLnByZWQgPSBwcmVkaWN0KGJheWVzLCB0ZXN0KQ0KdGFibGUoYmF5ZXMucHJlZCwgRGlyZWN0aW9uLjIwMDkpDQptZWFuKGJheWVzLnByZWQgPT0gRGlyZWN0aW9uLjIwMDkpDQptZWFuKGJheWVzLnByZWQgIT0gRGlyZWN0aW9uLjIwMDkpDQpgYGANCg0KIyMjIChpKSBXaGljaCBvZiB0aGVzZSBtZXRob2RzIGFwcGVhcnMgdG8gcHJvdmlkZSB0aGUgYmVzdCByZXN1bHRzIG9uIHRoaXMgZGF0YT8NCkxpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgaGFzIGFuIGFjY3VyYWN5IG9mICQ2Mi41XCUkIGFuZCBMb2dpc3RpYyBSZWdyZXNzaW9uICQ2Mi41XCUkIGFyZSB0aGUgYmVzdCBtZXRob2RzIGluIHByZWRpY3RpbmcgdGhlIGRpcmVjdGlvbiBvZiB0aGUgc3RvY2sgbWFya2V0IGJldHdlZW4gMTk5MCBhbmQgMjAwOC4gDQoNCg0KIyMjIChqKSBFeHBlcmltZW50IHdpdGggZGlmZmVyZW50IGNvbWJpbmF0aW9ucyBvZiBwcmVkaWN0b3JzLCBpbmNsdWRpbmcgcG9zc2libGUgdHJhbnNmb3JtYXRpb25zIGFuZCBpbnRlcmFjdGlvbnMsIGZvciBlYWNoIG9mIHRoZSBtZXRob2RzLiBSZXBvcnQgdGhlIHZhcmlhYmxlcywgbWV0aG9kLCBhbmQgYXNzb2NpYXRlZCBjb25mdXNpb24gbWF0cml4IHRoYXQgYXBwZWFycyB0byBwcm92aWRlIHRoZSBiZXN0IHJlc3VsdHMgb24gdGhlIGhlbGQgb3V0IGRhdGEuIE5vdGUgdGhhdCB5b3Ugc2hvdWxkIGFsc28gZXhwZXJpbWVudCB3aXRoIHZhbHVlcyBmb3IgSyBpbiB0aGUgS05OIGNsYXNzaWZpZXIuDQpgYGB7cn0NCmhvbGRvdXQgPSAoWWVhciA+IDIwMDkpDQpgYGANCg0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24gUGFydCAyIA0KYGBge3J9DQpnbG0uZml0MiA9IGdsbShEaXJlY3Rpb24gfiBMYWcyICsgZXhwKExhZzMpICsgTGFnNCArIFZvbHVtZSwgZGF0YSA9IFdlZWtseSwgZmFtaWx5ID0gImJpbm9taWFsIiwgc3Vic2V0ID0gaG9sZG91dCkNCg0KDQpnbG0ucHJvYnMgPSBwcmVkaWN0KGdsbS5maXQyLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCmdsbS5wcmVkMiA9IHJlcCgiRG93biIsIGxlbmd0aChnbG0ucHJvYnMpKQ0KZ2xtLnByZWQyW2dsbS5wcm9icyA+IC41XSA9ICJVcCINCnRhYmxlKGdsbS5wcmVkMiwgRGlyZWN0aW9uLjIwMDkpDQptZWFuKGdsbS5wcmVkMiA9PSBEaXJlY3Rpb24uMjAwOSkNCm1lYW4oZ2xtLnByZWQyICE9IERpcmVjdGlvbi4yMDA5KQ0KYGBgDQoNCiMjIyBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzIFBhcnQgMg0KYGBge3J9DQpsZGEubW9kZWwgPSBsZGEoRGlyZWN0aW9uIH4gTGFnMSsgTGFnMiArIExhZzUgKyBWb2x1bWUsIGRhdGEgPSBXZWVrbHksIHN1YnNldCA9IGhvbGRvdXQpDQoNCg0KbGRhLnByb2JzID0gcHJlZGljdChsZGEubW9kZWwsdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpsZGEucHJlZCA9IHJlcCgiRG93biIsIDEwNCkNCmxkYS5wcm9ic191cCA9IGxkYS5wcm9icyRwb3N0ZXJpb3JbLCAyXQ0KbGRhLnByZWQgPSByZXAoIkRvd24iLCBsZW5ndGgobGRhLnByb2JzX3VwKSkNCmxkYS5wcmVkW2xkYS5wcm9ic191cCA+IC41XSA9ICJVcCINCnRhYmxlKGxkYS5wcmVkLCBEaXJlY3Rpb24uMjAwOSkNCm1lYW4obGRhLnByZWQgPT0gRGlyZWN0aW9uLjIwMDkpDQptZWFuKGxkYS5wcmVkICE9IERpcmVjdGlvbi4yMDA5KQ0KDQpgYGANCg0KIyMjIFFEQSBQYXJ0IDINCmBgYHtyfQ0KcWRhLm1vZGVsID0gcWRhKERpcmVjdGlvbiB+ICBMYWczICsgTGFnMSArIExhZzUgKyBWb2x1bWUsIGRhdGEgPSBXZWVrbHksIHN1YnNldCA9IGhvbGRvdXQpDQoNCnFkYS5wcmVkID0gcHJlZGljdChxZGEubW9kZWwsdGVzdCkkY2xhc3MNCg0KdGFibGUocWRhLnByZWQsIERpcmVjdGlvbi4yMDA5KQ0KbWVhbihxZGEucHJlZCA9PSBEaXJlY3Rpb24uMjAwOSkNCm1lYW4ocWRhLnByZWQgIT0gRGlyZWN0aW9uLjIwMDkpDQpgYGANCiMjIyBLTk4gUGFydCAyDQpgYGB7cn0NCnNldC5zZWVkKDEpDQprbm4gPSBrbm4od2Vlay50cmFpbix3ZWVrLnRlc3QsdHJhaW4uZGlyZWN0aW9uLGsgPSA1KQ0KdGFibGUoa25uLCBEaXJlY3Rpb24uMjAwOSkNCm1lYW4oa25uID09IERpcmVjdGlvbi4yMDA5KQ0KYGBgDQoNCiMjIyBOYWl2ZSBCYXllcyBQYXJ0IDINCmBgYHtyfQ0KYmF5ZXMgPSBuYWl2ZUJheWVzKERpcmVjdGlvbiB+IExhZzIgKyBWb2x1bWUgKyBMYWcxICsgTGFnMyArIExhZzQgKyBMYWc1LCBXZWVrbHksIHN1YnNldCA9IGhvbGRvdXQpDQpiYXllcy5wcmVkID0gcHJlZGljdChiYXllcywgdGVzdCkNCnRhYmxlKGJheWVzLnByZWQsIERpcmVjdGlvbi4yMDA5KQ0KbWVhbihiYXllcy5wcmVkID09IERpcmVjdGlvbi4yMDA5KQ0KbWVhbihiYXllcy5wcmVkICE9IERpcmVjdGlvbi4yMDA5KQ0KDQpgYGANClRoZSBtZXRob2QgdGhhdCBoYXMgdGhlIGJlc3QgcmVzdWx0cyBpbiBtb3N0IGFjY3VyYXRlbHkgcHJlZGljdGluZyB0aGUgZGlyZWN0aW9uIG9mIHRoZSBTJlAgNTAwIGFmdGVyIHRoZSB5ZWFyIDIwMDksIGlzIHRoZSBRREEgbW9kZWwgd2l0aCBhbiBhY2N1cmFjeSBvZiAkNjYuMzRcJSQuDQoNCiMgUHJvYmxlbSAxNA0KIyMjIChhKSBDcmVhdGUgYSBiaW5hcnkgdmFyaWFibGUsIG1wZzAxLCB0aGF0IGNvbnRhaW5zIGEgMSBpZiBtcGcgY29udGFpbnMgYSB2YWx1ZSBhYm92ZSBpdHMgbWVkaWFuLCBhbmQgYSAwIGlmIG1wZyBjb250YWlucyBhIHZhbHVlIGJlbG93IGl0cyBtZWRpYW4uIFlvdSBjYW4gY29tcHV0ZSB0aGUgbWVkaWFuIHVzaW5nIHRoZSBtZWRpYW4oKSBmdW5jdGlvbi4gTm90ZSB5b3UgbWF5IGZpbmQgaXQgaGVscGZ1bCB0byB1c2UgdGhlIGRhdGEuZnJhbWUoKSBmdW5jdGlvbiB0byBjcmVhdGUgYSBzaW5nbGUgZGF0YSBzZXQgY29udGFpbmluZyBib3RoIG1wZzAxIGFuZCB0aGUgb3RoZXIgQXV0byB2YXJpYWJsZXMuDQpgYGB7cn0NCmxpYnJhcnkoSVNMUjIpDQpJU0xSMjo6QXV0bw0KYGBgDQoNCmBgYHtyfQ0KbXBnMDEgPSBpZmVsc2UobWVkaWFuKG1wZyk8IEF1dG8kbXBnLDEsIDApDQptcGcgPSBkYXRhLmZyYW1lKEF1dG8sbXBnMDEpDQptcGckbXBnMDEgPSBhcy5mYWN0b3IobXBnJG1wZzAxKQ0KYGBgDQoNCg0KIyMjIChiKQ0KVGhlc2UgdmFyaWFibGVzICptcGcsY3lsaW5kZXJzLGRpc3BsYWNlbWVudCxob3JzZXBvd2VyLHdlaWdodCxhY2NlbGVyYXRpb24qIGFyZSBub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQsIGJlc2lkZXMgYWNjZWxlcmF0aW9uIGFuZCBtcGcuIFRoZXJlIGlzIGV2aWRlbmNlIG9mIGhpZ2ggYW5kIGxvdyB2YWx1ZXMgb2YgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGNvcnJlbGF0aW9uLg0KDQoqTVBHLCBEaXNwbGFjZW1lbnQsIEhvcnNlcG93ZXIsIFdlaWdodCogYXJlIHNrZXdlZCB0byB0aGUgcmlnaHQsIHdoaWNoIGFnYWluIHNob3dzIHRoYXQgdGhlIG1lYW4gaXMgZ3JlYXRlciB0aGFuIHRoZSBtZWRpYW4sIHRodXMgdGhlIHJlYXNvbiB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHZhbHVlcyBhcmUgc2tld2VkIHRvIHRoZSByaWdodC4gV2hpbGUgKkN5bGluZGVycyogaXMgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGFuZCBpcyBsZWZ0IHRhaWxlZC4NCmBgYHtyfQ0KbXBnIHw+DQogZ2dzY2F0bWF0KGNvbHVtbnMgPSAxOjYsIGFscGhhID0gLjIpDQpgYGANCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygzLDMpKQ0KDQpib3hwbG90KG1wZyRtcGcsIG1haW4gPSAiQm94cGxvdCBvZiBNUEciKQ0KYm94cGxvdChtcGckY3lsaW5kZXJzLCBtYWluID0gIkJveHBsb3Qgb2YgQ3lsaW5kZXJzIikNCmJveHBsb3QobXBnJGRpc3BsYWNlbWVudCwgbWFpbiA9ICJCb3hwbG90IG9mIERpc3BsYWNlbWVudCIpDQpib3hwbG90KG1wZyRob3JzZXBvd2VyLCBtYWluID0gIkJveHBsb3Qgb2YgSG9yc2Vwb3dlciIpDQpib3hwbG90KG1wZyR3ZWlnaHQsIG1haW4gPSAiQm94cGxvdCBvZiBXZWlnaHQiKQ0KYm94cGxvdChtcGckYWNjZWxlcmF0aW9uLCBtYWluID0gIkJveHBsb3Qgb2YgQWNjZWxlcmF0aW9uIikNCmBgYA0KIyMjIChjKSBTcGxpdCB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIHRlc3Qgc2V0Lg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnRyYWluLmluZGV4ID0gY3JlYXRlRGF0YVBhcnRpdGlvbihtcGckbXBnMDEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCm1wZy50cmFpbiA9IG1wZ1t0cmFpbi5pbmRleCxdDQptcGcudGVzdCA9IG1wZ1stdHJhaW4uaW5kZXgsXQ0KYGBgDQoNCiMjIyBGb3JtdWxhIGZvciBwcm9ibGVtIDE0DQpgYGB7cn0NCmZvcm0xNCA9IG1wZzAxIH4gbXBnICsgY3lsaW5kZXJzICsgZGlzcGxhY2VtZW50ICsgd2VpZ2h0DQpgYGANCg0KIyMjIChkKSBQZXJmb3JtIExEQSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IG1wZzAxIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggbXBnMDEgaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCg0KIyMjIyBMREEgTW9kZWwNClRoaXMgbW9kZWwgZ2F2ZSBhICQ5My41OVwlJCBhY2N1cmFjeSENCmBgYHtyfQ0KbXBnLmxkYSA9IGxkYShmb3JtMTQsIGRhdGEgPSBtcGcpDQptcGcubGRhLnByZWQgPSBwcmVkaWN0KG1wZy5sZGEsbXBnLnRlc3QpJGNsYXNzDQp0YWJsZShtcGcubGRhLnByZWQsIG1wZy50ZXN0JG1wZzAxKQ0KbWVhbihtcGcubGRhLnByZWQgPT0gbXBnLnRlc3QkbXBnMDEpDQpgYGANCg0KIyMjIChlKSBQZXJmb3JtIFFEQSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IG1wZzAxIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggbXBnMDEgaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCg0KIyMjIyBRREEgTW9kZWwNClRoaXMgbW9kZWwgeWllbGRzIGFuIGFjY3VyYWN5IG9mICQ5Mi4yXCUkLg0KYGBge3J9DQptcGcucWRhID0gcWRhKGZvcm0xNCwgZGF0YSA9IG1wZykNCm1wZy5xZGEucHJlZCA9IHByZWRpY3QobXBnLnFkYSwgbXBnLnRlc3QpJGNsYXNzDQp0YWJsZShtcGcucWRhLnByZWQsbXBnLnRlc3QkbXBnMDEpDQptZWFuKG1wZy5xZGEucHJlZCA9PSBtcGcudGVzdCRtcGcwMSkNCmBgYA0KDQojIyMgKGYpIFBlcmZvcm0gbG9naXN0aWMgcmVncmVzc2lvbiBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IG1wZzAxIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggbXBnMDEgaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCg0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgDQpMb2dpc3RpYyBSZWdyZXNzaW9uIGhhcyAkMTAwXCUkIGFjY3VyYWN5IQ0KYGBge3J9DQptcGcuZ2xtID0gZ2xtKGZvcm0xNCwgZGF0YSA9IG1wZywgZmFtaWx5ID0gImJpbm9taWFsIikNCm1wZy5nbG0ucHJlZCA9IHByZWRpY3QobXBnLmdsbSwgbXBnLnRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KbXBnLmdsbS5wcmVkLmNsYXNzID0gaWZlbHNlKG1wZy5nbG0ucHJlZCA+IC41LCAxLCAwKQ0KdGFibGUobXBnLmdsbS5wcmVkLmNsYXNzLG1wZy50ZXN0JG1wZzAxKQ0KbWVhbihtcGcuZ2xtLnByZWQuY2xhc3MgPT0gbXBnLnRlc3QkbXBnMDEpDQpgYGANCg0KIyMjICAoZykgUGVyZm9ybSBuYWl2ZSBCYXllcyBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0IG1wZzAxIHVzaW5nIHRoZSB2YXJpYWJsZXMgdGhhdCBzZWVtZWQgbW9zdCBhc3NvY2lhdGVkIHdpdGggbXBnMDEgaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCk5haXZlIEJheWVzIG1vZGVsIGRpc3BsYXllZCBhbiBhY2N1cmFjeSBvZiAkODguNDZcJSQhDQpgYGB7cn0NCm1wZy5iYXllcyA9IG5haXZlQmF5ZXMoZm9ybTE0LCBkYXRhID0gbXBnKQ0KbXBnLmJheWVzLnByZWQgPSBwcmVkaWN0KG1wZy5iYXllcywgbXBnLnRlc3QpDQp0YWJsZShtcGcuYmF5ZXMucHJlZCwgbXBnLnRlc3QkbXBnMDEpDQptZWFuKG1wZy5iYXllcy5wcmVkID09IG1wZy50ZXN0JG1wZzAxKQ0KYGBgDQoNCiMjIyAoaCkgUGVyZm9ybSBLTk4gb24gdGhlIHRyYWluaW5nIGRhdGEsIHdpdGggc2V2ZXJhbCB2YWx1ZXMgb2YgSywgaW4gb3JkZXIgdG8gcHJlZGljdCBtcGcwMS4gVXNlIG9ubHkgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBtcGcwMSBpbiAoYikuIFdoYXQgdGVzdCBlcnJvcnMgZG8geW91IG9idGFpbj8gV2hpY2ggdmFsdWUgb2YgSyBzZWVtcyB0byBwZXJmb3JtIHRoZSBiZXN0IG9uIHRoaXMgZGF0YSBzZXQ/DQoNCktOTiBvZiAyMyBjbHVzdGVycyBoYXMgYW4gYWNjdXJhY3kgb2YgJDg2XCUkDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCmlkeCA9IHNhbXBsZSgxOm5yb3coQXV0byksIDAuOCpucm93KEF1dG8pKQ0KdHJhaW4uYXV0byA9IEF1dG9baWR4LF0NCnRlc3QuYXV0byA9IEF1dG9bLWlkeCxdDQp5LnRyYWluID0gdHJhaW4uYXV0byRtcGcNCngudHJhaW4gPSB0cmFpbi5hdXRvWywtMV0NCngudGVzdCA9IHRlc3QuYXV0b1ssLTFdDQp4LnRyYWluID0geC50cmFpblssLThdDQp4LnRlc3QgPSB4LnRlc3RbLC04XQ0KeS50ZXN0ID0gaWZlbHNlKHRlc3QuYXV0byRtcGc+PTIzLDEsMCkNCmhpZ2gubG93ID0gaWZlbHNlKHkudHJhaW4+PTIzLDEsMCkNCmtubi5wcmVkIDwtIGtubiAodHJhaW49eC50cmFpbiwgdGVzdD14LnRlc3QsIGNsPWhpZ2gubG93LCBrPTEpDQp0YWJsZShrbm4ucHJlZCwgeS50ZXN0KQ0KbWVhbihrbm4ucHJlZCA9PSB5LnRlc3QpDQpgYGANCg0KDQpgYGB7cn0NCnNldC5zZWVkKDcpDQprbm4ucHJlZCA8LSBrbm4gKHRyYWluPXgudHJhaW4sIHRlc3Q9eC50ZXN0LCBjbD1oaWdoLmxvdywgaz0yMykNCnRhYmxlKGtubi5wcmVkLCB5LnRlc3QpDQptZWFuKGtubi5wcmVkID09IHkudGVzdCkNCmBgYA0KDQoNCg0KIyBQcm9ibGVtIDE2DQpgYGB7cn0NCklTTFIyOjpCb3N0b24NCmBgYA0KYGBge3J9DQpDcmltZSA9IGlmZWxzZShCb3N0b24kY3JpbSA+IG1lZGlhbihCb3N0b24kY3JpbSksIDEsIDApDQpCb3N0b24ucDIgPSBkYXRhLmZyYW1lKEJvc3RvbixDcmltZSkNCmBgYA0KDQoNCiMjIFZpc3VhbGl6YWl0b25zIA0KYGBge3J9DQpwYWlycyhCb3N0b24ucDIpDQpgYGANCmBgYHtyfQ0KY29yKEJvc3Rvbi5wMikNCmBgYA0KYGBge3J9DQpnZ3NjYXRtYXQoQm9zdG9uLnAyKQ0KYGBgDQojIyMgU3BsaXR0aW5nIHRoZSBkYXRhIA0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpCb3N0b24uaW5kZXggPSBjcmVhdGVEYXRhUGFydGl0aW9uKEJvc3Rvbi5wMiRDcmltZSwgcCA9IC43LCBsaXN0ID0gRikNCkJvc3Rvbi50cmFpbiA9IEJvc3Rvbi5wMltCb3N0b24uaW5kZXgsXQ0KQm9zdG9uLnRlc3QgPSBCb3N0b24ucDJbLUJvc3Rvbi5pbmRleCxdDQp0cmFpbiA9IDE6KGRpbShCb3N0b24ucDIpWzFdLzIpDQp0ZXN0ID0gKGRpbShCb3N0b24ucDIpWzFdLzIgKyAxKTpkaW0oQm9zdG9uLnAyKVsxXQ0KQ3JpbWUudGVzdCA9IENyaW1lW3Rlc3RdDQoNCmBgYA0KDQpgYGB7cn0NCmZvcm0xNiA9IENyaW1lIH4gaW5kdXMgKyBub3ggKyBhZ2UgKyByYWQgKyB0YXgNCmBgYA0KDQoNCiMjIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIA0KVGhpcyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIHdpdGggKmluZHVzLG5veCxhZ2UscmFkLHRheCogaGFzIGFuIGFjY3VyYWN5IG9mICQ5MC42NlwlJC4NCmBgYHtyfQ0KYm9zdG9uLmdsbSA9IGdsbShmb3JtMTYsIGRhdGEgPSBCb3N0b24udHJhaW4sZmFtaWx5ID0gImJpbm9taWFsIikNCmJvc3Rvbi5nbG0ucHJvYnMgPSBwcmVkaWN0KGJvc3Rvbi5nbG0sQm9zdG9uLnRlc3QsdHlwZSA9ICJyZXNwb25zZSIpDQpib3N0b24uZ2xtLnByZWQgPSBpZmVsc2UoYm9zdG9uLmdsbS5wcm9icyA+IC41LDEsMCkNCnRhYmxlKGJvc3Rvbi5nbG0ucHJlZCxCb3N0b24udGVzdCRDcmltZSkNCm1lYW4oYm9zdG9uLmdsbS5wcmVkID09IEJvc3Rvbi50ZXN0JENyaW1lKQ0KYGBgDQojIyMgTERBDQpMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzIHdpdGggKmluZHVzLG5veCxhZ2UscmFkLHRheCogaGFzIGFuIGFjY3VyYWN5IG9mICQ4NS4zM1wlJA0KYGBge3J9DQpib3N0b24ubGRhID0gbGRhKGZvcm0xNiwgZGF0YSA9IEJvc3Rvbi50cmFpbikNCmJvc3Rvbi5sZGEucHJlZCA9IHByZWRpY3QoYm9zdG9uLmxkYSwgQm9zdG9uLnRlc3QpJGNsYXNzDQp0YWJsZShib3N0b24ubGRhLnByZWQsQm9zdG9uLnRlc3QkQ3JpbWUpDQptZWFuKGJvc3Rvbi5sZGEucHJlZCA9PSBCb3N0b24udGVzdCRDcmltZSkNCmBgYA0KIyMjIE5haXZlIEJheWVzDQpUaGlzIG1vZGVsIHlpZWxkcyBhbiAkODQuNjZcJSQgYWNjdXJhY3kuDQpgYGB7cn0NCmJvc3Rvbi5uYWl2ZSA9IG5haXZlQmF5ZXMoZm9ybTE2LCBkYXRhID0gQm9zdG9uLnRyYWluKQ0KYm9zdG9uLm5haXZlLnByZWQgPSBwcmVkaWN0KGJvc3Rvbi5uYWl2ZSwgQm9zdG9uLnRlc3QpDQp0YWJsZShib3N0b24ubmFpdmUucHJlZCwgQm9zdG9uLnRlc3QkQ3JpbWUpDQptZWFuKGJvc3Rvbi5uYWl2ZS5wcmVkID09IEJvc3Rvbi50ZXN0JENyaW1lKQ0KYGBgDQoNCiMjIyBLTk4gDQoNCktOTiB3aXRoIDUgY2x1c3RlcnMgaGFzIGFuIGFjY3VyYWN5IG9mICQ3Ni42N1wlJA0KYGBge3J9DQpzZXQuc2VlZCgyKQ0KdHJhaW4ua25uID0gY2JpbmQoaW5kdXMsbm94LGFnZSxkaXMscmFkLHRheClbdHJhaW4sXQ0KdGVzdC5rbm4gPSBjYmluZChpbmR1cyxub3gsYWdlLGRpcyxyYWQsdGF4KVt0ZXN0LF0NCkJvc3Rvbmtubi5wcmVkID0ga25uKHRyYWluLmtubiwgdGVzdC5rbm4sIENyaW1lLnRlc3QsIGs9MTApDQp0YWJsZShCb3N0b25rbm4ucHJlZCwgQ3JpbWUudGVzdCkNCm1lYW4oQm9zdG9ua25uLnByZWQgPT0gQ3JpbWUudGVzdCkNCmBgYA0KDQo=