1. 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.
library(ISLR2)
library(caret)
library(MASS)
library(class)
library(e1071)
data("Weekly")
  1. Produce some numerical and graphical summaries of the Weekly data. Do there appear to be any patterns?
pairs(Weekly[, -9], main = "Scatterplot Matrix of Weekly Data (Numeric Variables)", col = "blue")

NA
NA

I don’t see any patterns in the pairs scatterplot matrix but I will try some other graphs to keep checking

hist(Weekly$Today, breaks = 30, col = "lightblue", main = "Histogram of Weekly Returns",
     xlab = "Weekly Return", border = "white")


boxplot(Weekly[,2:6], main = "Boxplots of Lagged Weekly Returns",
        col = c("lightblue", "lightgreen", "pink", "yellow", "orange"))


plot(Weekly$Volume, type = "l", col = "blue",
     main = "Time Series of Trading Volume",
     ylab = "Volume", xlab = "Time Index")


plot(Weekly$Lag1, Weekly$Today, 
     main = "Scatterplot of Today’s Return vs. Lag1",
     xlab = "Lag1", ylab = "Today", col = "red", pch = 19)
abline(lm(Today ~ Lag1, data = Weekly), col = "blue", lwd = 2)


table(Weekly$Direction)

Down   Up 
 484  605 
barplot(table(Weekly$Direction), col = c("red", "green"),
        main = "Market Direction Frequency",
        ylab = "Count")

NA
NA
NA

I notice on the histogram it looks to be a normal distribution, on the box plots they look even, on the frequency plot there is more up than down which is good for the American Investing Public

  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?
# Convert Direction to a binary factor (Up = 1, Down = 0)
Weekly$Direction <- as.factor(Weekly$Direction)

# Fit the logistic regression model
logit_model <- glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume, 
                   data = Weekly, family = binomial)

# Display model summary
summary(logit_model)

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

Lag 2, for some reason seems significant but none of the rest are. One thing I did notice is that lag 2 which is significant has a positive estimate, and the model over predicts up, and all the other variables are negitive and are also not significant.

  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.
# Predict probabilities
pred_probs <- predict(logit_model, type = "response")

# Convert probabilities to class labels (threshold = 0.5)
pred_labels <- ifelse(pred_probs > 0.5, "Up", "Down")

# Convert to factor to match actual Direction variable
pred_labels <- factor(pred_labels, levels = levels(Weekly$Direction))

# Create confusion matrix
conf_matrix <- table(Predicted = pred_labels, Actual = Weekly$Direction)
print(conf_matrix)
         Actual
Predicted Down  Up
     Down   54  48
     Up    430 557
# Compute overall accuracy
accuracy <- sum(diag(conf_matrix)) / sum(conf_matrix)
print(paste("Accuracy:", round(accuracy, 4)))
[1] "Accuracy: 0.5611"
confusionMatrix(pred_labels, Weekly$Direction, positive = "Up")
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.9207         
            Specificity : 0.1116         
         Pos Pred Value : 0.5643         
         Neg Pred Value : 0.5294         
             Prevalence : 0.5556         
         Detection Rate : 0.5115         
   Detection Prevalence : 0.9063         
      Balanced Accuracy : 0.5161         
                                         
       'Positive' Class : Up             
                                         

The model predicts “Up” much more often than “Down”, leading to many false negatives (430 cases). It struggles to correctly predict “Down” movements. This suggests that logistic regression does not perform well in predicting market declines

  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).
# Split data into training (1990-2008) and testing (2009-2010)
train_data <- subset(Weekly, Year <= 2008)
test_data  <- subset(Weekly, Year > 2008)
# Fit logistic regression using Lag2 as the only predictor
logit_model_lag2 <- glm(Direction ~ Lag2, data = train_data, family = binomial)

# Print summary of the model
summary(logit_model_lag2)

Call:
glm(formula = Direction ~ Lag2, family = binomial, data = train_data)

Coefficients:
            Estimate Std. Error z value Pr(>|z|)   
(Intercept)  0.20326    0.06428   3.162  0.00157 **
Lag2         0.05810    0.02870   2.024  0.04298 * 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1354.7  on 984  degrees of freedom
Residual deviance: 1350.5  on 983  degrees of freedom
AIC: 1354.5

Number of Fisher Scoring iterations: 4
# Predict probabilities for test data
pred_probs_test <- predict(logit_model_lag2, test_data, type = "response")

# Convert probabilities to class labels (threshold = 0.5)
pred_labels_test <- ifelse(pred_probs_test > 0.5, "Up", "Down")

# Convert to factor to match actual Direction variable
pred_labels_test <- factor(pred_labels_test, levels = levels(Weekly$Direction))
# Compute confusion matrix
conf_matrix_test <- table(Predicted = pred_labels_test, Actual = test_data$Direction)
print(conf_matrix_test)
         Actual
Predicted Down Up
     Down    9  5
     Up     34 56
# Compute overall accuracy
accuracy_test <- sum(diag(conf_matrix_test)) / sum(conf_matrix_test)
print(paste("Test Accuracy:", round(accuracy_test, 4)))
[1] "Test Accuracy: 0.625"
  1. Repeat (d) using LDA.
# Training data: 1990-2008
train_data <- subset(Weekly, Year <= 2008)

# Test data: 2009-2010
test_data  <- subset(Weekly, Year > 2008)
# Fit LDA using Lag2 as the only predictor
lda_model <- lda(Direction ~ Lag2, data = train_data)

# Print model summary
lda_model
Call:
lda(Direction ~ Lag2, data = train_data)

Prior probabilities of groups:
     Down        Up 
0.4477157 0.5522843 

Group means:
            Lag2
Down -0.03568254
Up    0.26036581

Coefficients of linear discriminants:
           LD1
Lag2 0.4414162
# Predict class labels on test data
lda_predictions <- predict(lda_model, test_data)

# Extract predicted class labels
pred_labels_lda <- lda_predictions$class
# Compute confusion matrix
conf_matrix_lda <- table(Predicted = pred_labels_lda, Actual = test_data$Direction)
print(conf_matrix_lda)
         Actual
Predicted Down Up
     Down    9  5
     Up     34 56
# Compute overall accuracy
accuracy_lda <- sum(diag(conf_matrix_lda)) / sum(conf_matrix_lda)
print(paste("LDA Test Accuracy:", round(accuracy_lda, 4)))
[1] "LDA Test Accuracy: 0.625"
  1. Repeat (d) using QDA.
# Training data: 1990-2008
train_data <- subset(Weekly, Year <= 2008)

# Test data: 2009-2010
test_data  <- subset(Weekly, Year > 2008)
# Fit QDA using Lag2 as the only predictor
qda_model <- qda(Direction ~ Lag2, data = train_data)

# Print model summary
qda_model
Call:
qda(Direction ~ Lag2, data = train_data)

Prior probabilities of groups:
     Down        Up 
0.4477157 0.5522843 

Group means:
            Lag2
Down -0.03568254
Up    0.26036581
# Predict class labels on test data
qda_predictions <- predict(qda_model, test_data)

# Extract predicted class labels
pred_labels_qda <- qda_predictions$class
# Compute confusion matrix
conf_matrix_qda <- table(Predicted = pred_labels_qda, Actual = test_data$Direction)
print(conf_matrix_qda)
         Actual
Predicted Down Up
     Down    0  0
     Up     43 61
# Compute overall accuracy
accuracy_qda <- sum(diag(conf_matrix_qda)) / sum(conf_matrix_qda)
print(paste("QDA Test Accuracy:", round(accuracy_qda, 4)))
[1] "QDA Test Accuracy: 0.5865"
  1. Repeat (d) using KNN with K = 1.
accuracy_knn <- sum(diag(conf_matrix_knn)) / sum(conf_matrix_knn)
print(paste("KNN Test Accuracy (K=1):", round(accuracy_knn, 4)))
[1] "KNN Test Accuracy (K=1): 0.5"
confusionMatrix(knn_pred, test_data$Direction, positive = "Up")
Confusion Matrix and Statistics

          Reference
Prediction Down Up
      Down   21 30
      Up     22 31
                                          
               Accuracy : 0.5             
                 95% CI : (0.4003, 0.5997)
    No Information Rate : 0.5865          
    P-Value [Acc > NIR] : 0.9700          
                                          
                  Kappa : -0.0033         
                                          
 Mcnemar's Test P-Value : 0.3317          
                                          
            Sensitivity : 0.5082          
            Specificity : 0.4884          
         Pos Pred Value : 0.5849          
         Neg Pred Value : 0.4118          
             Prevalence : 0.5865          
         Detection Rate : 0.2981          
   Detection Prevalence : 0.5096          
      Balanced Accuracy : 0.4983          
                                          
       'Positive' Class : Up              
                                          
  1. Repeat (d) using naive Bayes.
# Split data into training (1990-2008) and testing (2009-2010)
train_data <- subset(Weekly, Year <= 2008)
test_data  <- subset(Weekly, Year > 2008)

# Define predictor and response variables
train_X <- train_data[, "Lag2", drop = FALSE]  # Keep as data frame
test_X  <- test_data[, "Lag2", drop = FALSE]

train_Y <- train_data$Direction  # Response variable for training
test_Y  <- test_data$Direction   # Response variable for testing
# Fit Naïve Bayes model
nb_model <- naiveBayes(Direction ~ Lag2, data = train_data)

# Print model summary
print(nb_model)

Naive Bayes Classifier for Discrete Predictors

Call:
naiveBayes.default(x = X, y = Y, laplace = laplace)

A-priori probabilities:
Y
     Down        Up 
0.4477157 0.5522843 

Conditional probabilities:
      Lag2
Y             [,1]     [,2]
  Down -0.03568254 2.199504
  Up    0.26036581 2.317485
# Predict class labels on test data
nb_predictions <- predict(nb_model, test_X)
# Compute confusion matrix
conf_matrix_nb <- table(Predicted = nb_predictions, Actual = test_Y)
print(conf_matrix_nb)
         Actual
Predicted Down Up
     Down    0  0
     Up     43 61
# Compute overall accuracy
accuracy_nb <- sum(diag(conf_matrix_nb)) / sum(conf_matrix_nb)
print(paste("Naïve Bayes Test Accuracy:", round(accuracy_nb, 4)))
[1] "Naïve Bayes Test Accuracy: 0.5865"
  1. Which of these methods appears to provide the best results on this data?

Logistic Regression 62.5% LDA (Linear Discriminant Analysis) 62.5% QDA (Quadratic Discriminant Analysis) 52% KNN (K = 1) 50% Naïve Bayes 58%

this would suggest the Logistic Regression and Linear Discriminant Analysis gave the best results for this data set but none are that good.

  1. 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.
# Create a new model with additional predictors
train_data <- subset(Weekly, Year <= 2008)
test_data  <- subset(Weekly, Year > 2008)

# Create interactions
train_data$Lag1_Lag2 <- train_data$Lag1 * train_data$Lag2
test_data$Lag1_Lag2  <- test_data$Lag1 * test_data$Lag2

# Logistic regression with multiple predictors
logit_model_exp <- glm(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume + Lag1_Lag2, 
                       data = train_data, family = binomial)

# Make predictions on test data
logit_probs <- predict(logit_model_exp, test_data, type = "response")
logit_preds <- factor(ifelse(logit_probs > 0.5, "Up", "Down"), levels = levels(test_data$Direction))

# Compute confusion matrix
conf_matrix_logit <- table(Predicted = logit_preds, Actual = test_data$Direction)
print(conf_matrix_logit)
         Actual
Predicted Down Up
     Down   29 42
     Up     14 19
# Compute accuracy
accuracy_logit <- sum(diag(conf_matrix_logit)) / sum(conf_matrix_logit)
print(paste("Logistic Regression Accuracy:", round(accuracy_logit, 4)))
[1] "Logistic Regression Accuracy: 0.4615"
# LDA with more predictors
train_data$Direction <- as.factor(train_data$Direction)
 
lda_model_exp <- lda(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume,data = train_data)

# Make predictions
lda_preds <- predict(lda_model_exp, test_data)$class

# Compute confusion matrix
conf_matrix_lda <- table(Predicted = lda_preds, Actual = test_data$Direction)
print(conf_matrix_lda)
         Actual
Predicted Down Up
     Down   31 44
     Up     12 17
# Compute accuracy
accuracy_lda <- sum(diag(conf_matrix_lda)) / sum(conf_matrix_lda)
print(paste("LDA Accuracy:", round(accuracy_lda, 4)))
[1] "LDA Accuracy: 0.4615"
# Standardize predictors
train_X <- scale(train_data[, c("Lag1", "Lag2", "Lag3", "Lag4", "Lag5", "Volume")])
test_X  <- scale(test_data[, c("Lag1", "Lag2", "Lag3", "Lag4", "Lag5", "Volume")], 
                 center = attr(train_X, "scaled:center"), 
                 scale = attr(train_X, "scaled:scale"))

train_Y <- train_data$Direction
test_Y  <- test_data$Direction

# Try different values of K
for (k in c(1, 3, 5, 10, 20)) {
  knn_preds <- knn(train_X, test_X, train_Y, k = k)
  conf_matrix_knn <- table(Predicted = knn_preds, Actual = test_Y)
  accuracy_knn <- sum(diag(conf_matrix_knn)) / sum(conf_matrix_knn)
  print(paste("KNN Accuracy (K =", k, "):", round(accuracy_knn, 4)))
}
[1] "KNN Accuracy (K = 1 ): 0.4519"
[1] "KNN Accuracy (K = 3 ): 0.4904"
[1] "KNN Accuracy (K = 5 ): 0.5096"
[1] "KNN Accuracy (K = 10 ): 0.5481"
[1] "KNN Accuracy (K = 20 ): 0.5288"
# Train Naïve Bayes with more predictors
nb_model_exp <- naiveBayes(Direction ~ Lag1 + Lag2 + Lag3 + Lag4 + Lag5 + Volume, 
                           data = train_data)

# Make predictions
nb_preds <- predict(nb_model_exp, test_data)

# Compute confusion matrix
conf_matrix_nb <- table(Predicted = nb_preds, Actual = test_data$Direction)
print(conf_matrix_nb)
         Actual
Predicted Down Up
     Down   42 56
     Up      1  5
# Compute accuracy
accuracy_nb <- sum(diag(conf_matrix_nb)) / sum(conf_matrix_nb)
print(paste("Naïve Bayes Accuracy:", round(accuracy_nb, 4)))
[1] "Naïve Bayes Accuracy: 0.4519"
  1. 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 <- median(Auto$mpg)
Auto$mpg01 <- ifelse(Auto$mpg > mpg, 1, 0)
  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.

# Boxplots to compare numeric features across mpg01 groups
boxplot(Auto$horsepower ~ Auto$mpg01, col = c("red", "blue"),
        main = "Horsepower vs. MPG",
        xlab = "MPG Category (0 = Low, 1 = High)", ylab = "Horsepower")


boxplot(Auto$weight ~ Auto$mpg01, col = c("red", "blue"),
        main = "Weight vs. MPG",
        xlab = "MPG Category (0 = Low, 1 = High)", ylab = "Weight")


boxplot(Auto$acceleration ~ Auto$mpg01, col = c("red", "blue"),
        main = "Acceleration vs. MPG",
        xlab = "MPG Category (0 = Low, 1 = High)", ylab = "Acceleration")


boxplot(Auto$displacement ~ Auto$mpg01, col = c("red", "blue"),
        main = "Displacement vs. MPG",
        xlab = "MPG Category (0 = Low, 1 = High)", ylab = "Displacement")

NA
NA
# Scatterplot of weight vs. horsepower, colored by mpg01
plot(Auto$horsepower, Auto$weight, col = as.numeric(Auto$mpg01) + 2, 
     main = "Weight vs. Horsepower (Colored by MPG Category)",
     xlab = "Horsepower", ylab = "Weight", pch = 19)
legend("topright", legend = c("Low MPG", "High MPG"), col = c(2, 3), pch = 19)

Seperating MPG into a high and low became very useful because there is clear seperation between groups based on that variable. High MPG correlates to low weight, low acceleration, low displacement, and on the scatter plot you can see the linear relationship between weight and horsepower.

  1. Split the data into a training set and a test set.
set.seed(42)
# Create a partition (80% training, 20% testing)
train_idx <- createDataPartition(Auto$mpg01, p = 0.8, list = FALSE)

# Create training and test sets
train <- Auto[train_idx, ]
test  <- Auto[-train_idx, ]

# Check the dimensions of the split
dim(train)  # Training set size
[1] 314  10
dim(test)   # Test set size
[1] 78 10
  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_mpg <- lda(mpg01 ~ horsepower + weight + displacement, data=train)
summary(lda_mpg)
        Length Class  Mode     
prior   2      -none- numeric  
counts  2      -none- numeric  
means   6      -none- numeric  
scaling 3      -none- numeric  
lev     2      -none- character
svd     1      -none- numeric  
N       1      -none- numeric  
call    3      -none- call     
terms   3      terms  call     
xlevels 0      -none- list     
lda_preds <- predict(lda_mpg, test)$class

conf_matrix_lda <- table(Predicted = lda_preds, Actual = test$mpg01)
print(conf_matrix_lda)
         Actual
Predicted  0  1
        0 31  1
        1  8 38
confusionMatrix(lda_preds, test$mpg01, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 31  1
         1  8 38
                                          
               Accuracy : 0.8846          
                 95% CI : (0.7922, 0.9459)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 6.906e-13       
                                          
                  Kappa : 0.7692          
                                          
 Mcnemar's Test P-Value : 0.0455          
                                          
            Sensitivity : 0.9744          
            Specificity : 0.7949          
         Pos Pred Value : 0.8261          
         Neg Pred Value : 0.9688          
             Prevalence : 0.5000          
         Detection Rate : 0.4872          
   Detection Prevalence : 0.5897          
      Balanced Accuracy : 0.8846          
                                          
       'Positive' Class : 1               
                                          
test_error_lda <- 1 - sum(diag(conf_matrix_lda)) / sum(conf_matrix_lda)
print(paste("LDA Test Error Rate:", round(test_error_lda, 4)))
[1] "LDA Test Error Rate: 0.1154"

the error rate was 11.54%

  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_mpg <- qda(mpg01 ~ horsepower + weight + displacement, data = train)
qda_preds <- predict(qda_mpg, test)$class
conf_matrix_qda <- table(Predicted = qda_preds, Actual = test_data$mpg01)
confusionMatrix(qda_preds, test$mpg01, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 33  3
         1  6 36
                                          
               Accuracy : 0.8846          
                 95% CI : (0.7922, 0.9459)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 6.906e-13       
                                          
                  Kappa : 0.7692          
                                          
 Mcnemar's Test P-Value : 0.505           
                                          
            Sensitivity : 0.9231          
            Specificity : 0.8462          
         Pos Pred Value : 0.8571          
         Neg Pred Value : 0.9167          
             Prevalence : 0.5000          
         Detection Rate : 0.4615          
   Detection Prevalence : 0.5385          
      Balanced Accuracy : 0.8846          
                                          
       'Positive' Class : 1               
                                          
test_error_qda <- 1 - sum(diag(conf_matrix_qda)) / sum(conf_matrix_qda)
print(paste("QDA Test Error Rate:", round(test_error_qda, 4)))
[1] "QDA Test Error Rate: 0.1154"

it’s notable that the error rate for the LDA and QDA are the same and I suspect it has something to do with the nature of the tests

  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?
logit_mpg <- glm(mpg01 ~ horsepower + weight + displacement, 
                 data = train, family = binomial)
logit_probs <- predict(logit_mpg, test, type = "response")
logit_preds <- factor(ifelse(logit_probs > 0.5, 1, 0), levels = c(0, 1))

conf_matrix_logit <- table(Predicted = logit_preds, Actual = test$mpg01)
confusionMatrix(logit_preds, test$mpg01, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 32  2
         1  7 37
                                          
               Accuracy : 0.8846          
                 95% CI : (0.7922, 0.9459)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 6.906e-13       
                                          
                  Kappa : 0.7692          
                                          
 Mcnemar's Test P-Value : 0.1824          
                                          
            Sensitivity : 0.9487          
            Specificity : 0.8205          
         Pos Pred Value : 0.8409          
         Neg Pred Value : 0.9412          
             Prevalence : 0.5000          
         Detection Rate : 0.4744          
   Detection Prevalence : 0.5641          
      Balanced Accuracy : 0.8846          
                                          
       'Positive' Class : 1               
                                          
test_error_logit <- 1 - sum(diag(conf_matrix_logit)) / sum(conf_matrix_logit)
print(paste("Logistic Regression Test Error Rate:", round(test_error_logit, 4)))
[1] "Logistic Regression Test Error Rate: 0.1154"

and the error rate is the same on this test, either I’m doing something terribly wrong or this is how this data effects all the tests,

  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_mpg <- naiveBayes(mpg01 ~ horsepower + weight + displacement, data = train)
nb_preds <- predict(nb_mpg, test)
confusionMatrix(nb_preds, test$mpg01, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 31  1
         1  8 38
                                          
               Accuracy : 0.8846          
                 95% CI : (0.7922, 0.9459)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 6.906e-13       
                                          
                  Kappa : 0.7692          
                                          
 Mcnemar's Test P-Value : 0.0455          
                                          
            Sensitivity : 0.9744          
            Specificity : 0.7949          
         Pos Pred Value : 0.8261          
         Neg Pred Value : 0.9688          
             Prevalence : 0.5000          
         Detection Rate : 0.4872          
   Detection Prevalence : 0.5897          
      Balanced Accuracy : 0.8846          
                                          
       'Positive' Class : 1               
                                          
conf_matrix_nb <- table(Predicted = nb_preds, Actual = test$mpg01)
test_error_nb <- 1 - sum(diag(conf_matrix_nb)) / sum(conf_matrix_nb)
print(paste("Naïve Bayes Test Error Rate:", round(test_error_nb, 4)))
[1] "Naïve Bayes Test Error Rate: 0.1154"

the error rate coming out of this test is…the same as the other tests and now I’m officailly freaked out

  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?
train_X <- scale(train_data[, c("horsepower", "weight", "displacement")])
test_X  <- scale(test_data[, c("horsepower", "weight", "displacement")], 
                 center = attr(train_X, "scaled:center"), 
                 scale = attr(train_X, "scaled:scale"))

train_Y <- train_data$mpg01
test_Y  <- test_data$mpg01
k_values <- c(1, 3, 5, 10, 15, 20)
test_errors <- numeric(length(k_values))
for (i in seq_along(k_values)) {
  set.seed(123)  # Ensure reproducibility
  knn_preds <- knn(train_X, test_X, train_Y, k = k_values[i])
  
  conf_matrix_knn <- table(Predicted = knn_preds, Actual = test_Y)
  test_errors[i] <- 1 - sum(diag(conf_matrix_knn)) / sum(conf_matrix_knn)
  print(paste("K =", k_values[i], "Test Error Rate:", round(test_errors[i], 4)))
} 
[1] "K = 1 Test Error Rate: 0.1026"
[1] "K = 3 Test Error Rate: 0.0897"
[1] "K = 5 Test Error Rate: 0.0641"
[1] "K = 10 Test Error Rate: 0.1026"
[1] "K = 15 Test Error Rate: 0.1026"
[1] "K = 20 Test Error Rate: 0.1154"
  

k = 5 was the most accurate and K = 20 gives the equal error rate to the other tests.

  1. 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.
# Compute the median crime rate
crime_median <- median(Boston$crim)

# Create the binary response variable
Boston$HighCrime <- ifelse(Boston$crim > crime_median, "High", "Low")

# Convert it to a factor for classification
Boston$HighCrime <- factor(Boston$HighCrime, levels = c("Low", "High"))

# Check distribution
table(Boston$HighCrime)

 Low High 
 253  253 
set.seed(123)  # Ensure reproducibility
train_idx <- createDataPartition(Boston$HighCrime, p = 0.7, list = FALSE)
train_data <- Boston[train_idx, ]
test_data  <- Boston[-train_idx, ]

# Fit logistic regression model using selected predictors
logit_model <- glm(HighCrime ~ lstat + rm + dis + tax + ptratio, 
                   data = train_data, family = binomial)

# Make predictions on test data
logit_probs <- predict(logit_model, test_data, type = "response")

# Convert probabilities to class labels
logit_preds <- factor(ifelse(logit_probs > 0.5, "High", "Low"), levels = c("Low", "High"))

# Compute confusion matrix
conf_matrix_logit <- confusionMatrix(logit_preds, test_data$HighCrime)
print(conf_matrix_logit)
Confusion Matrix and Statistics

          Reference
Prediction Low High
      Low   68   13
      High   7   62
                                          
               Accuracy : 0.8667          
                 95% CI : (0.8016, 0.9166)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.7333          
                                          
 Mcnemar's Test P-Value : 0.2636          
                                          
            Sensitivity : 0.9067          
            Specificity : 0.8267          
         Pos Pred Value : 0.8395          
         Neg Pred Value : 0.8986          
             Prevalence : 0.5000          
         Detection Rate : 0.4533          
   Detection Prevalence : 0.5400          
      Balanced Accuracy : 0.8667          
                                          
       'Positive' Class : Low             
                                          
# Load MASS package (if not already loaded)
library(MASS)

# Fit LDA model
lda_model <- lda(HighCrime ~ lstat + rm + dis + tax + ptratio, data = train_data)

# Make predictions
lda_preds <- predict(lda_model, test_data)$class

# Compute confusion matrix
conf_matrix_lda <- confusionMatrix(lda_preds, test_data$HighCrime)
print(conf_matrix_lda)
Confusion Matrix and Statistics

          Reference
Prediction Low High
      Low   68   16
      High   7   59
                                          
               Accuracy : 0.8467          
                 95% CI : (0.7789, 0.9002)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.6933          
                                          
 Mcnemar's Test P-Value : 0.09529         
                                          
            Sensitivity : 0.9067          
            Specificity : 0.7867          
         Pos Pred Value : 0.8095          
         Neg Pred Value : 0.8939          
             Prevalence : 0.5000          
         Detection Rate : 0.4533          
   Detection Prevalence : 0.5600          
      Balanced Accuracy : 0.8467          
                                          
       'Positive' Class : Low             
                                          
# Fit Naïve Bayes model
nb_model <- naiveBayes(HighCrime ~ lstat + rm + dis + tax + ptratio, data = train_data)

# Make predictions
nb_preds <- predict(nb_model, test_data)

# Compute confusion matrix
conf_matrix_nb <- confusionMatrix(nb_preds, test_data$HighCrime)
print(conf_matrix_nb)
Confusion Matrix and Statistics

          Reference
Prediction Low High
      Low   67   16
      High   8   59
                                          
               Accuracy : 0.84            
                 95% CI : (0.7714, 0.8947)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.68            
                                          
 Mcnemar's Test P-Value : 0.153           
                                          
            Sensitivity : 0.8933          
            Specificity : 0.7867          
         Pos Pred Value : 0.8072          
         Neg Pred Value : 0.8806          
             Prevalence : 0.5000          
         Detection Rate : 0.4467          
   Detection Prevalence : 0.5533          
      Balanced Accuracy : 0.8400          
                                          
       'Positive' Class : Low             
                                          
# Extract predictors and response
train_X <- scale(train_data[, c("lstat", "rm", "dis", "tax", "ptratio")])
test_X  <- scale(test_data[, c("lstat", "rm", "dis", "tax", "ptratio")])
train_Y <- train_data$HighCrime
test_Y  <- test_data$HighCrime

# Try different values of K
for (k in c(1, 3, 5, 10, 15)) {
  knn_preds <- knn(train_X, test_X, train_Y, k = k)
  conf_matrix_knn <- confusionMatrix(knn_preds, test_Y)
  print(paste("KNN Accuracy (K =", k, "):", round(conf_matrix_knn$overall["Accuracy"], 4)))
}
[1] "KNN Accuracy (K = 1 ): 0.9267"
[1] "KNN Accuracy (K = 3 ): 0.9267"
[1] "KNN Accuracy (K = 5 ): 0.9267"
[1] "KNN Accuracy (K = 10 ): 0.9133"
[1] "KNN Accuracy (K = 15 ): 0.9267"

__Model Accuracy Logistic Regression ~86% LDA ~84% Naïve Bayes ~84% KNN (Best K=10) ~91%

the KNN (with K=10) was the most accurate, however, all were highly accurate and all four were close. I think this also shows the difference in data usability from the other two data sets as this was by far the most accurate during the variety of tests __

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoxMy4gVGhpcyBxdWVzdGlvbiBzaG91bGQgYmUgYW5zd2VyZWQgdXNpbmcgdGhlIFdlZWtseSBkYXRhIHNldCwgd2hpY2ggaXMgcGFydCBvZiB0aGUgSVNMUjIgcGFja2FnZS4gVGhpcyBkYXRhIGlzIHNpbWlsYXIgaW4gbmF0dXJlIHRvIHRoZSBTbWFya2V0IGRhdGEgZnJvbSB0aGlzIGNoYXB0ZXLigJlzIGxhYiwgZXhjZXB0IHRoYXQgaXQgY29udGFpbnMgMSwgMDg5IHdlZWtseSByZXR1cm5zIGZvciAyMSB5ZWFycywgZnJvbSB0aGUgYmVnaW5uaW5nIG9mIDE5OTAgdG8gdGhlIGVuZCBvZiAyMDEwLg0KDQpgYGB7cn0NCmxpYnJhcnkoSVNMUjIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShNQVNTKQ0KbGlicmFyeShjbGFzcykNCmxpYnJhcnkoZTEwNzEpDQpkYXRhKCJXZWVrbHkiKQ0KYGBgDQoNCg0KKGEpIFByb2R1Y2Ugc29tZSBudW1lcmljYWwgYW5kIGdyYXBoaWNhbCBzdW1tYXJpZXMgb2YgdGhlIFdlZWtseQ0KZGF0YS4gRG8gdGhlcmUgYXBwZWFyIHRvIGJlIGFueSBwYXR0ZXJucz8NCmBgYHtyfQ0KcGFpcnMoV2Vla2x5WywgLTldLCBtYWluID0gIlNjYXR0ZXJwbG90IE1hdHJpeCBvZiBXZWVrbHkgRGF0YSAoTnVtZXJpYyBWYXJpYWJsZXMpIiwgY29sID0gImJsdWUiKQ0KDQoNCmBgYA0KDQoqKkkgZG9uJ3Qgc2VlIGFueSBwYXR0ZXJucyBpbiB0aGUgcGFpcnMgc2NhdHRlcnBsb3QgbWF0cml4IGJ1dCBJIHdpbGwgdHJ5IHNvbWUgb3RoZXIgZ3JhcGhzIHRvIGtlZXAgY2hlY2tpbmcqKg0KDQoNCmBgYHtyfQ0KaGlzdChXZWVrbHkkVG9kYXksIGJyZWFrcyA9IDMwLCBjb2wgPSAibGlnaHRibHVlIiwgbWFpbiA9ICJIaXN0b2dyYW0gb2YgV2Vla2x5IFJldHVybnMiLA0KICAgICB4bGFiID0gIldlZWtseSBSZXR1cm4iLCBib3JkZXIgPSAid2hpdGUiKQ0KDQpib3hwbG90KFdlZWtseVssMjo2XSwgbWFpbiA9ICJCb3hwbG90cyBvZiBMYWdnZWQgV2Vla2x5IFJldHVybnMiLA0KICAgICAgICBjb2wgPSBjKCJsaWdodGJsdWUiLCAibGlnaHRncmVlbiIsICJwaW5rIiwgInllbGxvdyIsICJvcmFuZ2UiKSkNCg0KcGxvdChXZWVrbHkkVm9sdW1lLCB0eXBlID0gImwiLCBjb2wgPSAiYmx1ZSIsDQogICAgIG1haW4gPSAiVGltZSBTZXJpZXMgb2YgVHJhZGluZyBWb2x1bWUiLA0KICAgICB5bGFiID0gIlZvbHVtZSIsIHhsYWIgPSAiVGltZSBJbmRleCIpDQoNCnBsb3QoV2Vla2x5JExhZzEsIFdlZWtseSRUb2RheSwgDQogICAgIG1haW4gPSAiU2NhdHRlcnBsb3Qgb2YgVG9kYXnigJlzIFJldHVybiB2cy4gTGFnMSIsDQogICAgIHhsYWIgPSAiTGFnMSIsIHlsYWIgPSAiVG9kYXkiLCBjb2wgPSAicmVkIiwgcGNoID0gMTkpDQphYmxpbmUobG0oVG9kYXkgfiBMYWcxLCBkYXRhID0gV2Vla2x5KSwgY29sID0gImJsdWUiLCBsd2QgPSAyKQ0KDQp0YWJsZShXZWVrbHkkRGlyZWN0aW9uKQ0KYmFycGxvdCh0YWJsZShXZWVrbHkkRGlyZWN0aW9uKSwgY29sID0gYygicmVkIiwgImdyZWVuIiksDQogICAgICAgIG1haW4gPSAiTWFya2V0IERpcmVjdGlvbiBGcmVxdWVuY3kiLA0KICAgICAgICB5bGFiID0gIkNvdW50IikNCg0KDQoNCmBgYA0KDQpfX0kgbm90aWNlIG9uIHRoZSBoaXN0b2dyYW0gaXQgbG9va3MgdG8gYmUgYSBub3JtYWwgZGlzdHJpYnV0aW9uLCBvbiB0aGUgYm94IHBsb3RzIHRoZXkgbG9vayBldmVuLCBvbiB0aGUgZnJlcXVlbmN5IHBsb3QgdGhlcmUgaXMgbW9yZSB1cCB0aGFuIGRvd24gd2hpY2ggaXMgZ29vZCBmb3IgdGhlIEFtZXJpY2FuIEludmVzdGluZyBQdWJsaWNfXw0KDQooYikgVXNlIHRoZSBmdWxsIGRhdGEgc2V0IHRvIHBlcmZvcm0gYSBsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGgNCkRpcmVjdGlvbiBhcyB0aGUgcmVzcG9uc2UgYW5kIHRoZSBmaXZlIGxhZyB2YXJpYWJsZXMgcGx1cyBWb2x1bWUNCmFzIHByZWRpY3RvcnMuIFVzZSB0aGUgc3VtbWFyeSBmdW5jdGlvbiB0byBwcmludCB0aGUgcmVzdWx0cy4gRG8NCmFueSBvZiB0aGUgcHJlZGljdG9ycyBhcHBlYXIgdG8gYmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudD8gSWYgc28sDQp3aGljaCBvbmVzPw0KYGBge3J9DQojIENvbnZlcnQgRGlyZWN0aW9uIHRvIGEgYmluYXJ5IGZhY3RvciAoVXAgPSAxLCBEb3duID0gMCkNCldlZWtseSREaXJlY3Rpb24gPC0gYXMuZmFjdG9yKFdlZWtseSREaXJlY3Rpb24pDQoNCiMgRml0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQpsb2dpdF9tb2RlbCA8LSBnbG0oRGlyZWN0aW9uIH4gTGFnMSArIExhZzIgKyBMYWczICsgTGFnNCArIExhZzUgKyBWb2x1bWUsIA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBXZWVrbHksIGZhbWlseSA9IGJpbm9taWFsKQ0KDQojIERpc3BsYXkgbW9kZWwgc3VtbWFyeQ0Kc3VtbWFyeShsb2dpdF9tb2RlbCkNCmBgYA0KDQpfX0xhZyAyLCBmb3Igc29tZSByZWFzb24gc2VlbXMgc2lnbmlmaWNhbnQgYnV0IG5vbmUgb2YgdGhlIHJlc3QgYXJlLiAgT25lIHRoaW5nIEkgZGlkIG5vdGljZSBpcyB0aGF0IGxhZyAyIHdoaWNoIGlzIHNpZ25pZmljYW50IGhhcyBhIHBvc2l0aXZlIGVzdGltYXRlLCBhbmQgdGhlIG1vZGVsIG92ZXIgcHJlZGljdHMgdXAsIGFuZCBhbGwgdGhlIG90aGVyIHZhcmlhYmxlcyBhcmUgbmVnaXRpdmUgYW5kIGFyZSBhbHNvIG5vdCBzaWduaWZpY2FudC4gX18gDQoNCg0KKGMpIENvbXB1dGUgdGhlIGNvbmZ1c2lvbiBtYXRyaXggYW5kIG92ZXJhbGwgZnJhY3Rpb24gb2YgY29ycmVjdA0KcHJlZGljdGlvbnMuIEV4cGxhaW4gd2hhdCB0aGUgY29uZnVzaW9uIG1hdHJpeCBpcyB0ZWxsaW5nIHlvdQ0KYWJvdXQgdGhlIHR5cGVzIG9mIG1pc3Rha2VzIG1hZGUgYnkgbG9naXN0aWMgcmVncmVzc2lvbi4NCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMNCnByZWRfcHJvYnMgPC0gcHJlZGljdChsb2dpdF9tb2RlbCwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgQ29udmVydCBwcm9iYWJpbGl0aWVzIHRvIGNsYXNzIGxhYmVscyAodGhyZXNob2xkID0gMC41KQ0KcHJlZF9sYWJlbHMgPC0gaWZlbHNlKHByZWRfcHJvYnMgPiAwLjUsICJVcCIsICJEb3duIikNCg0KIyBDb252ZXJ0IHRvIGZhY3RvciB0byBtYXRjaCBhY3R1YWwgRGlyZWN0aW9uIHZhcmlhYmxlDQpwcmVkX2xhYmVscyA8LSBmYWN0b3IocHJlZF9sYWJlbHMsIGxldmVscyA9IGxldmVscyhXZWVrbHkkRGlyZWN0aW9uKSkNCg0KIyBDcmVhdGUgY29uZnVzaW9uIG1hdHJpeA0KY29uZl9tYXRyaXggPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9sYWJlbHMsIEFjdHVhbCA9IFdlZWtseSREaXJlY3Rpb24pDQpwcmludChjb25mX21hdHJpeCkNCmBgYA0KDQpgYGB7cn0NCiMgQ29tcHV0ZSBvdmVyYWxsIGFjY3VyYWN5DQphY2N1cmFjeSA8LSBzdW0oZGlhZyhjb25mX21hdHJpeCkpIC8gc3VtKGNvbmZfbWF0cml4KQ0KcHJpbnQocGFzdGUoIkFjY3VyYWN5OiIsIHJvdW5kKGFjY3VyYWN5LCA0KSkpDQoNCmNvbmZ1c2lvbk1hdHJpeChwcmVkX2xhYmVscywgV2Vla2x5JERpcmVjdGlvbiwgcG9zaXRpdmUgPSAiVXAiKQ0KDQpgYGANCl9fVGhlIG1vZGVsIHByZWRpY3RzICJVcCIgbXVjaCBtb3JlIG9mdGVuIHRoYW4gIkRvd24iLCBsZWFkaW5nIHRvIG1hbnkgZmFsc2UgbmVnYXRpdmVzICg0MzAgY2FzZXMpLg0KSXQgc3RydWdnbGVzIHRvIGNvcnJlY3RseSBwcmVkaWN0ICJEb3duIiBtb3ZlbWVudHMuDQpUaGlzIHN1Z2dlc3RzIHRoYXQgbG9naXN0aWMgcmVncmVzc2lvbiBkb2VzIG5vdCBwZXJmb3JtIHdlbGwgaW4gcHJlZGljdGluZyBtYXJrZXQgZGVjbGluZXNfXw0KDQoNCihkKSBOb3cgZml0IHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIGEgdHJhaW5pbmcgZGF0YSBwZXJpb2QNCmZyb20gMTk5MCB0byAyMDA4LCB3aXRoIExhZzIgYXMgdGhlIG9ubHkgcHJlZGljdG9yLiBDb21wdXRlIHRoZQ0KY29uZnVzaW9uIG1hdHJpeCBhbmQgdGhlIG92ZXJhbGwgZnJhY3Rpb24gb2YgY29ycmVjdCBwcmVkaWN0aW9ucw0KZm9yIHRoZSBoZWxkIG91dCBkYXRhICh0aGF0IGlzLCB0aGUgZGF0YSBmcm9tIDIwMDkgYW5kIDIwMTApLg0KDQpgYGB7cn0NCiMgU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nICgxOTkwLTIwMDgpIGFuZCB0ZXN0aW5nICgyMDA5LTIwMTApDQp0cmFpbl9kYXRhIDwtIHN1YnNldChXZWVrbHksIFllYXIgPD0gMjAwOCkNCnRlc3RfZGF0YSAgPC0gc3Vic2V0KFdlZWtseSwgWWVhciA+IDIwMDgpDQojIEZpdCBsb2dpc3RpYyByZWdyZXNzaW9uIHVzaW5nIExhZzIgYXMgdGhlIG9ubHkgcHJlZGljdG9yDQpsb2dpdF9tb2RlbF9sYWcyIDwtIGdsbShEaXJlY3Rpb24gfiBMYWcyLCBkYXRhID0gdHJhaW5fZGF0YSwgZmFtaWx5ID0gYmlub21pYWwpDQoNCiMgUHJpbnQgc3VtbWFyeSBvZiB0aGUgbW9kZWwNCnN1bW1hcnkobG9naXRfbW9kZWxfbGFnMikNCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciB0ZXN0IGRhdGENCnByZWRfcHJvYnNfdGVzdCA8LSBwcmVkaWN0KGxvZ2l0X21vZGVsX2xhZzIsIHRlc3RfZGF0YSwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgQ29udmVydCBwcm9iYWJpbGl0aWVzIHRvIGNsYXNzIGxhYmVscyAodGhyZXNob2xkID0gMC41KQ0KcHJlZF9sYWJlbHNfdGVzdCA8LSBpZmVsc2UocHJlZF9wcm9ic190ZXN0ID4gMC41LCAiVXAiLCAiRG93biIpDQoNCiMgQ29udmVydCB0byBmYWN0b3IgdG8gbWF0Y2ggYWN0dWFsIERpcmVjdGlvbiB2YXJpYWJsZQ0KcHJlZF9sYWJlbHNfdGVzdCA8LSBmYWN0b3IocHJlZF9sYWJlbHNfdGVzdCwgbGV2ZWxzID0gbGV2ZWxzKFdlZWtseSREaXJlY3Rpb24pKQ0KIyBDb21wdXRlIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZfbWF0cml4X3Rlc3QgPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9sYWJlbHNfdGVzdCwgQWN0dWFsID0gdGVzdF9kYXRhJERpcmVjdGlvbikNCnByaW50KGNvbmZfbWF0cml4X3Rlc3QpDQojIENvbXB1dGUgb3ZlcmFsbCBhY2N1cmFjeQ0KYWNjdXJhY3lfdGVzdCA8LSBzdW0oZGlhZyhjb25mX21hdHJpeF90ZXN0KSkgLyBzdW0oY29uZl9tYXRyaXhfdGVzdCkNCnByaW50KHBhc3RlKCJUZXN0IEFjY3VyYWN5OiIsIHJvdW5kKGFjY3VyYWN5X3Rlc3QsIDQpKSkNCg0KYGBgDQoNCg0KDQooZSkgUmVwZWF0IChkKSB1c2luZyBMREEuDQpgYGB7cn0NCiMgVHJhaW5pbmcgZGF0YTogMTk5MC0yMDA4DQp0cmFpbl9kYXRhIDwtIHN1YnNldChXZWVrbHksIFllYXIgPD0gMjAwOCkNCg0KIyBUZXN0IGRhdGE6IDIwMDktMjAxMA0KdGVzdF9kYXRhICA8LSBzdWJzZXQoV2Vla2x5LCBZZWFyID4gMjAwOCkNCiMgRml0IExEQSB1c2luZyBMYWcyIGFzIHRoZSBvbmx5IHByZWRpY3Rvcg0KbGRhX21vZGVsIDwtIGxkYShEaXJlY3Rpb24gfiBMYWcyLCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KIyBQcmludCBtb2RlbCBzdW1tYXJ5DQpsZGFfbW9kZWwNCiMgUHJlZGljdCBjbGFzcyBsYWJlbHMgb24gdGVzdCBkYXRhDQpsZGFfcHJlZGljdGlvbnMgPC0gcHJlZGljdChsZGFfbW9kZWwsIHRlc3RfZGF0YSkNCg0KIyBFeHRyYWN0IHByZWRpY3RlZCBjbGFzcyBsYWJlbHMNCnByZWRfbGFiZWxzX2xkYSA8LSBsZGFfcHJlZGljdGlvbnMkY2xhc3MNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeF9sZGEgPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9sYWJlbHNfbGRhLCBBY3R1YWwgPSB0ZXN0X2RhdGEkRGlyZWN0aW9uKQ0KcHJpbnQoY29uZl9tYXRyaXhfbGRhKQ0KIyBDb21wdXRlIG92ZXJhbGwgYWNjdXJhY3kNCmFjY3VyYWN5X2xkYSA8LSBzdW0oZGlhZyhjb25mX21hdHJpeF9sZGEpKSAvIHN1bShjb25mX21hdHJpeF9sZGEpDQpwcmludChwYXN0ZSgiTERBIFRlc3QgQWNjdXJhY3k6Iiwgcm91bmQoYWNjdXJhY3lfbGRhLCA0KSkpDQoNCmBgYA0KDQooZikgUmVwZWF0IChkKSB1c2luZyBRREEuDQpgYGB7cn0NCiMgVHJhaW5pbmcgZGF0YTogMTk5MC0yMDA4DQp0cmFpbl9kYXRhIDwtIHN1YnNldChXZWVrbHksIFllYXIgPD0gMjAwOCkNCg0KIyBUZXN0IGRhdGE6IDIwMDktMjAxMA0KdGVzdF9kYXRhICA8LSBzdWJzZXQoV2Vla2x5LCBZZWFyID4gMjAwOCkNCiMgRml0IFFEQSB1c2luZyBMYWcyIGFzIHRoZSBvbmx5IHByZWRpY3Rvcg0KcWRhX21vZGVsIDwtIHFkYShEaXJlY3Rpb24gfiBMYWcyLCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KIyBQcmludCBtb2RlbCBzdW1tYXJ5DQpxZGFfbW9kZWwNCiMgUHJlZGljdCBjbGFzcyBsYWJlbHMgb24gdGVzdCBkYXRhDQpxZGFfcHJlZGljdGlvbnMgPC0gcHJlZGljdChxZGFfbW9kZWwsIHRlc3RfZGF0YSkNCg0KIyBFeHRyYWN0IHByZWRpY3RlZCBjbGFzcyBsYWJlbHMNCnByZWRfbGFiZWxzX3FkYSA8LSBxZGFfcHJlZGljdGlvbnMkY2xhc3MNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeF9xZGEgPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZF9sYWJlbHNfcWRhLCBBY3R1YWwgPSB0ZXN0X2RhdGEkRGlyZWN0aW9uKQ0KcHJpbnQoY29uZl9tYXRyaXhfcWRhKQ0KIyBDb21wdXRlIG92ZXJhbGwgYWNjdXJhY3kNCmFjY3VyYWN5X3FkYSA8LSBzdW0oZGlhZyhjb25mX21hdHJpeF9xZGEpKSAvIHN1bShjb25mX21hdHJpeF9xZGEpDQpwcmludChwYXN0ZSgiUURBIFRlc3QgQWNjdXJhY3k6Iiwgcm91bmQoYWNjdXJhY3lfcWRhLCA0KSkpDQoNCmBgYA0KDQoNCihnKSBSZXBlYXQgKGQpIHVzaW5nIEtOTiB3aXRoIEsgPSAxLg0KYGBge3J9DQojIFNwbGl0IGRhdGEgaW50byB0cmFpbmluZyAoMTk5MC0yMDA4KSBhbmQgdGVzdGluZyAoMjAwOS0yMDEwKQ0KdHJhaW5fZGF0YSA8LSBzdWJzZXQoV2Vla2x5LCBZZWFyIDw9IDIwMDgpDQp0ZXN0X2RhdGEgIDwtIHN1YnNldChXZWVrbHksIFllYXIgPiAyMDA4KQ0KDQojIERlZmluZSBwcmVkaWN0b3IgYW5kIHJlc3BvbnNlIHZhcmlhYmxlcw0KdHJhaW5fWCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YSRMYWcyKSAgIyBQcmVkaWN0b3IgZm9yIHRyYWluaW5nDQp0ZXN0X1ggIDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGEkTGFnMikgICAjIFByZWRpY3RvciBmb3IgdGVzdGluZw0KDQp0cmFpbl9ZIDwtIHRyYWluX2RhdGEkRGlyZWN0aW9uICAjIFJlc3BvbnNlIHZhcmlhYmxlIGZvciB0cmFpbmluZw0KdGVzdF9ZICA8LSB0ZXN0X2RhdGEkRGlyZWN0aW9uICAgIyBSZXNwb25zZSB2YXJpYWJsZSBmb3IgdGVzdGluZw0KIyBTdGFuZGFyZGl6aW5nIChzY2FsaW5nIHRvIG1lYW4gMCwgdmFyaWFuY2UgMSkgaW1wcm92ZXMgS05OIHBlcmZvcm1hbmNlDQp0cmFpbl9YX3NjYWxlZCA8LSBzY2FsZSh0cmFpbl9YKQ0KdGVzdF9YX3NjYWxlZCAgPC0gc2NhbGUodGVzdF9YLCBjZW50ZXIgPSBhdHRyKHRyYWluX1hfc2NhbGVkLCAic2NhbGVkOmNlbnRlciIpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlID0gYXR0cih0cmFpbl9YX3NjYWxlZCwgInNjYWxlZDpzY2FsZSIpKQ0KIyBGaXQgS05OIG1vZGVsIHdpdGggSyA9IDEgYW5kIG1ha2UgcHJlZGljdGlvbnMNCnNldC5zZWVkKDEpICAjIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCmtubl9wcmVkIDwtIGtubih0cmFpbl9YX3NjYWxlZCwgdGVzdF9YX3NjYWxlZCwgdHJhaW5fWSwgayA9IDEpDQojIENvbXB1dGUgY29uZnVzaW9uIG1hdHJpeA0KY29uZl9tYXRyaXhfa25uIDwtIHRhYmxlKFByZWRpY3RlZCA9IGtubl9wcmVkLCBBY3R1YWwgPSB0ZXN0X1kpDQpwcmludChjb25mX21hdHJpeF9rbm4pDQojIENvbXB1dGUgb3ZlcmFsbCBhY2N1cmFjeQ0KYWNjdXJhY3lfa25uIDwtIHN1bShkaWFnKGNvbmZfbWF0cml4X2tubikpIC8gc3VtKGNvbmZfbWF0cml4X2tubikNCnByaW50KHBhc3RlKCJLTk4gVGVzdCBBY2N1cmFjeSAoSz0xKToiLCByb3VuZChhY2N1cmFjeV9rbm4sIDQpKSkNCmNvbmZ1c2lvbk1hdHJpeChrbm5fcHJlZCwgdGVzdF9kYXRhJERpcmVjdGlvbiwgcG9zaXRpdmUgPSAiVXAiKQ0KYGBgDQoNCg0KKGgpIFJlcGVhdCAoZCkgdXNpbmcgbmFpdmUgQmF5ZXMuDQpgYGB7cn0NCiMgU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nICgxOTkwLTIwMDgpIGFuZCB0ZXN0aW5nICgyMDA5LTIwMTApDQp0cmFpbl9kYXRhIDwtIHN1YnNldChXZWVrbHksIFllYXIgPD0gMjAwOCkNCnRlc3RfZGF0YSAgPC0gc3Vic2V0KFdlZWtseSwgWWVhciA+IDIwMDgpDQoNCiMgRGVmaW5lIHByZWRpY3RvciBhbmQgcmVzcG9uc2UgdmFyaWFibGVzDQp0cmFpbl9YIDwtIHRyYWluX2RhdGFbLCAiTGFnMiIsIGRyb3AgPSBGQUxTRV0gICMgS2VlcCBhcyBkYXRhIGZyYW1lDQp0ZXN0X1ggIDwtIHRlc3RfZGF0YVssICJMYWcyIiwgZHJvcCA9IEZBTFNFXQ0KDQp0cmFpbl9ZIDwtIHRyYWluX2RhdGEkRGlyZWN0aW9uICAjIFJlc3BvbnNlIHZhcmlhYmxlIGZvciB0cmFpbmluZw0KdGVzdF9ZICA8LSB0ZXN0X2RhdGEkRGlyZWN0aW9uICAgIyBSZXNwb25zZSB2YXJpYWJsZSBmb3IgdGVzdGluZw0KIyBGaXQgTmHDr3ZlIEJheWVzIG1vZGVsDQpuYl9tb2RlbCA8LSBuYWl2ZUJheWVzKERpcmVjdGlvbiB+IExhZzIsIGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQojIFByaW50IG1vZGVsIHN1bW1hcnkNCnByaW50KG5iX21vZGVsKQ0KIyBQcmVkaWN0IGNsYXNzIGxhYmVscyBvbiB0ZXN0IGRhdGENCm5iX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QobmJfbW9kZWwsIHRlc3RfWCkNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeF9uYiA8LSB0YWJsZShQcmVkaWN0ZWQgPSBuYl9wcmVkaWN0aW9ucywgQWN0dWFsID0gdGVzdF9ZKQ0KcHJpbnQoY29uZl9tYXRyaXhfbmIpDQojIENvbXB1dGUgb3ZlcmFsbCBhY2N1cmFjeQ0KYWNjdXJhY3lfbmIgPC0gc3VtKGRpYWcoY29uZl9tYXRyaXhfbmIpKSAvIHN1bShjb25mX21hdHJpeF9uYikNCnByaW50KHBhc3RlKCJOYcOvdmUgQmF5ZXMgVGVzdCBBY2N1cmFjeToiLCByb3VuZChhY2N1cmFjeV9uYiwgNCkpKQ0KDQpgYGANCg0KDQooaSkgV2hpY2ggb2YgdGhlc2UgbWV0aG9kcyBhcHBlYXJzIHRvIHByb3ZpZGUgdGhlIGJlc3QgcmVzdWx0cyBvbg0KdGhpcyBkYXRhPw0KDQpMb2dpc3RpYyBSZWdyZXNzaW9uCTYyLjUlDQpMREEgKExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMpCTYyLjUlDQpRREEgKFF1YWRyYXRpYyBEaXNjcmltaW5hbnQgQW5hbHlzaXMpCTUyJQ0KS05OIChLID0gMSkJNTAlDQpOYcOvdmUgQmF5ZXMJNTglDQoNCl9fdGhpcyB3b3VsZCBzdWdnZXN0IHRoZSBMb2dpc3RpYyBSZWdyZXNzaW9uIGFuZCBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzIGdhdmUgdGhlIGJlc3QgcmVzdWx0cyBmb3IgdGhpcyBkYXRhIHNldCBidXQgbm9uZSBhcmUgdGhhdCBnb29kLiAgX18NCg0KDQooaikgRXhwZXJpbWVudCB3aXRoIGRpZmZlcmVudCBjb21iaW5hdGlvbnMgb2YgcHJlZGljdG9ycywgaW5jbHVkaW5nDQpwb3NzaWJsZSB0cmFuc2Zvcm1hdGlvbnMgYW5kIGludGVyYWN0aW9ucywgZm9yIGVhY2ggb2YgdGhlDQptZXRob2RzLiBSZXBvcnQgdGhlIHZhcmlhYmxlcywgbWV0aG9kLCBhbmQgYXNzb2NpYXRlZCBjb25mdXNpb24NCm1hdHJpeCB0aGF0IGFwcGVhcnMgdG8gcHJvdmlkZSB0aGUgYmVzdCByZXN1bHRzIG9uIHRoZSBoZWxkDQpvdXQgZGF0YS4gTm90ZSB0aGF0IHlvdSBzaG91bGQgYWxzbyBleHBlcmltZW50IHdpdGggdmFsdWVzIGZvcg0KSyBpbiB0aGUgS05OIGNsYXNzaWZpZXIuDQpgYGB7cn0NCiMgQ3JlYXRlIGEgbmV3IG1vZGVsIHdpdGggYWRkaXRpb25hbCBwcmVkaWN0b3JzDQp0cmFpbl9kYXRhIDwtIHN1YnNldChXZWVrbHksIFllYXIgPD0gMjAwOCkNCnRlc3RfZGF0YSAgPC0gc3Vic2V0KFdlZWtseSwgWWVhciA+IDIwMDgpDQoNCiMgQ3JlYXRlIGludGVyYWN0aW9ucw0KdHJhaW5fZGF0YSRMYWcxX0xhZzIgPC0gdHJhaW5fZGF0YSRMYWcxICogdHJhaW5fZGF0YSRMYWcyDQp0ZXN0X2RhdGEkTGFnMV9MYWcyICA8LSB0ZXN0X2RhdGEkTGFnMSAqIHRlc3RfZGF0YSRMYWcyDQoNCiMgTG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIG11bHRpcGxlIHByZWRpY3RvcnMNCmxvZ2l0X21vZGVsX2V4cCA8LSBnbG0oRGlyZWN0aW9uIH4gTGFnMSArIExhZzIgKyBMYWczICsgTGFnNCArIExhZzUgKyBWb2x1bWUgKyBMYWcxX0xhZzIsIA0KICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSwgZmFtaWx5ID0gYmlub21pYWwpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0ZXN0IGRhdGENCmxvZ2l0X3Byb2JzIDwtIHByZWRpY3QobG9naXRfbW9kZWxfZXhwLCB0ZXN0X2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KbG9naXRfcHJlZHMgPC0gZmFjdG9yKGlmZWxzZShsb2dpdF9wcm9icyA+IDAuNSwgIlVwIiwgIkRvd24iKSwgbGV2ZWxzID0gbGV2ZWxzKHRlc3RfZGF0YSREaXJlY3Rpb24pKQ0KDQojIENvbXB1dGUgY29uZnVzaW9uIG1hdHJpeA0KY29uZl9tYXRyaXhfbG9naXQgPC0gdGFibGUoUHJlZGljdGVkID0gbG9naXRfcHJlZHMsIEFjdHVhbCA9IHRlc3RfZGF0YSREaXJlY3Rpb24pDQpwcmludChjb25mX21hdHJpeF9sb2dpdCkNCg0KIyBDb21wdXRlIGFjY3VyYWN5DQphY2N1cmFjeV9sb2dpdCA8LSBzdW0oZGlhZyhjb25mX21hdHJpeF9sb2dpdCkpIC8gc3VtKGNvbmZfbWF0cml4X2xvZ2l0KQ0KcHJpbnQocGFzdGUoIkxvZ2lzdGljIFJlZ3Jlc3Npb24gQWNjdXJhY3k6Iiwgcm91bmQoYWNjdXJhY3lfbG9naXQsIDQpKSkNCg0KIyBMREEgd2l0aCBtb3JlIHByZWRpY3RvcnMNCnRyYWluX2RhdGEkRGlyZWN0aW9uIDwtIGFzLmZhY3Rvcih0cmFpbl9kYXRhJERpcmVjdGlvbikNCiANCmxkYV9tb2RlbF9leHAgPC0gbGRhKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyICsgTGFnMyArIExhZzQgKyBMYWc1ICsgVm9sdW1lLGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCmxkYV9wcmVkcyA8LSBwcmVkaWN0KGxkYV9tb2RlbF9leHAsIHRlc3RfZGF0YSkkY2xhc3MNCg0KIyBDb21wdXRlIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZfbWF0cml4X2xkYSA8LSB0YWJsZShQcmVkaWN0ZWQgPSBsZGFfcHJlZHMsIEFjdHVhbCA9IHRlc3RfZGF0YSREaXJlY3Rpb24pDQpwcmludChjb25mX21hdHJpeF9sZGEpDQoNCiMgQ29tcHV0ZSBhY2N1cmFjeQ0KYWNjdXJhY3lfbGRhIDwtIHN1bShkaWFnKGNvbmZfbWF0cml4X2xkYSkpIC8gc3VtKGNvbmZfbWF0cml4X2xkYSkNCnByaW50KHBhc3RlKCJMREEgQWNjdXJhY3k6Iiwgcm91bmQoYWNjdXJhY3lfbGRhLCA0KSkpDQoNCg0KIyBTdGFuZGFyZGl6ZSBwcmVkaWN0b3JzDQp0cmFpbl9YIDwtIHNjYWxlKHRyYWluX2RhdGFbLCBjKCJMYWcxIiwgIkxhZzIiLCAiTGFnMyIsICJMYWc0IiwgIkxhZzUiLCAiVm9sdW1lIildKQ0KdGVzdF9YICA8LSBzY2FsZSh0ZXN0X2RhdGFbLCBjKCJMYWcxIiwgIkxhZzIiLCAiTGFnMyIsICJMYWc0IiwgIkxhZzUiLCAiVm9sdW1lIildLCANCiAgICAgICAgICAgICAgICAgY2VudGVyID0gYXR0cih0cmFpbl9YLCAic2NhbGVkOmNlbnRlciIpLCANCiAgICAgICAgICAgICAgICAgc2NhbGUgPSBhdHRyKHRyYWluX1gsICJzY2FsZWQ6c2NhbGUiKSkNCg0KdHJhaW5fWSA8LSB0cmFpbl9kYXRhJERpcmVjdGlvbg0KdGVzdF9ZICA8LSB0ZXN0X2RhdGEkRGlyZWN0aW9uDQoNCiMgVHJ5IGRpZmZlcmVudCB2YWx1ZXMgb2YgSw0KZm9yIChrIGluIGMoMSwgMywgNSwgMTAsIDIwKSkgew0KICBrbm5fcHJlZHMgPC0ga25uKHRyYWluX1gsIHRlc3RfWCwgdHJhaW5fWSwgayA9IGspDQogIGNvbmZfbWF0cml4X2tubiA8LSB0YWJsZShQcmVkaWN0ZWQgPSBrbm5fcHJlZHMsIEFjdHVhbCA9IHRlc3RfWSkNCiAgYWNjdXJhY3lfa25uIDwtIHN1bShkaWFnKGNvbmZfbWF0cml4X2tubikpIC8gc3VtKGNvbmZfbWF0cml4X2tubikNCiAgcHJpbnQocGFzdGUoIktOTiBBY2N1cmFjeSAoSyA9IiwgaywgIik6Iiwgcm91bmQoYWNjdXJhY3lfa25uLCA0KSkpDQp9DQoNCg0KIyBUcmFpbiBOYcOvdmUgQmF5ZXMgd2l0aCBtb3JlIHByZWRpY3RvcnMNCm5iX21vZGVsX2V4cCA8LSBuYWl2ZUJheWVzKERpcmVjdGlvbiB+IExhZzEgKyBMYWcyICsgTGFnMyArIExhZzQgKyBMYWc1ICsgVm9sdW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCm5iX3ByZWRzIDwtIHByZWRpY3QobmJfbW9kZWxfZXhwLCB0ZXN0X2RhdGEpDQoNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeF9uYiA8LSB0YWJsZShQcmVkaWN0ZWQgPSBuYl9wcmVkcywgQWN0dWFsID0gdGVzdF9kYXRhJERpcmVjdGlvbikNCnByaW50KGNvbmZfbWF0cml4X25iKQ0KDQojIENvbXB1dGUgYWNjdXJhY3kNCmFjY3VyYWN5X25iIDwtIHN1bShkaWFnKGNvbmZfbWF0cml4X25iKSkgLyBzdW0oY29uZl9tYXRyaXhfbmIpDQpwcmludChwYXN0ZSgiTmHDr3ZlIEJheWVzIEFjY3VyYWN5OiIsIHJvdW5kKGFjY3VyYWN5X25iLCA0KSkpDQoNCmBgYA0KDQoNCg0KDQoxNC4gSW4gdGhpcyBwcm9ibGVtLCB5b3Ugd2lsbCBkZXZlbG9wIGEgbW9kZWwgdG8gcHJlZGljdCB3aGV0aGVyIGEgZ2l2ZW4NCmNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlIGJhc2VkIG9uIHRoZSBBdXRvIGRhdGEgc2V0Lg0KKGEpIENyZWF0ZSBhIGJpbmFyeSB2YXJpYWJsZSwgbXBnMDEsIHRoYXQgY29udGFpbnMgYSAxIGlmIG1wZyBjb250YWlucw0KYSB2YWx1ZSBhYm92ZSBpdHMgbWVkaWFuLCBhbmQgYSAwIGlmIG1wZyBjb250YWlucyBhIHZhbHVlIGJlbG93DQppdHMgbWVkaWFuLiBZb3UgY2FuIGNvbXB1dGUgdGhlIG1lZGlhbiB1c2luZyB0aGUgbWVkaWFuKCkNCmZ1bmN0aW9uLiBOb3RlIHlvdSBtYXkgZmluZCBpdCBoZWxwZnVsIHRvIHVzZSB0aGUgZGF0YS5mcmFtZSgpDQpmdW5jdGlvbiB0byBjcmVhdGUgYSBzaW5nbGUgZGF0YSBzZXQgY29udGFpbmluZyBib3RoIG1wZzAxIGFuZA0KdGhlIG90aGVyIEF1dG8gdmFyaWFibGVzLg0KDQpgYGB7cn0NCmRhdGEoIkF1dG8iKQ0KbXBnIDwtIG1lZGlhbihBdXRvJG1wZykNCkF1dG8kbXBnMDEgPC0gaWZlbHNlKEF1dG8kbXBnID4gbXBnLCAxLCAwKQ0KQXV0byRtcGcwMSA8LSBmYWN0b3IoQXV0byRtcGcwMSwgbGV2ZWxzID0gYygwLCAxKSkNCg0KYGBgDQoNCihiKSBFeHBsb3JlIHRoZSBkYXRhIGdyYXBoaWNhbGx5IGluIG9yZGVyIHRvIGludmVzdGlnYXRlIHRoZSBhc3NvY2lhdGlvbg0KYmV0d2VlbiBtcGcwMSBhbmQgdGhlIG90aGVyIGZlYXR1cmVzLiBXaGljaCBvZiB0aGUgb3RoZXINCmZlYXR1cmVzIHNlZW0gbW9zdCBsaWtlbHkgdG8gYmUgdXNlZnVsIGluIHByZWRpY3RpbmcgbXBnMDE/IFNjYXR0ZXJwbG90cw0KYW5kIGJveHBsb3RzIG1heSBiZSB1c2VmdWwgdG9vbHMgdG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24uDQpEZXNjcmliZSB5b3VyIGZpbmRpbmdzLg0KDQpgYGB7cn0NCnBhaXJzKEF1dG8pDQpgYGANCmBgYHtyfQ0KIyBCb3hwbG90cyB0byBjb21wYXJlIG51bWVyaWMgZmVhdHVyZXMgYWNyb3NzIG1wZzAxIGdyb3Vwcw0KYm94cGxvdChBdXRvJGhvcnNlcG93ZXIgfiBBdXRvJG1wZzAxLCBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIpLA0KICAgICAgICBtYWluID0gIkhvcnNlcG93ZXIgdnMuIE1QRyIsDQogICAgICAgIHhsYWIgPSAiTVBHIENhdGVnb3J5ICgwID0gTG93LCAxID0gSGlnaCkiLCB5bGFiID0gIkhvcnNlcG93ZXIiKQ0KDQpib3hwbG90KEF1dG8kd2VpZ2h0IH4gQXV0byRtcGcwMSwgY29sID0gYygicmVkIiwgImJsdWUiKSwNCiAgICAgICAgbWFpbiA9ICJXZWlnaHQgdnMuIE1QRyIsDQogICAgICAgIHhsYWIgPSAiTVBHIENhdGVnb3J5ICgwID0gTG93LCAxID0gSGlnaCkiLCB5bGFiID0gIldlaWdodCIpDQoNCmJveHBsb3QoQXV0byRhY2NlbGVyYXRpb24gfiBBdXRvJG1wZzAxLCBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIpLA0KICAgICAgICBtYWluID0gIkFjY2VsZXJhdGlvbiB2cy4gTVBHIiwNCiAgICAgICAgeGxhYiA9ICJNUEcgQ2F0ZWdvcnkgKDAgPSBMb3csIDEgPSBIaWdoKSIsIHlsYWIgPSAiQWNjZWxlcmF0aW9uIikNCg0KYm94cGxvdChBdXRvJGRpc3BsYWNlbWVudCB+IEF1dG8kbXBnMDEsIGNvbCA9IGMoInJlZCIsICJibHVlIiksDQogICAgICAgIG1haW4gPSAiRGlzcGxhY2VtZW50IHZzLiBNUEciLA0KICAgICAgICB4bGFiID0gIk1QRyBDYXRlZ29yeSAoMCA9IExvdywgMSA9IEhpZ2gpIiwgeWxhYiA9ICJEaXNwbGFjZW1lbnQiKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCiMgU2NhdHRlcnBsb3Qgb2Ygd2VpZ2h0IHZzLiBob3JzZXBvd2VyLCBjb2xvcmVkIGJ5IG1wZzAxDQpwbG90KEF1dG8kaG9yc2Vwb3dlciwgQXV0byR3ZWlnaHQsIGNvbCA9IGFzLm51bWVyaWMoQXV0byRtcGcwMSkgKyAyLCANCiAgICAgbWFpbiA9ICJXZWlnaHQgdnMuIEhvcnNlcG93ZXIgKENvbG9yZWQgYnkgTVBHIENhdGVnb3J5KSIsDQogICAgIHhsYWIgPSAiSG9yc2Vwb3dlciIsIHlsYWIgPSAiV2VpZ2h0IiwgcGNoID0gMTkpDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiTG93IE1QRyIsICJIaWdoIE1QRyIpLCBjb2wgPSBjKDIsIDMpLCBwY2ggPSAxOSkNCg0KYGBgDQpfX1NlcGVyYXRpbmcgTVBHIGludG8gYSBoaWdoIGFuZCBsb3cgYmVjYW1lIHZlcnkgdXNlZnVsIGJlY2F1c2UgdGhlcmUgaXMgY2xlYXIgc2VwZXJhdGlvbiBiZXR3ZWVuIGdyb3VwcyBiYXNlZCBvbiB0aGF0IHZhcmlhYmxlLiAgSGlnaCBNUEcgY29ycmVsYXRlcyB0byBsb3cgd2VpZ2h0LCBsb3cgYWNjZWxlcmF0aW9uLCBsb3cgZGlzcGxhY2VtZW50LCBhbmQgb24gdGhlIHNjYXR0ZXIgcGxvdCB5b3UgY2FuIHNlZSB0aGUgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHdlaWdodCBhbmQgaG9yc2Vwb3dlci4gICBfXw0KDQooYykgU3BsaXQgdGhlIGRhdGEgaW50byBhIHRyYWluaW5nIHNldCBhbmQgYSB0ZXN0IHNldC4NCmBgYHtyfQ0Kc2V0LnNlZWQoNDIpDQojIENyZWF0ZSBhIHBhcnRpdGlvbiAoODAlIHRyYWluaW5nLCAyMCUgdGVzdGluZykNCnRyYWluX2lkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKEF1dG8kbXBnMDEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCg0KIyBDcmVhdGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cw0KdHJhaW4gPC0gQXV0b1t0cmFpbl9pZHgsIF0NCnRlc3QgIDwtIEF1dG9bLXRyYWluX2lkeCwgXQ0KDQojIENoZWNrIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBzcGxpdA0KZGltKHRyYWluKSAgIyBUcmFpbmluZyBzZXQgc2l6ZQ0KZGltKHRlc3QpICAgIyBUZXN0IHNldCBzaXplDQpgYGANCg0KDQooZCkgUGVyZm9ybSBMREEgb24gdGhlIHRyYWluaW5nIGRhdGEgaW4gb3JkZXIgdG8gcHJlZGljdCBtcGcwMQ0KdXNpbmcgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBtcGcwMSBpbg0KKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCg0KYGBge3J9DQpsZGFfbXBnIDwtIGxkYShtcGcwMSB+IGhvcnNlcG93ZXIgKyB3ZWlnaHQgKyBkaXNwbGFjZW1lbnQsIGRhdGE9dHJhaW4pDQpzdW1tYXJ5KGxkYV9tcGcpDQoNCmxkYV9wcmVkcyA8LSBwcmVkaWN0KGxkYV9tcGcsIHRlc3QpJGNsYXNzDQoNCmNvbmZfbWF0cml4X2xkYSA8LSB0YWJsZShQcmVkaWN0ZWQgPSBsZGFfcHJlZHMsIEFjdHVhbCA9IHRlc3QkbXBnMDEpDQpwcmludChjb25mX21hdHJpeF9sZGEpDQpjb25mdXNpb25NYXRyaXgobGRhX3ByZWRzLCB0ZXN0JG1wZzAxLCBwb3NpdGl2ZSA9ICIxIikNCg0KdGVzdF9lcnJvcl9sZGEgPC0gMSAtIHN1bShkaWFnKGNvbmZfbWF0cml4X2xkYSkpIC8gc3VtKGNvbmZfbWF0cml4X2xkYSkNCnByaW50KHBhc3RlKCJMREEgVGVzdCBFcnJvciBSYXRlOiIsIHJvdW5kKHRlc3RfZXJyb3JfbGRhLCA0KSkpDQoNCg0KYGBgDQpfX3RoZSBlcnJvciByYXRlIHdhcyAxMS41NCVfXw0KDQooZSkgUGVyZm9ybSBRREEgb24gdGhlIHRyYWluaW5nIGRhdGEgaW4gb3JkZXIgdG8gcHJlZGljdCBtcGcwMQ0KdXNpbmcgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBtcGcwMSBpbg0KKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCg0KYGBge3J9DQpxZGFfbXBnIDwtIHFkYShtcGcwMSB+IGhvcnNlcG93ZXIgKyB3ZWlnaHQgKyBkaXNwbGFjZW1lbnQsIGRhdGEgPSB0cmFpbikNCnFkYV9wcmVkcyA8LSBwcmVkaWN0KHFkYV9tcGcsIHRlc3QpJGNsYXNzDQpjb25mX21hdHJpeF9xZGEgPC0gdGFibGUoUHJlZGljdGVkID0gcWRhX3ByZWRzLCBBY3R1YWwgPSB0ZXN0X2RhdGEkbXBnMDEpDQpjb25mdXNpb25NYXRyaXgocWRhX3ByZWRzLCB0ZXN0JG1wZzAxLCBwb3NpdGl2ZSA9ICIxIikNCnRlc3RfZXJyb3JfcWRhIDwtIDEgLSBzdW0oZGlhZyhjb25mX21hdHJpeF9xZGEpKSAvIHN1bShjb25mX21hdHJpeF9xZGEpDQpwcmludChwYXN0ZSgiUURBIFRlc3QgRXJyb3IgUmF0ZToiLCByb3VuZCh0ZXN0X2Vycm9yX3FkYSwgNCkpKQ0KDQpgYGANCl9faXQncyBub3RhYmxlIHRoYXQgdGhlIGVycm9yIHJhdGUgZm9yIHRoZSBMREEgYW5kIFFEQSBhcmUgdGhlIHNhbWUgYW5kIEkgc3VzcGVjdCBpdCBoYXMgc29tZXRoaW5nIHRvIGRvIHdpdGggdGhlIG5hdHVyZSBvZiB0aGUgdGVzdHMgX18NCg0KKGYpIFBlcmZvcm0gbG9naXN0aWMgcmVncmVzc2lvbiBvbiB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBwcmVkaWN0DQptcGcwMSB1c2luZyB0aGUgdmFyaWFibGVzIHRoYXQgc2VlbWVkIG1vc3QgYXNzb2NpYXRlZCB3aXRoDQptcGcwMSBpbiAoYikuIFdoYXQgaXMgdGhlIHRlc3QgZXJyb3Igb2YgdGhlIG1vZGVsIG9idGFpbmVkPw0KDQpgYGB7cn0NCmxvZ2l0X21wZyA8LSBnbG0obXBnMDEgfiBob3JzZXBvd2VyICsgd2VpZ2h0ICsgZGlzcGxhY2VtZW50LCANCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluLCBmYW1pbHkgPSBiaW5vbWlhbCkNCmxvZ2l0X3Byb2JzIDwtIHByZWRpY3QobG9naXRfbXBnLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCmxvZ2l0X3ByZWRzIDwtIGZhY3RvcihpZmVsc2UobG9naXRfcHJvYnMgPiAwLjUsIDEsIDApLCBsZXZlbHMgPSBjKDAsIDEpKQ0KDQpjb25mX21hdHJpeF9sb2dpdCA8LSB0YWJsZShQcmVkaWN0ZWQgPSBsb2dpdF9wcmVkcywgQWN0dWFsID0gdGVzdCRtcGcwMSkNCmNvbmZ1c2lvbk1hdHJpeChsb2dpdF9wcmVkcywgdGVzdCRtcGcwMSwgcG9zaXRpdmUgPSAiMSIpDQoNCnRlc3RfZXJyb3JfbG9naXQgPC0gMSAtIHN1bShkaWFnKGNvbmZfbWF0cml4X2xvZ2l0KSkgLyBzdW0oY29uZl9tYXRyaXhfbG9naXQpDQpwcmludChwYXN0ZSgiTG9naXN0aWMgUmVncmVzc2lvbiBUZXN0IEVycm9yIFJhdGU6Iiwgcm91bmQodGVzdF9lcnJvcl9sb2dpdCwgNCkpKQ0KDQpgYGANCl9fYW5kIHRoZSBlcnJvciByYXRlIGlzIHRoZSBzYW1lIG9uIHRoaXMgdGVzdCwgZWl0aGVyIEknbSBkb2luZyBzb21ldGhpbmcgdGVycmlibHkgd3Jvbmcgb3IgdGhpcyBpcyBob3cgdGhpcyBkYXRhIGVmZmVjdHMgYWxsIHRoZSB0ZXN0cywgX18NCg0KKGcpIFBlcmZvcm0gbmFpdmUgQmF5ZXMgb24gdGhlIHRyYWluaW5nIGRhdGEgaW4gb3JkZXIgdG8gcHJlZGljdA0KbXBnMDEgdXNpbmcgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0IGFzc29jaWF0ZWQgd2l0aCBtcGcwMQ0KaW4gKGIpLiBXaGF0IGlzIHRoZSB0ZXN0IGVycm9yIG9mIHRoZSBtb2RlbCBvYnRhaW5lZD8NCmBgYHtyfQ0KbmJfbXBnIDwtIG5haXZlQmF5ZXMobXBnMDEgfiBob3JzZXBvd2VyICsgd2VpZ2h0ICsgZGlzcGxhY2VtZW50LCBkYXRhID0gdHJhaW4pDQpuYl9wcmVkcyA8LSBwcmVkaWN0KG5iX21wZywgdGVzdCkNCmNvbmZ1c2lvbk1hdHJpeChuYl9wcmVkcywgdGVzdCRtcGcwMSwgcG9zaXRpdmUgPSAiMSIpDQpjb25mX21hdHJpeF9uYiA8LSB0YWJsZShQcmVkaWN0ZWQgPSBuYl9wcmVkcywgQWN0dWFsID0gdGVzdCRtcGcwMSkNCnRlc3RfZXJyb3JfbmIgPC0gMSAtIHN1bShkaWFnKGNvbmZfbWF0cml4X25iKSkgLyBzdW0oY29uZl9tYXRyaXhfbmIpDQpwcmludChwYXN0ZSgiTmHDr3ZlIEJheWVzIFRlc3QgRXJyb3IgUmF0ZToiLCByb3VuZCh0ZXN0X2Vycm9yX25iLCA0KSkpDQpgYGANCl9fdGhlIGVycm9yIHJhdGUgY29taW5nIG91dCBvZiB0aGlzIHRlc3QgaXMuLi50aGUgc2FtZSBhcyB0aGUgb3RoZXIgdGVzdHMgYW5kIG5vdyBJJ20gb2ZmaWNhaWxseSBmcmVha2VkIG91dF9fDQoNCihoKSBQZXJmb3JtIEtOTiBvbiB0aGUgdHJhaW5pbmcgZGF0YSwgd2l0aCBzZXZlcmFsIHZhbHVlcyBvZiBLLCBpbg0Kb3JkZXIgdG8gcHJlZGljdCBtcGcwMS4gVXNlIG9ubHkgdGhlIHZhcmlhYmxlcyB0aGF0IHNlZW1lZCBtb3N0DQphc3NvY2lhdGVkIHdpdGggbXBnMDEgaW4gKGIpLiBXaGF0IHRlc3QgZXJyb3JzIGRvIHlvdSBvYnRhaW4/DQpXaGljaCB2YWx1ZSBvZiBLIHNlZW1zIHRvIHBlcmZvcm0gdGhlIGJlc3Qgb24gdGhpcyBkYXRhIHNldD8NCmBgYHtyfQ0KdHJhaW5fWCA8LSBzY2FsZSh0cmFpbl9kYXRhWywgYygiaG9yc2Vwb3dlciIsICJ3ZWlnaHQiLCAiZGlzcGxhY2VtZW50IildKQ0KdGVzdF9YICA8LSBzY2FsZSh0ZXN0X2RhdGFbLCBjKCJob3JzZXBvd2VyIiwgIndlaWdodCIsICJkaXNwbGFjZW1lbnQiKV0sIA0KICAgICAgICAgICAgICAgICBjZW50ZXIgPSBhdHRyKHRyYWluX1gsICJzY2FsZWQ6Y2VudGVyIiksIA0KICAgICAgICAgICAgICAgICBzY2FsZSA9IGF0dHIodHJhaW5fWCwgInNjYWxlZDpzY2FsZSIpKQ0KDQp0cmFpbl9ZIDwtIHRyYWluX2RhdGEkbXBnMDENCnRlc3RfWSAgPC0gdGVzdF9kYXRhJG1wZzAxDQprX3ZhbHVlcyA8LSBjKDEsIDMsIDUsIDEwLCAxNSwgMjApDQp0ZXN0X2Vycm9ycyA8LSBudW1lcmljKGxlbmd0aChrX3ZhbHVlcykpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKGtfdmFsdWVzKSkgew0KICBzZXQuc2VlZCgxMjMpICAjIEVuc3VyZSByZXByb2R1Y2liaWxpdHkNCiAga25uX3ByZWRzIDwtIGtubih0cmFpbl9YLCB0ZXN0X1gsIHRyYWluX1ksIGsgPSBrX3ZhbHVlc1tpXSkNCiAgDQogIGNvbmZfbWF0cml4X2tubiA8LSB0YWJsZShQcmVkaWN0ZWQgPSBrbm5fcHJlZHMsIEFjdHVhbCA9IHRlc3RfWSkNCiAgdGVzdF9lcnJvcnNbaV0gPC0gMSAtIHN1bShkaWFnKGNvbmZfbWF0cml4X2tubikpIC8gc3VtKGNvbmZfbWF0cml4X2tubikNCiAgcHJpbnQocGFzdGUoIksgPSIsIGtfdmFsdWVzW2ldLCAiVGVzdCBFcnJvciBSYXRlOiIsIHJvdW5kKHRlc3RfZXJyb3JzW2ldLCA0KSkpDQp9IA0KICANCmBgYA0KX19rID0gNSB3YXMgdGhlIG1vc3QgYWNjdXJhdGUgYW5kIEsgPSAyMCBnaXZlcyB0aGUgZXF1YWwgZXJyb3IgcmF0ZSB0byB0aGUgb3RoZXIgdGVzdHMuICBfXw0KDQoNCjE2LiBVc2luZyB0aGUgQm9zdG9uIGRhdGEgc2V0LCBmaXQgY2xhc3NpZmljYXRpb24gbW9kZWxzIGluIG9yZGVyIHRvIHByZWRpY3QNCndoZXRoZXIgYSBnaXZlbiBjZW5zdXMgdHJhY3QgaGFzIGEgY3JpbWUgcmF0ZSBhYm92ZSBvciBiZWxvdyB0aGUgbWVkaWFuLg0KRXhwbG9yZSBsb2dpc3RpYyByZWdyZXNzaW9uLCBMREEsIG5haXZlIEJheWVzLCBhbmQgS05OIG1vZGVscw0KdXNpbmcgdmFyaW91cyBzdWJzZXRzIG9mIHRoZSBwcmVkaWN0b3JzLiBEZXNjcmliZSB5b3VyIGZpbmRpbmdzLg0KSGludDogWW91IHdpbGwgaGF2ZSB0byBjcmVhdGUgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIHlvdXJzZWxmLCB1c2luZyB0aGUNCnZhcmlhYmxlcyB0aGF0IGFyZSBjb250YWluZWQgaW4gdGhlIEJvc3RvbiBkYXRhIHNldC4NCmBgYHtyfQ0KZGF0YSgiQm9zdG9uIikNCmBgYA0KDQoNCmBgYHtyfQ0KIyBDb21wdXRlIHRoZSBtZWRpYW4gY3JpbWUgcmF0ZQ0KY3JpbWVfbWVkaWFuIDwtIG1lZGlhbihCb3N0b24kY3JpbSkNCg0KIyBDcmVhdGUgdGhlIGJpbmFyeSByZXNwb25zZSB2YXJpYWJsZQ0KQm9zdG9uJEhpZ2hDcmltZSA8LSBpZmVsc2UoQm9zdG9uJGNyaW0gPiBjcmltZV9tZWRpYW4sICJIaWdoIiwgIkxvdyIpDQoNCiMgQ29udmVydCBpdCB0byBhIGZhY3RvciBmb3IgY2xhc3NpZmljYXRpb24NCkJvc3RvbiRIaWdoQ3JpbWUgPC0gZmFjdG9yKEJvc3RvbiRIaWdoQ3JpbWUsIGxldmVscyA9IGMoIkxvdyIsICJIaWdoIikpDQoNCiMgQ2hlY2sgZGlzdHJpYnV0aW9uDQp0YWJsZShCb3N0b24kSGlnaENyaW1lKQ0KDQpzZXQuc2VlZCgxMjMpICAjIEVuc3VyZSByZXByb2R1Y2liaWxpdHkNCnRyYWluX2lkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKEJvc3RvbiRIaWdoQ3JpbWUsIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSkNCnRyYWluX2RhdGEgPC0gQm9zdG9uW3RyYWluX2lkeCwgXQ0KdGVzdF9kYXRhICA8LSBCb3N0b25bLXRyYWluX2lkeCwgXQ0KDQojIEZpdCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHNlbGVjdGVkIHByZWRpY3RvcnMNCmxvZ2l0X21vZGVsIDwtIGdsbShIaWdoQ3JpbWUgfiBsc3RhdCArIHJtICsgZGlzICsgdGF4ICsgcHRyYXRpbywgDQogICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEsIGZhbWlseSA9IGJpbm9taWFsKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGVzdCBkYXRhDQpsb2dpdF9wcm9icyA8LSBwcmVkaWN0KGxvZ2l0X21vZGVsLCB0ZXN0X2RhdGEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIENvbnZlcnQgcHJvYmFiaWxpdGllcyB0byBjbGFzcyBsYWJlbHMNCmxvZ2l0X3ByZWRzIDwtIGZhY3RvcihpZmVsc2UobG9naXRfcHJvYnMgPiAwLjUsICJIaWdoIiwgIkxvdyIpLCBsZXZlbHMgPSBjKCJMb3ciLCAiSGlnaCIpKQ0KDQojIENvbXB1dGUgY29uZnVzaW9uIG1hdHJpeA0KY29uZl9tYXRyaXhfbG9naXQgPC0gY29uZnVzaW9uTWF0cml4KGxvZ2l0X3ByZWRzLCB0ZXN0X2RhdGEkSGlnaENyaW1lKQ0KcHJpbnQoY29uZl9tYXRyaXhfbG9naXQpDQoNCiMgTG9hZCBNQVNTIHBhY2thZ2UgKGlmIG5vdCBhbHJlYWR5IGxvYWRlZCkNCmxpYnJhcnkoTUFTUykNCg0KIyBGaXQgTERBIG1vZGVsDQpsZGFfbW9kZWwgPC0gbGRhKEhpZ2hDcmltZSB+IGxzdGF0ICsgcm0gKyBkaXMgKyB0YXggKyBwdHJhdGlvLCBkYXRhID0gdHJhaW5fZGF0YSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpsZGFfcHJlZHMgPC0gcHJlZGljdChsZGFfbW9kZWwsIHRlc3RfZGF0YSkkY2xhc3MNCg0KIyBDb21wdXRlIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZfbWF0cml4X2xkYSA8LSBjb25mdXNpb25NYXRyaXgobGRhX3ByZWRzLCB0ZXN0X2RhdGEkSGlnaENyaW1lKQ0KcHJpbnQoY29uZl9tYXRyaXhfbGRhKQ0KDQojIEZpdCBOYcOvdmUgQmF5ZXMgbW9kZWwNCm5iX21vZGVsIDwtIG5haXZlQmF5ZXMoSGlnaENyaW1lIH4gbHN0YXQgKyBybSArIGRpcyArIHRheCArIHB0cmF0aW8sIGRhdGEgPSB0cmFpbl9kYXRhKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCm5iX3ByZWRzIDwtIHByZWRpY3QobmJfbW9kZWwsIHRlc3RfZGF0YSkNCg0KIyBDb21wdXRlIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZfbWF0cml4X25iIDwtIGNvbmZ1c2lvbk1hdHJpeChuYl9wcmVkcywgdGVzdF9kYXRhJEhpZ2hDcmltZSkNCnByaW50KGNvbmZfbWF0cml4X25iKQ0KDQojIEV4dHJhY3QgcHJlZGljdG9ycyBhbmQgcmVzcG9uc2UNCnRyYWluX1ggPC0gc2NhbGUodHJhaW5fZGF0YVssIGMoImxzdGF0IiwgInJtIiwgImRpcyIsICJ0YXgiLCAicHRyYXRpbyIpXSkNCnRlc3RfWCAgPC0gc2NhbGUodGVzdF9kYXRhWywgYygibHN0YXQiLCAicm0iLCAiZGlzIiwgInRheCIsICJwdHJhdGlvIildKQ0KdHJhaW5fWSA8LSB0cmFpbl9kYXRhJEhpZ2hDcmltZQ0KdGVzdF9ZICA8LSB0ZXN0X2RhdGEkSGlnaENyaW1lDQoNCiMgVHJ5IGRpZmZlcmVudCB2YWx1ZXMgb2YgSw0KZm9yIChrIGluIGMoMSwgMywgNSwgMTAsIDE1KSkgew0KICBrbm5fcHJlZHMgPC0ga25uKHRyYWluX1gsIHRlc3RfWCwgdHJhaW5fWSwgayA9IGspDQogIGNvbmZfbWF0cml4X2tubiA8LSBjb25mdXNpb25NYXRyaXgoa25uX3ByZWRzLCB0ZXN0X1kpDQogIHByaW50KHBhc3RlKCJLTk4gQWNjdXJhY3kgKEsgPSIsIGssICIpOiIsIHJvdW5kKGNvbmZfbWF0cml4X2tubiRvdmVyYWxsWyJBY2N1cmFjeSJdLCA0KSkpDQp9DQoNCg0KYGBgDQoNCl9fTW9kZWwJQWNjdXJhY3kNCkxvZ2lzdGljIFJlZ3Jlc3Npb24Jfjg2JQ0KTERBCX44NCUNCk5hw692ZSBCYXllcwl+ODQlDQpLTk4gKEJlc3QgSz0xMCkJfjkxJQ0KDQp0aGUgS05OICh3aXRoIEs9MTApIHdhcyB0aGUgbW9zdCBhY2N1cmF0ZSwgaG93ZXZlciwgYWxsIHdlcmUgaGlnaGx5IGFjY3VyYXRlIGFuZCBhbGwgZm91ciB3ZXJlIGNsb3NlLiAgSSB0aGluayB0aGlzIGFsc28gc2hvd3MgdGhlIGRpZmZlcmVuY2UgaW4gZGF0YSB1c2FiaWxpdHkgZnJvbSB0aGUgb3RoZXIgdHdvIGRhdGEgc2V0cyBhcyB0aGlzIHdhcyBieSBmYXIgdGhlIG1vc3QgYWNjdXJhdGUgZHVyaW5nIHRoZSB2YXJpZXR5IG9mIHRlc3RzIF9fDQoNCg0K