Hockey has been a cornerstone of culture at R.I.T since the team founding in 1957. The purpose of this project is to predict the outcome of a RIT Men`s hockey match based on key indicators in the 1st and 2nd periods. These indicators include point differences, time in power play differences and whether the game is played at home or away. The full project can be accessed on github here.

require(ggplot2)
require(dplyr)
require(corrplot)
require(MASS)
require(class)

1-Data Preperation

1.1 Load Data

The RIT men’s hockey team website displays data of all past games in an unstructured format across multiple pages. The data shows the game location, the date the game was played and various in-game statistics. The data was collected into an Excel CSV file. Our training data includes all matches from the 2016 - 2017 up to the 2018 - 2019 hockey seasons. The test data is from the 2019 - 2020 hockey season. The full data dictionary can be found here.. A glimpse of the raw training data is found below.

dirdata <- "/Users/endererimhan/Documents/Capstone/"
hockey_raw <- read.table(paste0(dirdata,"HData2.CSV"), header = TRUE, sep = ",")
hockey_raw_test <- read.table(paste0(dirdata,"TestHData2.CSV"), header = TRUE, sep = ",")
glimpse(hockey_raw)
Rows: 114
Columns: 14
$ Date     <chr> "10/1/16", "10/7/16", "10/8/16", "10/15/16", "10/21/16", "10/22/16", "10/28/16", "10/29/16", "…
$ Location <chr> "H", "H", "H", "H", "A", "A", "A", "A", "A", "H", "A", "A", "H", "H", "A", "A", "A", "H", "H",…
$ p1T      <int> 1, 1, 3, 0, 0, 1, 2, 3, 0, 0, 0, 1, 0, 1, 1, 1, 2, 0, 0, 0, 1, 1, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1…
$ p2T      <int> 1, 3, 2, 0, 1, 2, 0, 2, 0, 1, 1, 2, 1, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0…
$ p3T      <int> 1, 2, 0, 1, 1, 0, 2, 1, 0, 0, 2, 0, 5, 0, 2, 2, 1, 1, 1, 0, 2, 0, 3, 2, 0, 0, 0, 0, 2, 1, 0, 1…
$ p1G      <int> 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 2, 3, 1, 0, 1, 0, 1, 2, 2, 2, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1…
$ p2G      <int> 0, 2, 4, 0, 2, 3, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 0, 1, 2, 3, 1, 2, 1, 0, 0, 0…
$ p3G      <int> 0, 1, 1, 1, 3, 2, 0, 2, 0, 1, 1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 0, 3, 0, 3, 1, 2, 1, 3, 0, 3, 3, 0…
$ POW1T    <int> 259, 118, 200, 146, 246, 32, 31, 240, 72, 0, 120, 312, 120, 120, 197, 178, 162, 0, 0, 0, 95, 1…
$ POW2T    <int> 187, 120, 318, 346, 226, 0, 0, 240, 301, 120, 120, 105, 120, 0, 180, 305, 374, 120, 120, 209, …
$ POW3T    <int> 136, 434, 195, 0, 115, 120, 0, 60, 59, 240, 80, 239, 84, 0, 100, 249, 0, 0, 240, 31, 469, 240,…
$ POW1G    <int> 120, 242, 148, 266, 240, 128, 60, 288, 192, 47, 240, 258, 331, 211, 240, 240, 291, 162, 73, 11…
$ POW2G    <int> 236, 242, 342, 0, 240, 151, 236, 25, 0, 313, 240, 120, 329, 29, 221, 195, 0, 0, 0, 120, 77, 12…
$ POW3G    <int> 212, 120, 0, 120, 162, 61, 0, 0, 120, 120, 299, 120, 146, 0, 220, 173, 240, 120, 120, 420, 109…

1.2 Preprocess Data

Before any analysis is done, we need to define some new variables. The pre-processing is done for both the training and test data. A glimpse of the transformed training data is shown below.

hockey_train <- hockey_raw 

hockey_train <- hockey_train %>% mutate(Location = as.character(Location)) 

#Cleaning up training data
hockey_train <- hockey_train %>% 
  mutate(scorediffp1 = p1T - p1G, 
         scorediffp2 = scorediffp1 + p2T  - p2G, 
         scoreddiffFin = scorediffp2 + p3T - p3G,
         ritp1pwr = POW1T - POW1G, 
         ritp2pwr = ritp1pwr + POW2T - POW2G,
         rit3pwr = ritp2pwr + POW3T - POW3G,
         rit_final = p1T + p2T + p3T,
         op_final = p1G + p2G + p3G,
         Location = ifelse(Location == "H", "Home", "Away"),
         outcome = ifelse(scoreddiffFin > 0, "Win", "Loss")
         ) 

hockey_train_full <- hockey_train 

hockey_train <- hockey_train[,-c(1,3:14)]

#Cleaning up the test data
hockey_test <- hockey_raw_test 

hockey_test <- hockey_test %>% 
  mutate(Location = as.character(Location),
         scorediffp1 = p1T - p1G, 
         scorediffp2 = p1T + p2T - p1G - p2G, 
         scoreddiffFin = p1T + p2T + p3T - p1G - p2G -p3G,
         ritp1pwr = POW1T - POW1G, 
         ritp2pwr = ritp1pwr + POW2T - POW2G,
         rit_final = p1T + p2T + p3T,
         op_final = p1G + p2G + p3G,
         outcome = ifelse(scoreddiffFin > 0, "Win", "Loss"),
         Location = ifelse(Location == "H", "Home", "Away"))

hockey_test_full <- hockey_test 
hockey_test <- hockey_test[,-c(1,3:12)]

#For future correlation analyis and model training
hockey_final <- hockey_train %>% 
  mutate(Location = ifelse(Location == "Home", 1, 0) , 
                        outcome = ifelse(outcome == "Win", 1 ,0))

glimpse(hockey_train)
Rows: 114
Columns: 10
$ Location      <chr> "Home", "Home", "Home", "Home", "Away", "Away", "Away", "Away", "Away", "Home", "Away", "…
$ scorediffp1   <int> 1, 1, 2, 0, 0, 0, 2, 3, -1, -1, -2, -2, -1, 1, 0, 1, 1, -2, -2, -2, 1, 1, 2, 0, 1, -1, -1…
$ scorediffp2   <int> 2, 2, 0, 0, -1, -1, 1, 4, -1, 0, -2, -1, -1, 0, 0, 5, 4, -3, -2, -2, 0, -1, 2, -1, -1, -3…
$ scoreddiffFin <int> 3, 3, -1, 0, -3, -3, 3, 3, -1, -1, -1, -1, 4, 0, 1, 6, 4, -4, -2, -3, 2, -4, 5, -2, -2, -…
$ ritp1pwr      <int> 139, -124, 52, -120, 6, -96, -29, -48, -120, -47, -120, 54, -211, -91, -43, -62, -129, -1…
$ ritp2pwr      <int> 90, -246, 28, 226, -8, -247, -265, 167, 181, -240, -240, 39, -420, -120, -84, 48, 245, -4…
$ rit3pwr       <int> 14, 68, 223, 106, -55, -188, -265, 227, 120, -120, -459, 158, -482, -120, -204, 124, 5, -…
$ rit_final     <int> 3, 6, 5, 1, 2, 3, 4, 6, 0, 1, 3, 3, 6, 1, 4, 7, 6, 1, 1, 0, 3, 1, 5, 3, 1, 1, 2, 3, 4, 1,…
$ op_final      <int> 0, 3, 6, 1, 5, 6, 1, 3, 1, 2, 4, 4, 2, 1, 3, 1, 2, 5, 3, 3, 1, 5, 0, 5, 3, 6, 3, 6, 1, 3,…
$ outcome       <chr> "Win", "Win", "Loss", "Loss", "Loss", "Loss", "Win", "Win", "Loss", "Loss", "Loss", "Loss…

2 - Data Visualization

2.1 How good is the RIT Mens Hockey Team?

Point differences between RIT and the rival team were calculated for each period. The final score difference for each match is shown visually in the bar chart below. A positive score difference indicates an RIT win.

scoreddiffFin.counts <- as.data.frame(table(hockey_train$scoreddiffFin))
ggplot(data = scoreddiffFin.counts, aes(x = Var1, y = Freq)) +
  geom_bar(stat = "identity", fill = "steelblue")+
  geom_text(aes(label = Freq), vjust=1.6, color = "white", size = 3.5)+
  theme_minimal() +xlab("Difference") + ylab("Frequency") +
  ggtitle("Final Score Difference")

RIT won 41% of games and tied 7% of them. When RIT loses, 44% of the time its only by a 1 point difference. RIT wins by an average of 2.5 points and loses by an average of 2.2 points.

2.2 Can score difference predict game outcome?

The point differences for period 1 and period 2 are displayed in the scatter diagram below.

ggplot(hockey_train, aes(x = scorediffp1, y = scorediffp2, color = outcome)) + 
    geom_jitter(size = 3, width = 0.1 , height = 0.2) + 
    xlab("Score Difference at Period 1") + 
    ylab("Score Difference at Period 2") + 
    ggtitle("Game Outcome")

There seems to be a positive relationship between period 1 and period 2 point differences. The lower left section of the plot has a unanimous outcome of an RIT loss while the upper right section has the opposite effect. The middle has a mix of both wins and losses. There does seem to be some degree of separation between wins and loss which is good for predicting game outcome.

2.3 Can game location predict game outcome?

A double bar chart of game outcome based on location is shown below.

win_location_counts <- hockey_train %>% 
  group_by(Location, outcome) %>% 
  summarise(count = n())

ggplot(win_location_counts, aes(Location, count, fill = outcome)) +
  geom_bar(stat = "identity", position = "dodge") +
  labs(title = "Outcome of Game by Location")

The games location does not seem to effect the outcome of a match.

2.4 Can difference of time in powerplay predict game outcome?

Time in power play difference between RIT and the rival team for each period was calculated.For each period, two histograms of these differences were generated and overlapped. One indicating an RIT win and the other a loss.

ggplot(data=hockey_train, aes(x = ritp1pwr, group = outcome, fill = outcome)) +
  geom_density(adjust = 1.3, alpha = 0.4) +
  labs(title = "Histogram of powerplay difference, period one")


ggplot(data=hockey_train, aes(x=ritp2pwr, group=outcome, fill = outcome)) +
  geom_density(adjust = 1.3, alpha = 0.4) +
  labs(title = "Histogram of powerplay difference, period two")

For each period, there seems to be a lot of overlap in the distribution of power play difference between a lost and won game. This seems to suggest that difference in power play between the teams has no effect on the result of the game.

2.5 What variables are correlated?

A correlation plot between all the variables in the training data is shown below.

corrplot(cor(hockey_final[ ,c(1:3,5:6,10)]), method = "number" , type = "lower")

We seem to have moderate correlations with game outcome and point differences in period 1 and 2. It is interesting to note that the point-difference in period 2 is more correlated with outcome than in period 1. This result is to be expected. It is surprising to note that home does not seem to correlate with game outcome. This will surprise any hockey enthusiast who will gladly tell you that the home team has a higher chance of winning in their own turf.

Now lets do some machine learning!

3 - Training the Classification Machines

These next two sections are for a technical audience. A knowledge of statistical machine learning is assumed. Please skip to the conclusion for a non-technical solution.

We will now train 4 classic classification algorithms. We optimize each algorithm using cross-validation. In Chapter 4, we will compare each method using ROC Analysis and choose the best one.

3.1 Logistic Regression

We begin with logistic regression. Using Stepwise Logistic Regression, we find the combination of predictors that output the best model.

glm.fit.all <- glm(outcome ~ scorediffp1 + scorediffp2 + Location, data = hockey_final, family = binomial)
step.model <- glm.fit.all %>% stepAIC(trace = FALSE)
step.model

Call:  glm(formula = outcome ~ scorediffp2, family = binomial, data = hockey_final)

Coefficients:
(Intercept)  scorediffp2  
     -0.958        1.689  

Degrees of Freedom: 113 Total (i.e. Null);  112 Residual
Null Deviance:      154.5 
Residual Deviance: 77.37    AIC: 81.37

A Logistic Regression model with the 2nd period score difference as a predictor is the best model to use for optimal prediction. Lets look at the diagnostics.

summary(glm(outcome ~ scorediffp2, data = hockey_final, family = binomial))

Call:
glm(formula = outcome ~ scorediffp2, family = binomial, data = hockey_final)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-2.23850  -0.37000  -0.06947   0.41271   2.33052  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -0.9580     0.3117  -3.074  0.00211 ** 
scorediffp2   1.6892     0.3221   5.244 1.57e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 154.511  on 113  degrees of freedom
Residual deviance:  77.369  on 112  degrees of freedom
AIC: 81.369

Number of Fisher Scoring iterations: 6

The intercept and predictor are both statistically significant. Overall the model seems to do very well. The graph of the logistic curve is shown below.

ggplot(hockey_final, aes(x = scorediffp2, y = outcome)) + geom_point() + 
  stat_smooth(method = "glm", method.args = list(family="binomial"), se = FALSE) +
  ylab("Probability of Win") + xlab("Period 2 difference") +
  labs(title = "Logistic Regression Model")

When tied after the 2nd period, RIT has about a 25% chance of winning. Note that a tie is indicated as a loss for both teams in this model. This probability jumps to 62.5% when RIT is up by one and around 90% when up by two. We will now use 6-fold cross-validation to find the optimal probability cut-off value that switches a prediction to a win. We use 1 - accuracy or error as a performance indicator for our model.

set.seed(123)
fold <- sample(rep(1:6, length = 114)) 
cutoff <- seq(0, 1, length.out = 51) 
glm.accur <- matrix(0, 6, 51)

#6-fold cross validation for probability cutoff
for (i in 1:6) {
  glm <- glm(outcome ~ scorediffp2, data = hockey_final[fold!=i,], family = binomial)
  test <- hockey_final[fold == i, 3]
  test <- as.data.frame(test)
  colnames(test) <- c("scorediffp2")
  glm.probs <- predict(glm ,test , type = 'response' ) 
  for (j in 1:51) {
    glm.pred <- ifelse(glm.probs > cutoff[j] ,1 ,0)
    glm.accur[i,j] <- 1 - mean(glm.pred == hockey_final[fold == i,10]) 
    }
}

error.log <- rep(0, 51) 

for (k in 1:51) {
  error.log[k] <- mean(glm.accur[ , k]) 
  }

min.log <- round(min(error.log), 3)
title.log <- paste0("Minimum error: " , min.log)
df.log <- data.frame(cutoff, error.log) 

ggplot(data = df.log, aes(x = cutoff, y = error.log, group = 1)) +
  geom_line(color = "red") + xlab("Cut-off Probabilty") + ylab("Mean Error rate") +
  ggtitle("6-fold cross validation: Logistic regression" , title.log) #Choose 0.5 as optimal probability cutoff

The error is minimized when the cut-off probability is at 0.5. This means that if RIT has a 50% or more chance of winning, the prediction would be a win.

We conclude that the best logistic model is the one that uses the 2nd period point difference as a predictor with a cut-off probability of 0.5.

3.2 K-Nearest Neighbors

We now train a K-Nearest Neighbors model with location and point differences in both periods as predictors. Since all three are integers with about the same mean and variance, normalizing the data is not needed. 6-fold cross-validation is used to find the optimal K.

set.seed(105)
fold <- sample(rep(1:6, length = 114)) 
K_choice <- seq(1, 95, length.out = 95) 
knn_accur <- matrix(0, 6, 95)

for (i in 1:6) {
  train2 <- data.frame(hockey_final[fold != i , c(1,2,3)]) 
  test2 <- data.frame(hockey_final[fold == i , c(1,2,3)]) 
  test2real <- data.frame(hockey_final[fold == i, 10]) 
  train2real <- as.vector(hockey_final[fold != i, 10])
  for (j in 1:95) {
    knn_model <- knn(train = train2, test = test2, cl = train2real, k = K_choice[j])
    knn_accur[i,j] <- 1 - mean(as.numeric(levels(knn_model)[as.integer(knn_model)]) == test2real)
  }
}

error_knn = rep(0, 95) 
for (i in 1:95) {
  error_knn[i] = mean(knn_accur[, i]) 
  }

min_knn <- round(min(error_knn), 3)
title_knn <- paste0("Minimum error: ", min_knn)
df_knn <- data.frame(K_choice, error_knn) 

ggplot(data=df_knn, aes(x = K_choice, y = error_knn, group = 1)) +
  geom_line(color = "red") + 
  xlab("Choice of K") + ylab("Mean Error rate") + 
  ggtitle("6-fold cross validation: KNN", title_knn)

Lets zoom in between k=1 and k=40. We will also output the variance of the error rates to get a closer look at how KNN is behaving.

set.seed(105)
fold <- sample(rep(1:6,length = 114)) 
K_choice <- seq(1 , 40 , length.out = 40) 
knn_accur <- matrix(0 , 6 , 40)

for (i in 1:6) {
  train2 <- data.frame(hockey_final[fold != i, c(1,2,3)]) 
  test2 <- data.frame(hockey_final[fold == i, c(1,2,3)]) 
  test2real <- data.frame(hockey_final[fold == i, 10]) 
  train2real <- as.vector(hockey_final[fold != i, 10])
  for (j in 1:40) {
    knn_model <- knn(train = train2, test = test2, cl = train2real, k = K_choice[j])
    knn_accur[i,j] <- 1 - mean(as.numeric(levels(knn_model)[as.integer(knn_model)]) == test2real)
  }
}

error_knn <- rep(0, 40) 
for (i in 1:40) {
  error_knn[i] = mean(knn_accur[, i]) 
}

sd_knn <- rep(0,40) 
for (i in 1:40) {
  sd_knn[i] = sd(knn_accur[, i]) 
  }

min_knn <- round(min(error_knn), 3)
title_knn <- paste0("Minimum error: ", min_knn)
df_knn <- data.frame(K_choice, error_knn) 

df_knn2 <- data.frame(K_choice, sd_knn)

ggplot(data = df_knn, aes(x = K_choice, y = error_knn, group = 1)) +
  geom_line(color = "red") + 
  xlab("Choice of K") + ylab("Mean Error rate") + 
  ggtitle("6-fold cross validation: KNN", title_knn)


ggplot(data = df_knn2, aes(x = K_choice, y = sd_knn, group = 1)) + 
  geom_line(color = "red") +
  xlab("Choice of K") + ylab("Standard deviation of Error rate") +
  ggtitle("6-fold cross validation: KNN", title_knn) #Choose K=10

10 is the best number of neighbors to consider since it achieves a low mean error rate with the lowest variance. We conclude that the best nearest neighbor model is the one that considers 10 neighbors.

3.3 Discriminant Analysis

Finally, we consider both Linear & Quadratic Discriminant Analysis. We use 6-fold cross validation to find the optimal prior probability that maximizes accuracy and minimizes error.

set.seed(121)
fold <- sample(rep(1:6, length = 114)) 
Priors <- seq(0.01, 0.99, length.out = 99) 
lda_accur <- matrix(0, 6, 99)

for (i in 1:6) {
  test <- data.frame(hockey_final[fold == i, ]) 
  train <- data.frame(hockey_final[fold != i, ]) 
  for (j in 1:99) {
    prior1 <- Priors[j]
    lda_model <- lda(outcome ~ scorediffp1 + scorediffp2, data = test, prior = c(prior1, 1 - prior1))
    predictions <- lda_model %>% predict(test)
    lda_accur[i,j] <- 1 - mean(predictions$class == test$outcome) 
    }
}

error_lda <- rep(0, 99) 
for (i in 1:99) {
  error_lda[i] = mean(lda_accur[, i])
}

min_lda <- round(min(error_lda), 3)
title_lda <- paste0("Minimum error: ", min_lda)

df_lda <- data.frame(Priors , error_lda) 

ggplot(data = df_lda, aes(x = Priors, y = error_lda, group = 1)) +
  geom_line(color = "red") + 
  xlab("Choice of Prior") + ylab("Mean Error rate") +
  ggtitle("6-fold cross validation: LDA", title_lda) #Choose 0.57 as optimal probability cut-off

We conclude that the best linear discriminant model is the one that uses 1st and 2nd period point difference as predictors with a cut-off prior probability of 0.57

set.seed(121)
fold <- sample(rep(1:6,length = 114)) 
Priors <- seq(0.01, 0.99, length.out = 99) 
qda_accur <- matrix(0, 6, 99)

for (i in 1:6) {
  test <- data.frame(hockey_final[fold == i, ]) 
  train <- data.frame(hockey_final[fold != i, ])
  for (j in 1:99) {
    prior1 <- Priors[j]
    qda_model <- qda(outcome ~ scorediffp1 + scorediffp2, data = test, prior = c(prior1 , 1 - prior1))
    predictions <- qda_model %>% predict(test)
    qda_accur[i,j] <- 1 - mean(predictions$class == test$outcome)
    } 
  }


error_qda <- rep( 0 , 99) 
for (i in 1:99) {
  error_qda[i] = mean(qda_accur[, i]) }

min_qda <- round(min(error_qda),3)
title_qda <- paste0("Minimum error: ", min_qda)
df_qda <- data.frame(Priors, error_qda) 

ggplot(data = df_qda, aes(x = Priors, y = error_qda, group = 1)) +
  geom_line(color = "red") + 
  xlab("Choice of Prior") + ylab("Mean Error rate") +
  ggtitle("6-fold cross validation: QDA" , title_qda) #Choose 0.67 as optimal probabilty cut-off

We conclude that the best quadratic discriminant model is the one that uses 1st and 2nd period point difference as predictors with a cut-off prior probability of 0.67

4 - ROC Analysis

Now that we have four classification algorithms that are the best in their respected classes, lets compare and contrast them.


hockey_test2 <- hockey_test %>% 
  mutate(Location = ifelse(Location == "Home" , 1, 0) , 
         outcome = ifelse(outcome == "Win" , 1 ,0))

glm_final <- glm(outcome ~ scorediffp2, data = hockey_final, family = binomial) 
test <- data.frame(hockey_test2[ ,3])
test <- as.data.frame(test)
colnames(test) <- c("scorediffp2")
glm_final_probs <- predict(glm_final, test, type = 'response') 
glm_final_pred <- ifelse(glm_final_probs > 0.5, 1, 0)
glm_table <- table(predicted = as.factor(glm_final_pred), actual = as.factor(hockey_test2$outcome))


set.seed(100)
train_knn <- hockey_final[c(1, 2, 3)]
test_knn <- hockey_test2[c(1, 2, 3)]
cl_knn <- as.factor(hockey_final$outcome)
knn_final <- knn(train = train_knn, test = test_knn, cl = cl_knn, k = 10)
knn_table <- table(predicted = as.factor(knn_final), actual = as.factor(hockey_test2$outcome) )


lda_final <- lda(outcome ~ scorediffp1 + scorediffp2, data = hockey_final, prior = c(0.57, 0.43))
predictions_lda <- lda_final %>% predict(hockey_test2)
lda_table <- table(predicted = predictions_lda$class, actual = hockey_test2$outcome )
#error_lda = 1 - mean(predictions.lda$class == testdata$outcome) 


qda_final <- qda(outcome ~ scorediffp1 + scorediffp2, data = hockey_final , prior = c(0.67 ,0.33))
predictions_qda <- qda_final %>% predict(hockey_test2)
qda_table <- table(predicted = predictions_qda$class, actual = hockey_test2$outcome)

log_sen <- glm_table[2,2] / (glm_table[2,2] + glm_table[1,2])
log_fp <- glm_table[2,1] / (glm_table[2,1] + glm_table[1,1]) 
log_act <- (glm_table[1,1] + glm_table[2,2]) / sum(glm_table)

knn_sen <- knn_table[2,2] / (knn_table[2,2] + knn_table[1,2]) 
knn_fp <- knn_table[2,1] / (knn_table[2,1] + knn_table[1,1]) 
knn_act <- (knn_table[1,1] + knn_table[2,2]) / sum(knn_table) 

lda_sen <- lda_table[2,2] / (lda_table[2,2] + lda_table[1,2]) 
lda_fp <- lda_table[2,1] / (lda_table[2,1] + lda_table[1,1]) 
lda_act <- (lda_table[1,1] + lda_table[2,2]) / sum(lda_table) 

qda_sen <- qda_table[2,2] / (qda_table[2,2] + qda_table[1,2]) 
qda_fp <- qda_table[2,1] / (qda_table[2,1] + qda_table[1,1]) 
qda_act <- (qda_table[1,1] + qda_table[2,2]) / sum(qda_table) 

Methods <- c("Logistic", "KNN" , "LDA" , "QDA")
Methods_roc <- c("Logistic/LDA" , "KNN" , "QDA")
Accuracy <- c(log_act, knn_act, lda_act, qda_act) 
Sensitivity <- c(log_sen , knn_sen , lda_sen , qda_sen) 
Specificity = c(1-log_fp , 1-knn_fp , 1-lda_fp , 1-qda_fp)
compare_roc <- data.frame(Methods_roc, Sensitivity[c(1,2,4)], 1-Specificity[c(1,2,4)])
colnames(compare_roc) <- c("Methods" , "tp_rate" , "fp_rate")

comparisions <- data.frame(Methods, Accuracy, Sensitivity, Specificity) 
comparisions

It seems that QDA is the most accurate algorithm while KNN performs the worst.

ggplot(compare_roc, aes(x = fp_rate, y = tp_rate , col = Methods)) + 
geom_point() + xlim(0,1) + ylim(0,1) + geom_abline(intercept = 0, slope = 1) + 
ggtitle("ROC graph") +xlab("1 - Specificity") + ylab("Sensitivity")

All 4 classifiers do much better than “random guessing” which is indicated by the black line. The closer towards the upper left corner, the better the classifier is. KNN is the closest towards the black line. This means that KNN under performs compared to the other classifiers. QDA seems to perform the best. QDA is the classifier to go in terms of predictability however if one wants a simple interpretable model with some loss of predictability, Logistic Regression is king.

5 - Conclusion

We conclude that the best predictors that determine the outcome of a game is the point-difference at period 1 and period 2. Time in Power play differences and location does not seem to predict very well. A Logistic Regression Model is the most interpretable but if one wants predictability, Quadratic Discriminant Analysis is the way to go.

LS0tCnRpdGxlOiAiSG9ja2V5IGluLWdhbWUgcHJlZGljdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpIb2NrZXkgaGFzIGJlZW4gYSBjb3JuZXJzdG9uZSBvZiBjdWx0dXJlIGF0IFIuSS5UIHNpbmNlIHRoZSB0ZWFtIGZvdW5kaW5nIGluIDE5NTcuIFRoZSBwdXJwb3NlIG9mIHRoaXMgcHJvamVjdCBpcyB0byBwcmVkaWN0IHRoZSBvdXRjb21lIG9mIGEgUklUIE1lbmBzIGhvY2tleSBtYXRjaCBiYXNlZCBvbiBrZXkgaW5kaWNhdG9ycyBpbiB0aGUgMXN0IGFuZCAybmQgcGVyaW9kcy4gVGhlc2UgaW5kaWNhdG9ycyBpbmNsdWRlIHBvaW50IGRpZmZlcmVuY2VzLCB0aW1lIGluIHBvd2VyIHBsYXkgZGlmZmVyZW5jZXMgYW5kIHdoZXRoZXIgdGhlIGdhbWUgaXMgcGxheWVkIGF0IGhvbWUgb3IgYXdheS4gVGhlIGZ1bGwgcHJvamVjdCBjYW4gYmUgYWNjZXNzZWQgb24gZ2l0aHViIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vRW5kZXJFcmltaGFuL0hvY2tleSkuCgpgYGB7cn0KcmVxdWlyZShnZ3Bsb3QyKQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKGNvcnJwbG90KQpyZXF1aXJlKE1BU1MpCnJlcXVpcmUoY2xhc3MpCgpgYGAKCiMjIDEtRGF0YSBQcmVwZXJhdGlvbgoKIyMjIDEuMSBMb2FkIERhdGEKClRoZSBSSVQgbWVu4oCZcyBob2NrZXkgdGVhbSB3ZWJzaXRlIGRpc3BsYXlzIGRhdGEgb2YgYWxsIHBhc3QgZ2FtZXMgaW4gYW4gdW5zdHJ1Y3R1cmVkIGZvcm1hdCBhY3Jvc3MgbXVsdGlwbGUgcGFnZXMuIFRoZSBkYXRhIHNob3dzIHRoZSBnYW1lIGxvY2F0aW9uLCB0aGUgZGF0ZSB0aGUgZ2FtZSB3YXMgcGxheWVkIGFuZCB2YXJpb3VzIGluLWdhbWUgc3RhdGlzdGljcy4gVGhlIGRhdGEgd2FzIGNvbGxlY3RlZCBpbnRvIGFuIEV4Y2VsIENTViBmaWxlLiBPdXIgdHJhaW5pbmcgZGF0YSBpbmNsdWRlcyBhbGwgbWF0Y2hlcyBmcm9tIHRoZSAyMDE2IC0gMjAxNyB1cCB0byB0aGUgMjAxOCAtIDIwMTkgaG9ja2V5IHNlYXNvbnMuIFRoZSB0ZXN0IGRhdGEgaXMgZnJvbSB0aGUgMjAxOSAtIDIwMjAgaG9ja2V5IHNlYXNvbi4gVGhlIGZ1bGwgZGF0YSBkaWN0aW9uYXJ5IGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vZ2l0aHViLmNvbS9FbmRlckVyaW1oYW4vSG9ja2V5I2RhdGEtZGljdGlvbmFyeSkuIEEgZ2xpbXBzZSBvZiB0aGUgcmF3IHRyYWluaW5nIGRhdGEgaXMgZm91bmQgYmVsb3cuCgpgYGB7ciwgZWNobyA9IFR9CmRpcmRhdGEgPC0gIi9Vc2Vycy9lbmRlcmVyaW1oYW4vRG9jdW1lbnRzL0NhcHN0b25lLyIKaG9ja2V5X3JhdyA8LSByZWFkLnRhYmxlKHBhc3RlMChkaXJkYXRhLCJIRGF0YTIuQ1NWIiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICIsIikKaG9ja2V5X3Jhd190ZXN0IDwtIHJlYWQudGFibGUocGFzdGUwKGRpcmRhdGEsIlRlc3RIRGF0YTIuQ1NWIiksIGhlYWRlciA9IFRSVUUsIHNlcCA9ICIsIikKZ2xpbXBzZShob2NrZXlfcmF3KQpgYGAKIyMjIDEuMiBQcmVwcm9jZXNzIERhdGEKCkJlZm9yZSBhbnkgYW5hbHlzaXMgaXMgZG9uZSwgd2UgbmVlZCB0byBkZWZpbmUgc29tZSBuZXcgdmFyaWFibGVzLiBUaGUgcHJlLXByb2Nlc3NpbmcgaXMgZG9uZSBmb3IgYm90aCB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4gQSBnbGltcHNlIG9mIHRoZSB0cmFuc2Zvcm1lZCB0cmFpbmluZyBkYXRhIGlzIHNob3duIGJlbG93LiAgCgpgYGB7ciwgZWNobyA9IFR9CmhvY2tleV90cmFpbiA8LSBob2NrZXlfcmF3IAoKaG9ja2V5X3RyYWluIDwtIGhvY2tleV90cmFpbiAlPiUgbXV0YXRlKExvY2F0aW9uID0gYXMuY2hhcmFjdGVyKExvY2F0aW9uKSkgCgojQ2xlYW5pbmcgdXAgdHJhaW5pbmcgZGF0YQpob2NrZXlfdHJhaW4gPC0gaG9ja2V5X3RyYWluICU+JSAKICBtdXRhdGUoc2NvcmVkaWZmcDEgPSBwMVQgLSBwMUcsIAogICAgICAgICBzY29yZWRpZmZwMiA9IHNjb3JlZGlmZnAxICsgcDJUICAtIHAyRywgCiAgICAgICAgIHNjb3JlZGRpZmZGaW4gPSBzY29yZWRpZmZwMiArIHAzVCAtIHAzRywKICAgICAgICAgcml0cDFwd3IgPSBQT1cxVCAtIFBPVzFHLCAKICAgICAgICAgcml0cDJwd3IgPSByaXRwMXB3ciArIFBPVzJUIC0gUE9XMkcsCiAgICAgICAgIHJpdDNwd3IgPSByaXRwMnB3ciArIFBPVzNUIC0gUE9XM0csCiAgICAgICAgIHJpdF9maW5hbCA9IHAxVCArIHAyVCArIHAzVCwKICAgICAgICAgb3BfZmluYWwgPSBwMUcgKyBwMkcgKyBwM0csCiAgICAgICAgIExvY2F0aW9uID0gaWZlbHNlKExvY2F0aW9uID09ICJIIiwgIkhvbWUiLCAiQXdheSIpLAogICAgICAgICBvdXRjb21lID0gaWZlbHNlKHNjb3JlZGRpZmZGaW4gPiAwLCAiV2luIiwgIkxvc3MiKQogICAgICAgICApIAoKaG9ja2V5X3RyYWluX2Z1bGwgPC0gaG9ja2V5X3RyYWluIAoKaG9ja2V5X3RyYWluIDwtIGhvY2tleV90cmFpblssLWMoMSwzOjE0KV0KCiNDbGVhbmluZyB1cCB0aGUgdGVzdCBkYXRhCmhvY2tleV90ZXN0IDwtIGhvY2tleV9yYXdfdGVzdCAKCmhvY2tleV90ZXN0IDwtIGhvY2tleV90ZXN0ICU+JSAKICBtdXRhdGUoTG9jYXRpb24gPSBhcy5jaGFyYWN0ZXIoTG9jYXRpb24pLAogICAgICAgICBzY29yZWRpZmZwMSA9IHAxVCAtIHAxRywgCiAgICAgICAgIHNjb3JlZGlmZnAyID0gcDFUICsgcDJUIC0gcDFHIC0gcDJHLCAKICAgICAgICAgc2NvcmVkZGlmZkZpbiA9IHAxVCArIHAyVCArIHAzVCAtIHAxRyAtIHAyRyAtcDNHLAogICAgICAgICByaXRwMXB3ciA9IFBPVzFUIC0gUE9XMUcsIAogICAgICAgICByaXRwMnB3ciA9IHJpdHAxcHdyICsgUE9XMlQgLSBQT1cyRywKICAgICAgICAgcml0X2ZpbmFsID0gcDFUICsgcDJUICsgcDNULAogICAgICAgICBvcF9maW5hbCA9IHAxRyArIHAyRyArIHAzRywKICAgICAgICAgb3V0Y29tZSA9IGlmZWxzZShzY29yZWRkaWZmRmluID4gMCwgIldpbiIsICJMb3NzIiksCiAgICAgICAgIExvY2F0aW9uID0gaWZlbHNlKExvY2F0aW9uID09ICJIIiwgIkhvbWUiLCAiQXdheSIpKQoKaG9ja2V5X3Rlc3RfZnVsbCA8LSBob2NrZXlfdGVzdCAKaG9ja2V5X3Rlc3QgPC0gaG9ja2V5X3Rlc3RbLC1jKDEsMzoxMildCgojRm9yIGZ1dHVyZSBjb3JyZWxhdGlvbiBhbmFseXNpcyBhbmQgbW9kZWwgdHJhaW5pbmcKaG9ja2V5X2ZpbmFsIDwtIGhvY2tleV90cmFpbiAlPiUgCiAgbXV0YXRlKExvY2F0aW9uID0gaWZlbHNlKExvY2F0aW9uID09ICJIb21lIiwgMSwgMCkgLCAKICAgICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZSA9IGlmZWxzZShvdXRjb21lID09ICJXaW4iLCAxICwwKSkKCmdsaW1wc2UoaG9ja2V5X3RyYWluKQoKYGBgCgojIyAyIC0gRGF0YSBWaXN1YWxpemF0aW9uCgojIyMgMi4xIEhvdyBnb29kIGlzIHRoZSBSSVQgTWVucyBIb2NrZXkgVGVhbT8KClBvaW50IGRpZmZlcmVuY2VzIGJldHdlZW4gUklUIGFuZCB0aGUgcml2YWwgdGVhbSB3ZXJlIGNhbGN1bGF0ZWQgZm9yIGVhY2ggcGVyaW9kLiBUaGUgZmluYWwgc2NvcmUgZGlmZmVyZW5jZSBmb3IgZWFjaCBtYXRjaCBpcyBzaG93biB2aXN1YWxseSBpbiB0aGUgYmFyIGNoYXJ0IGJlbG93LiBBIHBvc2l0aXZlIHNjb3JlIGRpZmZlcmVuY2UgaW5kaWNhdGVzIGFuIFJJVCB3aW4uIAoKYGBge3IsIGVjaG8gPSBUIH0Kc2NvcmVkZGlmZkZpbi5jb3VudHMgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShob2NrZXlfdHJhaW4kc2NvcmVkZGlmZkZpbikpCmdncGxvdChkYXRhID0gc2NvcmVkZGlmZkZpbi5jb3VudHMsIGFlcyh4ID0gVmFyMSwgeSA9IEZyZXEpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikrCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IEZyZXEpLCB2anVzdD0xLjYsIGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDMuNSkrCiAgdGhlbWVfbWluaW1hbCgpICt4bGFiKCJEaWZmZXJlbmNlIikgKyB5bGFiKCJGcmVxdWVuY3kiKSArCiAgZ2d0aXRsZSgiRmluYWwgU2NvcmUgRGlmZmVyZW5jZSIpCmBgYAoKUklUIHdvbiA0MSUgb2YgZ2FtZXMgYW5kIHRpZWQgNyUgb2YgdGhlbS4gV2hlbiBSSVQgbG9zZXMsIDQ0JSBvZiB0aGUgdGltZSBpdHMgb25seSBieSBhIDEgcG9pbnQgZGlmZmVyZW5jZS4gUklUIHdpbnMgYnkgYW4gYXZlcmFnZSBvZiAyLjUgcG9pbnRzIGFuZCBsb3NlcyBieSBhbiBhdmVyYWdlIG9mIDIuMiBwb2ludHMuIAoKIyMjIDIuMiBDYW4gc2NvcmUgZGlmZmVyZW5jZSBwcmVkaWN0IGdhbWUgb3V0Y29tZT8KClRoZSBwb2ludCBkaWZmZXJlbmNlcyBmb3IgcGVyaW9kIDEgYW5kIHBlcmlvZCAyIGFyZSBkaXNwbGF5ZWQgaW4gdGhlIHNjYXR0ZXIgZGlhZ3JhbSBiZWxvdy4KCmBgYHtyLCBlY2hvID0gVH0KZ2dwbG90KGhvY2tleV90cmFpbiwgYWVzKHggPSBzY29yZWRpZmZwMSwgeSA9IHNjb3JlZGlmZnAyLCBjb2xvciA9IG91dGNvbWUpKSArIAogICAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDMsIHdpZHRoID0gMC4xICwgaGVpZ2h0ID0gMC4yKSArIAogICAgeGxhYigiU2NvcmUgRGlmZmVyZW5jZSBhdCBQZXJpb2QgMSIpICsgCiAgICB5bGFiKCJTY29yZSBEaWZmZXJlbmNlIGF0IFBlcmlvZCAyIikgKyAKICAgIGdndGl0bGUoIkdhbWUgT3V0Y29tZSIpCmBgYAoKVGhlcmUgc2VlbXMgdG8gYmUgYSBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBwZXJpb2QgMSBhbmQgcGVyaW9kIDIgcG9pbnQgZGlmZmVyZW5jZXMuIFRoZSBsb3dlciBsZWZ0IHNlY3Rpb24gb2YgdGhlIHBsb3QgaGFzIGEgdW5hbmltb3VzIG91dGNvbWUgb2YgYW4gUklUIGxvc3Mgd2hpbGUgdGhlIHVwcGVyIHJpZ2h0IHNlY3Rpb24gaGFzIHRoZSBvcHBvc2l0ZSBlZmZlY3QuIFRoZSBtaWRkbGUgaGFzIGEgbWl4IG9mIGJvdGggd2lucyBhbmQgbG9zc2VzLiBUaGVyZSBkb2VzIHNlZW0gdG8gYmUgc29tZSBkZWdyZWUgb2Ygc2VwYXJhdGlvbiBiZXR3ZWVuIHdpbnMgYW5kIGxvc3Mgd2hpY2ggaXMgZ29vZCBmb3IgcHJlZGljdGluZyBnYW1lIG91dGNvbWUuIAoKIyMjIDIuMyBDYW4gZ2FtZSBsb2NhdGlvbiBwcmVkaWN0IGdhbWUgb3V0Y29tZT8KCkEgZG91YmxlIGJhciBjaGFydCBvZiBnYW1lIG91dGNvbWUgYmFzZWQgb24gbG9jYXRpb24gaXMgc2hvd24gYmVsb3cuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvID0gVH0Kd2luX2xvY2F0aW9uX2NvdW50cyA8LSBob2NrZXlfdHJhaW4gJT4lIAogIGdyb3VwX2J5KExvY2F0aW9uLCBvdXRjb21lKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQoKZ2dwbG90KHdpbl9sb2NhdGlvbl9jb3VudHMsIGFlcyhMb2NhdGlvbiwgY291bnQsIGZpbGwgPSBvdXRjb21lKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBsYWJzKHRpdGxlID0gIk91dGNvbWUgb2YgR2FtZSBieSBMb2NhdGlvbiIpCgpgYGAKClRoZSBnYW1lcyBsb2NhdGlvbiBkb2VzIG5vdCBzZWVtIHRvIGVmZmVjdCB0aGUgb3V0Y29tZSBvZiBhIG1hdGNoLgoKIyMjIDIuNCBDYW4gZGlmZmVyZW5jZSBvZiB0aW1lIGluIHBvd2VycGxheSBwcmVkaWN0IGdhbWUgb3V0Y29tZT8KClRpbWUgaW4gcG93ZXIgcGxheSBkaWZmZXJlbmNlIGJldHdlZW4gUklUIGFuZCB0aGUgcml2YWwgdGVhbSBmb3IgZWFjaCBwZXJpb2Qgd2FzIGNhbGN1bGF0ZWQuRm9yIGVhY2ggcGVyaW9kLCB0d28gaGlzdG9ncmFtcyBvZiB0aGVzZSBkaWZmZXJlbmNlcyB3ZXJlIGdlbmVyYXRlZCBhbmQgb3ZlcmxhcHBlZC4gT25lIGluZGljYXRpbmcgYW4gUklUIHdpbiBhbmQgdGhlIG90aGVyIGEgbG9zcy4gICAgCgpgYGB7ciwgZmlnLnNob3c9J2hvbGQnLCBvdXQud2lkdGggPSAnMjUlJywgZmlnLmFsaWduPSdjZW50ZXInLCBlY2hvID0gVCB9CmdncGxvdChkYXRhPWhvY2tleV90cmFpbiwgYWVzKHggPSByaXRwMXB3ciwgZ3JvdXAgPSBvdXRjb21lLCBmaWxsID0gb3V0Y29tZSkpICsKICBnZW9tX2RlbnNpdHkoYWRqdXN0ID0gMS4zLCBhbHBoYSA9IDAuNCkgKwogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtIG9mIHBvd2VyIHBsYXkgZGlmZmVyZW5jZSwgcGVyaW9kIG9uZSIpCgpnZ3Bsb3QoZGF0YT1ob2NrZXlfdHJhaW4sIGFlcyh4PXJpdHAycHdyLCBncm91cD1vdXRjb21lLCBmaWxsID0gb3V0Y29tZSkpICsKICBnZW9tX2RlbnNpdHkoYWRqdXN0ID0gMS4zLCBhbHBoYSA9IDAuNCkgKwogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtIG9mIHBvd2VyIHBsYXkgZGlmZmVyZW5jZSwgcGVyaW9kIHR3byIpCmBgYApGb3IgZWFjaCBwZXJpb2QsIHRoZXJlIHNlZW1zIHRvIGJlIGEgbG90IG9mIG92ZXJsYXAgaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb3dlciBwbGF5IGRpZmZlcmVuY2UgYmV0d2VlbiBhIGxvc3QgYW5kIHdvbiBnYW1lLiBUaGlzIHNlZW1zIHRvIHN1Z2dlc3QgdGhhdCBkaWZmZXJlbmNlIGluIHBvd2VyIHBsYXkgYmV0d2VlbiB0aGUgdGVhbXMgaGFzIG5vIGVmZmVjdCBvbiB0aGUgcmVzdWx0IG9mIHRoZSBnYW1lLgoKIyMjIDIuNSBXaGF0IHZhcmlhYmxlcyBhcmUgY29ycmVsYXRlZD8KCkEgY29ycmVsYXRpb24gcGxvdCBiZXR3ZWVuIGFsbCB0aGUgdmFyaWFibGVzIGluIHRoZSB0cmFpbmluZyBkYXRhIGlzIHNob3duIGJlbG93LiAKCmBgYHtyLCBlY2hvID0gVCB9CmNvcnJwbG90KGNvcihob2NrZXlfZmluYWxbICxjKDE6Myw1OjYsMTApXSksIG1ldGhvZCA9ICJudW1iZXIiICwgdHlwZSA9ICJsb3dlciIpCmBgYAoKV2Ugc2VlbSB0byBoYXZlIG1vZGVyYXRlIGNvcnJlbGF0aW9ucyB3aXRoIGdhbWUgb3V0Y29tZSBhbmQgcG9pbnQgZGlmZmVyZW5jZXMgaW4gcGVyaW9kIDEgYW5kIDIuIEl0IGlzIGludGVyZXN0aW5nIHRvIG5vdGUgdGhhdCB0aGUgcG9pbnQtZGlmZmVyZW5jZSBpbiBwZXJpb2QgMiBpcyBtb3JlIGNvcnJlbGF0ZWQgd2l0aCBvdXRjb21lIHRoYW4gaW4gcGVyaW9kIDEuIFRoaXMgcmVzdWx0IGlzIHRvIGJlIGV4cGVjdGVkLiBJdCBpcyBzdXJwcmlzaW5nIHRvIG5vdGUgdGhhdCBob21lIGRvZXMgbm90IHNlZW0gdG8gY29ycmVsYXRlIHdpdGggZ2FtZSBvdXRjb21lLiBUaGlzIHdpbGwgc3VycHJpc2UgYW55IGhvY2tleSBlbnRodXNpYXN0IHdobyB3aWxsIGdsYWRseSB0ZWxsIHlvdSB0aGF0IHRoZSBob21lIHRlYW0gaGFzIGEgaGlnaGVyIGNoYW5jZSBvZiB3aW5uaW5nIGluIHRoZWlyIG93biB0dXJmLgoKTm93IGxldHMgZG8gc29tZSBtYWNoaW5lIGxlYXJuaW5nIQoKIyMgMyAtIFRyYWluaW5nIHRoZSBDbGFzc2lmaWNhdGlvbiBNYWNoaW5lcwoKVGhlc2UgbmV4dCB0d28gc2VjdGlvbnMgYXJlIGZvciBhIHRlY2huaWNhbCBhdWRpZW5jZS4gQSBrbm93bGVkZ2Ugb2Ygc3RhdGlzdGljYWwgbWFjaGluZSBsZWFybmluZyBpcyBhc3N1bWVkLiBQbGVhc2Ugc2tpcCB0byB0aGUgY29uY2x1c2lvbiBmb3IgYSBub24tdGVjaG5pY2FsIHNvbHV0aW9uLgoKV2Ugd2lsbCBub3cgdHJhaW4gNCBjbGFzc2ljIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMuIFdlIG9wdGltaXplIGVhY2ggYWxnb3JpdGhtIHVzaW5nIGNyb3NzLXZhbGlkYXRpb24uIEluIENoYXB0ZXIgNCwgd2Ugd2lsbCBjb21wYXJlIGVhY2ggbWV0aG9kIHVzaW5nIFJPQyBBbmFseXNpcyBhbmQgY2hvb3NlIHRoZSBiZXN0IG9uZS4KCiMjIyAzLjEgTG9naXN0aWMgUmVncmVzc2lvbiAKCldlIGJlZ2luIHdpdGggbG9naXN0aWMgcmVncmVzc2lvbi4gVXNpbmcgU3RlcHdpc2UgTG9naXN0aWMgUmVncmVzc2lvbiwgd2UgZmluZCB0aGUgY29tYmluYXRpb24gb2YgcHJlZGljdG9ycyB0aGF0IG91dHB1dCB0aGUgYmVzdCBtb2RlbC4gCgpgYGB7ciwgZWNobyA9IFRSVUUgfQpnbG0uZml0LmFsbCA8LSBnbG0ob3V0Y29tZSB+IHNjb3JlZGlmZnAxICsgc2NvcmVkaWZmcDIgKyBMb2NhdGlvbiwgZGF0YSA9IGhvY2tleV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwpCnN0ZXAubW9kZWwgPC0gZ2xtLmZpdC5hbGwgJT4lIHN0ZXBBSUModHJhY2UgPSBGQUxTRSkKc3RlcC5tb2RlbAoKYGBgCgpBIExvZ2lzdGljIFJlZ3Jlc3Npb24gbW9kZWwgd2l0aCB0aGUgMm5kIHBlcmlvZCBzY29yZSBkaWZmZXJlbmNlIGFzIGEgcHJlZGljdG9yIGlzIHRoZSBiZXN0IG1vZGVsIHRvIHVzZSBmb3Igb3B0aW1hbCBwcmVkaWN0aW9uLiBMZXRzIGxvb2sgYXQgdGhlIGRpYWdub3N0aWNzLiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCBlY2hvID0gVFJVRSB9CnN1bW1hcnkoZ2xtKG91dGNvbWUgfiBzY29yZWRpZmZwMiwgZGF0YSA9IGhvY2tleV9maW5hbCwgZmFtaWx5ID0gYmlub21pYWwpKQpgYGAKClRoZSBpbnRlcmNlcHQgYW5kIHByZWRpY3RvciBhcmUgYm90aCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LiBPdmVyYWxsIHRoZSBtb2RlbCBzZWVtcyB0byBkbyB2ZXJ5IHdlbGwuIFRoZSBncmFwaCBvZiB0aGUgbG9naXN0aWMgY3VydmUgaXMgc2hvd24gYmVsb3cuCgpgYGB7ciwgZWNobyA9IFQsIG1lc3NhZ2U9RiB9CmdncGxvdChob2NrZXlfZmluYWwsIGFlcyh4ID0gc2NvcmVkaWZmcDIsIHkgPSBvdXRjb21lKSkgKyBnZW9tX3BvaW50KCkgKyAKICBzdGF0X3Ntb290aChtZXRob2QgPSAiZ2xtIiwgbWV0aG9kLmFyZ3MgPSBsaXN0KGZhbWlseT0iYmlub21pYWwiKSwgc2UgPSBGQUxTRSkgKwogIHlsYWIoIlByb2JhYmlsaXR5IG9mIFdpbiIpICsgeGxhYigiUGVyaW9kIDIgZGlmZmVyZW5jZSIpICsKICBsYWJzKHRpdGxlID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwiKQoKYGBgCgpXaGVuIHRpZWQgYWZ0ZXIgdGhlIDJuZCBwZXJpb2QsIFJJVCBoYXMgYWJvdXQgYSAyNSUgY2hhbmNlIG9mIHdpbm5pbmcuIE5vdGUgdGhhdCBhIHRpZSBpcyBpbmRpY2F0ZWQgYXMgYSBsb3NzIGZvciBib3RoIHRlYW1zIGluIHRoaXMgbW9kZWwuIFRoaXMgcHJvYmFiaWxpdHkganVtcHMgdG8gNjIuNSUgd2hlbiBSSVQgaXMgdXAgYnkgb25lIGFuZCBhcm91bmQgOTAlIHdoZW4gdXAgYnkgdHdvLiBXZSB3aWxsIG5vdyB1c2UgNi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gdG8gZmluZCB0aGUgb3B0aW1hbCBwcm9iYWJpbGl0eSBjdXQtb2ZmIHZhbHVlIHRoYXQgc3dpdGNoZXMgYSBwcmVkaWN0aW9uIHRvIGEgd2luLiBXZSB1c2UgMSAtIGFjY3VyYWN5IG9yIGVycm9yIGFzIGEgcGVyZm9ybWFuY2UgaW5kaWNhdG9yIGZvciBvdXIgbW9kZWwuCgpgYGB7ciwgZWNobyA9IFRSVUV9IApzZXQuc2VlZCgxMjMpCmZvbGQgPC0gc2FtcGxlKHJlcCgxOjYsIGxlbmd0aCA9IDExNCkpIApjdXRvZmYgPC0gc2VxKDAsIDEsIGxlbmd0aC5vdXQgPSA1MSkgCmdsbS5hY2N1ciA8LSBtYXRyaXgoMCwgNiwgNTEpCgojNi1mb2xkIGNyb3NzIHZhbGlkYXRpb24gZm9yIHByb2JhYmlsaXR5IGN1dG9mZgpmb3IgKGkgaW4gMTo2KSB7CiAgZ2xtIDwtIGdsbShvdXRjb21lIH4gc2NvcmVkaWZmcDIsIGRhdGEgPSBob2NrZXlfZmluYWxbZm9sZCE9aSxdLCBmYW1pbHkgPSBiaW5vbWlhbCkKICB0ZXN0IDwtIGhvY2tleV9maW5hbFtmb2xkID09IGksIDNdCiAgdGVzdCA8LSBhcy5kYXRhLmZyYW1lKHRlc3QpCiAgY29sbmFtZXModGVzdCkgPC0gYygic2NvcmVkaWZmcDIiKQogIGdsbS5wcm9icyA8LSBwcmVkaWN0KGdsbSAsdGVzdCAsIHR5cGUgPSAncmVzcG9uc2UnICkgCiAgZm9yIChqIGluIDE6NTEpIHsKICAgIGdsbS5wcmVkIDwtIGlmZWxzZShnbG0ucHJvYnMgPiBjdXRvZmZbal0gLDEgLDApCiAgICBnbG0uYWNjdXJbaSxqXSA8LSAxIC0gbWVhbihnbG0ucHJlZCA9PSBob2NrZXlfZmluYWxbZm9sZCA9PSBpLDEwXSkgCiAgICB9Cn0KCmVycm9yLmxvZyA8LSByZXAoMCwgNTEpIAoKZm9yIChrIGluIDE6NTEpIHsKICBlcnJvci5sb2dba10gPC0gbWVhbihnbG0uYWNjdXJbICwga10pIAogIH0KCm1pbi5sb2cgPC0gcm91bmQobWluKGVycm9yLmxvZyksIDMpCnRpdGxlLmxvZyA8LSBwYXN0ZTAoIk1pbmltdW0gZXJyb3I6ICIgLCBtaW4ubG9nKQpkZi5sb2cgPC0gZGF0YS5mcmFtZShjdXRvZmYsIGVycm9yLmxvZykgCgpnZ3Bsb3QoZGF0YSA9IGRmLmxvZywgYWVzKHggPSBjdXRvZmYsIHkgPSBlcnJvci5sb2csIGdyb3VwID0gMSkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAicmVkIikgKyB4bGFiKCJDdXQtb2ZmIFByb2JhYmlsdHkiKSArIHlsYWIoIk1lYW4gRXJyb3IgcmF0ZSIpICsKICBnZ3RpdGxlKCI2LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbjogTG9naXN0aWMgcmVncmVzc2lvbiIgLCB0aXRsZS5sb2cpICNDaG9vc2UgMC41IGFzIG9wdGltYWwgcHJvYmFiaWxpdHkgY3V0b2ZmCmBgYAoKVGhlIGVycm9yIGlzIG1pbmltaXplZCB3aGVuIHRoZSBjdXQtb2ZmIHByb2JhYmlsaXR5IGlzIGF0IDAuNS4gVGhpcyBtZWFucyB0aGF0IGlmIFJJVCBoYXMgYSA1MCUgb3IgbW9yZSBjaGFuY2Ugb2Ygd2lubmluZywgdGhlIHByZWRpY3Rpb24gd291bGQgYmUgYSB3aW4uIAoKV2UgY29uY2x1ZGUgdGhhdCB0aGUgYmVzdCBsb2dpc3RpYyBtb2RlbCBpcyB0aGUgb25lIHRoYXQgdXNlcyB0aGUgMm5kIHBlcmlvZCBwb2ludCBkaWZmZXJlbmNlIGFzIGEgcHJlZGljdG9yIHdpdGggYSBjdXQtb2ZmIHByb2JhYmlsaXR5IG9mIDAuNS4KCiMjIyAzLjIgSy1OZWFyZXN0IE5laWdoYm9ycyAKCldlIG5vdyB0cmFpbiBhIEstTmVhcmVzdCBOZWlnaGJvcnMgbW9kZWwgd2l0aCBsb2NhdGlvbiBhbmQgcG9pbnQgZGlmZmVyZW5jZXMgaW4gYm90aCBwZXJpb2RzIGFzIHByZWRpY3RvcnMuIFNpbmNlIGFsbCB0aHJlZSBhcmUgaW50ZWdlcnMgd2l0aCBhYm91dCB0aGUgc2FtZSBtZWFuIGFuZCB2YXJpYW5jZSwgbm9ybWFsaXppbmcgdGhlIGRhdGEgaXMgbm90IG5lZWRlZC4gNi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gaXMgdXNlZCB0byBmaW5kIHRoZSBvcHRpbWFsIEsuCgpgYGB7ciwgZWNobyA9IFRSVUUgfQpzZXQuc2VlZCgxMDUpCmZvbGQgPC0gc2FtcGxlKHJlcCgxOjYsIGxlbmd0aCA9IDExNCkpIApLX2Nob2ljZSA8LSBzZXEoMSwgOTUsIGxlbmd0aC5vdXQgPSA5NSkgCmtubl9hY2N1ciA8LSBtYXRyaXgoMCwgNiwgOTUpCgpmb3IgKGkgaW4gMTo2KSB7CiAgdHJhaW4yIDwtIGRhdGEuZnJhbWUoaG9ja2V5X2ZpbmFsW2ZvbGQgIT0gaSAsIGMoMSwyLDMpXSkgCiAgdGVzdDIgPC0gZGF0YS5mcmFtZShob2NrZXlfZmluYWxbZm9sZCA9PSBpICwgYygxLDIsMyldKSAKICB0ZXN0MnJlYWwgPC0gZGF0YS5mcmFtZShob2NrZXlfZmluYWxbZm9sZCA9PSBpLCAxMF0pIAogIHRyYWluMnJlYWwgPC0gYXMudmVjdG9yKGhvY2tleV9maW5hbFtmb2xkICE9IGksIDEwXSkKICBmb3IgKGogaW4gMTo5NSkgewogICAga25uX21vZGVsIDwtIGtubih0cmFpbiA9IHRyYWluMiwgdGVzdCA9IHRlc3QyLCBjbCA9IHRyYWluMnJlYWwsIGsgPSBLX2Nob2ljZVtqXSkKICAgIGtubl9hY2N1cltpLGpdIDwtIDEgLSBtZWFuKGFzLm51bWVyaWMobGV2ZWxzKGtubl9tb2RlbClbYXMuaW50ZWdlcihrbm5fbW9kZWwpXSkgPT0gdGVzdDJyZWFsKQogIH0KfQoKZXJyb3Jfa25uID0gcmVwKDAsIDk1KSAKZm9yIChpIGluIDE6OTUpIHsKICBlcnJvcl9rbm5baV0gPSBtZWFuKGtubl9hY2N1clssIGldKSAKICB9CgptaW5fa25uIDwtIHJvdW5kKG1pbihlcnJvcl9rbm4pLCAzKQp0aXRsZV9rbm4gPC0gcGFzdGUwKCJNaW5pbXVtIGVycm9yOiAiLCBtaW5fa25uKQpkZl9rbm4gPC0gZGF0YS5mcmFtZShLX2Nob2ljZSwgZXJyb3Jfa25uKSAKCmdncGxvdChkYXRhPWRmX2tubiwgYWVzKHggPSBLX2Nob2ljZSwgeSA9IGVycm9yX2tubiwgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZShjb2xvciA9ICJyZWQiKSArIAogIHhsYWIoIkNob2ljZSBvZiBLIikgKyB5bGFiKCJNZWFuIEVycm9yIHJhdGUiKSArIAogIGdndGl0bGUoIjYtZm9sZCBjcm9zcyB2YWxpZGF0aW9uOiBLTk4iLCB0aXRsZV9rbm4pCgpgYGAKCkxldHMgem9vbSBpbiBiZXR3ZWVuIGs9MSBhbmQgaz00MC4gV2Ugd2lsbCBhbHNvIG91dHB1dCB0aGUgdmFyaWFuY2Ugb2YgdGhlIGVycm9yIHJhdGVzIHRvIGdldCBhIGNsb3NlciBsb29rIGF0IGhvdyBLTk4gaXMgYmVoYXZpbmcuIAoKYGBge3IsIGVjaG8gPSBUUlVFLCBmaWcuc2hvdz0naG9sZCcsIG91dC53aWR0aCA9ICcyNSUnLCBmaWcuYWxpZ249J2NlbnRlcicgfQpzZXQuc2VlZCgxMDUpCmZvbGQgPC0gc2FtcGxlKHJlcCgxOjYsbGVuZ3RoID0gMTE0KSkgCktfY2hvaWNlIDwtIHNlcSgxICwgNDAgLCBsZW5ndGgub3V0ID0gNDApIAprbm5fYWNjdXIgPC0gbWF0cml4KDAgLCA2ICwgNDApCgpmb3IgKGkgaW4gMTo2KSB7CiAgdHJhaW4yIDwtIGRhdGEuZnJhbWUoaG9ja2V5X2ZpbmFsW2ZvbGQgIT0gaSwgYygxLDIsMyldKSAKICB0ZXN0MiA8LSBkYXRhLmZyYW1lKGhvY2tleV9maW5hbFtmb2xkID09IGksIGMoMSwyLDMpXSkgCiAgdGVzdDJyZWFsIDwtIGRhdGEuZnJhbWUoaG9ja2V5X2ZpbmFsW2ZvbGQgPT0gaSwgMTBdKSAKICB0cmFpbjJyZWFsIDwtIGFzLnZlY3Rvcihob2NrZXlfZmluYWxbZm9sZCAhPSBpLCAxMF0pCiAgZm9yIChqIGluIDE6NDApIHsKICAgIGtubl9tb2RlbCA8LSBrbm4odHJhaW4gPSB0cmFpbjIsIHRlc3QgPSB0ZXN0MiwgY2wgPSB0cmFpbjJyZWFsLCBrID0gS19jaG9pY2Vbal0pCiAgICBrbm5fYWNjdXJbaSxqXSA8LSAxIC0gbWVhbihhcy5udW1lcmljKGxldmVscyhrbm5fbW9kZWwpW2FzLmludGVnZXIoa25uX21vZGVsKV0pID09IHRlc3QycmVhbCkKICB9Cn0KCmVycm9yX2tubiA8LSByZXAoMCwgNDApIApmb3IgKGkgaW4gMTo0MCkgewogIGVycm9yX2tubltpXSA9IG1lYW4oa25uX2FjY3VyWywgaV0pIAp9CgpzZF9rbm4gPC0gcmVwKDAsNDApIApmb3IgKGkgaW4gMTo0MCkgewogIHNkX2tubltpXSA9IHNkKGtubl9hY2N1clssIGldKSAKICB9CgptaW5fa25uIDwtIHJvdW5kKG1pbihlcnJvcl9rbm4pLCAzKQp0aXRsZV9rbm4gPC0gcGFzdGUwKCJNaW5pbXVtIGVycm9yOiAiLCBtaW5fa25uKQpkZl9rbm4gPC0gZGF0YS5mcmFtZShLX2Nob2ljZSwgZXJyb3Jfa25uKSAKCmRmX2tubjIgPC0gZGF0YS5mcmFtZShLX2Nob2ljZSwgc2Rfa25uKQoKZ2dwbG90KGRhdGEgPSBkZl9rbm4sIGFlcyh4ID0gS19jaG9pY2UsIHkgPSBlcnJvcl9rbm4sIGdyb3VwID0gMSkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAicmVkIikgKyAKICB4bGFiKCJDaG9pY2Ugb2YgSyIpICsgeWxhYigiTWVhbiBFcnJvciByYXRlIikgKyAKICBnZ3RpdGxlKCI2LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbjogS05OIiwgdGl0bGVfa25uKQoKZ2dwbG90KGRhdGEgPSBkZl9rbm4yLCBhZXMoeCA9IEtfY2hvaWNlLCB5ID0gc2Rfa25uLCBncm91cCA9IDEpKSArIAogIGdlb21fbGluZShjb2xvciA9ICJyZWQiKSArCiAgeGxhYigiQ2hvaWNlIG9mIEsiKSArIHlsYWIoIlN0YW5kYXJkIGRldmlhdGlvbiBvZiBFcnJvciByYXRlIikgKwogIGdndGl0bGUoIjYtZm9sZCBjcm9zcyB2YWxpZGF0aW9uOiBLTk4iLCB0aXRsZV9rbm4pICNDaG9vc2UgSz0xMAoKYGBgCgoxMCBpcyB0aGUgYmVzdCBudW1iZXIgb2YgbmVpZ2hib3JzIHRvIGNvbnNpZGVyIHNpbmNlIGl0IGFjaGlldmVzIGEgbG93IG1lYW4gZXJyb3IgcmF0ZSB3aXRoIHRoZSBsb3dlc3QgdmFyaWFuY2UuIFdlIGNvbmNsdWRlIHRoYXQgdGhlIGJlc3QgbmVhcmVzdCBuZWlnaGJvciBtb2RlbCBpcyB0aGUgb25lIHRoYXQgY29uc2lkZXJzIDEwIG5laWdoYm9ycy4gCgojIyMgMy4zIERpc2NyaW1pbmFudCBBbmFseXNpcyAKCkZpbmFsbHksIHdlIGNvbnNpZGVyIGJvdGggTGluZWFyICYgUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcy4gV2UgdXNlIDYtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHRvIGZpbmQgdGhlIG9wdGltYWwgcHJpb3IgcHJvYmFiaWxpdHkgdGhhdCBtYXhpbWl6ZXMgYWNjdXJhY3kgYW5kIG1pbmltaXplcyBlcnJvci4gCgoKYGBge3IsIGVjaG8gPSBUUlVFIH0Kc2V0LnNlZWQoMTIxKQpmb2xkIDwtIHNhbXBsZShyZXAoMTo2LCBsZW5ndGggPSAxMTQpKSAKUHJpb3JzIDwtIHNlcSgwLjAxLCAwLjk5LCBsZW5ndGgub3V0ID0gOTkpIApsZGFfYWNjdXIgPC0gbWF0cml4KDAsIDYsIDk5KQoKZm9yIChpIGluIDE6NikgewogIHRlc3QgPC0gZGF0YS5mcmFtZShob2NrZXlfZmluYWxbZm9sZCA9PSBpLCBdKSAKICB0cmFpbiA8LSBkYXRhLmZyYW1lKGhvY2tleV9maW5hbFtmb2xkICE9IGksIF0pIAogIGZvciAoaiBpbiAxOjk5KSB7CiAgICBwcmlvcjEgPC0gUHJpb3JzW2pdCiAgICBsZGFfbW9kZWwgPC0gbGRhKG91dGNvbWUgfiBzY29yZWRpZmZwMSArIHNjb3JlZGlmZnAyLCBkYXRhID0gdGVzdCwgcHJpb3IgPSBjKHByaW9yMSwgMSAtIHByaW9yMSkpCiAgICBwcmVkaWN0aW9ucyA8LSBsZGFfbW9kZWwgJT4lIHByZWRpY3QodGVzdCkKICAgIGxkYV9hY2N1cltpLGpdIDwtIDEgLSBtZWFuKHByZWRpY3Rpb25zJGNsYXNzID09IHRlc3Qkb3V0Y29tZSkgCiAgICB9Cn0KCmVycm9yX2xkYSA8LSByZXAoMCwgOTkpIApmb3IgKGkgaW4gMTo5OSkgewogIGVycm9yX2xkYVtpXSA9IG1lYW4obGRhX2FjY3VyWywgaV0pCn0KCm1pbl9sZGEgPC0gcm91bmQobWluKGVycm9yX2xkYSksIDMpCnRpdGxlX2xkYSA8LSBwYXN0ZTAoIk1pbmltdW0gZXJyb3I6ICIsIG1pbl9sZGEpCgpkZl9sZGEgPC0gZGF0YS5mcmFtZShQcmlvcnMgLCBlcnJvcl9sZGEpIAoKZ2dwbG90KGRhdGEgPSBkZl9sZGEsIGFlcyh4ID0gUHJpb3JzLCB5ID0gZXJyb3JfbGRhLCBncm91cCA9IDEpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gInJlZCIpICsgCiAgeGxhYigiQ2hvaWNlIG9mIFByaW9yIikgKyB5bGFiKCJNZWFuIEVycm9yIHJhdGUiKSArCiAgZ2d0aXRsZSgiNi1mb2xkIGNyb3NzIHZhbGlkYXRpb246IExEQSIsIHRpdGxlX2xkYSkgI0Nob29zZSAwLjU3IGFzIG9wdGltYWwgcHJvYmFiaWxpdHkgY3V0LW9mZgoKYGBgCgpXZSBjb25jbHVkZSB0aGF0IHRoZSBiZXN0IGxpbmVhciBkaXNjcmltaW5hbnQgbW9kZWwgaXMgdGhlIG9uZSB0aGF0IHVzZXMgMXN0IGFuZCAybmQgcGVyaW9kIHBvaW50IGRpZmZlcmVuY2UgYXMgcHJlZGljdG9ycyB3aXRoIGEgY3V0LW9mZiBwcmlvciBwcm9iYWJpbGl0eSBvZiAwLjU3CgpgYGB7ciwgZWNobyA9IFRSVUV9CnNldC5zZWVkKDEyMSkKZm9sZCA8LSBzYW1wbGUocmVwKDE6NixsZW5ndGggPSAxMTQpKSAKUHJpb3JzIDwtIHNlcSgwLjAxLCAwLjk5LCBsZW5ndGgub3V0ID0gOTkpIApxZGFfYWNjdXIgPC0gbWF0cml4KDAsIDYsIDk5KQoKZm9yIChpIGluIDE6NikgewogIHRlc3QgPC0gZGF0YS5mcmFtZShob2NrZXlfZmluYWxbZm9sZCA9PSBpLCBdKSAKICB0cmFpbiA8LSBkYXRhLmZyYW1lKGhvY2tleV9maW5hbFtmb2xkICE9IGksIF0pCiAgZm9yIChqIGluIDE6OTkpIHsKICAgIHByaW9yMSA8LSBQcmlvcnNbal0KICAgIHFkYV9tb2RlbCA8LSBxZGEob3V0Y29tZSB+IHNjb3JlZGlmZnAxICsgc2NvcmVkaWZmcDIsIGRhdGEgPSB0ZXN0LCBwcmlvciA9IGMocHJpb3IxICwgMSAtIHByaW9yMSkpCiAgICBwcmVkaWN0aW9ucyA8LSBxZGFfbW9kZWwgJT4lIHByZWRpY3QodGVzdCkKICAgIHFkYV9hY2N1cltpLGpdIDwtIDEgLSBtZWFuKHByZWRpY3Rpb25zJGNsYXNzID09IHRlc3Qkb3V0Y29tZSkKICAgIH0gCiAgfQoKCmVycm9yX3FkYSA8LSByZXAoIDAgLCA5OSkgCmZvciAoaSBpbiAxOjk5KSB7CiAgZXJyb3JfcWRhW2ldID0gbWVhbihxZGFfYWNjdXJbLCBpXSkgfQoKbWluX3FkYSA8LSByb3VuZChtaW4oZXJyb3JfcWRhKSwzKQp0aXRsZV9xZGEgPC0gcGFzdGUwKCJNaW5pbXVtIGVycm9yOiAiLCBtaW5fcWRhKQpkZl9xZGEgPC0gZGF0YS5mcmFtZShQcmlvcnMsIGVycm9yX3FkYSkgCgpnZ3Bsb3QoZGF0YSA9IGRmX3FkYSwgYWVzKHggPSBQcmlvcnMsIHkgPSBlcnJvcl9xZGEsIGdyb3VwID0gMSkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAicmVkIikgKyAKICB4bGFiKCJDaG9pY2Ugb2YgUHJpb3IiKSArIHlsYWIoIk1lYW4gRXJyb3IgcmF0ZSIpICsKICBnZ3RpdGxlKCI2LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbjogUURBIiAsIHRpdGxlX3FkYSkgI0Nob29zZSAwLjY3IGFzIG9wdGltYWwgcHJvYmFiaWx0eSBjdXQtb2ZmCgpgYGAKCldlIGNvbmNsdWRlIHRoYXQgdGhlIGJlc3QgcXVhZHJhdGljIGRpc2NyaW1pbmFudCBtb2RlbCBpcyB0aGUgb25lIHRoYXQgdXNlcyAxc3QgYW5kIDJuZCBwZXJpb2QgcG9pbnQgZGlmZmVyZW5jZSBhcyBwcmVkaWN0b3JzIHdpdGggYSBjdXQtb2ZmIHByaW9yIHByb2JhYmlsaXR5IG9mIDAuNjcKCiMjIDQgLSBST0MgQW5hbHlzaXMgCgpOb3cgdGhhdCB3ZSBoYXZlIGZvdXIgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcyB0aGF0IGFyZSB0aGUgYmVzdCBpbiB0aGVpciByZXNwZWN0ZWQgY2xhc3NlcywgbGV0cyBjb21wYXJlIGFuZCBjb250cmFzdCB0aGVtLgoKYGBge3IsIGVjaG8gPSBUUlVFfSAKCmhvY2tleV90ZXN0MiA8LSBob2NrZXlfdGVzdCAlPiUgCiAgbXV0YXRlKExvY2F0aW9uID0gaWZlbHNlKExvY2F0aW9uID09ICJIb21lIiAsIDEsIDApICwgCiAgICAgICAgIG91dGNvbWUgPSBpZmVsc2Uob3V0Y29tZSA9PSAiV2luIiAsIDEgLDApKQoKZ2xtX2ZpbmFsIDwtIGdsbShvdXRjb21lIH4gc2NvcmVkaWZmcDIsIGRhdGEgPSBob2NrZXlfZmluYWwsIGZhbWlseSA9IGJpbm9taWFsKSAKdGVzdCA8LSBkYXRhLmZyYW1lKGhvY2tleV90ZXN0MlsgLDNdKQp0ZXN0IDwtIGFzLmRhdGEuZnJhbWUodGVzdCkKY29sbmFtZXModGVzdCkgPC0gYygic2NvcmVkaWZmcDIiKQpnbG1fZmluYWxfcHJvYnMgPC0gcHJlZGljdChnbG1fZmluYWwsIHRlc3QsIHR5cGUgPSAncmVzcG9uc2UnKSAKZ2xtX2ZpbmFsX3ByZWQgPC0gaWZlbHNlKGdsbV9maW5hbF9wcm9icyA+IDAuNSwgMSwgMCkKZ2xtX3RhYmxlIDwtIHRhYmxlKHByZWRpY3RlZCA9IGFzLmZhY3RvcihnbG1fZmluYWxfcHJlZCksIGFjdHVhbCA9IGFzLmZhY3Rvcihob2NrZXlfdGVzdDIkb3V0Y29tZSkpCgoKc2V0LnNlZWQoMTAwKQp0cmFpbl9rbm4gPC0gaG9ja2V5X2ZpbmFsW2MoMSwgMiwgMyldCnRlc3Rfa25uIDwtIGhvY2tleV90ZXN0MltjKDEsIDIsIDMpXQpjbF9rbm4gPC0gYXMuZmFjdG9yKGhvY2tleV9maW5hbCRvdXRjb21lKQprbm5fZmluYWwgPC0ga25uKHRyYWluID0gdHJhaW5fa25uLCB0ZXN0ID0gdGVzdF9rbm4sIGNsID0gY2xfa25uLCBrID0gMTApCmtubl90YWJsZSA8LSB0YWJsZShwcmVkaWN0ZWQgPSBhcy5mYWN0b3Ioa25uX2ZpbmFsKSwgYWN0dWFsID0gYXMuZmFjdG9yKGhvY2tleV90ZXN0MiRvdXRjb21lKSApCgoKbGRhX2ZpbmFsIDwtIGxkYShvdXRjb21lIH4gc2NvcmVkaWZmcDEgKyBzY29yZWRpZmZwMiwgZGF0YSA9IGhvY2tleV9maW5hbCwgcHJpb3IgPSBjKDAuNTcsIDAuNDMpKQpwcmVkaWN0aW9uc19sZGEgPC0gbGRhX2ZpbmFsICU+JSBwcmVkaWN0KGhvY2tleV90ZXN0MikKbGRhX3RhYmxlIDwtIHRhYmxlKHByZWRpY3RlZCA9IHByZWRpY3Rpb25zX2xkYSRjbGFzcywgYWN0dWFsID0gaG9ja2V5X3Rlc3QyJG91dGNvbWUgKQojZXJyb3JfbGRhID0gMSAtIG1lYW4ocHJlZGljdGlvbnMubGRhJGNsYXNzID09IHRlc3RkYXRhJG91dGNvbWUpIAoKCnFkYV9maW5hbCA8LSBxZGEob3V0Y29tZSB+IHNjb3JlZGlmZnAxICsgc2NvcmVkaWZmcDIsIGRhdGEgPSBob2NrZXlfZmluYWwgLCBwcmlvciA9IGMoMC42NyAsMC4zMykpCnByZWRpY3Rpb25zX3FkYSA8LSBxZGFfZmluYWwgJT4lIHByZWRpY3QoaG9ja2V5X3Rlc3QyKQpxZGFfdGFibGUgPC0gdGFibGUocHJlZGljdGVkID0gcHJlZGljdGlvbnNfcWRhJGNsYXNzLCBhY3R1YWwgPSBob2NrZXlfdGVzdDIkb3V0Y29tZSkKCmxvZ19zZW4gPC0gZ2xtX3RhYmxlWzIsMl0gLyAoZ2xtX3RhYmxlWzIsMl0gKyBnbG1fdGFibGVbMSwyXSkKbG9nX2ZwIDwtIGdsbV90YWJsZVsyLDFdIC8gKGdsbV90YWJsZVsyLDFdICsgZ2xtX3RhYmxlWzEsMV0pIApsb2dfYWN0IDwtIChnbG1fdGFibGVbMSwxXSArIGdsbV90YWJsZVsyLDJdKSAvIHN1bShnbG1fdGFibGUpCgprbm5fc2VuIDwtIGtubl90YWJsZVsyLDJdIC8gKGtubl90YWJsZVsyLDJdICsga25uX3RhYmxlWzEsMl0pIAprbm5fZnAgPC0ga25uX3RhYmxlWzIsMV0gLyAoa25uX3RhYmxlWzIsMV0gKyBrbm5fdGFibGVbMSwxXSkgCmtubl9hY3QgPC0gKGtubl90YWJsZVsxLDFdICsga25uX3RhYmxlWzIsMl0pIC8gc3VtKGtubl90YWJsZSkgCgpsZGFfc2VuIDwtIGxkYV90YWJsZVsyLDJdIC8gKGxkYV90YWJsZVsyLDJdICsgbGRhX3RhYmxlWzEsMl0pIApsZGFfZnAgPC0gbGRhX3RhYmxlWzIsMV0gLyAobGRhX3RhYmxlWzIsMV0gKyBsZGFfdGFibGVbMSwxXSkgCmxkYV9hY3QgPC0gKGxkYV90YWJsZVsxLDFdICsgbGRhX3RhYmxlWzIsMl0pIC8gc3VtKGxkYV90YWJsZSkgCgpxZGFfc2VuIDwtIHFkYV90YWJsZVsyLDJdIC8gKHFkYV90YWJsZVsyLDJdICsgcWRhX3RhYmxlWzEsMl0pIApxZGFfZnAgPC0gcWRhX3RhYmxlWzIsMV0gLyAocWRhX3RhYmxlWzIsMV0gKyBxZGFfdGFibGVbMSwxXSkgCnFkYV9hY3QgPC0gKHFkYV90YWJsZVsxLDFdICsgcWRhX3RhYmxlWzIsMl0pIC8gc3VtKHFkYV90YWJsZSkgCgpNZXRob2RzIDwtIGMoIkxvZ2lzdGljIiwgIktOTiIgLCAiTERBIiAsICJRREEiKQpNZXRob2RzX3JvYyA8LSBjKCJMb2dpc3RpYy9MREEiICwgIktOTiIgLCAiUURBIikKQWNjdXJhY3kgPC0gYyhsb2dfYWN0LCBrbm5fYWN0LCBsZGFfYWN0LCBxZGFfYWN0KSAKU2Vuc2l0aXZpdHkgPC0gYyhsb2dfc2VuICwga25uX3NlbiAsIGxkYV9zZW4gLCBxZGFfc2VuKSAKU3BlY2lmaWNpdHkgPSBjKDEtbG9nX2ZwICwgMS1rbm5fZnAgLCAxLWxkYV9mcCAsIDEtcWRhX2ZwKQpjb21wYXJlX3JvYyA8LSBkYXRhLmZyYW1lKE1ldGhvZHNfcm9jLCBTZW5zaXRpdml0eVtjKDEsMiw0KV0sIDEtU3BlY2lmaWNpdHlbYygxLDIsNCldKQpjb2xuYW1lcyhjb21wYXJlX3JvYykgPC0gYygiTWV0aG9kcyIgLCAidHBfcmF0ZSIgLCAiZnBfcmF0ZSIpCgpjb21wYXJpc2lvbnMgPC0gZGF0YS5mcmFtZShNZXRob2RzLCBBY2N1cmFjeSwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5KSAKY29tcGFyaXNpb25zCmBgYAoKSXQgc2VlbXMgdGhhdCBRREEgaXMgdGhlIG1vc3QgYWNjdXJhdGUgYWxnb3JpdGhtIHdoaWxlIEtOTiBwZXJmb3JtcyB0aGUgd29yc3QuCgpgYGB7ciwgZWNobyA9IFRSVUV9CmdncGxvdChjb21wYXJlX3JvYywgYWVzKHggPSBmcF9yYXRlLCB5ID0gdHBfcmF0ZSAsIGNvbCA9IE1ldGhvZHMpKSArIApnZW9tX3BvaW50KCkgKyB4bGltKDAsMSkgKyB5bGltKDAsMSkgKyBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEpICsgCmdndGl0bGUoIlJPQyBncmFwaCIpICt4bGFiKCIxIC0gU3BlY2lmaWNpdHkiKSArIHlsYWIoIlNlbnNpdGl2aXR5IikKYGBgCgpBbGwgNCBjbGFzc2lmaWVycyBkbyBtdWNoIGJldHRlciB0aGFuICJyYW5kb20gZ3Vlc3NpbmciIHdoaWNoIGlzIGluZGljYXRlZCBieSB0aGUgYmxhY2sgbGluZS4gVGhlIGNsb3NlciB0b3dhcmRzIHRoZSB1cHBlciBsZWZ0IGNvcm5lciwgdGhlIGJldHRlciB0aGUgY2xhc3NpZmllciBpcy4gS05OIGlzIHRoZSBjbG9zZXN0IHRvd2FyZHMgdGhlIGJsYWNrIGxpbmUuIFRoaXMgbWVhbnMgdGhhdCBLTk4gdW5kZXIgcGVyZm9ybXMgY29tcGFyZWQgdG8gdGhlIG90aGVyIGNsYXNzaWZpZXJzLiBRREEgc2VlbXMgdG8gcGVyZm9ybSB0aGUgYmVzdC4gUURBIGlzIHRoZSBjbGFzc2lmaWVyIHRvIGdvIGluIHRlcm1zIG9mIHByZWRpY3RhYmlsaXR5IGhvd2V2ZXIgaWYgb25lIHdhbnRzIGEgc2ltcGxlIGludGVycHJldGFibGUgbW9kZWwgd2l0aCBzb21lIGxvc3Mgb2YgcHJlZGljdGFiaWxpdHksIExvZ2lzdGljIFJlZ3Jlc3Npb24gaXMga2luZy4gIAoKIyMgNSAtIENvbmNsdXNpb24gCgpXZSBjb25jbHVkZSB0aGF0IHRoZSBiZXN0IHByZWRpY3RvcnMgdGhhdCBkZXRlcm1pbmUgdGhlIG91dGNvbWUgb2YgYSBnYW1lIGlzIHRoZSBwb2ludC1kaWZmZXJlbmNlIGF0IHBlcmlvZCAxIGFuZCBwZXJpb2QgMi4gIFRpbWUgaW4gUG93ZXIgcGxheSBkaWZmZXJlbmNlcyBhbmQgbG9jYXRpb24gZG9lcyBub3Qgc2VlbSB0byBwcmVkaWN0IHZlcnkgd2VsbC4gQSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIGlzIHRoZSBtb3N0IGludGVycHJldGFibGUgYnV0IGlmIG9uZSB3YW50cyBwcmVkaWN0YWJpbGl0eSwgUXVhZHJhdGljIERpc2NyaW1pbmFudCBBbmFseXNpcyBpcyB0aGUgd2F5IHRvIGdvLgoK